| import { | ||
| BatchworkError, | ||
| getAdapter, | ||
| isTerminalStatus | ||
| } from "./chunk-h2he3d16.js"; | ||
| // src/server/events.ts | ||
| var EVENT_BY_STATUS = { | ||
| cancelled: "batch.cancelled", | ||
| completed: "batch.completed", | ||
| expired: "batch.expired", | ||
| failed: "batch.failed" | ||
| }; | ||
| var toEvent = (provider, snapshot) => ({ | ||
| completedAt: snapshot.completedAt?.toISOString(), | ||
| createdAt: snapshot.createdAt?.toISOString(), | ||
| id: snapshot.id, | ||
| provider, | ||
| requestCounts: snapshot.requestCounts, | ||
| type: EVENT_BY_STATUS[snapshot.status] ?? "batch.completed" | ||
| }); | ||
| // src/server/signing.ts | ||
| var SIGNATURE_VERSION = "v1"; | ||
| var TOLERANCE_SECONDS = 300; | ||
| var SECRET_PREFIX = "whsec_"; | ||
| var encoder = new TextEncoder; | ||
| var replayCache = new Map; | ||
| var replayLocks = new WeakMap; | ||
| var pruneReplayCache = (now) => { | ||
| for (const [id, expiresAt] of replayCache) { | ||
| if (expiresAt <= now) { | ||
| replayCache.delete(id); | ||
| } | ||
| } | ||
| }; | ||
| var defaultReplayStore = { | ||
| claim: (id, expiresAt, now) => { | ||
| pruneReplayCache(now); | ||
| const existing = replayCache.get(id); | ||
| if (existing && existing > now) { | ||
| return false; | ||
| } | ||
| replayCache.set(id, expiresAt); | ||
| return true; | ||
| }, | ||
| get: (id) => replayCache.get(id), | ||
| set: (id, expiresAt) => { | ||
| replayCache.set(id, expiresAt); | ||
| } | ||
| }; | ||
| var withReplayLock = async (store, id, task) => { | ||
| let locks = replayLocks.get(store); | ||
| if (!locks) { | ||
| locks = new Map; | ||
| replayLocks.set(store, locks); | ||
| } | ||
| const previous = locks.get(id); | ||
| let release; | ||
| const current = new Promise((resolve) => { | ||
| release = resolve; | ||
| }); | ||
| locks.set(id, current); | ||
| if (previous) { | ||
| await previous; | ||
| } | ||
| try { | ||
| return await task(); | ||
| } finally { | ||
| release(); | ||
| if (locks.get(id) === current) { | ||
| locks.delete(id); | ||
| } | ||
| } | ||
| }; | ||
| var base64ToBytes = (value) => Uint8Array.from(atob(value), (char) => char.codePointAt(0) ?? 0); | ||
| var bytesToBase64 = (bytes) => { | ||
| let binary = ""; | ||
| for (const byte of bytes) { | ||
| binary += String.fromCodePoint(byte); | ||
| } | ||
| return btoa(binary); | ||
| }; | ||
| var importKey = (secret) => { | ||
| const raw = secret.startsWith(SECRET_PREFIX) ? base64ToBytes(secret.slice(SECRET_PREFIX.length)) : encoder.encode(secret); | ||
| return crypto.subtle.importKey("raw", raw, { hash: "SHA-256", name: "HMAC" }, false, ["sign", "verify"]); | ||
| }; | ||
| var signContent = async (secret, content) => { | ||
| const key = await importKey(secret); | ||
| const signature = await crypto.subtle.sign("HMAC", key, encoder.encode(content)); | ||
| return bytesToBase64(new Uint8Array(signature)); | ||
| }; | ||
| var verifyContent = async (secret, content, signature) => { | ||
| const key = await importKey(secret); | ||
| return crypto.subtle.verify("HMAC", key, base64ToBytes(signature), encoder.encode(content)); | ||
| }; | ||
| var rememberWebhookId = async (id, timestamp, now, store) => { | ||
| const expiresAt = timestamp + TOLERANCE_SECONDS; | ||
| if (store.claim) { | ||
| const claimed = await store.claim(id, expiresAt, now); | ||
| if (!claimed) { | ||
| throw new BatchworkError("batchwork: webhook replay detected."); | ||
| } | ||
| return; | ||
| } | ||
| await withReplayLock(store, id, async () => { | ||
| if (store === defaultReplayStore) { | ||
| pruneReplayCache(now); | ||
| } | ||
| const existing = await store.get(id); | ||
| if (existing && existing > now) { | ||
| throw new BatchworkError("batchwork: webhook replay detected."); | ||
| } | ||
| await store.set(id, expiresAt); | ||
| }); | ||
| }; | ||
| var signWebhook = async (secret, id, body, timestampSeconds) => { | ||
| const timestamp = Math.floor(timestampSeconds).toString(); | ||
| const signature = await signContent(secret, `${id}.${timestamp}.${body}`); | ||
| return { | ||
| "webhook-id": id, | ||
| "webhook-signature": `${SIGNATURE_VERSION},${signature}`, | ||
| "webhook-timestamp": timestamp | ||
| }; | ||
| }; | ||
| var verifyWebhook = async (request, secret, options) => { | ||
| const id = request.headers.get("webhook-id"); | ||
| const timestamp = request.headers.get("webhook-timestamp"); | ||
| const signatureHeader = request.headers.get("webhook-signature"); | ||
| if (!(id && timestamp && signatureHeader)) { | ||
| throw new BatchworkError("batchwork: missing webhook signature headers."); | ||
| } | ||
| const seconds = Number(timestamp); | ||
| const now = Date.now() / 1000; | ||
| if (!Number.isFinite(seconds) || Math.abs(now - seconds) > TOLERANCE_SECONDS) { | ||
| throw new BatchworkError("batchwork: webhook timestamp outside tolerance."); | ||
| } | ||
| const body = await request.text(); | ||
| const content = `${id}.${timestamp}.${body}`; | ||
| const signatures = signatureHeader.split(" ").map((part) => { | ||
| const comma = part.indexOf(","); | ||
| return comma === -1 ? part : part.slice(comma + 1); | ||
| }); | ||
| let valid = false; | ||
| for (const signature of signatures) { | ||
| if (await verifyContent(secret, content, signature)) { | ||
| valid = true; | ||
| break; | ||
| } | ||
| } | ||
| if (!valid) { | ||
| throw new BatchworkError("batchwork: webhook signature verification failed."); | ||
| } | ||
| await rememberWebhookId(id, seconds, now, options?.replayStore ?? defaultReplayStore); | ||
| return { body, id, timestamp: seconds }; | ||
| }; | ||
| var verifyBatchWebhook = async (request, secret, options) => { | ||
| const { body } = await verifyWebhook(request, secret, options); | ||
| return JSON.parse(body); | ||
| }; | ||
| // src/server/poller.ts | ||
| var parseIpv4 = (host) => { | ||
| if (!/^\d{1,3}(?:\.\d{1,3}){3}$/u.test(host)) { | ||
| return; | ||
| } | ||
| const parts = host.split(".").map(Number); | ||
| const valid = parts.every((part) => Number.isInteger(part) && part >= 0 && part <= 255); | ||
| return valid ? parts : undefined; | ||
| }; | ||
| var isPrivateIpv4 = (parts) => { | ||
| const [a = 0, b = 0] = parts; | ||
| return a === 0 || a === 10 || a === 127 || a === 100 && b >= 64 && b <= 127 || a === 169 && b === 254 || a === 172 && b >= 16 && b <= 31 || a === 192 && b === 168 || a === 198 && (b === 18 || b === 19) || a >= 224; | ||
| }; | ||
| var parseIpv4MappedIpv6 = (host) => { | ||
| const normalized = host.replace(/^\[/u, "").replace(/\]$/u, "").toLowerCase(); | ||
| if (!normalized.startsWith("::ffff:")) { | ||
| return; | ||
| } | ||
| const suffix = normalized.slice("::ffff:".length); | ||
| const dotted = parseIpv4(suffix); | ||
| if (dotted) { | ||
| return dotted; | ||
| } | ||
| const [high, low, extra] = suffix.split(":"); | ||
| if (!(high && low) || extra !== undefined) { | ||
| return; | ||
| } | ||
| const highBits = Number.parseInt(high, 16); | ||
| const lowBits = Number.parseInt(low, 16); | ||
| if (!Number.isInteger(highBits) || !Number.isInteger(lowBits) || highBits < 0 || highBits > 65535 || lowBits < 0 || lowBits > 65535) { | ||
| return; | ||
| } | ||
| return [ | ||
| Math.floor(highBits / 256), | ||
| highBits % 256, | ||
| Math.floor(lowBits / 256), | ||
| lowBits % 256 | ||
| ]; | ||
| }; | ||
| var isPrivateIpv6 = (host) => { | ||
| const normalized = host.replace(/^\[/u, "").replace(/\]$/u, "").toLowerCase(); | ||
| if (!normalized.includes(":")) { | ||
| return false; | ||
| } | ||
| return normalized === "::" || normalized === "::1" || normalized.startsWith("fc") || normalized.startsWith("fd") || normalized.startsWith("fe80:"); | ||
| }; | ||
| var isRedirectStatus = (status) => status >= 300 && status < 400; | ||
| var assertSafeWebhookUrl = (url) => { | ||
| if (url.protocol !== "https:") { | ||
| throw new BatchworkError("batchwork: webhookUrl must use https."); | ||
| } | ||
| if (url.username || url.password) { | ||
| throw new BatchworkError("batchwork: webhookUrl must not include credentials."); | ||
| } | ||
| const host = url.hostname.toLowerCase(); | ||
| const ipv4 = parseIpv4(host); | ||
| const mappedIpv4 = parseIpv4MappedIpv6(host); | ||
| if (host === "localhost" || host.endsWith(".localhost") || host.endsWith(".local") || ipv4 && isPrivateIpv4(ipv4) || mappedIpv4 && isPrivateIpv4(mappedIpv4) || isPrivateIpv6(host)) { | ||
| throw new BatchworkError("batchwork: webhookUrl must not target localhost or private networks."); | ||
| } | ||
| }; | ||
| var validateWebhookUrl = async (rawUrl, validator) => { | ||
| let url; | ||
| try { | ||
| url = new URL(rawUrl); | ||
| } catch (error) { | ||
| throw new BatchworkError("batchwork: webhookUrl must be a valid URL.", { | ||
| cause: error | ||
| }); | ||
| } | ||
| await validator(url); | ||
| return url.toString(); | ||
| }; | ||
| var createWebhookSink = (validator) => async (record, snapshot) => { | ||
| if (!record.webhookUrl) { | ||
| throw new BatchworkError("batchwork: tracked batch has no webhookUrl to deliver to."); | ||
| } | ||
| const webhookUrl = await validateWebhookUrl(record.webhookUrl, validator); | ||
| const body = JSON.stringify(toEvent(record.provider, snapshot)); | ||
| const headers = { | ||
| "content-type": "application/json" | ||
| }; | ||
| if (record.webhookSecret) { | ||
| Object.assign(headers, await signWebhook(record.webhookSecret, record.id, body, Date.now() / 1000)); | ||
| } | ||
| const response = await fetch(webhookUrl, { | ||
| body, | ||
| headers, | ||
| method: "POST", | ||
| redirect: "manual" | ||
| }); | ||
| if (isRedirectStatus(response.status)) { | ||
| throw new BatchworkError(`batchwork: webhook delivery to ${webhookUrl} redirected (${response.status}).`); | ||
| } | ||
| if (!response.ok) { | ||
| throw new BatchworkError(`batchwork: webhook delivery to ${webhookUrl} failed (${response.status}).`); | ||
| } | ||
| }; | ||
| var createBatchPoller = (options) => { | ||
| const resolveCredentials = (provider) => { | ||
| if (typeof options.credentials === "function") { | ||
| return options.credentials(provider); | ||
| } | ||
| return options.credentials ?? {}; | ||
| }; | ||
| const webhookUrlValidator = options.validateWebhookUrl ?? assertSafeWebhookUrl; | ||
| const sink = options.onComplete ?? createWebhookSink(webhookUrlValidator); | ||
| const deliver = async (record, snapshot) => { | ||
| await sink(record, snapshot); | ||
| await options.store.set({ | ||
| ...record, | ||
| deliveredAt: new Date().toISOString(), | ||
| status: snapshot.status | ||
| }); | ||
| }; | ||
| const track = async (target, opts) => { | ||
| const webhookUrl = opts.webhookUrl && !options.onComplete ? await validateWebhookUrl(opts.webhookUrl, webhookUrlValidator) : opts.webhookUrl; | ||
| const record = { | ||
| createdAt: new Date().toISOString(), | ||
| id: target.id, | ||
| provider: target.provider, | ||
| status: target.status ?? "in_progress", | ||
| webhookSecret: opts.secret, | ||
| webhookUrl | ||
| }; | ||
| await options.store.set(record); | ||
| return record; | ||
| }; | ||
| const process = async (record, delivered) => { | ||
| const adapter = getAdapter(record.provider); | ||
| const snapshot = await adapter.retrieve(record.id, resolveCredentials(record.provider)); | ||
| if (isTerminalStatus(snapshot.status)) { | ||
| await deliver(record, snapshot); | ||
| delivered.push(record.id); | ||
| } else if (snapshot.status !== record.status) { | ||
| await options.store.set({ ...record, status: snapshot.status }); | ||
| } | ||
| }; | ||
| const tick = async () => { | ||
| const pending = await options.store.list({ delivered: false }); | ||
| const delivered = []; | ||
| const failed = []; | ||
| for (const record of pending) { | ||
| try { | ||
| await process(record, delivered); | ||
| } catch (error) { | ||
| if (!options.onError) { | ||
| throw error; | ||
| } | ||
| options.onError(record, error); | ||
| failed.push({ | ||
| error: error instanceof Error ? error.message : String(error), | ||
| id: record.id | ||
| }); | ||
| } | ||
| } | ||
| return failed.length > 0 ? { checked: pending.length, delivered, failed } : { checked: pending.length, delivered }; | ||
| }; | ||
| const openaiWebhookHandler = (config) => async (request) => { | ||
| let verified; | ||
| try { | ||
| verified = await verifyWebhook(request, config.signingSecret); | ||
| } catch { | ||
| return new Response("invalid signature", { status: 400 }); | ||
| } | ||
| const payload = JSON.parse(verified.body); | ||
| if (!payload.type?.startsWith("batch.")) { | ||
| return new Response("ignored", { status: 202 }); | ||
| } | ||
| const id = payload.data?.id; | ||
| if (!id) { | ||
| return new Response("missing batch id", { status: 400 }); | ||
| } | ||
| const record = await options.store.get(id); | ||
| if (!record || record.deliveredAt) { | ||
| return new Response("ok", { status: 200 }); | ||
| } | ||
| const snapshot = await getAdapter("openai").retrieve(id, resolveCredentials("openai")); | ||
| if (isTerminalStatus(snapshot.status)) { | ||
| await deliver(record, snapshot); | ||
| } | ||
| return new Response("ok", { status: 200 }); | ||
| }; | ||
| return { deliver, openaiWebhookHandler, tick, track }; | ||
| }; | ||
| // src/server/store.ts | ||
| var createMemoryStore = () => { | ||
| const records = new Map; | ||
| return { | ||
| delete: (id) => { | ||
| records.delete(id); | ||
| return Promise.resolve(); | ||
| }, | ||
| get: (id) => Promise.resolve(records.get(id) ?? null), | ||
| list: (filter) => { | ||
| const all = [...records.values()]; | ||
| if (filter?.delivered === undefined) { | ||
| return Promise.resolve(all); | ||
| } | ||
| const { delivered } = filter; | ||
| return Promise.resolve(all.filter((record) => record.deliveredAt !== undefined === delivered)); | ||
| }, | ||
| set: (record) => { | ||
| records.set(record.id, record); | ||
| return Promise.resolve(); | ||
| } | ||
| }; | ||
| }; | ||
| export { toEvent, signWebhook, verifyWebhook, verifyBatchWebhook, createBatchPoller, createMemoryStore }; | ||
| //# debugId=BAF36CF27957AA5864756E2164756E21 | ||
| //# sourceMappingURL=chunk-3yqs6x71.js.map |
| { | ||
| "version": 3, | ||
| "sources": ["../src/server/events.ts", "../src/server/signing.ts", "../src/server/poller.ts", "../src/server/store.ts"], | ||
| "sourcesContent": [ | ||
| "import type { BatchProvider, BatchSnapshot, BatchStatus } from \"../types\";\nimport type { BatchWebhookEvent, BatchWebhookEventType } from \"./types\";\n\nconst EVENT_BY_STATUS: Partial<Record<BatchStatus, BatchWebhookEventType>> = {\n cancelled: \"batch.cancelled\",\n completed: \"batch.completed\",\n expired: \"batch.expired\",\n failed: \"batch.failed\",\n};\n\n/** Map a terminal snapshot to the unified webhook event batchwork delivers. */\nexport const toEvent = (\n provider: BatchProvider,\n snapshot: BatchSnapshot\n): BatchWebhookEvent => ({\n completedAt: snapshot.completedAt?.toISOString(),\n createdAt: snapshot.createdAt?.toISOString(),\n id: snapshot.id,\n provider,\n requestCounts: snapshot.requestCounts,\n type: EVENT_BY_STATUS[snapshot.status] ?? \"batch.completed\",\n});\n", | ||
| "import { BatchworkError } from \"../errors\";\nimport type { BatchWebhookEvent } from \"./types\";\n\n// Standard Webhooks-style HMAC-SHA256 signing, compatible with OpenAI's webhook\n// signatures (so the same verifier handles inbound OpenAI events and batchwork's\n// own outbound deliveries). Uses Web Crypto so it runs on edge runtimes.\n\nconst SIGNATURE_VERSION = \"v1\";\nconst TOLERANCE_SECONDS = 300;\nconst SECRET_PREFIX = \"whsec_\";\nconst encoder = new TextEncoder();\n\nexport interface WebhookReplayStore {\n claim?: (\n id: string,\n expiresAt: number,\n now: number\n ) => boolean | Promise<boolean>;\n get: (id: string) => number | Promise<number | undefined> | undefined;\n set: (id: string, expiresAt: number) => Promise<void> | void;\n}\n\nexport interface VerifyWebhookOptions {\n replayStore?: WebhookReplayStore;\n}\n\nconst replayCache = new Map<string, number>();\nconst replayLocks = new WeakMap<\n WebhookReplayStore,\n Map<string, Promise<void>>\n>();\n\nconst pruneReplayCache = (now: number): void => {\n for (const [id, expiresAt] of replayCache) {\n if (expiresAt <= now) {\n replayCache.delete(id);\n }\n }\n};\n\nconst defaultReplayStore: WebhookReplayStore = {\n claim: (id, expiresAt, now) => {\n pruneReplayCache(now);\n const existing = replayCache.get(id);\n if (existing && existing > now) {\n return false;\n }\n replayCache.set(id, expiresAt);\n return true;\n },\n get: (id) => replayCache.get(id),\n set: (id, expiresAt) => {\n replayCache.set(id, expiresAt);\n },\n};\n\nconst withReplayLock = async <Result>(\n store: WebhookReplayStore,\n id: string,\n task: () => Promise<Result>\n): Promise<Result> => {\n let locks = replayLocks.get(store);\n if (!locks) {\n locks = new Map();\n replayLocks.set(store, locks);\n }\n\n const previous = locks.get(id);\n let release!: () => void;\n // oxlint-disable-next-line promise/avoid-new -- a per-id lock needs a deferred release signal.\n const current = new Promise<void>((resolve) => {\n release = resolve;\n });\n locks.set(id, current);\n\n if (previous) {\n await previous;\n }\n try {\n return await task();\n } finally {\n release();\n if (locks.get(id) === current) {\n locks.delete(id);\n }\n }\n};\n\nconst base64ToBytes = (value: string): Uint8Array<ArrayBuffer> =>\n Uint8Array.from(atob(value), (char) => char.codePointAt(0) ?? 0);\n\nconst bytesToBase64 = (bytes: Uint8Array): string => {\n let binary = \"\";\n for (const byte of bytes) {\n binary += String.fromCodePoint(byte);\n }\n return btoa(binary);\n};\n\nconst importKey = (secret: string): Promise<CryptoKey> => {\n const raw = secret.startsWith(SECRET_PREFIX)\n ? base64ToBytes(secret.slice(SECRET_PREFIX.length))\n : encoder.encode(secret);\n return crypto.subtle.importKey(\n \"raw\",\n raw,\n { hash: \"SHA-256\", name: \"HMAC\" },\n false,\n [\"sign\", \"verify\"]\n );\n};\n\nconst signContent = async (\n secret: string,\n content: string\n): Promise<string> => {\n const key = await importKey(secret);\n const signature = await crypto.subtle.sign(\n \"HMAC\",\n key,\n encoder.encode(content)\n );\n return bytesToBase64(new Uint8Array(signature));\n};\n\nconst verifyContent = async (\n secret: string,\n content: string,\n signature: string\n): Promise<boolean> => {\n const key = await importKey(secret);\n return crypto.subtle.verify(\n \"HMAC\",\n key,\n base64ToBytes(signature),\n encoder.encode(content)\n );\n};\n\nconst rememberWebhookId = async (\n id: string,\n timestamp: number,\n now: number,\n store: WebhookReplayStore\n): Promise<void> => {\n const expiresAt = timestamp + TOLERANCE_SECONDS;\n if (store.claim) {\n const claimed = await store.claim(id, expiresAt, now);\n if (!claimed) {\n throw new BatchworkError(\"batchwork: webhook replay detected.\");\n }\n return;\n }\n\n await withReplayLock(store, id, async () => {\n if (store === defaultReplayStore) {\n pruneReplayCache(now);\n }\n const existing = await store.get(id);\n if (existing && existing > now) {\n throw new BatchworkError(\"batchwork: webhook replay detected.\");\n }\n await store.set(id, expiresAt);\n });\n};\n\n/** Build Standard Webhooks signature headers for an outbound delivery. */\nexport const signWebhook = async (\n secret: string,\n id: string,\n body: string,\n timestampSeconds: number\n): Promise<Record<string, string>> => {\n const timestamp = Math.floor(timestampSeconds).toString();\n const signature = await signContent(secret, `${id}.${timestamp}.${body}`);\n return {\n \"webhook-id\": id,\n \"webhook-signature\": `${SIGNATURE_VERSION},${signature}`,\n \"webhook-timestamp\": timestamp,\n };\n};\n\nexport interface VerifiedWebhook {\n id: string;\n timestamp: number;\n body: string;\n}\n\n/**\n * Verify a Standard Webhooks-signed request and return its raw body. Throws if\n * headers are missing, the timestamp is outside tolerance, or no signature\n * matches. Consumes the request body.\n */\nexport const verifyWebhook = async (\n request: Request,\n secret: string,\n options?: VerifyWebhookOptions\n): Promise<VerifiedWebhook> => {\n const id = request.headers.get(\"webhook-id\");\n const timestamp = request.headers.get(\"webhook-timestamp\");\n const signatureHeader = request.headers.get(\"webhook-signature\");\n if (!(id && timestamp && signatureHeader)) {\n throw new BatchworkError(\"batchwork: missing webhook signature headers.\");\n }\n\n const seconds = Number(timestamp);\n const now = Date.now() / 1000;\n if (\n !Number.isFinite(seconds) ||\n Math.abs(now - seconds) > TOLERANCE_SECONDS\n ) {\n throw new BatchworkError(\"batchwork: webhook timestamp outside tolerance.\");\n }\n\n const body = await request.text();\n const content = `${id}.${timestamp}.${body}`;\n const signatures = signatureHeader.split(\" \").map((part) => {\n const comma = part.indexOf(\",\");\n return comma === -1 ? part : part.slice(comma + 1);\n });\n\n let valid = false;\n for (const signature of signatures) {\n // oxlint-disable-next-line no-await-in-loop -- usually a single signature.\n if (await verifyContent(secret, content, signature)) {\n valid = true;\n break;\n }\n }\n if (!valid) {\n throw new BatchworkError(\n \"batchwork: webhook signature verification failed.\"\n );\n }\n\n await rememberWebhookId(\n id,\n seconds,\n now,\n options?.replayStore ?? defaultReplayStore\n );\n\n return { body, id, timestamp: seconds };\n};\n\n/**\n * Verify and parse a batchwork webhook on your receiving endpoint. Returns the\n * unified {@link BatchWebhookEvent}.\n */\nexport const verifyBatchWebhook = async (\n request: Request,\n secret: string,\n options?: VerifyWebhookOptions\n): Promise<BatchWebhookEvent> => {\n const { body } = await verifyWebhook(request, secret, options);\n return JSON.parse(body) as BatchWebhookEvent;\n};\n", | ||
| "import { BatchworkError } from \"../errors\";\nimport { isTerminalStatus } from \"../job\";\nimport { getAdapter } from \"../providers\";\nimport type {\n BatchProvider,\n BatchSnapshot,\n BatchStatus,\n ProviderCredentials,\n} from \"../types\";\nimport { toEvent } from \"./events\";\nimport { signWebhook, verifyWebhook } from \"./signing\";\nimport type { BatchStore, TrackedBatch } from \"./types\";\n\n/** Credentials for polling: a fixed config, or one resolved per provider. */\nexport type CredentialResolver =\n | ProviderCredentials\n | ((provider: BatchProvider) => ProviderCredentials);\n\n/**\n * Handles a batch reaching a terminal status. Replaces the default signed\n * webhook delivery — e.g. to invoke a callback instead (see `batchwork/next`).\n */\nexport type CompletionSink = (\n record: TrackedBatch,\n snapshot: BatchSnapshot\n) => Promise<void>;\n\nexport type WebhookUrlValidator = (url: URL) => void | Promise<void>;\n\nexport interface BatchPollerOptions {\n store: BatchStore;\n /** Falls back to provider env vars (e.g. `OPENAI_API_KEY`) when omitted. */\n credentials?: CredentialResolver;\n /** Replaces signed-webhook delivery when a batch finishes. */\n onComplete?: CompletionSink;\n /** Override the default webhook URL policy with an application allowlist. */\n validateWebhookUrl?: WebhookUrlValidator;\n /**\n * Called when processing a single batch throws during `tick`. When provided,\n * the tick reports the error and continues to the next batch; when omitted,\n * the error propagates out of `tick`.\n */\n onError?: (record: TrackedBatch, error: unknown) => void;\n}\n\nexport interface TrackTarget {\n id: string;\n provider: BatchProvider;\n status?: BatchStatus;\n}\n\nexport interface TrackOptions {\n /** Where to POST the completion webhook. Omit for callback-based delivery. */\n webhookUrl?: string;\n /** Signs the outbound webhook (Standard Webhooks HMAC) when provided. */\n secret?: string;\n}\n\nexport interface TickResult {\n checked: number;\n delivered: string[];\n /** Batches whose processing threw this tick (only when `onError` is set). */\n failed?: { id: string; error: string }[];\n}\n\nexport interface OpenAIWebhookOptions {\n /** The OpenAI webhook signing secret (`whsec_…`). */\n signingSecret: string;\n}\n\nexport interface BatchPoller {\n track: (target: TrackTarget, options: TrackOptions) => Promise<TrackedBatch>;\n tick: () => Promise<TickResult>;\n deliver: (record: TrackedBatch, snapshot: BatchSnapshot) => Promise<void>;\n openaiWebhookHandler: (\n options: OpenAIWebhookOptions\n ) => (request: Request) => Promise<Response>;\n}\n\nconst parseIpv4 = (host: string): number[] | undefined => {\n if (!/^\\d{1,3}(?:\\.\\d{1,3}){3}$/u.test(host)) {\n return;\n }\n const parts = host.split(\".\").map(Number);\n const valid = parts.every(\n (part) => Number.isInteger(part) && part >= 0 && part <= 255\n );\n return valid ? parts : undefined;\n};\n\nconst isPrivateIpv4 = (parts: number[]): boolean => {\n const [a = 0, b = 0] = parts;\n return (\n a === 0 ||\n a === 10 ||\n a === 127 ||\n (a === 100 && b >= 64 && b <= 127) ||\n (a === 169 && b === 254) ||\n (a === 172 && b >= 16 && b <= 31) ||\n (a === 192 && b === 168) ||\n (a === 198 && (b === 18 || b === 19)) ||\n a >= 224\n );\n};\n\nconst parseIpv4MappedIpv6 = (host: string): number[] | undefined => {\n const normalized = host.replace(/^\\[/u, \"\").replace(/\\]$/u, \"\").toLowerCase();\n if (!normalized.startsWith(\"::ffff:\")) {\n return;\n }\n\n const suffix = normalized.slice(\"::ffff:\".length);\n const dotted = parseIpv4(suffix);\n if (dotted) {\n return dotted;\n }\n\n const [high, low, extra] = suffix.split(\":\");\n if (!(high && low) || extra !== undefined) {\n return;\n }\n const highBits = Number.parseInt(high, 16);\n const lowBits = Number.parseInt(low, 16);\n if (\n !Number.isInteger(highBits) ||\n !Number.isInteger(lowBits) ||\n highBits < 0 ||\n highBits > 65_535 ||\n lowBits < 0 ||\n lowBits > 65_535\n ) {\n return;\n }\n return [\n Math.floor(highBits / 256),\n highBits % 256,\n Math.floor(lowBits / 256),\n lowBits % 256,\n ];\n};\n\nconst isPrivateIpv6 = (host: string): boolean => {\n const normalized = host.replace(/^\\[/u, \"\").replace(/\\]$/u, \"\").toLowerCase();\n // Only an IPv6 literal can be a private address, and one always contains a\n // colon — guard so a bare DNS name like `fc2.com` is not mistaken for `fc00::/7`.\n if (!normalized.includes(\":\")) {\n return false;\n }\n return (\n normalized === \"::\" ||\n normalized === \"::1\" ||\n normalized.startsWith(\"fc\") ||\n normalized.startsWith(\"fd\") ||\n normalized.startsWith(\"fe80:\")\n );\n};\n\nconst isRedirectStatus = (status: number): boolean =>\n status >= 300 && status < 400;\n\nconst assertSafeWebhookUrl: WebhookUrlValidator = (url) => {\n if (url.protocol !== \"https:\") {\n throw new BatchworkError(\"batchwork: webhookUrl must use https.\");\n }\n if (url.username || url.password) {\n throw new BatchworkError(\n \"batchwork: webhookUrl must not include credentials.\"\n );\n }\n\n const host = url.hostname.toLowerCase();\n const ipv4 = parseIpv4(host);\n const mappedIpv4 = parseIpv4MappedIpv6(host);\n if (\n host === \"localhost\" ||\n host.endsWith(\".localhost\") ||\n host.endsWith(\".local\") ||\n (ipv4 && isPrivateIpv4(ipv4)) ||\n (mappedIpv4 && isPrivateIpv4(mappedIpv4)) ||\n isPrivateIpv6(host)\n ) {\n throw new BatchworkError(\n \"batchwork: webhookUrl must not target localhost or private networks.\"\n );\n }\n};\n\nconst validateWebhookUrl = async (\n rawUrl: string,\n validator: WebhookUrlValidator\n): Promise<string> => {\n let url: URL;\n try {\n url = new URL(rawUrl);\n } catch (error) {\n throw new BatchworkError(\"batchwork: webhookUrl must be a valid URL.\", {\n cause: error,\n });\n }\n await validator(url);\n return url.toString();\n};\n\n/** The default completion sink: POST a signed webhook to the tracked URL. */\nconst createWebhookSink =\n (validator: WebhookUrlValidator): CompletionSink =>\n async (record, snapshot) => {\n if (!record.webhookUrl) {\n throw new BatchworkError(\n \"batchwork: tracked batch has no webhookUrl to deliver to.\"\n );\n }\n const webhookUrl = await validateWebhookUrl(record.webhookUrl, validator);\n const body = JSON.stringify(toEvent(record.provider, snapshot));\n const headers: Record<string, string> = {\n \"content-type\": \"application/json\",\n };\n if (record.webhookSecret) {\n Object.assign(\n headers,\n await signWebhook(\n record.webhookSecret,\n record.id,\n body,\n Date.now() / 1000\n )\n );\n }\n const response = await fetch(webhookUrl, {\n body,\n headers,\n method: \"POST\",\n redirect: \"manual\",\n });\n if (isRedirectStatus(response.status)) {\n throw new BatchworkError(\n `batchwork: webhook delivery to ${webhookUrl} redirected (${response.status}).`\n );\n }\n if (!response.ok) {\n throw new BatchworkError(\n `batchwork: webhook delivery to ${webhookUrl} failed (${response.status}).`\n );\n }\n };\n\n/**\n * Create a managed poller: register submitted batches with `track`, then run\n * `tick` on a schedule (cron) to poll open batches and deliver one unified,\n * signed webhook per batch when it finishes. For OpenAI, mount\n * `openaiWebhookHandler` to skip polling and react to native webhooks instead.\n */\nexport const createBatchPoller = (options: BatchPollerOptions): BatchPoller => {\n const resolveCredentials = (provider: BatchProvider): ProviderCredentials => {\n if (typeof options.credentials === \"function\") {\n return options.credentials(provider);\n }\n return options.credentials ?? {};\n };\n\n const webhookUrlValidator =\n options.validateWebhookUrl ?? assertSafeWebhookUrl;\n const sink = options.onComplete ?? createWebhookSink(webhookUrlValidator);\n\n const deliver = async (\n record: TrackedBatch,\n snapshot: BatchSnapshot\n ): Promise<void> => {\n // Run the side effect before marking delivered: if it throws, the record\n // stays pending and is retried on the next tick (at-least-once delivery).\n await sink(record, snapshot);\n await options.store.set({\n ...record,\n deliveredAt: new Date().toISOString(),\n status: snapshot.status,\n });\n };\n\n const track = async (\n target: TrackTarget,\n opts: TrackOptions\n ): Promise<TrackedBatch> => {\n const webhookUrl =\n opts.webhookUrl && !options.onComplete\n ? await validateWebhookUrl(opts.webhookUrl, webhookUrlValidator)\n : opts.webhookUrl;\n const record: TrackedBatch = {\n createdAt: new Date().toISOString(),\n id: target.id,\n provider: target.provider,\n status: target.status ?? \"in_progress\",\n webhookSecret: opts.secret,\n webhookUrl,\n };\n await options.store.set(record);\n return record;\n };\n\n const process = async (\n record: TrackedBatch,\n delivered: string[]\n ): Promise<void> => {\n const adapter = getAdapter(record.provider);\n const snapshot = await adapter.retrieve(\n record.id,\n resolveCredentials(record.provider)\n );\n if (isTerminalStatus(snapshot.status)) {\n await deliver(record, snapshot);\n delivered.push(record.id);\n } else if (snapshot.status !== record.status) {\n await options.store.set({ ...record, status: snapshot.status });\n }\n };\n\n const tick = async (): Promise<TickResult> => {\n const pending = await options.store.list({ delivered: false });\n const delivered: string[] = [];\n const failed: { id: string; error: string }[] = [];\n for (const record of pending) {\n // oxlint-disable-next-line no-await-in-loop -- batches are polled serially\n // to avoid hammering provider rate limits; deliver before the next.\n try {\n // oxlint-disable-next-line no-await-in-loop -- see above.\n await process(record, delivered);\n } catch (error) {\n // Without an `onError` handler, preserve the propagate-the-throw\n // behavior; with one, report and continue so a single failing batch\n // can't starve the rest of the queue. Either way the record stays\n // pending (it was never marked delivered) and retries next tick.\n if (!options.onError) {\n throw error;\n }\n options.onError(record, error);\n failed.push({\n error: error instanceof Error ? error.message : String(error),\n id: record.id,\n });\n }\n }\n return failed.length > 0\n ? { checked: pending.length, delivered, failed }\n : { checked: pending.length, delivered };\n };\n\n const openaiWebhookHandler =\n (config: OpenAIWebhookOptions) =>\n async (request: Request): Promise<Response> => {\n let verified: { body: string };\n try {\n verified = await verifyWebhook(request, config.signingSecret);\n } catch {\n return new Response(\"invalid signature\", { status: 400 });\n }\n\n const payload = JSON.parse(verified.body) as {\n type?: string;\n data?: { id?: string };\n };\n if (!payload.type?.startsWith(\"batch.\")) {\n return new Response(\"ignored\", { status: 202 });\n }\n const id = payload.data?.id;\n if (!id) {\n return new Response(\"missing batch id\", { status: 400 });\n }\n const record = await options.store.get(id);\n if (!record || record.deliveredAt) {\n return new Response(\"ok\", { status: 200 });\n }\n const snapshot = await getAdapter(\"openai\").retrieve(\n id,\n resolveCredentials(\"openai\")\n );\n if (isTerminalStatus(snapshot.status)) {\n await deliver(record, snapshot);\n }\n return new Response(\"ok\", { status: 200 });\n };\n\n return { deliver, openaiWebhookHandler, tick, track };\n};\n", | ||
| "import type { BatchStore, TrackedBatch } from \"./types\";\n\n/** An in-memory `BatchStore`. Suitable for development and single-process use. */\nexport const createMemoryStore = (): BatchStore => {\n const records = new Map<string, TrackedBatch>();\n\n return {\n delete: (id) => {\n records.delete(id);\n return Promise.resolve();\n },\n get: (id) => Promise.resolve(records.get(id) ?? null),\n list: (filter) => {\n const all = [...records.values()];\n if (filter?.delivered === undefined) {\n return Promise.resolve(all);\n }\n const { delivered } = filter;\n return Promise.resolve(\n all.filter((record) => (record.deliveredAt !== undefined) === delivered)\n );\n },\n set: (record) => {\n records.set(record.id, record);\n return Promise.resolve();\n },\n };\n};\n" | ||
| ], | ||
| "mappings": "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", | ||
| "debugId": "BAF36CF27957AA5864756E2164756E21", | ||
| "names": [] | ||
| } |
| import { | ||
| BatchJob, | ||
| BatchworkError, | ||
| MissingDependencyError, | ||
| UnsupportedProviderError, | ||
| assertByteLength, | ||
| getAdapter, | ||
| mapWithConcurrency, | ||
| resolveBatchLimits | ||
| } from "./chunk-h2he3d16.js"; | ||
| import { | ||
| __require | ||
| } from "./chunk-v0bahtg2.js"; | ||
| // src/model.ts | ||
| var CAPTURE_API_KEY = "batchwork-capture"; | ||
| var PACKAGE_BY_PROVIDER = { | ||
| anthropic: { label: "Anthropic", specifier: "@ai-sdk/anthropic" }, | ||
| google: { label: "Google Gemini", specifier: "@ai-sdk/google" }, | ||
| groq: { label: "Groq", specifier: "@ai-sdk/groq" }, | ||
| mistral: { label: "Mistral", specifier: "@ai-sdk/mistral" }, | ||
| openai: { label: "OpenAI", specifier: "@ai-sdk/openai" }, | ||
| together: { label: "Together AI", specifier: "@ai-sdk/togetherai" }, | ||
| xai: { label: "xAI", specifier: "@ai-sdk/xai" } | ||
| }; | ||
| var PROVIDER_BY_FAMILY = { | ||
| anthropic: "anthropic", | ||
| google: "google", | ||
| groq: "groq", | ||
| mistral: "mistral", | ||
| openai: "openai", | ||
| together: "together", | ||
| togetherai: "together", | ||
| xai: "xai" | ||
| }; | ||
| var PROVIDER_BY_ALIAS = { | ||
| ...PROVIDER_BY_FAMILY, | ||
| gemini: "google" | ||
| }; | ||
| var splitOnce = (value, separator) => { | ||
| const index = value.indexOf(separator); | ||
| if (index === -1) { | ||
| return [value, ""]; | ||
| } | ||
| return [value.slice(0, index), value.slice(index + separator.length)]; | ||
| }; | ||
| var openaiKind = (suffix) => { | ||
| if (suffix === "responses") { | ||
| return "responses"; | ||
| } | ||
| if (suffix === "completion") { | ||
| return "completion"; | ||
| } | ||
| return "chat"; | ||
| }; | ||
| var resolveModelString = (value) => { | ||
| const [providerId, modelId] = splitOnce(value, "/"); | ||
| if (modelId === "") { | ||
| throw new UnsupportedProviderError(value); | ||
| } | ||
| const provider = PROVIDER_BY_ALIAS[providerId]; | ||
| if (!provider) { | ||
| throw new UnsupportedProviderError(providerId); | ||
| } | ||
| return { kind: "chat", modelId, provider }; | ||
| }; | ||
| var resolveModel = (model) => { | ||
| if (typeof model === "string") { | ||
| return resolveModelString(model); | ||
| } | ||
| const [family, suffix] = splitOnce(model.provider, "."); | ||
| const provider = PROVIDER_BY_FAMILY[family]; | ||
| if (provider === "openai") { | ||
| return { kind: openaiKind(suffix), modelId: model.modelId, provider }; | ||
| } | ||
| if (provider) { | ||
| return { kind: "chat", modelId: model.modelId, provider }; | ||
| } | ||
| if (model.modelId.includes("/")) { | ||
| return resolveModelString(model.modelId); | ||
| } | ||
| throw new UnsupportedProviderError(model.provider); | ||
| }; | ||
| var importProvider = (provider) => { | ||
| switch (provider) { | ||
| case "anthropic": { | ||
| return import("@ai-sdk/anthropic"); | ||
| } | ||
| case "google": { | ||
| return import("@ai-sdk/google"); | ||
| } | ||
| case "groq": { | ||
| return import("@ai-sdk/groq"); | ||
| } | ||
| case "mistral": { | ||
| return import("@ai-sdk/mistral"); | ||
| } | ||
| case "openai": { | ||
| return import("@ai-sdk/openai"); | ||
| } | ||
| case "together": { | ||
| return import("@ai-sdk/togetherai"); | ||
| } | ||
| case "xai": { | ||
| return import("@ai-sdk/xai"); | ||
| } | ||
| default: { | ||
| return Promise.reject(new UnsupportedProviderError(provider)); | ||
| } | ||
| } | ||
| }; | ||
| var loadProvider = async (provider, load = importProvider) => { | ||
| try { | ||
| return await load(provider); | ||
| } catch (error) { | ||
| if (error instanceof UnsupportedProviderError) { | ||
| throw error; | ||
| } | ||
| const { specifier, label } = PACKAGE_BY_PROVIDER[provider]; | ||
| throw new MissingDependencyError(specifier, label); | ||
| } | ||
| }; | ||
| var createCaptureModel = async (resolved, credentials, fetchImpl) => { | ||
| const settings = { | ||
| apiKey: credentials.apiKey ?? CAPTURE_API_KEY, | ||
| baseURL: credentials.baseURL, | ||
| fetch: fetchImpl, | ||
| headers: credentials.headers | ||
| }; | ||
| switch (resolved.provider) { | ||
| case "openai": { | ||
| const { createOpenAI } = await loadProvider("openai"); | ||
| const provider = createOpenAI(settings); | ||
| if (resolved.kind === "responses") { | ||
| return provider.responses(resolved.modelId); | ||
| } | ||
| if (resolved.kind === "completion") { | ||
| return provider.completion(resolved.modelId); | ||
| } | ||
| return provider.chat(resolved.modelId); | ||
| } | ||
| case "anthropic": { | ||
| const { createAnthropic } = await loadProvider("anthropic"); | ||
| return createAnthropic(settings).messages(resolved.modelId); | ||
| } | ||
| case "groq": { | ||
| const { createGroq } = await loadProvider("groq"); | ||
| return createGroq(settings).languageModel(resolved.modelId); | ||
| } | ||
| case "mistral": { | ||
| const { createMistral } = await loadProvider("mistral"); | ||
| return createMistral(settings).languageModel(resolved.modelId); | ||
| } | ||
| case "google": { | ||
| const { createGoogleGenerativeAI } = await loadProvider("google"); | ||
| return createGoogleGenerativeAI(settings).languageModel(resolved.modelId); | ||
| } | ||
| case "xai": { | ||
| const { createXai } = await loadProvider("xai"); | ||
| return createXai(settings).languageModel(resolved.modelId); | ||
| } | ||
| case "together": { | ||
| const { createTogetherAI } = await loadProvider("together"); | ||
| return createTogetherAI(settings).languageModel(resolved.modelId); | ||
| } | ||
| default: { | ||
| throw new UnsupportedProviderError(resolved.provider); | ||
| } | ||
| } | ||
| }; | ||
| var EMBEDDING_PROVIDERS = new Set([ | ||
| "google", | ||
| "mistral", | ||
| "openai" | ||
| ]); | ||
| var unsupportedEmbeddingProvider = (provider) => new UnsupportedProviderError(provider, `batchwork: provider "${provider}" does not offer batch embeddings. Embeddings are supported for: openai, mistral, google.`); | ||
| var createCaptureEmbeddingModel = async (resolved, credentials, fetchImpl) => { | ||
| const settings = { | ||
| apiKey: credentials.apiKey ?? CAPTURE_API_KEY, | ||
| baseURL: credentials.baseURL, | ||
| fetch: fetchImpl, | ||
| headers: credentials.headers | ||
| }; | ||
| switch (resolved.provider) { | ||
| case "openai": { | ||
| const { createOpenAI } = await loadProvider("openai"); | ||
| return createOpenAI(settings).embeddingModel(resolved.modelId); | ||
| } | ||
| case "mistral": { | ||
| const { createMistral } = await loadProvider("mistral"); | ||
| return createMistral(settings).embeddingModel(resolved.modelId); | ||
| } | ||
| case "google": { | ||
| const { createGoogleGenerativeAI } = await loadProvider("google"); | ||
| return createGoogleGenerativeAI(settings).embeddingModel(resolved.modelId); | ||
| } | ||
| default: { | ||
| throw unsupportedEmbeddingProvider(resolved.provider); | ||
| } | ||
| } | ||
| }; | ||
| var IMAGE_PROVIDERS = new Set([ | ||
| "google", | ||
| "openai", | ||
| "xai" | ||
| ]); | ||
| var unsupportedImageProvider = (provider) => new UnsupportedProviderError(provider, `batchwork: provider "${provider}" does not offer batch image generation. Image batches are supported for: openai, google, xai.`); | ||
| var createCaptureImageModel = async (resolved, credentials, fetchImpl) => { | ||
| const settings = { | ||
| apiKey: credentials.apiKey ?? CAPTURE_API_KEY, | ||
| baseURL: credentials.baseURL, | ||
| fetch: fetchImpl, | ||
| headers: credentials.headers | ||
| }; | ||
| switch (resolved.provider) { | ||
| case "openai": { | ||
| const { createOpenAI } = await loadProvider("openai"); | ||
| return createOpenAI(settings).image(resolved.modelId); | ||
| } | ||
| case "google": { | ||
| const { createGoogleGenerativeAI } = await loadProvider("google"); | ||
| return createGoogleGenerativeAI(settings).image(resolved.modelId); | ||
| } | ||
| case "xai": { | ||
| const { createXai } = await loadProvider("xai"); | ||
| return createXai(settings).image(resolved.modelId); | ||
| } | ||
| default: { | ||
| throw unsupportedImageProvider(resolved.provider); | ||
| } | ||
| } | ||
| }; | ||
| // src/body.ts | ||
| import { embed, generateImage, generateText } from "ai"; | ||
| var MAX_CAUSE_DEPTH = 10; | ||
| class CaptureSignalError extends Error { | ||
| url; | ||
| rawBody; | ||
| constructor(url, rawBody) { | ||
| super("batchwork:capture"); | ||
| this.name = "CaptureSignalError"; | ||
| this.url = url; | ||
| this.rawBody = rawBody; | ||
| } | ||
| } | ||
| var resolveUrl = (input) => { | ||
| if (typeof input === "string") { | ||
| return input; | ||
| } | ||
| if (input instanceof URL) { | ||
| return input.toString(); | ||
| } | ||
| return input.url; | ||
| }; | ||
| var extractBody = (init) => { | ||
| const body = init?.body; | ||
| if (typeof body === "string") { | ||
| return body; | ||
| } | ||
| if (body instanceof Uint8Array) { | ||
| return new TextDecoder().decode(body); | ||
| } | ||
| throw new BatchworkError("batchwork: unable to read the provider request body during capture."); | ||
| }; | ||
| var captureFetch = (input, init) => Promise.reject(new CaptureSignalError(resolveUrl(input), extractBody(init))); | ||
| var findCapture = (error) => { | ||
| let current = error; | ||
| let depth = 0; | ||
| while (current && depth < MAX_CAUSE_DEPTH) { | ||
| if (current instanceof CaptureSignalError) { | ||
| return current; | ||
| } | ||
| current = current.cause; | ||
| depth += 1; | ||
| } | ||
| }; | ||
| var endpointFromUrl = (url) => { | ||
| try { | ||
| return new URL(url).pathname; | ||
| } catch { | ||
| return url; | ||
| } | ||
| }; | ||
| var mergeDefaults = (request, defaults) => { | ||
| if (!defaults) { | ||
| return request; | ||
| } | ||
| return { ...defaults, ...request }; | ||
| }; | ||
| var toGenerateInput = (model, request) => ({ | ||
| frequencyPenalty: request.frequencyPenalty, | ||
| maxOutputTokens: request.maxOutputTokens, | ||
| maxRetries: 0, | ||
| messages: request.messages, | ||
| model, | ||
| presencePenalty: request.presencePenalty, | ||
| prompt: request.prompt, | ||
| providerOptions: request.providerOptions, | ||
| seed: request.seed, | ||
| stopSequences: request.stopSequences, | ||
| system: request.system, | ||
| temperature: request.temperature, | ||
| toolChoice: request.toolChoice, | ||
| tools: request.tools, | ||
| topK: request.topK, | ||
| topP: request.topP | ||
| }); | ||
| var bodyFromCapture = (error, customId, maxRequestBytes) => { | ||
| const capture = findCapture(error); | ||
| if (capture) { | ||
| assertByteLength(`request "${customId}"`, capture.rawBody, maxRequestBytes); | ||
| return { | ||
| body: JSON.parse(capture.rawBody), | ||
| customId, | ||
| endpoint: endpointFromUrl(capture.url) | ||
| }; | ||
| } | ||
| throw error; | ||
| }; | ||
| var captureOne = async (model, request, customId, maxRequestBytes) => { | ||
| try { | ||
| await generateText(toGenerateInput(model, request)); | ||
| } catch (error) { | ||
| return bodyFromCapture(error, customId, maxRequestBytes); | ||
| } | ||
| throw new BatchworkError("batchwork: the request was not intercepted while building the batch body."); | ||
| }; | ||
| var captureEmbeddingOne = async (model, request, customId, maxRequestBytes) => { | ||
| try { | ||
| await embed({ | ||
| maxRetries: 0, | ||
| model, | ||
| providerOptions: request.providerOptions, | ||
| value: request.value | ||
| }); | ||
| } catch (error) { | ||
| return bodyFromCapture(error, customId, maxRequestBytes); | ||
| } | ||
| throw new BatchworkError("batchwork: the request was not intercepted while building the embedding body."); | ||
| }; | ||
| var captureImageOne = async (model, request, customId, maxRequestBytes) => { | ||
| try { | ||
| await generateImage({ | ||
| aspectRatio: request.aspectRatio, | ||
| maxImagesPerCall: request.n, | ||
| maxRetries: 0, | ||
| model, | ||
| n: request.n, | ||
| prompt: request.prompt, | ||
| providerOptions: request.providerOptions, | ||
| seed: request.seed, | ||
| size: request.size | ||
| }); | ||
| } catch (error) { | ||
| return bodyFromCapture(error, customId, maxRequestBytes); | ||
| } | ||
| throw new BatchworkError("batchwork: the request was not intercepted while building the image body."); | ||
| }; | ||
| var assignCustomIds = (requests) => { | ||
| const seen = new Set; | ||
| return requests.map((request, index) => { | ||
| const customId = request.customId ?? `request-${index}`; | ||
| if (seen.has(customId)) { | ||
| throw new BatchworkError(`batchwork: duplicate customId "${customId}". customId values must be unique within a batch.`); | ||
| } | ||
| seen.add(customId); | ||
| return { customId, request }; | ||
| }); | ||
| }; | ||
| var buildRequestBodies = async (resolved, requests, defaults, credentials, rawLimits) => { | ||
| const limits = resolveBatchLimits(rawLimits); | ||
| if (requests.length > limits.maxRequests) { | ||
| throw new BatchworkError(`batchwork: requests length ${requests.length} exceeds the ${limits.maxRequests} request limit.`); | ||
| } | ||
| const model = await createCaptureModel(resolved, credentials, captureFetch); | ||
| const items = assignCustomIds(requests); | ||
| return await mapWithConcurrency(items, limits.captureConcurrency, async (item) => { | ||
| const built = await captureOne(model, mergeDefaults(item.request, defaults), item.customId, limits.maxRequestBytes); | ||
| assertByteLength(`request "${item.customId}"`, JSON.stringify(built.body), limits.maxRequestBytes); | ||
| return built; | ||
| }); | ||
| }; | ||
| var buildEmbeddingBodies = async (resolved, requests, credentials, rawLimits) => { | ||
| const limits = resolveBatchLimits(rawLimits); | ||
| if (requests.length > limits.maxRequests) { | ||
| throw new BatchworkError(`batchwork: requests length ${requests.length} exceeds the ${limits.maxRequests} request limit.`); | ||
| } | ||
| const model = await createCaptureEmbeddingModel(resolved, credentials, captureFetch); | ||
| const items = assignCustomIds(requests); | ||
| return await mapWithConcurrency(items, limits.captureConcurrency, async (item) => { | ||
| const built = await captureEmbeddingOne(model, item.request, item.customId, limits.maxRequestBytes); | ||
| assertByteLength(`request "${item.customId}"`, JSON.stringify(built.body), limits.maxRequestBytes); | ||
| return built; | ||
| }); | ||
| }; | ||
| var mergeImageDefaults = (request, defaults) => { | ||
| if (!defaults) { | ||
| return request; | ||
| } | ||
| return { ...defaults, ...request }; | ||
| }; | ||
| var buildImageBodies = async (resolved, requests, defaults, credentials, rawLimits) => { | ||
| const limits = resolveBatchLimits(rawLimits); | ||
| if (requests.length > limits.maxRequests) { | ||
| throw new BatchworkError(`batchwork: requests length ${requests.length} exceeds the ${limits.maxRequests} request limit.`); | ||
| } | ||
| const model = await createCaptureImageModel(resolved, credentials, captureFetch); | ||
| const items = assignCustomIds(requests); | ||
| return await mapWithConcurrency(items, limits.captureConcurrency, async (item) => { | ||
| const built = await captureImageOne(model, mergeImageDefaults(item.request, defaults), item.customId, limits.maxRequestBytes); | ||
| assertByteLength(`request "${item.customId}"`, JSON.stringify(built.body), limits.maxRequestBytes); | ||
| return built; | ||
| }); | ||
| }; | ||
| // src/batch.ts | ||
| var pickCredentials = (source) => ({ | ||
| apiKey: source.apiKey, | ||
| baseURL: source.baseURL, | ||
| headers: source.headers | ||
| }); | ||
| var providerFromRef = (ref) => { | ||
| if (ref.provider) { | ||
| return ref.provider; | ||
| } | ||
| if (ref.model !== undefined) { | ||
| return resolveModel(ref.model).provider; | ||
| } | ||
| throw new BatchworkError("batchwork: provide `provider` or `model` to identify the batch."); | ||
| }; | ||
| var submitText = async (options) => { | ||
| if (options.requests.length === 0) { | ||
| throw new BatchworkError("batchwork: `requests` must not be empty."); | ||
| } | ||
| const resolved = resolveModel(options.model); | ||
| const credentials = pickCredentials(options); | ||
| const limits = resolveBatchLimits(options.limits); | ||
| const adapter = getAdapter(resolved.provider); | ||
| const built = await buildRequestBodies(resolved, options.requests, options.defaults, credentials, limits); | ||
| const snapshot = await adapter.submit({ | ||
| built, | ||
| credentials, | ||
| endpoint: built[0]?.endpoint ?? "", | ||
| limits, | ||
| metadata: options.metadata, | ||
| modelId: resolved.modelId | ||
| }); | ||
| return new BatchJob(adapter, credentials, snapshot); | ||
| }; | ||
| var submitEmbeddings = async (options) => { | ||
| if (options.requests.length === 0) { | ||
| throw new BatchworkError("batchwork: `requests` must not be empty."); | ||
| } | ||
| const resolved = resolveModel(options.model); | ||
| if (!EMBEDDING_PROVIDERS.has(resolved.provider)) { | ||
| throw unsupportedEmbeddingProvider(resolved.provider); | ||
| } | ||
| const credentials = pickCredentials(options); | ||
| const limits = resolveBatchLimits(options.limits); | ||
| const adapter = getAdapter(resolved.provider); | ||
| const built = await buildEmbeddingBodies(resolved, options.requests, credentials, limits); | ||
| const snapshot = await adapter.submit({ | ||
| built, | ||
| credentials, | ||
| endpoint: built[0]?.endpoint ?? "", | ||
| limits, | ||
| metadata: options.metadata, | ||
| modelId: resolved.modelId | ||
| }); | ||
| return new BatchJob(adapter, credentials, snapshot); | ||
| }; | ||
| var submitImages = async (options) => { | ||
| if (options.requests.length === 0) { | ||
| throw new BatchworkError("batchwork: `requests` must not be empty."); | ||
| } | ||
| const resolved = resolveModel(options.model); | ||
| if (!IMAGE_PROVIDERS.has(resolved.provider)) { | ||
| throw unsupportedImageProvider(resolved.provider); | ||
| } | ||
| const credentials = pickCredentials(options); | ||
| const limits = resolveBatchLimits(options.limits); | ||
| const adapter = getAdapter(resolved.provider); | ||
| const built = await buildImageBodies(resolved, options.requests, options.defaults, credentials, limits); | ||
| const snapshot = await adapter.submit({ | ||
| built, | ||
| credentials, | ||
| endpoint: built[0]?.endpoint ?? "", | ||
| limits, | ||
| metadata: options.metadata, | ||
| modelId: resolved.modelId | ||
| }); | ||
| return new BatchJob(adapter, credentials, snapshot); | ||
| }; | ||
| var batch = Object.assign(submitText, { | ||
| embeddings: submitEmbeddings, | ||
| images: submitImages, | ||
| text: submitText | ||
| }); | ||
| var batchEmbeddings = submitEmbeddings; | ||
| var batchImages = submitImages; | ||
| var getBatch = async (ref) => { | ||
| const adapter = getAdapter(providerFromRef(ref)); | ||
| const credentials = pickCredentials(ref); | ||
| const snapshot = await adapter.retrieve(ref.id, credentials); | ||
| return new BatchJob(adapter, credentials, snapshot); | ||
| }; | ||
| var getBatchResults = (ref) => { | ||
| const adapter = getAdapter(providerFromRef(ref)); | ||
| return adapter.results(ref.id, pickCredentials(ref)); | ||
| }; | ||
| var cancelBatch = async (ref) => { | ||
| const adapter = getAdapter(providerFromRef(ref)); | ||
| await adapter.cancel(ref.id, pickCredentials(ref)); | ||
| }; | ||
| export { resolveModel, batch, batchEmbeddings, batchImages, getBatch, getBatchResults, cancelBatch }; | ||
| //# debugId=21B5A5E0E7FCF0AE64756E2164756E21 | ||
| //# sourceMappingURL=chunk-bjkbtj1q.js.map |
| { | ||
| "version": 3, | ||
| "sources": ["../src/model.ts", "../src/body.ts", "../src/batch.ts"], | ||
| "sourcesContent": [ | ||
| "import type * as AnthropicModule from \"@ai-sdk/anthropic\";\nimport type * as GoogleModule from \"@ai-sdk/google\";\nimport type * as GroqModule from \"@ai-sdk/groq\";\nimport type * as MistralModule from \"@ai-sdk/mistral\";\nimport type * as OpenAIModule from \"@ai-sdk/openai\";\nimport type * as TogetherModule from \"@ai-sdk/togetherai\";\nimport type * as XaiModule from \"@ai-sdk/xai\";\nimport type { EmbeddingModel, ImageModel, LanguageModel } from \"ai\";\n\nimport { MissingDependencyError, UnsupportedProviderError } from \"./errors\";\nimport type { BatchProvider, ProviderCredentials } from \"./types\";\n\n/** A fetch implementation compatible with the AI SDK provider `fetch` option. */\nexport type CapturingFetch = typeof globalThis.fetch;\n\n/** OpenAI exposes several request shapes; we mirror the one the model implies. */\nexport type OpenAIModelKind = \"chat\" | \"responses\" | \"completion\";\n\nexport interface ResolvedModel {\n /** Relevant for OpenAI; other providers always use a single chat endpoint. */\n kind: OpenAIModelKind;\n modelId: string;\n provider: BatchProvider;\n}\n\n/**\n * Placeholder API key used only when building request bodies. Body building\n * intercepts the request before it is sent, so no real credential is needed —\n * but the provider refuses to construct a model without one.\n */\nconst CAPTURE_API_KEY = \"batchwork-capture\";\n\n/** The optional `@ai-sdk/*` package backing each provider. */\nconst PACKAGE_BY_PROVIDER: Record<\n BatchProvider,\n { label: string; specifier: string }\n> = {\n anthropic: { label: \"Anthropic\", specifier: \"@ai-sdk/anthropic\" },\n google: { label: \"Google Gemini\", specifier: \"@ai-sdk/google\" },\n groq: { label: \"Groq\", specifier: \"@ai-sdk/groq\" },\n mistral: { label: \"Mistral\", specifier: \"@ai-sdk/mistral\" },\n openai: { label: \"OpenAI\", specifier: \"@ai-sdk/openai\" },\n together: { label: \"Together AI\", specifier: \"@ai-sdk/togetherai\" },\n xai: { label: \"xAI\", specifier: \"@ai-sdk/xai\" },\n};\n\n/**\n * AI SDK provider id prefixes (the part before the first `.` in `model.provider`)\n * mapped to batch providers.\n */\nconst PROVIDER_BY_FAMILY: Record<string, BatchProvider> = {\n anthropic: \"anthropic\",\n google: \"google\",\n groq: \"groq\",\n mistral: \"mistral\",\n openai: \"openai\",\n together: \"together\",\n togetherai: \"together\",\n xai: \"xai\",\n};\n\n/** Aliases accepted in the `\"provider/model\"` string form. */\nconst PROVIDER_BY_ALIAS: Record<string, BatchProvider> = {\n ...PROVIDER_BY_FAMILY,\n gemini: \"google\",\n};\n\nconst splitOnce = (value: string, separator: string): [string, string] => {\n const index = value.indexOf(separator);\n if (index === -1) {\n return [value, \"\"];\n }\n return [value.slice(0, index), value.slice(index + separator.length)];\n};\n\nconst openaiKind = (suffix: string): OpenAIModelKind => {\n if (suffix === \"responses\") {\n return \"responses\";\n }\n if (suffix === \"completion\") {\n return \"completion\";\n }\n // Default to chat completions: the most widely supported batch endpoint.\n return \"chat\";\n};\n\n/** Resolve a `\"provider/model\"` string into a provider + model id. */\nconst resolveModelString = (value: string): ResolvedModel => {\n const [providerId, modelId] = splitOnce(value, \"/\");\n if (modelId === \"\") {\n throw new UnsupportedProviderError(value);\n }\n const provider = PROVIDER_BY_ALIAS[providerId];\n if (!provider) {\n throw new UnsupportedProviderError(providerId);\n }\n return { kind: \"chat\", modelId, provider };\n};\n\n/**\n * Resolve any AI SDK `model` (a `\"provider/model\"` string or a provider model\n * object such as `openai(\"gpt-5.5\")`) to a provider + model id + request\n * shape. Gateway/registry model objects whose `modelId` is itself\n * `\"provider/model\"` are also handled.\n */\nexport const resolveModel = (\n model: LanguageModel | EmbeddingModel | ImageModel\n): ResolvedModel => {\n if (typeof model === \"string\") {\n return resolveModelString(model);\n }\n\n const [family, suffix] = splitOnce(model.provider, \".\");\n const provider = PROVIDER_BY_FAMILY[family];\n if (provider === \"openai\") {\n return { kind: openaiKind(suffix), modelId: model.modelId, provider };\n }\n if (provider) {\n return { kind: \"chat\", modelId: model.modelId, provider };\n }\n // Gateway/registry providers carry the real target in the model id.\n if (model.modelId.includes(\"/\")) {\n return resolveModelString(model.modelId);\n }\n throw new UnsupportedProviderError(model.provider);\n};\n\nconst importProvider = (provider: BatchProvider): Promise<unknown> => {\n switch (provider) {\n case \"anthropic\": {\n return import(\"@ai-sdk/anthropic\");\n }\n case \"google\": {\n return import(\"@ai-sdk/google\");\n }\n case \"groq\": {\n return import(\"@ai-sdk/groq\");\n }\n case \"mistral\": {\n return import(\"@ai-sdk/mistral\");\n }\n case \"openai\": {\n return import(\"@ai-sdk/openai\");\n }\n case \"together\": {\n return import(\"@ai-sdk/togetherai\");\n }\n case \"xai\": {\n return import(\"@ai-sdk/xai\");\n }\n default: {\n return Promise.reject(new UnsupportedProviderError(provider));\n }\n }\n};\n\n/**\n * Import the `@ai-sdk/*` package for a provider, translating a missing optional\n * dependency into a `MissingDependencyError`. The importer is injectable (like\n * the capturing `fetch`) so tests can drive the failure paths without\n * uninstalling a package. Exported for testing; not part of the public API.\n */\nexport const loadProvider = async <T>(\n provider: BatchProvider,\n load: (target: BatchProvider) => Promise<unknown> = importProvider\n): Promise<T> => {\n try {\n return (await load(provider)) as T;\n } catch (error) {\n if (error instanceof UnsupportedProviderError) {\n throw error;\n }\n const { specifier, label } = PACKAGE_BY_PROVIDER[provider];\n throw new MissingDependencyError(specifier, label);\n }\n};\n\n/**\n * Construct an AI SDK model wired to a capturing `fetch`, used to derive the\n * provider request body for each batch item without making a network call.\n */\nexport const createCaptureModel = async (\n resolved: ResolvedModel,\n credentials: ProviderCredentials,\n fetchImpl: CapturingFetch\n): Promise<LanguageModel> => {\n const settings = {\n apiKey: credentials.apiKey ?? CAPTURE_API_KEY,\n baseURL: credentials.baseURL,\n fetch: fetchImpl,\n headers: credentials.headers,\n };\n\n switch (resolved.provider) {\n case \"openai\": {\n const { createOpenAI } =\n await loadProvider<typeof OpenAIModule>(\"openai\");\n const provider = createOpenAI(settings);\n if (resolved.kind === \"responses\") {\n return provider.responses(resolved.modelId);\n }\n if (resolved.kind === \"completion\") {\n return provider.completion(resolved.modelId);\n }\n return provider.chat(resolved.modelId);\n }\n case \"anthropic\": {\n const { createAnthropic } =\n await loadProvider<typeof AnthropicModule>(\"anthropic\");\n return createAnthropic(settings).messages(resolved.modelId);\n }\n case \"groq\": {\n const { createGroq } = await loadProvider<typeof GroqModule>(\"groq\");\n return createGroq(settings).languageModel(resolved.modelId);\n }\n case \"mistral\": {\n const { createMistral } =\n await loadProvider<typeof MistralModule>(\"mistral\");\n return createMistral(settings).languageModel(resolved.modelId);\n }\n case \"google\": {\n const { createGoogleGenerativeAI } =\n await loadProvider<typeof GoogleModule>(\"google\");\n return createGoogleGenerativeAI(settings).languageModel(resolved.modelId);\n }\n case \"xai\": {\n const { createXai } = await loadProvider<typeof XaiModule>(\"xai\");\n return createXai(settings).languageModel(resolved.modelId);\n }\n case \"together\": {\n const { createTogetherAI } =\n await loadProvider<typeof TogetherModule>(\"together\");\n return createTogetherAI(settings).languageModel(resolved.modelId);\n }\n default: {\n throw new UnsupportedProviderError(resolved.provider);\n }\n }\n};\n\n/**\n * Providers whose batch API accepts embeddings. Together is excluded: it exposes\n * an embedding model, but its batch endpoint rejects `/v1/embeddings`. Anthropic,\n * Groq, and xAI have no embedding model at all.\n */\nexport const EMBEDDING_PROVIDERS = new Set<BatchProvider>([\n \"google\",\n \"mistral\",\n \"openai\",\n]);\n\nexport const unsupportedEmbeddingProvider = (\n provider: BatchProvider\n): UnsupportedProviderError =>\n new UnsupportedProviderError(\n provider,\n `batchwork: provider \"${provider}\" does not offer batch embeddings. Embeddings are supported for: openai, mistral, google.`\n );\n\n/**\n * Construct an AI SDK text embedding model wired to a capturing `fetch`, used to\n * derive the provider embedding request body for each batch item without making\n * a network call. Throws for providers without an embedding model.\n */\nexport const createCaptureEmbeddingModel = async (\n resolved: ResolvedModel,\n credentials: ProviderCredentials,\n fetchImpl: CapturingFetch\n): Promise<EmbeddingModel> => {\n const settings = {\n apiKey: credentials.apiKey ?? CAPTURE_API_KEY,\n baseURL: credentials.baseURL,\n fetch: fetchImpl,\n headers: credentials.headers,\n };\n\n switch (resolved.provider) {\n case \"openai\": {\n const { createOpenAI } =\n await loadProvider<typeof OpenAIModule>(\"openai\");\n return createOpenAI(settings).embeddingModel(resolved.modelId);\n }\n case \"mistral\": {\n const { createMistral } =\n await loadProvider<typeof MistralModule>(\"mistral\");\n return createMistral(settings).embeddingModel(resolved.modelId);\n }\n case \"google\": {\n const { createGoogleGenerativeAI } =\n await loadProvider<typeof GoogleModule>(\"google\");\n return createGoogleGenerativeAI(settings).embeddingModel(\n resolved.modelId\n );\n }\n default: {\n throw unsupportedEmbeddingProvider(resolved.provider);\n }\n }\n};\n\n/**\n * Providers whose batch API accepts image generation. OpenAI and xAI expose\n * `/v1/images/generations` as a batch endpoint; Google runs Gemini image models\n * through `:batchGenerateContent`. Imagen (Google `:predict`) is not\n * batch-supported, Together's batch API is chat/audio only, and Anthropic, Groq,\n * and Mistral have no image model.\n */\nexport const IMAGE_PROVIDERS = new Set<BatchProvider>([\n \"google\",\n \"openai\",\n \"xai\",\n]);\n\nexport const unsupportedImageProvider = (\n provider: BatchProvider\n): UnsupportedProviderError =>\n new UnsupportedProviderError(\n provider,\n `batchwork: provider \"${provider}\" does not offer batch image generation. Image batches are supported for: openai, google, xai.`\n );\n\n/**\n * Construct an AI SDK image model wired to a capturing `fetch`, used to derive\n * the provider image-generation request body for each batch item without making\n * a network call. Throws for providers without batch image support.\n */\nexport const createCaptureImageModel = async (\n resolved: ResolvedModel,\n credentials: ProviderCredentials,\n fetchImpl: CapturingFetch\n): Promise<ImageModel> => {\n const settings = {\n apiKey: credentials.apiKey ?? CAPTURE_API_KEY,\n baseURL: credentials.baseURL,\n fetch: fetchImpl,\n headers: credentials.headers,\n };\n\n switch (resolved.provider) {\n case \"openai\": {\n const { createOpenAI } =\n await loadProvider<typeof OpenAIModule>(\"openai\");\n return createOpenAI(settings).image(resolved.modelId);\n }\n case \"google\": {\n const { createGoogleGenerativeAI } =\n await loadProvider<typeof GoogleModule>(\"google\");\n return createGoogleGenerativeAI(settings).image(resolved.modelId);\n }\n case \"xai\": {\n const { createXai } = await loadProvider<typeof XaiModule>(\"xai\");\n return createXai(settings).image(resolved.modelId);\n }\n default: {\n throw unsupportedImageProvider(resolved.provider);\n }\n }\n};\n", | ||
| "import { embed, generateImage, generateText } from \"ai\";\nimport type { EmbeddingModel, ImageModel, LanguageModel } from \"ai\";\n\nimport { BatchworkError } from \"./errors\";\nimport {\n assertByteLength,\n mapWithConcurrency,\n resolveBatchLimits,\n} from \"./limits\";\nimport type { ResolvedBatchLimits } from \"./limits\";\nimport {\n createCaptureEmbeddingModel,\n createCaptureImageModel,\n createCaptureModel,\n} from \"./model\";\nimport type { CapturingFetch, ResolvedModel } from \"./model\";\nimport type {\n BatchDefaults,\n BatchEmbeddingRequest,\n BatchImageDefaults,\n BatchImageRequest,\n BatchLimits,\n BatchRequest,\n ProviderCredentials,\n} from \"./types\";\n\ntype GenerateTextInput = Parameters<typeof generateText>[0];\n\n/** A provider request body derived from a single batch item. */\nexport interface BuiltRequest {\n /** The serialized provider request body (becomes the batch line). */\n body: Record<string, unknown>;\n customId: string;\n /** API endpoint path the model targets, e.g. `/v1/chat/completions`. */\n endpoint: string;\n}\n\nconst MAX_CAUSE_DEPTH = 10;\n\n/**\n * Thrown by the capturing `fetch` to abort the request after its body has been\n * serialized. The body travels inside the error (not shared state), so capture\n * is correct even under concurrency.\n */\nclass CaptureSignalError extends Error {\n readonly url: string;\n readonly rawBody: string;\n\n constructor(url: string, rawBody: string) {\n super(\"batchwork:capture\");\n this.name = \"CaptureSignalError\";\n this.url = url;\n this.rawBody = rawBody;\n }\n}\n\nconst resolveUrl = (input: string | URL | Request): string => {\n if (typeof input === \"string\") {\n return input;\n }\n if (input instanceof URL) {\n return input.toString();\n }\n return input.url;\n};\n\nconst extractBody = (init?: RequestInit): string => {\n const body = init?.body;\n if (typeof body === \"string\") {\n return body;\n }\n if (body instanceof Uint8Array) {\n return new TextDecoder().decode(body);\n }\n throw new BatchworkError(\n \"batchwork: unable to read the provider request body during capture.\"\n );\n};\n\n// `CapturingFetch` is `typeof fetch`, whose shape varies by runtime types (e.g.\n// Bun adds a required `preconnect` method). We only ever call it as a plain\n// fetch, so cast the bare implementation rather than stub the extra members.\nconst captureFetch = ((input: string | URL | Request, init?: RequestInit) =>\n Promise.reject(\n new CaptureSignalError(resolveUrl(input), extractBody(init))\n )) as unknown as CapturingFetch;\n\nconst findCapture = (error: unknown): CaptureSignalError | undefined => {\n let current: unknown = error;\n let depth = 0;\n while (current && depth < MAX_CAUSE_DEPTH) {\n if (current instanceof CaptureSignalError) {\n return current;\n }\n current = (current as { cause?: unknown }).cause;\n depth += 1;\n }\n};\n\nconst endpointFromUrl = (url: string): string => {\n try {\n return new URL(url).pathname;\n } catch {\n return url;\n }\n};\n\nconst mergeDefaults = (\n request: BatchRequest,\n defaults: BatchDefaults | undefined\n): BatchRequest => {\n if (!defaults) {\n return request;\n }\n return { ...defaults, ...request };\n};\n\n/**\n * Map a batch request to AI SDK `generateText` input. Fields are listed\n * explicitly so `customId` never leaks into the provider request.\n */\nconst toGenerateInput = (\n model: LanguageModel,\n request: BatchRequest\n): GenerateTextInput =>\n // `prompt`/`messages` form a discriminated union in the AI SDK types; we\n // pass both keys and let `generateText` validate the XOR at runtime.\n ({\n frequencyPenalty: request.frequencyPenalty,\n maxOutputTokens: request.maxOutputTokens,\n maxRetries: 0,\n messages: request.messages,\n model,\n presencePenalty: request.presencePenalty,\n prompt: request.prompt,\n providerOptions: request.providerOptions,\n seed: request.seed,\n stopSequences: request.stopSequences,\n system: request.system,\n temperature: request.temperature,\n toolChoice: request.toolChoice,\n tools: request.tools,\n topK: request.topK,\n topP: request.topP,\n }) as GenerateTextInput;\n\n/**\n * Turn a thrown capture into a {@link BuiltRequest}. A genuine failure (one that\n * never reached the capturing `fetch`, e.g. an invalid prompt) is rethrown.\n */\nconst bodyFromCapture = (\n error: unknown,\n customId: string,\n maxRequestBytes: number\n): BuiltRequest => {\n const capture = findCapture(error);\n if (capture) {\n assertByteLength(`request \"${customId}\"`, capture.rawBody, maxRequestBytes);\n return {\n body: JSON.parse(capture.rawBody) as Record<string, unknown>,\n customId,\n endpoint: endpointFromUrl(capture.url),\n };\n }\n throw error;\n};\n\nconst captureOne = async (\n model: LanguageModel,\n request: BatchRequest,\n customId: string,\n maxRequestBytes: number\n): Promise<BuiltRequest> => {\n try {\n await generateText(toGenerateInput(model, request));\n } catch (error) {\n return bodyFromCapture(error, customId, maxRequestBytes);\n }\n throw new BatchworkError(\n \"batchwork: the request was not intercepted while building the batch body.\"\n );\n};\n\nconst captureEmbeddingOne = async (\n model: EmbeddingModel,\n request: BatchEmbeddingRequest,\n customId: string,\n maxRequestBytes: number\n): Promise<BuiltRequest> => {\n try {\n // `maxRetries: 0` is load-bearing: with retries enabled the capture error\n // is wrapped in a `RetryError` (under `.errors`, not `.cause`) and\n // `findCapture`'s cause walk would miss it.\n await embed({\n maxRetries: 0,\n model,\n providerOptions: request.providerOptions,\n value: request.value,\n });\n } catch (error) {\n return bodyFromCapture(error, customId, maxRequestBytes);\n }\n throw new BatchworkError(\n \"batchwork: the request was not intercepted while building the embedding body.\"\n );\n};\n\nconst captureImageOne = async (\n model: ImageModel,\n request: BatchImageRequest,\n customId: string,\n maxRequestBytes: number\n): Promise<BuiltRequest> => {\n try {\n await generateImage({\n aspectRatio: request.aspectRatio,\n // `maxImagesPerCall: n` forces a single `doGenerate` call so the captured\n // body carries the requested `n` rather than a fanned-out per-call count.\n maxImagesPerCall: request.n,\n // `maxRetries: 0` is load-bearing: with retries enabled the capture error\n // is wrapped in a `RetryError` (under `.errors`, not `.cause`) and\n // `findCapture`'s cause walk would miss it.\n maxRetries: 0,\n model,\n n: request.n,\n prompt: request.prompt,\n providerOptions: request.providerOptions,\n seed: request.seed,\n size: request.size,\n });\n } catch (error) {\n return bodyFromCapture(error, customId, maxRequestBytes);\n }\n throw new BatchworkError(\n \"batchwork: the request was not intercepted while building the image body.\"\n );\n};\n\n/**\n * Assign and validate a unique `customId` for each request (sequentially, so\n * duplicates are reported deterministically before bodies are captured in\n * parallel). Auto-generates `request-{index}` when omitted.\n */\nconst assignCustomIds = <T extends { customId?: string }>(\n requests: readonly T[]\n): { customId: string; request: T }[] => {\n const seen = new Set<string>();\n return requests.map((request, index) => {\n const customId = request.customId ?? `request-${index}`;\n if (seen.has(customId)) {\n throw new BatchworkError(\n `batchwork: duplicate customId \"${customId}\". customId values must be unique within a batch.`\n );\n }\n seen.add(customId);\n return { customId, request };\n });\n};\n\n/**\n * Derive provider request bodies for every batch item by running each through\n * the AI SDK with a capturing `fetch`. This reuses the AI SDK's full message,\n * tool, and multimodal conversion, so the body matches what `generateText`\n * would send — minus the network call.\n */\nexport const buildRequestBodies = async (\n resolved: ResolvedModel,\n requests: readonly BatchRequest[],\n defaults: BatchDefaults | undefined,\n credentials: ProviderCredentials,\n rawLimits?: BatchLimits | ResolvedBatchLimits\n): Promise<BuiltRequest[]> => {\n const limits = resolveBatchLimits(rawLimits);\n if (requests.length > limits.maxRequests) {\n throw new BatchworkError(\n `batchwork: requests length ${requests.length} exceeds the ${limits.maxRequests} request limit.`\n );\n }\n const model = await createCaptureModel(resolved, credentials, captureFetch);\n const items = assignCustomIds(requests);\n\n return await mapWithConcurrency(\n items,\n limits.captureConcurrency,\n async (item) => {\n const built = await captureOne(\n model,\n mergeDefaults(item.request, defaults),\n item.customId,\n limits.maxRequestBytes\n );\n assertByteLength(\n `request \"${item.customId}\"`,\n JSON.stringify(built.body),\n limits.maxRequestBytes\n );\n return built;\n }\n );\n};\n\n/**\n * Derive provider embedding request bodies for every batch item by running each\n * through the AI SDK `embed` with a capturing `fetch`. Mirrors\n * {@link buildRequestBodies} for the embedding endpoint; each item maps to a\n * single embedding (`input: [value]`), correlated by `customId`.\n */\nexport const buildEmbeddingBodies = async (\n resolved: ResolvedModel,\n requests: readonly BatchEmbeddingRequest[],\n credentials: ProviderCredentials,\n rawLimits?: BatchLimits | ResolvedBatchLimits\n): Promise<BuiltRequest[]> => {\n const limits = resolveBatchLimits(rawLimits);\n if (requests.length > limits.maxRequests) {\n throw new BatchworkError(\n `batchwork: requests length ${requests.length} exceeds the ${limits.maxRequests} request limit.`\n );\n }\n const model = await createCaptureEmbeddingModel(\n resolved,\n credentials,\n captureFetch\n );\n const items = assignCustomIds(requests);\n\n return await mapWithConcurrency(\n items,\n limits.captureConcurrency,\n async (item) => {\n const built = await captureEmbeddingOne(\n model,\n item.request,\n item.customId,\n limits.maxRequestBytes\n );\n assertByteLength(\n `request \"${item.customId}\"`,\n JSON.stringify(built.body),\n limits.maxRequestBytes\n );\n return built;\n }\n );\n};\n\nconst mergeImageDefaults = (\n request: BatchImageRequest,\n defaults: BatchImageDefaults | undefined\n): BatchImageRequest => {\n if (!defaults) {\n return request;\n }\n return { ...defaults, ...request };\n};\n\n/**\n * Derive provider image-generation request bodies for every batch item by\n * running each through the AI SDK `generateImage` with a capturing `fetch`.\n * Mirrors {@link buildRequestBodies} for the image endpoint; each item maps to a\n * single image-generation call, correlated by `customId`.\n */\nexport const buildImageBodies = async (\n resolved: ResolvedModel,\n requests: readonly BatchImageRequest[],\n defaults: BatchImageDefaults | undefined,\n credentials: ProviderCredentials,\n rawLimits?: BatchLimits | ResolvedBatchLimits\n): Promise<BuiltRequest[]> => {\n const limits = resolveBatchLimits(rawLimits);\n if (requests.length > limits.maxRequests) {\n throw new BatchworkError(\n `batchwork: requests length ${requests.length} exceeds the ${limits.maxRequests} request limit.`\n );\n }\n const model = await createCaptureImageModel(\n resolved,\n credentials,\n captureFetch\n );\n const items = assignCustomIds(requests);\n\n return await mapWithConcurrency(\n items,\n limits.captureConcurrency,\n async (item) => {\n const built = await captureImageOne(\n model,\n mergeImageDefaults(item.request, defaults),\n item.customId,\n limits.maxRequestBytes\n );\n assertByteLength(\n `request \"${item.customId}\"`,\n JSON.stringify(built.body),\n limits.maxRequestBytes\n );\n return built;\n }\n );\n};\n", | ||
| "import {\n buildEmbeddingBodies,\n buildImageBodies,\n buildRequestBodies,\n} from \"./body\";\nimport { BatchworkError } from \"./errors\";\nimport { BatchJob } from \"./job\";\nimport { resolveBatchLimits } from \"./limits\";\nimport {\n EMBEDDING_PROVIDERS,\n IMAGE_PROVIDERS,\n resolveModel,\n unsupportedEmbeddingProvider,\n unsupportedImageProvider,\n} from \"./model\";\nimport { getAdapter } from \"./providers\";\nimport type {\n BatchEmbeddingsOptions,\n BatchImageOptions,\n BatchOptions,\n BatchProvider,\n BatchRef,\n BatchResult,\n ProviderCredentials,\n} from \"./types\";\n\nconst pickCredentials = (source: ProviderCredentials): ProviderCredentials => ({\n apiKey: source.apiKey,\n baseURL: source.baseURL,\n headers: source.headers,\n});\n\nconst providerFromRef = (ref: BatchRef): BatchProvider => {\n if (ref.provider) {\n return ref.provider;\n }\n if (ref.model !== undefined) {\n return resolveModel(ref.model).provider;\n }\n throw new BatchworkError(\n \"batchwork: provide `provider` or `model` to identify the batch.\"\n );\n};\n\n/**\n * Submit a batch of text/chat requests to the model's provider and return a\n * handle. Reachable as both `batch()` (shorthand) and `batch.text()`.\n *\n * Resolves immediately once the batch is accepted — it does not wait for\n * processing. Use the returned {@link BatchJob} to poll, wait, or stream\n * results.\n *\n * @example\n * const job = await batch({\n * model: openai(\"gpt-5.5\"),\n * requests: [{ customId: \"a\", prompt: \"Say hi\" }],\n * });\n * const results = await job.wait().then(() => job.collect());\n */\nconst submitText = async (options: BatchOptions): Promise<BatchJob> => {\n if (options.requests.length === 0) {\n throw new BatchworkError(\"batchwork: `requests` must not be empty.\");\n }\n\n const resolved = resolveModel(options.model);\n const credentials = pickCredentials(options);\n const limits = resolveBatchLimits(options.limits);\n const adapter = getAdapter(resolved.provider);\n\n const built = await buildRequestBodies(\n resolved,\n options.requests,\n options.defaults,\n credentials,\n limits\n );\n const snapshot = await adapter.submit({\n built,\n credentials,\n endpoint: built[0]?.endpoint ?? \"\",\n limits,\n metadata: options.metadata,\n modelId: resolved.modelId,\n });\n\n return new BatchJob(adapter, credentials, snapshot);\n};\n\n/**\n * Submit a batch of embedding requests to the model's provider and return a\n * handle. Each request's `value` produces one vector, correlated by `customId`\n * via {@link BatchJob.results}. Supported for OpenAI, Mistral, Together, and\n * Google; other providers throw {@link UnsupportedProviderError}.\n *\n * @example\n * const job = await batch.embeddings({\n * model: openai.embeddingModel(\"text-embedding-3-small\"),\n * requests: [{ customId: \"a\", value: \"hello world\" }],\n * });\n * const results = await job.wait().then(() => job.collect());\n * for (const r of results) {\n * console.log(r.customId, r.embedding?.length);\n * }\n */\nconst submitEmbeddings = async (\n options: BatchEmbeddingsOptions\n): Promise<BatchJob> => {\n if (options.requests.length === 0) {\n throw new BatchworkError(\"batchwork: `requests` must not be empty.\");\n }\n\n const resolved = resolveModel(options.model);\n if (!EMBEDDING_PROVIDERS.has(resolved.provider)) {\n throw unsupportedEmbeddingProvider(resolved.provider);\n }\n const credentials = pickCredentials(options);\n const limits = resolveBatchLimits(options.limits);\n const adapter = getAdapter(resolved.provider);\n\n const built = await buildEmbeddingBodies(\n resolved,\n options.requests,\n credentials,\n limits\n );\n const snapshot = await adapter.submit({\n built,\n credentials,\n endpoint: built[0]?.endpoint ?? \"\",\n limits,\n metadata: options.metadata,\n modelId: resolved.modelId,\n });\n\n return new BatchJob(adapter, credentials, snapshot);\n};\n\n/**\n * Submit a batch of image-generation requests to the model's provider and\n * return a handle. Each request's `prompt` produces one or more images,\n * correlated by `customId` via {@link BatchJob.results}. Supported for OpenAI\n * and Google; other providers throw {@link UnsupportedProviderError}.\n *\n * @example\n * const job = await batch.images({\n * model: openai.image(\"gpt-image-2\"),\n * requests: [{ customId: \"a\", prompt: \"a red bicycle\" }],\n * });\n * const results = await job.wait().then(() => job.collect());\n * for (const r of results) {\n * console.log(r.customId, r.images?.length);\n * }\n */\nconst submitImages = async (options: BatchImageOptions): Promise<BatchJob> => {\n if (options.requests.length === 0) {\n throw new BatchworkError(\"batchwork: `requests` must not be empty.\");\n }\n\n const resolved = resolveModel(options.model);\n if (!IMAGE_PROVIDERS.has(resolved.provider)) {\n throw unsupportedImageProvider(resolved.provider);\n }\n const credentials = pickCredentials(options);\n const limits = resolveBatchLimits(options.limits);\n const adapter = getAdapter(resolved.provider);\n\n const built = await buildImageBodies(\n resolved,\n options.requests,\n options.defaults,\n credentials,\n limits\n );\n const snapshot = await adapter.submit({\n built,\n credentials,\n endpoint: built[0]?.endpoint ?? \"\",\n limits,\n metadata: options.metadata,\n modelId: resolved.modelId,\n });\n\n return new BatchJob(adapter, credentials, snapshot);\n};\n\n/**\n * Submit a batch of requests and return a {@link BatchJob} handle.\n *\n * Callable directly as a shorthand for text/chat batches, with one method per\n * modality:\n *\n * - `batch()` / {@link batch.text} — text & chat completions\n * - {@link batch.embeddings} — embedding vectors\n * - {@link batch.images} — image generation\n *\n * @example\n * const job = await batch({\n * model: openai(\"gpt-5.5\"),\n * requests: [{ customId: \"a\", prompt: \"Say hi\" }],\n * });\n */\nexport const batch = Object.assign(submitText, {\n /** Submit a batch of embedding requests. */\n embeddings: submitEmbeddings,\n /** Submit a batch of image-generation requests. */\n images: submitImages,\n /** Submit a batch of text/chat requests. Equivalent to calling `batch()`. */\n text: submitText,\n});\n\n/**\n * @deprecated Use {@link batch.embeddings} instead. Kept as a standalone alias\n * for backward compatibility; it will be removed in a future major.\n */\nexport const batchEmbeddings = submitEmbeddings;\n\n/**\n * @deprecated Use {@link batch.images} instead. Kept as a standalone alias for\n * backward compatibility; it will be removed in a future major.\n */\nexport const batchImages = submitImages;\n\n/**\n * Rehydrate a {@link BatchJob} for an existing batch id (e.g. one persisted\n * after submission). Identify the provider with `provider` or `model`.\n */\nexport const getBatch = async (ref: BatchRef): Promise<BatchJob> => {\n const adapter = getAdapter(providerFromRef(ref));\n const credentials = pickCredentials(ref);\n const snapshot = await adapter.retrieve(ref.id, credentials);\n return new BatchJob(adapter, credentials, snapshot);\n};\n\n/** Stream the results of an existing batch by id, without a handle. */\nexport const getBatchResults = (ref: BatchRef): AsyncGenerator<BatchResult> => {\n const adapter = getAdapter(providerFromRef(ref));\n return adapter.results(ref.id, pickCredentials(ref));\n};\n\n/** Request cancellation of an existing batch by id. */\nexport const cancelBatch = async (ref: BatchRef): Promise<void> => {\n const adapter = getAdapter(providerFromRef(ref));\n await adapter.cancel(ref.id, pickCredentials(ref));\n};\n" | ||
| ], | ||
| "mappings": "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", | ||
| "debugId": "21B5A5E0E7FCF0AE64756E2164756E21", | ||
| "names": [] | ||
| } |
| // src/errors.ts | ||
| class BatchworkError extends Error { | ||
| constructor(message, options) { | ||
| super(message, options); | ||
| this.name = "BatchworkError"; | ||
| } | ||
| } | ||
| class UnsupportedProviderError extends BatchworkError { | ||
| provider; | ||
| constructor(provider, detail) { | ||
| super(detail ?? `batchwork: provider "${provider}" is not supported yet. Supported providers: openai, anthropic, google, groq, mistral, together, xai.`); | ||
| this.name = "UnsupportedProviderError"; | ||
| this.provider = provider; | ||
| } | ||
| } | ||
| class MissingDependencyError extends BatchworkError { | ||
| constructor(pkg, provider) { | ||
| super(`batchwork: install \`${pkg}\` to batch ${provider} models (\`npm install ${pkg}\`).`); | ||
| this.name = "MissingDependencyError"; | ||
| } | ||
| } | ||
| // src/job.ts | ||
| var DEFAULT_POLL_INTERVAL_MS = 15000; | ||
| var TERMINAL_STATUSES = new Set([ | ||
| "completed", | ||
| "failed", | ||
| "expired", | ||
| "cancelled" | ||
| ]); | ||
| var isTerminalStatus = (status) => TERMINAL_STATUSES.has(status); | ||
| var delay = (ms, signal) => new Promise((resolve, reject) => { | ||
| if (signal?.aborted) { | ||
| reject(new BatchworkError("batchwork: wait aborted.")); | ||
| return; | ||
| } | ||
| let timer; | ||
| const onAbort = () => { | ||
| clearTimeout(timer); | ||
| reject(new BatchworkError("batchwork: wait aborted.")); | ||
| }; | ||
| timer = setTimeout(() => { | ||
| signal?.removeEventListener("abort", onAbort); | ||
| resolve(); | ||
| }, ms); | ||
| signal?.addEventListener("abort", onAbort, { once: true }); | ||
| }); | ||
| class BatchJob { | ||
| provider; | ||
| id; | ||
| #adapter; | ||
| #credentials; | ||
| #snapshot; | ||
| constructor(adapter, credentials, snapshot) { | ||
| this.#adapter = adapter; | ||
| this.#credentials = credentials; | ||
| this.#snapshot = snapshot; | ||
| this.id = snapshot.id; | ||
| this.provider = snapshot.provider; | ||
| } | ||
| get status() { | ||
| return this.#snapshot.status; | ||
| } | ||
| get requestCounts() { | ||
| return this.#snapshot.requestCounts; | ||
| } | ||
| get snapshot() { | ||
| return this.#snapshot; | ||
| } | ||
| async poll() { | ||
| this.#snapshot = await this.#adapter.retrieve(this.id, this.#credentials); | ||
| return this.#snapshot; | ||
| } | ||
| async wait(options = {}) { | ||
| const interval = options.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS; | ||
| const deadline = options.timeoutMs ? Date.now() + options.timeoutMs : undefined; | ||
| let snapshot = await this.poll(); | ||
| options.onPoll?.(snapshot); | ||
| while (!isTerminalStatus(snapshot.status)) { | ||
| if (options.signal?.aborted) { | ||
| throw new BatchworkError("batchwork: wait aborted."); | ||
| } | ||
| if (deadline !== undefined && Date.now() > deadline) { | ||
| throw new BatchworkError(`batchwork: timed out waiting for batch "${this.id}".`); | ||
| } | ||
| await delay(interval, options.signal); | ||
| snapshot = await this.poll(); | ||
| options.onPoll?.(snapshot); | ||
| } | ||
| return snapshot; | ||
| } | ||
| results() { | ||
| return this.#adapter.results(this.id, this.#credentials); | ||
| } | ||
| async collect() { | ||
| const out = []; | ||
| for await (const result of this.results()) { | ||
| out.push(result); | ||
| } | ||
| return out; | ||
| } | ||
| async cancel() { | ||
| await this.#adapter.cancel(this.id, this.#credentials); | ||
| return await this.poll(); | ||
| } | ||
| } | ||
| // src/limits.ts | ||
| var DEFAULT_LIMITS = { | ||
| captureConcurrency: 16, | ||
| maxRequestBytes: 20 * 1024 * 1024, | ||
| maxRequests: 50000, | ||
| maxUploadBytes: 200 * 1024 * 1024 | ||
| }; | ||
| var encoder = new TextEncoder; | ||
| var positiveInteger = (name, value) => { | ||
| if (!(Number.isInteger(value) && value > 0)) { | ||
| throw new BatchworkError(`batchwork: limits.${name} must be a positive integer.`); | ||
| } | ||
| return value; | ||
| }; | ||
| var resolveBatchLimits = (limits) => ({ | ||
| captureConcurrency: positiveInteger("captureConcurrency", limits?.captureConcurrency ?? DEFAULT_LIMITS.captureConcurrency), | ||
| maxRequestBytes: positiveInteger("maxRequestBytes", limits?.maxRequestBytes ?? DEFAULT_LIMITS.maxRequestBytes), | ||
| maxRequests: positiveInteger("maxRequests", limits?.maxRequests ?? DEFAULT_LIMITS.maxRequests), | ||
| maxUploadBytes: positiveInteger("maxUploadBytes", limits?.maxUploadBytes ?? DEFAULT_LIMITS.maxUploadBytes) | ||
| }); | ||
| var byteLength = (value) => encoder.encode(value).length; | ||
| var assertByteCount = (label, bytes, maxBytes) => { | ||
| if (bytes > maxBytes) { | ||
| throw new BatchworkError(`batchwork: ${label} is ${bytes} bytes, exceeding the ${maxBytes} byte limit.`); | ||
| } | ||
| }; | ||
| var assertByteLength = (label, value, maxBytes) => { | ||
| assertByteCount(label, byteLength(value), maxBytes); | ||
| }; | ||
| var mapWithConcurrency = async (items, concurrency, mapper) => { | ||
| const results = []; | ||
| results.length = items.length; | ||
| let nextIndex = 0; | ||
| const workerCount = Math.min(concurrency, items.length); | ||
| const runNext = async () => { | ||
| const index = nextIndex; | ||
| nextIndex += 1; | ||
| if (index >= items.length) { | ||
| return; | ||
| } | ||
| results[index] = await mapper(items[index]); | ||
| await runNext(); | ||
| }; | ||
| await Promise.all(Array.from({ length: workerCount }, () => runNext())); | ||
| return results; | ||
| }; | ||
| // src/http.ts | ||
| var assertOk = (url, init, response) => { | ||
| if (!response.ok) { | ||
| throw new BatchworkError(`batchwork: ${init.method ?? "GET"} ${url} failed with ${response.status}.`); | ||
| } | ||
| }; | ||
| var requestJson = async (url, init) => { | ||
| const response = await fetch(url, init); | ||
| assertOk(url, init, response); | ||
| return await response.json(); | ||
| }; | ||
| var requestStream = async (url, init) => { | ||
| const response = await fetch(url, init); | ||
| assertOk(url, init, response); | ||
| if (!response.body) { | ||
| throw new BatchworkError(`batchwork: ${url} returned an empty body.`); | ||
| } | ||
| return response.body; | ||
| }; | ||
| // src/jsonl.ts | ||
| var NEWLINE = ` | ||
| `; | ||
| var DEFAULT_MAX_JSONL_LINE_BYTES = 20 * 1024 * 1024; | ||
| var NEWLINE_BYTES = byteLength(NEWLINE); | ||
| var resolveMaxLineBytes = (options) => { | ||
| const maxLineBytes = options?.maxLineBytes ?? DEFAULT_MAX_JSONL_LINE_BYTES; | ||
| if (!(Number.isInteger(maxLineBytes) && maxLineBytes > 0)) { | ||
| throw new BatchworkError("batchwork: JSONL maxLineBytes must be a positive integer."); | ||
| } | ||
| return maxLineBytes; | ||
| }; | ||
| var assertLineSize = (line, lineNumber, maxLineBytes) => { | ||
| const bytes = byteLength(line); | ||
| if (bytes > maxLineBytes) { | ||
| throw new BatchworkError(`batchwork: JSONL line ${lineNumber} is ${bytes} bytes, exceeding the ${maxLineBytes} byte limit.`); | ||
| } | ||
| }; | ||
| var parseLine = (line, lineNumber, maxLineBytes) => { | ||
| assertLineSize(line, lineNumber, maxLineBytes); | ||
| const trimmed = line.trim(); | ||
| if (trimmed.length === 0) { | ||
| return; | ||
| } | ||
| try { | ||
| return JSON.parse(trimmed); | ||
| } catch (error) { | ||
| throw new BatchworkError(`batchwork: invalid JSONL at line ${lineNumber}.`, { cause: error }); | ||
| } | ||
| }; | ||
| var resolveMaxBytes = (options) => { | ||
| const maxBytes = options?.maxBytes; | ||
| if (maxBytes !== undefined && !(Number.isInteger(maxBytes) && maxBytes > 0)) { | ||
| throw new BatchworkError("batchwork: JSONL maxBytes must be a positive integer."); | ||
| } | ||
| return maxBytes; | ||
| }; | ||
| var encodeJsonl = (items, options) => { | ||
| if (items.length === 0) { | ||
| return ""; | ||
| } | ||
| const lines = []; | ||
| const maxBytes = resolveMaxBytes(options); | ||
| const label = options?.label ?? "JSONL"; | ||
| let bytes = 0; | ||
| for (const item of items) { | ||
| const line = JSON.stringify(item); | ||
| if (line === undefined) { | ||
| throw new BatchworkError(`batchwork: ${label} contains a value that cannot be JSON encoded.`); | ||
| } | ||
| bytes += byteLength(line) + NEWLINE_BYTES; | ||
| if (maxBytes !== undefined) { | ||
| assertByteCount(label, bytes, maxBytes); | ||
| } | ||
| lines.push(line); | ||
| } | ||
| return `${lines.join(NEWLINE)}${NEWLINE}`; | ||
| }; | ||
| var isReadableStream = (source) => ("getReader" in source) && typeof source.getReader === "function"; | ||
| async function* toByteIterable(source) { | ||
| if (isReadableStream(source)) { | ||
| const reader = source.getReader(); | ||
| try { | ||
| let chunk = await reader.read(); | ||
| while (!chunk.done) { | ||
| if (chunk.value) { | ||
| yield chunk.value; | ||
| } | ||
| chunk = await reader.read(); | ||
| } | ||
| } finally { | ||
| reader.releaseLock(); | ||
| } | ||
| return; | ||
| } | ||
| yield* source; | ||
| } | ||
| async function* streamJsonl(source, options) { | ||
| const decoder = new TextDecoder; | ||
| const maxLineBytes = resolveMaxLineBytes(options); | ||
| let buffer = ""; | ||
| let lineNumber = 1; | ||
| for await (const chunk of toByteIterable(source)) { | ||
| buffer += decoder.decode(chunk, { stream: true }); | ||
| let newlineIndex = buffer.indexOf(NEWLINE); | ||
| while (newlineIndex !== -1) { | ||
| const line = buffer.slice(0, newlineIndex); | ||
| buffer = buffer.slice(newlineIndex + 1); | ||
| const parsed2 = parseLine(line, lineNumber, maxLineBytes); | ||
| if (parsed2 !== undefined) { | ||
| yield parsed2; | ||
| } | ||
| lineNumber += 1; | ||
| newlineIndex = buffer.indexOf(NEWLINE); | ||
| } | ||
| assertLineSize(buffer, lineNumber, maxLineBytes); | ||
| } | ||
| buffer += decoder.decode(); | ||
| const parsed = parseLine(buffer, lineNumber, maxLineBytes); | ||
| if (parsed !== undefined) { | ||
| yield parsed; | ||
| } | ||
| } | ||
| // src/payload.ts | ||
| var encodeJsonArrayPayload = ({ | ||
| items, | ||
| label, | ||
| maxBytes, | ||
| prefix, | ||
| suffix | ||
| }) => { | ||
| const encodedItems = []; | ||
| let bytes = byteLength(prefix) + byteLength(suffix); | ||
| assertByteCount(label, bytes, maxBytes); | ||
| for (const [index, item] of items.entries()) { | ||
| const encoded = JSON.stringify(item); | ||
| if (encoded === undefined) { | ||
| throw new BatchworkError(`batchwork: ${label} contains a value that cannot be JSON encoded.`); | ||
| } | ||
| bytes += byteLength(encoded); | ||
| if (index > 0) { | ||
| bytes += 1; | ||
| } | ||
| assertByteCount(label, bytes, maxBytes); | ||
| encodedItems.push(encoded); | ||
| } | ||
| return `${prefix}${encodedItems.join(",")}${suffix}`; | ||
| }; | ||
| // src/util.ts | ||
| var asRecord = (value) => { | ||
| if (typeof value === "object" && value !== null) { | ||
| return value; | ||
| } | ||
| return {}; | ||
| }; | ||
| var asString = (value) => typeof value === "string" ? value : undefined; | ||
| var asNumber = (value) => typeof value === "number" ? value : undefined; | ||
| var asArray = (value) => Array.isArray(value) ? value : []; | ||
| var asNumberArray = (value) => { | ||
| if (!Array.isArray(value) || value.length === 0) { | ||
| return; | ||
| } | ||
| const numbers = value.filter((item) => typeof item === "number"); | ||
| return numbers.length === value.length ? numbers : undefined; | ||
| }; | ||
| var omit = (obj, key) => { | ||
| const result = {}; | ||
| for (const [k, v] of Object.entries(obj)) { | ||
| if (k !== key) { | ||
| result[k] = v; | ||
| } | ||
| } | ||
| return result; | ||
| }; | ||
| var validDate = (date) => Number.isNaN(date.getTime()) ? undefined : date; | ||
| var toDate = (value) => { | ||
| if (typeof value === "string") { | ||
| return validDate(new Date(value)); | ||
| } | ||
| if (typeof value === "number") { | ||
| return validDate(new Date(value * 1000)); | ||
| } | ||
| }; | ||
| // src/providers/ids.ts | ||
| var SIMPLE_PROVIDER_ID = /^[A-Za-z0-9_-]+$/u; | ||
| var assertSimpleProviderId = (label, id) => { | ||
| if (!SIMPLE_PROVIDER_ID.test(id)) { | ||
| throw new BatchworkError(`batchwork: invalid ${label}.`); | ||
| } | ||
| return id; | ||
| }; | ||
| var assertPrefixedProviderId = (label, id, prefix) => { | ||
| const [actualPrefix, value, ...rest] = id.split("/"); | ||
| if (rest.length > 0 || actualPrefix !== prefix || !value || !SIMPLE_PROVIDER_ID.test(value)) { | ||
| throw new BatchworkError(`batchwork: invalid ${label}.`); | ||
| } | ||
| return id; | ||
| }; | ||
| // src/providers/anthropic.ts | ||
| var ANTHROPIC_BASE = "https://api.anthropic.com"; | ||
| var ANTHROPIC_VERSION = "2023-06-01"; | ||
| var apiKey = (credentials) => { | ||
| const key = credentials.apiKey ?? process.env.ANTHROPIC_API_KEY; | ||
| if (!key) { | ||
| throw new BatchworkError("batchwork: missing Anthropic API key. Set ANTHROPIC_API_KEY or pass `apiKey`."); | ||
| } | ||
| return key; | ||
| }; | ||
| var baseUrl = (credentials) => credentials.baseURL ?? ANTHROPIC_BASE; | ||
| var validateResultsUrl = (rawUrl, credentials) => { | ||
| let resultsUrl; | ||
| let expectedBase; | ||
| try { | ||
| resultsUrl = new URL(rawUrl); | ||
| expectedBase = new URL(baseUrl(credentials)); | ||
| } catch (error) { | ||
| throw new BatchworkError("batchwork: invalid Anthropic results_url.", { | ||
| cause: error | ||
| }); | ||
| } | ||
| if (resultsUrl.origin !== expectedBase.origin) { | ||
| throw new BatchworkError("batchwork: Anthropic results_url must match the configured API origin."); | ||
| } | ||
| if (resultsUrl.username || resultsUrl.password) { | ||
| throw new BatchworkError("batchwork: Anthropic results_url must not include credentials."); | ||
| } | ||
| return resultsUrl.toString(); | ||
| }; | ||
| var headers = (credentials) => ({ | ||
| "anthropic-version": ANTHROPIC_VERSION, | ||
| "content-type": "application/json", | ||
| "x-api-key": apiKey(credentials), | ||
| ...credentials.headers | ||
| }); | ||
| var mapStatus = (status) => { | ||
| if (status === "ended") { | ||
| return "completed"; | ||
| } | ||
| if (status === "canceling") { | ||
| return "cancelling"; | ||
| } | ||
| return "in_progress"; | ||
| }; | ||
| var normalizeSnapshot = (raw) => { | ||
| const obj = asRecord(raw); | ||
| const counts = asRecord(obj.request_counts); | ||
| const succeeded = asNumber(counts.succeeded) ?? 0; | ||
| const errored = asNumber(counts.errored) ?? 0; | ||
| const processing = asNumber(counts.processing) ?? 0; | ||
| const canceled = asNumber(counts.canceled) ?? 0; | ||
| const expired = asNumber(counts.expired) ?? 0; | ||
| return { | ||
| completedAt: toDate(obj.ended_at), | ||
| createdAt: toDate(obj.created_at), | ||
| expiresAt: toDate(obj.expires_at), | ||
| id: asString(obj.id) ?? "", | ||
| provider: "anthropic", | ||
| raw, | ||
| requestCounts: { | ||
| canceled, | ||
| completed: succeeded, | ||
| expired, | ||
| failed: errored, | ||
| processing, | ||
| total: succeeded + errored + processing + canceled + expired | ||
| }, | ||
| status: mapStatus(asString(obj.processing_status)) | ||
| }; | ||
| }; | ||
| var textFromMessage = (message) => { | ||
| const text = asArray(asRecord(message).content).map((block) => asRecord(block)).filter((block) => block.type === "text").map((block) => asString(block.text) ?? "").join(""); | ||
| return text.length > 0 ? text : undefined; | ||
| }; | ||
| var usageFromMessage = (message) => { | ||
| const usage = asRecord(asRecord(message).usage); | ||
| const inputTokens = asNumber(usage.input_tokens); | ||
| const outputTokens = asNumber(usage.output_tokens); | ||
| if (inputTokens === undefined && outputTokens === undefined) { | ||
| return; | ||
| } | ||
| return { | ||
| inputTokens, | ||
| outputTokens, | ||
| totalTokens: (inputTokens ?? 0) + (outputTokens ?? 0) | ||
| }; | ||
| }; | ||
| var normalizeResult = (line) => { | ||
| const obj = asRecord(line); | ||
| const customId = asString(obj.custom_id) ?? ""; | ||
| const result = asRecord(obj.result); | ||
| const type = asString(result.type); | ||
| if (type === "succeeded") { | ||
| return { | ||
| customId, | ||
| response: result.message, | ||
| status: "succeeded", | ||
| text: textFromMessage(result.message), | ||
| usage: usageFromMessage(result.message) | ||
| }; | ||
| } | ||
| if (type === "errored") { | ||
| const error = asRecord(result.error); | ||
| const nested = asRecord(error.error); | ||
| const source = Object.keys(nested).length > 0 ? nested : error; | ||
| return { | ||
| customId, | ||
| error: { | ||
| message: asString(source.message) ?? "Request errored.", | ||
| type: asString(source.type) | ||
| }, | ||
| response: result.error, | ||
| status: "errored" | ||
| }; | ||
| } | ||
| if (type === "expired") { | ||
| return { customId, status: "expired" }; | ||
| } | ||
| return { customId, status: "canceled" }; | ||
| }; | ||
| var submit = async (input) => { | ||
| const limits = resolveBatchLimits(input.limits); | ||
| const requests = input.built.map((item) => ({ | ||
| custom_id: item.customId, | ||
| params: omit(item.body, "stream") | ||
| })); | ||
| const body = encodeJsonArrayPayload({ | ||
| items: requests, | ||
| label: "batch upload payload", | ||
| maxBytes: limits.maxUploadBytes, | ||
| prefix: '{"requests":[', | ||
| suffix: "]}" | ||
| }); | ||
| const raw = await requestJson(`${baseUrl(input.credentials)}/v1/messages/batches`, { | ||
| body, | ||
| headers: headers(input.credentials), | ||
| method: "POST" | ||
| }); | ||
| return normalizeSnapshot(raw); | ||
| }; | ||
| var retrieve = async (id, credentials) => { | ||
| const batchId = assertSimpleProviderId("Anthropic batch id", id); | ||
| const raw = await requestJson(`${baseUrl(credentials)}/v1/messages/batches/${batchId}`, { headers: headers(credentials) }); | ||
| return normalizeSnapshot(raw); | ||
| }; | ||
| async function* results(id, credentials) { | ||
| const snapshot = await retrieve(id, credentials); | ||
| const resultsUrl = asString(asRecord(snapshot.raw).results_url); | ||
| if (!resultsUrl) { | ||
| throw new BatchworkError(`batchwork: results are not ready for batch "${id}" (status: ${snapshot.status}).`); | ||
| } | ||
| const stream = await requestStream(validateResultsUrl(resultsUrl, credentials), { | ||
| headers: headers(credentials), | ||
| redirect: "manual" | ||
| }); | ||
| for await (const line of streamJsonl(stream)) { | ||
| yield normalizeResult(line); | ||
| } | ||
| } | ||
| var cancel = async (id, credentials) => { | ||
| const batchId = assertSimpleProviderId("Anthropic batch id", id); | ||
| await requestJson(`${baseUrl(credentials)}/v1/messages/batches/${batchId}/cancel`, { | ||
| headers: headers(credentials), | ||
| method: "POST" | ||
| }); | ||
| }; | ||
| var anthropicAdapter = { | ||
| cancel, | ||
| id: "anthropic", | ||
| results, | ||
| retrieve, | ||
| submit | ||
| }; | ||
| // src/providers/google.ts | ||
| var GOOGLE_BASE = "https://generativelanguage.googleapis.com/v1beta"; | ||
| var GOOGLE_BATCH_PREFIX = "batches"; | ||
| var apiKey2 = (credentials) => { | ||
| const key = credentials.apiKey ?? process.env.GOOGLE_GENERATIVE_AI_API_KEY ?? process.env.GEMINI_API_KEY; | ||
| if (!key) { | ||
| throw new BatchworkError("batchwork: missing Google Gemini API key. Set GOOGLE_GENERATIVE_AI_API_KEY (or GEMINI_API_KEY) or pass `apiKey`."); | ||
| } | ||
| return key; | ||
| }; | ||
| var baseUrl2 = (credentials) => credentials.baseURL ?? GOOGLE_BASE; | ||
| var headers2 = (credentials) => ({ | ||
| "content-type": "application/json", | ||
| "x-goog-api-key": apiKey2(credentials), | ||
| ...credentials.headers | ||
| }); | ||
| var mapState = (state, done) => { | ||
| if (state) { | ||
| if (state.endsWith("SUCCEEDED")) { | ||
| return "completed"; | ||
| } | ||
| if (state.endsWith("FAILED")) { | ||
| return "failed"; | ||
| } | ||
| if (state.endsWith("CANCELLED")) { | ||
| return "cancelled"; | ||
| } | ||
| if (state.endsWith("EXPIRED")) { | ||
| return "expired"; | ||
| } | ||
| if (state.endsWith("PENDING")) { | ||
| return "validating"; | ||
| } | ||
| if (state.endsWith("RUNNING")) { | ||
| return "in_progress"; | ||
| } | ||
| } | ||
| return done ? "completed" : "in_progress"; | ||
| }; | ||
| var inlinedResponses = (raw) => { | ||
| const obj = asRecord(raw); | ||
| const response = asRecord(obj.response); | ||
| const dest = asRecord(obj.dest); | ||
| const responseInline = response.inlinedResponses ?? response.inlined_responses; | ||
| const destInline = dest.inlinedResponses ?? dest.inlined_responses; | ||
| const nestedResponseInline = asRecord(responseInline); | ||
| const nestedDestInline = asRecord(destInline); | ||
| return [ | ||
| ...asArray(responseInline), | ||
| ...asArray(nestedResponseInline.inlinedResponses), | ||
| ...asArray(nestedResponseInline.inlined_responses), | ||
| ...asArray(destInline), | ||
| ...asArray(nestedDestInline.inlinedResponses), | ||
| ...asArray(nestedDestInline.inlined_responses) | ||
| ]; | ||
| }; | ||
| var normalizeSnapshot2 = (raw) => { | ||
| const obj = asRecord(raw); | ||
| const items = inlinedResponses(raw); | ||
| const failed = items.filter((item) => asRecord(item).error).length; | ||
| const id = asString(obj.name) ?? ""; | ||
| return { | ||
| id: id ? assertPrefixedProviderId("Google operation id", id, GOOGLE_BATCH_PREFIX) : "", | ||
| provider: "google", | ||
| raw, | ||
| requestCounts: { | ||
| completed: items.length - failed, | ||
| failed, | ||
| total: items.length | ||
| }, | ||
| status: mapState(asString(obj.state) ?? asString(asRecord(obj.state).name) ?? asString(asRecord(obj.metadata).state), obj.done === true) | ||
| }; | ||
| }; | ||
| var textFromResponse = (response) => { | ||
| const candidate = asRecord(asArray(asRecord(response).candidates)[0]); | ||
| const text = asArray(asRecord(candidate.content).parts).map((part) => asString(asRecord(part).text) ?? "").join(""); | ||
| return text.length > 0 ? text : undefined; | ||
| }; | ||
| var embeddingFromResponse = (response) => asNumberArray(asRecord(asRecord(response).embedding).values); | ||
| var imagesFromResponse = (response) => { | ||
| const candidate = asRecord(asArray(asRecord(response).candidates)[0]); | ||
| const images = []; | ||
| for (const part of asArray(asRecord(candidate.content).parts)) { | ||
| const partObj = asRecord(part); | ||
| const inline = asRecord(partObj.inlineData ?? partObj.inline_data); | ||
| const data = asString(inline.data); | ||
| const mediaType = asString(inline.mimeType) ?? asString(inline.mime_type); | ||
| if (data && mediaType?.startsWith("image/")) { | ||
| images.push({ data, mediaType }); | ||
| } | ||
| } | ||
| return images.length > 0 ? images : undefined; | ||
| }; | ||
| var usageFromResponse = (response) => { | ||
| const usage = asRecord(asRecord(response).usageMetadata); | ||
| const inputTokens = asNumber(usage.promptTokenCount); | ||
| const outputTokens = asNumber(usage.candidatesTokenCount); | ||
| const totalTokens = asNumber(usage.totalTokenCount); | ||
| if (inputTokens === undefined && outputTokens === undefined && totalTokens === undefined) { | ||
| return; | ||
| } | ||
| return { | ||
| inputTokens, | ||
| outputTokens, | ||
| totalTokens: totalTokens ?? (inputTokens ?? 0) + (outputTokens ?? 0) | ||
| }; | ||
| }; | ||
| var normalizeResult2 = (item) => { | ||
| const obj = asRecord(item); | ||
| const customId = asString(asRecord(obj.metadata).key) ?? asString(obj.key) ?? asString(obj.custom_id) ?? ""; | ||
| if (obj.error) { | ||
| const error = asRecord(obj.error); | ||
| return { | ||
| customId, | ||
| error: { | ||
| code: asNumber(error.code) ?? asString(error.code), | ||
| message: asString(error.message) ?? "Request errored.", | ||
| type: asString(error.status) | ||
| }, | ||
| response: obj.error, | ||
| status: "errored" | ||
| }; | ||
| } | ||
| return { | ||
| customId, | ||
| embedding: embeddingFromResponse(obj.response), | ||
| images: imagesFromResponse(obj.response), | ||
| response: obj.response, | ||
| status: "succeeded", | ||
| text: textFromResponse(obj.response), | ||
| usage: usageFromResponse(obj.response) | ||
| }; | ||
| }; | ||
| var EMBED_CONFIG_KEYS = new Set([ | ||
| "outputDimensionality", | ||
| "taskType", | ||
| "title" | ||
| ]); | ||
| var toEmbedRequest = (body) => { | ||
| const request = {}; | ||
| const config = {}; | ||
| for (const [key, value] of Object.entries(body)) { | ||
| if (EMBED_CONFIG_KEYS.has(key)) { | ||
| config[key] = value; | ||
| } else { | ||
| request[key] = value; | ||
| } | ||
| } | ||
| if (Object.keys(config).length > 0) { | ||
| request.embedContentConfig = config; | ||
| } | ||
| return request; | ||
| }; | ||
| var submit2 = async (input) => { | ||
| const limits = resolveBatchLimits(input.limits); | ||
| const isEmbedding = input.endpoint.toLowerCase().includes("embedcontent"); | ||
| const method = isEmbedding ? "asyncBatchEmbedContent" : "batchGenerateContent"; | ||
| const requests = input.built.map((item) => { | ||
| const payload = omit(item.body, "stream"); | ||
| return { | ||
| metadata: { key: item.customId }, | ||
| request: isEmbedding ? toEmbedRequest(payload) : payload | ||
| }; | ||
| }); | ||
| const body = encodeJsonArrayPayload({ | ||
| items: requests, | ||
| label: "batch upload payload", | ||
| maxBytes: limits.maxUploadBytes, | ||
| prefix: '{"batch":{"display_name":"batchwork","input_config":{"requests":{"requests":[', | ||
| suffix: "]}}}}" | ||
| }); | ||
| const raw = await requestJson(`${baseUrl2(input.credentials)}/models/${input.modelId}:${method}`, { | ||
| body, | ||
| headers: headers2(input.credentials), | ||
| method: "POST" | ||
| }); | ||
| return normalizeSnapshot2(raw); | ||
| }; | ||
| var retrieve2 = async (id, credentials) => { | ||
| const operationId = assertPrefixedProviderId("Google operation id", id, GOOGLE_BATCH_PREFIX); | ||
| const raw = await requestJson(`${baseUrl2(credentials)}/${operationId}`, { | ||
| headers: headers2(credentials) | ||
| }); | ||
| return normalizeSnapshot2(raw); | ||
| }; | ||
| async function* results2(id, credentials) { | ||
| const snapshot = await retrieve2(id, credentials); | ||
| const raw = asRecord(snapshot.raw); | ||
| const response = asRecord(raw.response); | ||
| const dest = asRecord(raw.dest); | ||
| const responsesFile = asString(asRecord(response.responsesFile).name) ?? asString(response.responsesFile) ?? asString(asRecord(response.responses_file).name) ?? asString(response.responses_file) ?? asString(dest.fileName) ?? asString(dest.file_name); | ||
| if (responsesFile) { | ||
| throw new BatchworkError(`batchwork: batch "${id}" returned file-mode results, which are not supported yet.`); | ||
| } | ||
| const items = inlinedResponses(raw); | ||
| if (items.length === 0) { | ||
| throw new BatchworkError(`batchwork: results are not ready for batch "${id}" (status: ${snapshot.status}).`); | ||
| } | ||
| for (const item of items) { | ||
| yield normalizeResult2(item); | ||
| } | ||
| } | ||
| var cancel2 = async (id, credentials) => { | ||
| const operationId = assertPrefixedProviderId("Google operation id", id, GOOGLE_BATCH_PREFIX); | ||
| await requestJson(`${baseUrl2(credentials)}/${operationId}:cancel`, { | ||
| headers: headers2(credentials), | ||
| method: "POST" | ||
| }); | ||
| }; | ||
| var googleAdapter = { | ||
| cancel: cancel2, | ||
| id: "google", | ||
| results: results2, | ||
| retrieve: retrieve2, | ||
| submit: submit2 | ||
| }; | ||
| // src/providers/shared.ts | ||
| var HTTP_OK_MIN = 200; | ||
| var HTTP_OK_MAX = 300; | ||
| var resolveApiKey = (credentials, envVar, label) => { | ||
| const key = credentials.apiKey ?? process.env[envVar]; | ||
| if (!key) { | ||
| throw new BatchworkError(`batchwork: missing ${label} API key. Set ${envVar} or pass \`apiKey\`.`); | ||
| } | ||
| return key; | ||
| }; | ||
| var textFromBody = (body) => { | ||
| const obj = asRecord(body); | ||
| const choices = asArray(obj.choices); | ||
| if (choices.length > 0) { | ||
| const content = asString(asRecord(asRecord(choices[0]).message).content); | ||
| if (content) { | ||
| return content; | ||
| } | ||
| } | ||
| return asString(obj.output_text); | ||
| }; | ||
| var embeddingFromBody = (body) => { | ||
| const data = asArray(asRecord(body).data); | ||
| if (data.length === 0) { | ||
| return; | ||
| } | ||
| return asNumberArray(asRecord(data[0]).embedding); | ||
| }; | ||
| var imagesFromBody = (body) => { | ||
| const obj = asRecord(body); | ||
| const mediaType = `image/${asString(obj.output_format) ?? "png"}`; | ||
| const images = []; | ||
| for (const item of asArray(obj.data)) { | ||
| const b64 = asString(asRecord(item).b64_json); | ||
| if (b64) { | ||
| images.push({ data: b64, mediaType }); | ||
| } | ||
| } | ||
| return images.length > 0 ? images : undefined; | ||
| }; | ||
| var usageFromBody = (body) => { | ||
| const usage = asRecord(asRecord(body).usage); | ||
| const inputTokens = asNumber(usage.prompt_tokens) ?? asNumber(usage.input_tokens); | ||
| const outputTokens = asNumber(usage.completion_tokens) ?? asNumber(usage.output_tokens); | ||
| const totalTokens = asNumber(usage.total_tokens); | ||
| if (inputTokens === undefined && outputTokens === undefined && totalTokens === undefined) { | ||
| return; | ||
| } | ||
| return { | ||
| inputTokens, | ||
| outputTokens, | ||
| totalTokens: totalTokens ?? (inputTokens ?? 0) + (outputTokens ?? 0) | ||
| }; | ||
| }; | ||
| var errorFromValue = (value, fallback) => { | ||
| const obj = asRecord(value); | ||
| const nested = asRecord(obj.error); | ||
| const source = nested.message ? nested : obj; | ||
| return { | ||
| code: asNumber(source.code) ?? asString(source.code), | ||
| message: asString(source.message) ?? fallback, | ||
| type: asString(source.type) | ||
| }; | ||
| }; | ||
| var normalizeOpenAIResult = (line) => { | ||
| const obj = asRecord(line); | ||
| const customId = asString(obj.custom_id) ?? ""; | ||
| if (obj.error) { | ||
| return { | ||
| customId, | ||
| error: errorFromValue(obj.error, "Request errored."), | ||
| response: obj.error, | ||
| status: "errored" | ||
| }; | ||
| } | ||
| const response = asRecord(obj.response); | ||
| const statusCode = asNumber(response.status_code) ?? 0; | ||
| if (statusCode >= HTTP_OK_MIN && statusCode < HTTP_OK_MAX) { | ||
| return { | ||
| customId, | ||
| embedding: embeddingFromBody(response.body), | ||
| images: imagesFromBody(response.body), | ||
| response: response.body, | ||
| status: "succeeded", | ||
| text: textFromBody(response.body), | ||
| usage: usageFromBody(response.body) | ||
| }; | ||
| } | ||
| return { | ||
| customId, | ||
| error: errorFromValue(response.body, `Request failed with status ${statusCode}.`), | ||
| response: response.body, | ||
| status: "errored" | ||
| }; | ||
| }; | ||
| var uploadInputFile = async (jsonl, baseUrl3, headers3, options = {}) => { | ||
| const form = new FormData; | ||
| const purpose = options.purpose === undefined ? "batch" : options.purpose; | ||
| if (purpose !== null) { | ||
| form.append("purpose", purpose); | ||
| } | ||
| form.append("file", new Blob([jsonl], { type: "application/jsonl" }), "batchwork.jsonl"); | ||
| const raw = await requestJson(`${baseUrl3}/files`, { | ||
| body: form, | ||
| headers: headers3, | ||
| method: "POST", | ||
| redirect: "manual" | ||
| }); | ||
| return raw.id; | ||
| }; | ||
| async function* streamResultFile(fileId, baseUrl3, headers3) { | ||
| const stream = await requestStream(`${baseUrl3}/files/${fileId}/content`, { | ||
| headers: headers3, | ||
| redirect: "manual" | ||
| }); | ||
| for await (const line of streamJsonl(stream)) { | ||
| yield normalizeOpenAIResult(line); | ||
| } | ||
| } | ||
| // src/providers/openai-compatible.ts | ||
| var DEFAULT_COMPLETION_WINDOW = "24h"; | ||
| var mapStatus2 = (status) => { | ||
| const normalized = status?.toLowerCase(); | ||
| switch (normalized) { | ||
| case "validating": | ||
| case "in_progress": | ||
| case "finalizing": | ||
| case "completed": | ||
| case "failed": | ||
| case "expired": | ||
| case "cancelling": | ||
| case "cancelled": { | ||
| return normalized; | ||
| } | ||
| default: { | ||
| return "in_progress"; | ||
| } | ||
| } | ||
| }; | ||
| var normalizeSnapshot3 = (raw, provider) => { | ||
| const outer = asRecord(raw); | ||
| const obj = asRecord(outer.job); | ||
| const source = Object.keys(obj).length > 0 ? obj : outer; | ||
| const counts = asRecord(source.request_counts); | ||
| return { | ||
| completedAt: toDate(source.completed_at), | ||
| createdAt: toDate(source.created_at), | ||
| expiresAt: toDate(source.expires_at), | ||
| id: asString(source.id) ?? "", | ||
| provider, | ||
| raw: source, | ||
| requestCounts: { | ||
| completed: asNumber(counts.completed) ?? 0, | ||
| failed: asNumber(counts.failed) ?? 0, | ||
| total: asNumber(counts.total) ?? 0 | ||
| }, | ||
| status: mapStatus2(asString(source.status)) | ||
| }; | ||
| }; | ||
| var createOpenAICompatibleAdapter = (config) => { | ||
| const completionWindow = config.completionWindow ?? DEFAULT_COMPLETION_WINDOW; | ||
| const lineFormat = config.lineFormat ?? "method-url"; | ||
| const baseUrl3 = (credentials) => credentials.baseURL ?? config.baseUrl; | ||
| const authHeaders = (credentials) => ({ | ||
| Authorization: `Bearer ${resolveApiKey(credentials, config.apiKeyEnv, config.apiKeyLabel)}`, | ||
| ...credentials.headers | ||
| }); | ||
| const submit3 = async (input) => { | ||
| const limits = resolveBatchLimits(input.limits); | ||
| const endpoint = config.normalizeEndpoint ? config.normalizeEndpoint(input.endpoint) : input.endpoint; | ||
| const jsonl = encodeJsonl(input.built.map((item) => { | ||
| const body = omit(item.body, "stream"); | ||
| if (lineFormat === "body-only") { | ||
| return { body, custom_id: item.customId }; | ||
| } | ||
| return { | ||
| body, | ||
| custom_id: item.customId, | ||
| method: "POST", | ||
| url: endpoint | ||
| }; | ||
| }), { label: "batch upload JSONL", maxBytes: limits.maxUploadBytes }); | ||
| const headers3 = authHeaders(input.credentials); | ||
| const url = baseUrl3(input.credentials); | ||
| const purpose = config.filePurpose ?? "batch"; | ||
| const inputFileId = await (config.uploadFile ? config.uploadFile({ baseUrl: url, headers: headers3, jsonl, purpose }) : uploadInputFile(jsonl, url, headers3, { purpose })); | ||
| const raw = await requestJson(`${url}/batches`, { | ||
| body: JSON.stringify({ | ||
| completion_window: completionWindow, | ||
| endpoint, | ||
| input_file_id: inputFileId, | ||
| metadata: input.metadata | ||
| }), | ||
| headers: { ...headers3, "content-type": "application/json" }, | ||
| method: "POST" | ||
| }); | ||
| return normalizeSnapshot3(raw, config.id); | ||
| }; | ||
| const retrieve3 = async (id, credentials) => { | ||
| const batchId = assertSimpleProviderId(`${config.id} batch id`, id); | ||
| const raw = await requestJson(`${baseUrl3(credentials)}/batches/${batchId}`, { | ||
| headers: authHeaders(credentials) | ||
| }); | ||
| return normalizeSnapshot3(raw, config.id); | ||
| }; | ||
| async function* results3(id, credentials) { | ||
| const snapshot = await retrieve3(id, credentials); | ||
| const raw = asRecord(snapshot.raw); | ||
| const outputFileId = asString(raw.output_file_id); | ||
| const errorFileId = asString(raw.error_file_id); | ||
| if (!(outputFileId || errorFileId)) { | ||
| throw new BatchworkError(`batchwork: results are not ready for batch "${id}" (status: ${snapshot.status}).`); | ||
| } | ||
| const headers3 = authHeaders(credentials); | ||
| if (outputFileId) { | ||
| yield* streamResultFile(assertSimpleProviderId(`${config.id} output file id`, outputFileId), baseUrl3(credentials), headers3); | ||
| } | ||
| if (errorFileId) { | ||
| yield* streamResultFile(assertSimpleProviderId(`${config.id} error file id`, errorFileId), baseUrl3(credentials), headers3); | ||
| } | ||
| } | ||
| const cancel3 = async (id, credentials) => { | ||
| const batchId = assertSimpleProviderId(`${config.id} batch id`, id); | ||
| await requestJson(`${baseUrl3(credentials)}/batches/${batchId}/cancel`, { | ||
| headers: authHeaders(credentials), | ||
| method: "POST" | ||
| }); | ||
| }; | ||
| return { cancel: cancel3, id: config.id, results: results3, retrieve: retrieve3, submit: submit3 }; | ||
| }; | ||
| // src/providers/groq.ts | ||
| var groqAdapter = createOpenAICompatibleAdapter({ | ||
| apiKeyEnv: "GROQ_API_KEY", | ||
| apiKeyLabel: "Groq", | ||
| baseUrl: "https://api.groq.com/openai/v1", | ||
| id: "groq", | ||
| lineFormat: "method-url", | ||
| normalizeEndpoint: (endpoint) => endpoint.replace(/^\/openai/u, "") | ||
| }); | ||
| // src/providers/mistral.ts | ||
| var MISTRAL_BASE = "https://api.mistral.ai/v1"; | ||
| var apiKey3 = (credentials) => resolveApiKey(credentials, "MISTRAL_API_KEY", "Mistral"); | ||
| var baseUrl3 = (credentials) => credentials.baseURL ?? MISTRAL_BASE; | ||
| var authHeaders = (credentials) => ({ | ||
| Authorization: `Bearer ${apiKey3(credentials)}`, | ||
| ...credentials.headers | ||
| }); | ||
| var mapStatus3 = (status) => { | ||
| switch (status) { | ||
| case "QUEUED": { | ||
| return "validating"; | ||
| } | ||
| case "SUCCESS": { | ||
| return "completed"; | ||
| } | ||
| case "FAILED": { | ||
| return "failed"; | ||
| } | ||
| case "TIMEOUT_EXCEEDED": { | ||
| return "expired"; | ||
| } | ||
| case "CANCELLATION_REQUESTED": { | ||
| return "cancelling"; | ||
| } | ||
| case "CANCELLED": { | ||
| return "cancelled"; | ||
| } | ||
| default: { | ||
| return "in_progress"; | ||
| } | ||
| } | ||
| }; | ||
| var normalizeSnapshot4 = (raw) => { | ||
| const obj = asRecord(raw); | ||
| const succeeded = asNumber(obj.succeeded_requests) ?? 0; | ||
| const failed = asNumber(obj.failed_requests) ?? 0; | ||
| const id = asString(obj.id) ?? ""; | ||
| return { | ||
| completedAt: toDate(obj.completed_at), | ||
| createdAt: toDate(obj.created_at), | ||
| id: id ? assertSimpleProviderId("Mistral job id", id) : "", | ||
| provider: "mistral", | ||
| raw, | ||
| requestCounts: { | ||
| completed: succeeded, | ||
| failed, | ||
| total: asNumber(obj.total_requests) ?? succeeded + failed | ||
| }, | ||
| status: mapStatus3(asString(obj.status)) | ||
| }; | ||
| }; | ||
| var submit3 = async (input) => { | ||
| const limits = resolveBatchLimits(input.limits); | ||
| const jsonl = encodeJsonl(input.built.map((item) => ({ | ||
| body: omit(omit(item.body, "stream"), "model"), | ||
| custom_id: item.customId | ||
| })), { label: "batch upload JSONL", maxBytes: limits.maxUploadBytes }); | ||
| const inputFileId = await uploadInputFile(jsonl, baseUrl3(input.credentials), authHeaders(input.credentials)); | ||
| const raw = await requestJson(`${baseUrl3(input.credentials)}/batch/jobs`, { | ||
| body: JSON.stringify({ | ||
| endpoint: input.endpoint, | ||
| input_files: [inputFileId], | ||
| metadata: input.metadata, | ||
| model: input.modelId | ||
| }), | ||
| headers: { | ||
| ...authHeaders(input.credentials), | ||
| "content-type": "application/json" | ||
| }, | ||
| method: "POST" | ||
| }); | ||
| return normalizeSnapshot4(raw); | ||
| }; | ||
| var retrieve3 = async (id, credentials) => { | ||
| const jobId = assertSimpleProviderId("Mistral job id", id); | ||
| const raw = await requestJson(`${baseUrl3(credentials)}/batch/jobs/${jobId}`, { | ||
| headers: authHeaders(credentials) | ||
| }); | ||
| return normalizeSnapshot4(raw); | ||
| }; | ||
| async function* results3(id, credentials) { | ||
| const snapshot = await retrieve3(id, credentials); | ||
| const raw = asRecord(snapshot.raw); | ||
| const outputFileId = asString(raw.output_file); | ||
| const errorFileId = asString(raw.error_file); | ||
| const headers3 = authHeaders(credentials); | ||
| if (outputFileId) { | ||
| yield* streamResultFile(assertSimpleProviderId("Mistral output file id", outputFileId), baseUrl3(credentials), headers3); | ||
| } | ||
| if (errorFileId) { | ||
| yield* streamResultFile(assertSimpleProviderId("Mistral error file id", errorFileId), baseUrl3(credentials), headers3); | ||
| } | ||
| if (!(outputFileId || errorFileId)) { | ||
| throw new BatchworkError(`batchwork: results are not ready for batch "${id}" (status: ${snapshot.status}).`); | ||
| } | ||
| } | ||
| var cancel3 = async (id, credentials) => { | ||
| const jobId = assertSimpleProviderId("Mistral job id", id); | ||
| await requestJson(`${baseUrl3(credentials)}/batch/jobs/${jobId}/cancel`, { | ||
| headers: authHeaders(credentials), | ||
| method: "POST" | ||
| }); | ||
| }; | ||
| var mistralAdapter = { | ||
| cancel: cancel3, | ||
| id: "mistral", | ||
| results: results3, | ||
| retrieve: retrieve3, | ||
| submit: submit3 | ||
| }; | ||
| // src/providers/openai.ts | ||
| var openaiAdapter = createOpenAICompatibleAdapter({ | ||
| apiKeyEnv: "OPENAI_API_KEY", | ||
| apiKeyLabel: "OpenAI", | ||
| baseUrl: "https://api.openai.com/v1", | ||
| id: "openai", | ||
| lineFormat: "method-url" | ||
| }); | ||
| // src/providers/together.ts | ||
| var INPUT_FILE_NAME = "batchwork.jsonl"; | ||
| var HTTP_FOUND = 302; | ||
| var parseIpv4 = (host) => { | ||
| if (!/^\d{1,3}(?:\.\d{1,3}){3}$/u.test(host)) { | ||
| return; | ||
| } | ||
| const parts = host.split(".").map(Number); | ||
| const valid = parts.every((part) => Number.isInteger(part) && part >= 0 && part <= 255); | ||
| return valid ? parts : undefined; | ||
| }; | ||
| var isPrivateIpv4 = (parts) => { | ||
| const [a = 0, b = 0] = parts; | ||
| return a === 0 || a === 10 || a === 127 || a === 100 && b >= 64 && b <= 127 || a === 169 && b === 254 || a === 172 && b >= 16 && b <= 31 || a === 192 && b === 168 || a === 198 && (b === 18 || b === 19) || a >= 224; | ||
| }; | ||
| var isPrivateIpv6 = (host) => { | ||
| const normalized = host.replace(/^\[/u, "").replace(/\]$/u, "").toLowerCase(); | ||
| if (!normalized.includes(":")) { | ||
| return false; | ||
| } | ||
| return normalized === "::" || normalized === "::1" || normalized.startsWith("fc") || normalized.startsWith("fd") || normalized.startsWith("fe80:"); | ||
| }; | ||
| var validateUploadLocation = (location) => { | ||
| let url; | ||
| try { | ||
| url = new URL(location); | ||
| } catch (error) { | ||
| throw new BatchworkError("batchwork: Together upload Location must be a valid URL.", { cause: error }); | ||
| } | ||
| if (url.protocol !== "https:") { | ||
| throw new BatchworkError("batchwork: Together upload Location must use https."); | ||
| } | ||
| if (url.username || url.password) { | ||
| throw new BatchworkError("batchwork: Together upload Location must not include credentials."); | ||
| } | ||
| const host = url.hostname.toLowerCase(); | ||
| const ipv4 = parseIpv4(host); | ||
| if (host === "localhost" || host.endsWith(".localhost") || host.endsWith(".local") || ipv4 && isPrivateIpv4(ipv4) || isPrivateIpv6(host)) { | ||
| throw new BatchworkError("batchwork: Together upload Location must not target localhost or private networks."); | ||
| } | ||
| return url.toString(); | ||
| }; | ||
| var uploadTogetherFile = async (args) => { | ||
| const metadata = new FormData; | ||
| metadata.append("purpose", args.purpose); | ||
| metadata.append("file_name", INPUT_FILE_NAME); | ||
| metadata.append("file_type", "jsonl"); | ||
| const init = await fetch(`${args.baseUrl}/files`, { | ||
| body: metadata, | ||
| headers: args.headers, | ||
| method: "POST", | ||
| redirect: "manual" | ||
| }); | ||
| const location = init.headers.get("location"); | ||
| const fileId = init.headers.get("x-together-file-id"); | ||
| if (init.status !== HTTP_FOUND || !(location && fileId)) { | ||
| throw new BatchworkError(`batchwork: Together upload could not be initiated (${init.status}).`); | ||
| } | ||
| const uploadLocation = validateUploadLocation(location); | ||
| const upload = await fetch(uploadLocation, { | ||
| body: args.jsonl, | ||
| method: "PUT", | ||
| redirect: "manual" | ||
| }); | ||
| if (!upload.ok) { | ||
| throw new BatchworkError(`batchwork: Together file upload failed (${upload.status}).`); | ||
| } | ||
| const safeFileId = assertSimpleProviderId("Together file id", fileId); | ||
| await requestJson(`${args.baseUrl}/files/${safeFileId}/preprocess`, { | ||
| headers: args.headers, | ||
| method: "POST" | ||
| }); | ||
| return safeFileId; | ||
| }; | ||
| var togetherAdapter = createOpenAICompatibleAdapter({ | ||
| apiKeyEnv: "TOGETHER_API_KEY", | ||
| apiKeyLabel: "Together AI", | ||
| baseUrl: "https://api.together.xyz/v1", | ||
| filePurpose: "batch-api", | ||
| id: "together", | ||
| lineFormat: "body-only", | ||
| uploadFile: uploadTogetherFile | ||
| }); | ||
| // src/providers/xai.ts | ||
| var XAI_BASE = "https://api.x.ai/v1"; | ||
| var RESULTS_PAGE_SIZE = 100; | ||
| var apiKey4 = (credentials) => resolveApiKey(credentials, "XAI_API_KEY", "xAI"); | ||
| var baseUrl4 = (credentials) => credentials.baseURL ?? XAI_BASE; | ||
| var authHeaders2 = (credentials) => ({ | ||
| Authorization: `Bearer ${apiKey4(credentials)}`, | ||
| ...credentials.headers | ||
| }); | ||
| var deriveStatus = (state) => { | ||
| const pending = asNumber(state.num_pending); | ||
| if (pending === undefined) { | ||
| return "in_progress"; | ||
| } | ||
| const total = asNumber(state.num_requests) ?? 0; | ||
| const cancelled = asNumber(state.num_cancelled) ?? 0; | ||
| if (total === 0) { | ||
| return "in_progress"; | ||
| } | ||
| if (pending > 0) { | ||
| return "in_progress"; | ||
| } | ||
| if (cancelled > 0 && cancelled === total) { | ||
| return "cancelled"; | ||
| } | ||
| return "completed"; | ||
| }; | ||
| var normalizeSnapshot5 = (raw) => { | ||
| const obj = asRecord(raw); | ||
| const state = asRecord(obj.state); | ||
| const id = asString(obj.batch_id) ?? asString(obj.id) ?? ""; | ||
| return { | ||
| completedAt: toDate(obj.finish_time ?? obj.completed_at), | ||
| createdAt: toDate(obj.create_time), | ||
| expiresAt: toDate(obj.expire_time ?? obj.expires_at), | ||
| id: id ? assertSimpleProviderId("xAI batch id", id) : "", | ||
| provider: "xai", | ||
| raw, | ||
| requestCounts: { | ||
| canceled: asNumber(state.num_cancelled) ?? 0, | ||
| completed: asNumber(state.num_success) ?? 0, | ||
| failed: asNumber(state.num_error) ?? 0, | ||
| processing: asNumber(state.num_pending) ?? 0, | ||
| total: asNumber(state.num_requests) ?? 0 | ||
| }, | ||
| status: deriveStatus(state) | ||
| }; | ||
| }; | ||
| var imagesFromXaiCompletion = (completion) => { | ||
| const obj = asRecord(completion); | ||
| const entries = asArray(obj.data); | ||
| const sources = entries.length > 0 ? entries : [obj]; | ||
| const images = []; | ||
| for (const source of sources) { | ||
| const record = asRecord(source); | ||
| const data = asString(record.base64) ?? asString(record.b64_json); | ||
| const url = asString(record.url); | ||
| if (data || url) { | ||
| images.push({ | ||
| ...data ? { data } : {}, | ||
| ...url ? { url } : {} | ||
| }); | ||
| } | ||
| } | ||
| return images.length > 0 ? images : undefined; | ||
| }; | ||
| var normalizeResult3 = (item) => { | ||
| const obj = asRecord(item); | ||
| const customId = asString(obj.batch_request_id) ?? ""; | ||
| const batchResult = asRecord(obj.batch_result); | ||
| const resultError = batchResult.error; | ||
| const errorMessage = asString(obj.error_message) ?? asString(resultError) ?? asString(asRecord(resultError).message); | ||
| if (errorMessage) { | ||
| return { | ||
| customId, | ||
| error: { | ||
| message: errorMessage, | ||
| type: asString(asRecord(resultError).type) | ||
| }, | ||
| response: obj, | ||
| status: "errored" | ||
| }; | ||
| } | ||
| const response = asRecord(batchResult.response); | ||
| const completion = response.chat_get_completion ?? Object.values(response)[0]; | ||
| return { | ||
| customId, | ||
| images: imagesFromXaiCompletion(completion), | ||
| response: completion, | ||
| status: "succeeded", | ||
| text: textFromBody(completion), | ||
| usage: usageFromBody(completion) | ||
| }; | ||
| }; | ||
| var submit4 = async (input) => { | ||
| const limits = resolveBatchLimits(input.limits); | ||
| const jsonl = encodeJsonl(input.built.map((item) => ({ | ||
| body: omit(item.body, "stream"), | ||
| custom_id: item.customId, | ||
| method: "POST", | ||
| url: input.endpoint | ||
| })), { label: "batch upload JSONL", maxBytes: limits.maxUploadBytes }); | ||
| const inputFileId = await uploadInputFile(jsonl, baseUrl4(input.credentials), authHeaders2(input.credentials), { purpose: null }); | ||
| const raw = await requestJson(`${baseUrl4(input.credentials)}/batches`, { | ||
| body: JSON.stringify({ input_file_id: inputFileId, name: "batchwork" }), | ||
| headers: { | ||
| ...authHeaders2(input.credentials), | ||
| "content-type": "application/json" | ||
| }, | ||
| method: "POST" | ||
| }); | ||
| return normalizeSnapshot5(raw); | ||
| }; | ||
| var retrieve4 = async (id, credentials) => { | ||
| const batchId = assertSimpleProviderId("xAI batch id", id); | ||
| const raw = await requestJson(`${baseUrl4(credentials)}/batches/${batchId}`, { | ||
| headers: authHeaders2(credentials) | ||
| }); | ||
| return normalizeSnapshot5(raw); | ||
| }; | ||
| async function* results4(id, credentials) { | ||
| const batchId = assertSimpleProviderId("xAI batch id", id); | ||
| const headers3 = authHeaders2(credentials); | ||
| let token; | ||
| do { | ||
| const query = new URLSearchParams({ limit: String(RESULTS_PAGE_SIZE) }); | ||
| if (token) { | ||
| query.set("pagination_token", token); | ||
| } | ||
| const raw = await requestJson(`${baseUrl4(credentials)}/batches/${batchId}/results?${query.toString()}`, { headers: headers3 }); | ||
| const page = asRecord(raw); | ||
| for (const item of Array.isArray(page.results) ? page.results : []) { | ||
| yield normalizeResult3(item); | ||
| } | ||
| token = asString(page.pagination_token); | ||
| } while (token); | ||
| } | ||
| var cancel4 = async (id, credentials) => { | ||
| const batchId = assertSimpleProviderId("xAI batch id", id); | ||
| await requestJson(`${baseUrl4(credentials)}/batches/${batchId}:cancel`, { | ||
| headers: authHeaders2(credentials), | ||
| method: "POST" | ||
| }); | ||
| }; | ||
| var xaiAdapter = { | ||
| cancel: cancel4, | ||
| id: "xai", | ||
| results: results4, | ||
| retrieve: retrieve4, | ||
| submit: submit4 | ||
| }; | ||
| // src/providers/index.ts | ||
| var adapters = { | ||
| anthropic: anthropicAdapter, | ||
| google: googleAdapter, | ||
| groq: groqAdapter, | ||
| mistral: mistralAdapter, | ||
| openai: openaiAdapter, | ||
| together: togetherAdapter, | ||
| xai: xaiAdapter | ||
| }; | ||
| var getAdapter = (provider) => adapters[provider]; | ||
| export { BatchworkError, UnsupportedProviderError, MissingDependencyError, resolveBatchLimits, assertByteLength, mapWithConcurrency, isTerminalStatus, BatchJob, getAdapter }; | ||
| //# debugId=308C7FBEE03E10E564756E2164756E21 | ||
| //# sourceMappingURL=chunk-h2he3d16.js.map |
Sorry, the diff of this file is too big to display
+23
-21
| import { BatchJob } from "./job"; | ||
| import type { BatchEmbeddingsOptions, BatchOptions, BatchRef, BatchResult } from "./types"; | ||
| import type { BatchEmbeddingsOptions, BatchImageOptions, BatchOptions, BatchRef, BatchResult } from "./types"; | ||
| /** | ||
| * Submit a batch of requests to the model's provider and return a handle. | ||
| * Submit a batch of requests and return a {@link BatchJob} handle. | ||
| * | ||
| * Resolves immediately once the batch is accepted — it does not wait for | ||
| * processing. Use the returned {@link BatchJob} to poll, wait, or stream | ||
| * results. | ||
| * Callable directly as a shorthand for text/chat batches, with one method per | ||
| * modality: | ||
| * | ||
| * - `batch()` / {@link batch.text} — text & chat completions | ||
| * - {@link batch.embeddings} — embedding vectors | ||
| * - {@link batch.images} — image generation | ||
| * | ||
| * @example | ||
@@ -15,23 +18,22 @@ * const job = await batch({ | ||
| * }); | ||
| * const results = await job.wait().then(() => job.collect()); | ||
| */ | ||
| export declare const batch: (options: BatchOptions) => Promise<BatchJob>; | ||
| export declare const batch: ((options: BatchOptions) => Promise<BatchJob>) & { | ||
| /** Submit a batch of embedding requests. */ | ||
| embeddings: (options: BatchEmbeddingsOptions) => Promise<BatchJob>; | ||
| /** Submit a batch of image-generation requests. */ | ||
| images: (options: BatchImageOptions) => Promise<BatchJob>; | ||
| /** Submit a batch of text/chat requests. Equivalent to calling `batch()`. */ | ||
| text: (options: BatchOptions) => Promise<BatchJob>; | ||
| }; | ||
| /** | ||
| * Submit a batch of embedding requests to the model's provider and return a | ||
| * handle. Each request's `value` produces one vector, correlated by `customId` | ||
| * via {@link BatchJob.results}. Supported for OpenAI, Mistral, Together, and | ||
| * Google; other providers throw {@link UnsupportedProviderError}. | ||
| * | ||
| * @example | ||
| * const job = await batchEmbeddings({ | ||
| * model: openai.textEmbeddingModel("text-embedding-3-small"), | ||
| * requests: [{ customId: "a", value: "hello world" }], | ||
| * }); | ||
| * const results = await job.wait().then(() => job.collect()); | ||
| * for (const r of results) { | ||
| * console.log(r.customId, r.embedding?.length); | ||
| * } | ||
| * @deprecated Use {@link batch.embeddings} instead. Kept as a standalone alias | ||
| * for backward compatibility; it will be removed in a future major. | ||
| */ | ||
| export declare const batchEmbeddings: (options: BatchEmbeddingsOptions) => Promise<BatchJob>; | ||
| /** | ||
| * @deprecated Use {@link batch.images} instead. Kept as a standalone alias for | ||
| * backward compatibility; it will be removed in a future major. | ||
| */ | ||
| export declare const batchImages: (options: BatchImageOptions) => Promise<BatchJob>; | ||
| /** | ||
| * Rehydrate a {@link BatchJob} for an existing batch id (e.g. one persisted | ||
@@ -38,0 +40,0 @@ * after submission). Identify the provider with `provider` or `model`. |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"file":"batch.d.ts","sourceRoot":"","sources":["../src/batch.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAC;AAQjC,OAAO,KAAK,EACV,sBAAsB,EACtB,YAAY,EAEZ,QAAQ,EACR,WAAW,EAEZ,MAAM,SAAS,CAAC;AAoBjB;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,KAAK,YAAmB,YAAY,KAAG,OAAO,CAAC,QAAQ,CA2BnE,CAAC;AAEF;;;;;;;;;;;;;;;GAeG;AACH,eAAO,MAAM,eAAe,YACjB,sBAAsB,KAC9B,OAAO,CAAC,QAAQ,CA6BlB,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,QAAQ,QAAe,QAAQ,KAAG,OAAO,CAAC,QAAQ,CAK9D,CAAC;AAEF,uEAAuE;AACvE,eAAO,MAAM,eAAe,QAAS,QAAQ,KAAG,cAAc,CAAC,WAAW,CAGzE,CAAC;AAEF,uDAAuD;AACvD,eAAO,MAAM,WAAW,QAAe,QAAQ,KAAG,OAAO,CAAC,IAAI,CAG7D,CAAC"} | ||
| {"version":3,"file":"batch.d.ts","sourceRoot":"","sources":["../src/batch.ts"],"names":[],"mappings":"AAMA,OAAO,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAC;AAUjC,OAAO,KAAK,EACV,sBAAsB,EACtB,iBAAiB,EACjB,YAAY,EAEZ,QAAQ,EACR,WAAW,EAEZ,MAAM,SAAS,CAAC;AAiKjB;;;;;;;;;;;;;;;GAeG;AACH,eAAO,MAAM,KAAK,aA9IiB,YAAY,KAAG,OAAO,CAAC,QAAQ,CAAC;IA+IjE,4CAA4C;0BAjGnC,sBAAsB,KAC9B,OAAO,CAAC,QAAQ,CAAC;IAkGlB,mDAAmD;sBAnDhB,iBAAiB,KAAG,OAAO,CAAC,QAAQ,CAAC;IAqDxE,6EAA6E;oBAnJ5C,YAAY,KAAG,OAAO,CAAC,QAAQ,CAAC;CAqJjE,CAAC;AAEH;;;GAGG;AACH,eAAO,MAAM,eAAe,YA7GjB,sBAAsB,KAC9B,OAAO,CAAC,QAAQ,CA4G4B,CAAC;AAEhD;;;GAGG;AACH,eAAO,MAAM,WAAW,YAnEa,iBAAiB,KAAG,OAAO,CAAC,QAAQ,CAmElC,CAAC;AAExC;;;GAGG;AACH,eAAO,MAAM,QAAQ,QAAe,QAAQ,KAAG,OAAO,CAAC,QAAQ,CAK9D,CAAC;AAEF,uEAAuE;AACvE,eAAO,MAAM,eAAe,QAAS,QAAQ,KAAG,cAAc,CAAC,WAAW,CAGzE,CAAC;AAEF,uDAAuD;AACvD,eAAO,MAAM,WAAW,QAAe,QAAQ,KAAG,OAAO,CAAC,IAAI,CAG7D,CAAC"} |
+8
-1
| import type { ResolvedBatchLimits } from "./limits"; | ||
| import type { ResolvedModel } from "./model"; | ||
| import type { BatchDefaults, BatchEmbeddingRequest, BatchLimits, BatchRequest, ProviderCredentials } from "./types"; | ||
| import type { BatchDefaults, BatchEmbeddingRequest, BatchImageDefaults, BatchImageRequest, BatchLimits, BatchRequest, ProviderCredentials } from "./types"; | ||
| /** A provider request body derived from a single batch item. */ | ||
@@ -26,2 +26,9 @@ export interface BuiltRequest { | ||
| export declare const buildEmbeddingBodies: (resolved: ResolvedModel, requests: readonly BatchEmbeddingRequest[], credentials: ProviderCredentials, rawLimits?: BatchLimits | ResolvedBatchLimits) => Promise<BuiltRequest[]>; | ||
| /** | ||
| * Derive provider image-generation request bodies for every batch item by | ||
| * running each through the AI SDK `generateImage` with a capturing `fetch`. | ||
| * Mirrors {@link buildRequestBodies} for the image endpoint; each item maps to a | ||
| * single image-generation call, correlated by `customId`. | ||
| */ | ||
| export declare const buildImageBodies: (resolved: ResolvedModel, requests: readonly BatchImageRequest[], defaults: BatchImageDefaults | undefined, credentials: ProviderCredentials, rawLimits?: BatchLimits | ResolvedBatchLimits) => Promise<BuiltRequest[]>; | ||
| //# sourceMappingURL=body.d.ts.map |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"file":"body.d.ts","sourceRoot":"","sources":["../src/body.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,UAAU,CAAC;AAEpD,OAAO,KAAK,EAAkB,aAAa,EAAE,MAAM,SAAS,CAAC;AAC7D,OAAO,KAAK,EACV,aAAa,EACb,qBAAqB,EACrB,WAAW,EACX,YAAY,EACZ,mBAAmB,EACpB,MAAM,SAAS,CAAC;AAIjB,gEAAgE;AAChE,MAAM,WAAW,YAAY;IAC3B,qEAAqE;IACrE,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC9B,QAAQ,EAAE,MAAM,CAAC;IACjB,wEAAwE;IACxE,QAAQ,EAAE,MAAM,CAAC;CAClB;AAiMD;;;;;GAKG;AACH,eAAO,MAAM,kBAAkB,aACnB,aAAa,YACb,SAAS,YAAY,EAAE,YACvB,aAAa,GAAG,SAAS,eACtB,mBAAmB,cACpB,WAAW,GAAG,mBAAmB,KAC5C,OAAO,CAAC,YAAY,EAAE,CA4BxB,CAAC;AAEF;;;;;GAKG;AACH,eAAO,MAAM,oBAAoB,aACrB,aAAa,YACb,SAAS,qBAAqB,EAAE,eAC7B,mBAAmB,cACpB,WAAW,GAAG,mBAAmB,KAC5C,OAAO,CAAC,YAAY,EAAE,CAgCxB,CAAC"} | ||
| {"version":3,"file":"body.d.ts","sourceRoot":"","sources":["../src/body.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,UAAU,CAAC;AAMpD,OAAO,KAAK,EAAkB,aAAa,EAAE,MAAM,SAAS,CAAC;AAC7D,OAAO,KAAK,EACV,aAAa,EACb,qBAAqB,EACrB,kBAAkB,EAClB,iBAAiB,EACjB,WAAW,EACX,YAAY,EACZ,mBAAmB,EACpB,MAAM,SAAS,CAAC;AAIjB,gEAAgE;AAChE,MAAM,WAAW,YAAY;IAC3B,qEAAqE;IACrE,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC9B,QAAQ,EAAE,MAAM,CAAC;IACjB,wEAAwE;IACxE,QAAQ,EAAE,MAAM,CAAC;CAClB;AAgOD;;;;;GAKG;AACH,eAAO,MAAM,kBAAkB,aACnB,aAAa,YACb,SAAS,YAAY,EAAE,YACvB,aAAa,GAAG,SAAS,eACtB,mBAAmB,cACpB,WAAW,GAAG,mBAAmB,KAC5C,OAAO,CAAC,YAAY,EAAE,CA4BxB,CAAC;AAEF;;;;;GAKG;AACH,eAAO,MAAM,oBAAoB,aACrB,aAAa,YACb,SAAS,qBAAqB,EAAE,eAC7B,mBAAmB,cACpB,WAAW,GAAG,mBAAmB,KAC5C,OAAO,CAAC,YAAY,EAAE,CAgCxB,CAAC;AAYF;;;;;GAKG;AACH,eAAO,MAAM,gBAAgB,aACjB,aAAa,YACb,SAAS,iBAAiB,EAAE,YAC5B,kBAAkB,GAAG,SAAS,eAC3B,mBAAmB,cACpB,WAAW,GAAG,mBAAmB,KAC5C,OAAO,CAAC,YAAY,EAAE,CAgCxB,CAAC"} |
+2
-2
@@ -1,6 +0,6 @@ | ||
| export { batch, batchEmbeddings, cancelBatch, getBatch, getBatchResults, } from "./batch"; | ||
| export { batch, batchEmbeddings, batchImages, cancelBatch, getBatch, getBatchResults, } from "./batch"; | ||
| export { BatchworkError, MissingDependencyError, UnsupportedProviderError, } from "./errors"; | ||
| export { BatchJob, isTerminalStatus } from "./job"; | ||
| export { resolveModel } from "./model"; | ||
| export type { BatchDefaults, BatchEmbeddingRequest, BatchEmbeddingsOptions, BatchLimits, BatchOptions, BatchProvider, BatchRef, BatchRequest, BatchRequestCounts, BatchRequestSettings, BatchResult, BatchResultError, BatchResultStatus, BatchSnapshot, BatchStatus, BatchUsage, ProviderCredentials, ProviderOptions, WaitOptions, } from "./types"; | ||
| export type { BatchDefaults, BatchEmbeddingRequest, BatchEmbeddingsOptions, BatchImage, BatchImageDefaults, BatchImageOptions, BatchImageRequest, BatchLimits, BatchOptions, BatchProvider, BatchRef, BatchRequest, BatchRequestCounts, BatchRequestSettings, BatchResult, BatchResultError, BatchResultStatus, BatchSnapshot, BatchStatus, BatchUsage, ProviderCredentials, ProviderOptions, WaitOptions, } from "./types"; | ||
| //# sourceMappingURL=index.d.ts.map |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,EACL,eAAe,EACf,WAAW,EACX,QAAQ,EACR,eAAe,GAChB,MAAM,SAAS,CAAC;AACjB,OAAO,EACL,cAAc,EACd,sBAAsB,EACtB,wBAAwB,GACzB,MAAM,UAAU,CAAC;AAClB,OAAO,EAAE,QAAQ,EAAE,gBAAgB,EAAE,MAAM,OAAO,CAAC;AACnD,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACvC,YAAY,EACV,aAAa,EACb,qBAAqB,EACrB,sBAAsB,EACtB,WAAW,EACX,YAAY,EACZ,aAAa,EACb,QAAQ,EACR,YAAY,EACZ,kBAAkB,EAClB,oBAAoB,EACpB,WAAW,EACX,gBAAgB,EAChB,iBAAiB,EACjB,aAAa,EACb,WAAW,EACX,UAAU,EACV,mBAAmB,EACnB,eAAe,EACf,WAAW,GACZ,MAAM,SAAS,CAAC"} | ||
| {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,EACL,eAAe,EACf,WAAW,EACX,WAAW,EACX,QAAQ,EACR,eAAe,GAChB,MAAM,SAAS,CAAC;AACjB,OAAO,EACL,cAAc,EACd,sBAAsB,EACtB,wBAAwB,GACzB,MAAM,UAAU,CAAC;AAClB,OAAO,EAAE,QAAQ,EAAE,gBAAgB,EAAE,MAAM,OAAO,CAAC;AACnD,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACvC,YAAY,EACV,aAAa,EACb,qBAAqB,EACrB,sBAAsB,EACtB,UAAU,EACV,kBAAkB,EAClB,iBAAiB,EACjB,iBAAiB,EACjB,WAAW,EACX,YAAY,EACZ,aAAa,EACb,QAAQ,EACR,YAAY,EACZ,kBAAkB,EAClB,oBAAoB,EACpB,WAAW,EACX,gBAAgB,EAChB,iBAAiB,EACjB,aAAa,EACb,WAAW,EACX,UAAU,EACV,mBAAmB,EACnB,eAAe,EACf,WAAW,GACZ,MAAM,SAAS,CAAC"} |
+5
-3
| import { | ||
| batch, | ||
| batchEmbeddings, | ||
| batchImages, | ||
| cancelBatch, | ||
@@ -8,3 +9,3 @@ getBatch, | ||
| resolveModel | ||
| } from "./chunk-yzjd83s9.js"; | ||
| } from "./chunk-bjkbtj1q.js"; | ||
| import { | ||
@@ -16,3 +17,3 @@ BatchJob, | ||
| isTerminalStatus | ||
| } from "./chunk-xtk2ycy6.js"; | ||
| } from "./chunk-h2he3d16.js"; | ||
| import"./chunk-v0bahtg2.js"; | ||
@@ -25,2 +26,3 @@ export { | ||
| cancelBatch, | ||
| batchImages, | ||
| batchEmbeddings, | ||
@@ -34,3 +36,3 @@ batch, | ||
| //# debugId=43C9E162162AB1F064756E2164756E21 | ||
| //# debugId=403BD62DE792A0F964756E2164756E21 | ||
| //# sourceMappingURL=index.js.map |
@@ -7,4 +7,4 @@ { | ||
| "mappings": "", | ||
| "debugId": "43C9E162162AB1F064756E2164756E21", | ||
| "debugId": "403BD62DE792A0F964756E2164756E21", | ||
| "names": [] | ||
| } |
+17
-2
@@ -1,2 +0,2 @@ | ||
| import type { EmbeddingModel, LanguageModel } from "ai"; | ||
| import type { EmbeddingModel, ImageModel, LanguageModel } from "ai"; | ||
| import { UnsupportedProviderError } from "./errors"; | ||
@@ -20,3 +20,3 @@ import type { BatchProvider, ProviderCredentials } from "./types"; | ||
| */ | ||
| export declare const resolveModel: (model: LanguageModel | EmbeddingModel) => ResolvedModel; | ||
| export declare const resolveModel: (model: LanguageModel | EmbeddingModel | ImageModel) => ResolvedModel; | ||
| /** | ||
@@ -47,2 +47,17 @@ * Import the `@ai-sdk/*` package for a provider, translating a missing optional | ||
| export declare const createCaptureEmbeddingModel: (resolved: ResolvedModel, credentials: ProviderCredentials, fetchImpl: CapturingFetch) => Promise<EmbeddingModel>; | ||
| /** | ||
| * Providers whose batch API accepts image generation. OpenAI and xAI expose | ||
| * `/v1/images/generations` as a batch endpoint; Google runs Gemini image models | ||
| * through `:batchGenerateContent`. Imagen (Google `:predict`) is not | ||
| * batch-supported, Together's batch API is chat/audio only, and Anthropic, Groq, | ||
| * and Mistral have no image model. | ||
| */ | ||
| export declare const IMAGE_PROVIDERS: Set<BatchProvider>; | ||
| export declare const unsupportedImageProvider: (provider: BatchProvider) => UnsupportedProviderError; | ||
| /** | ||
| * Construct an AI SDK image model wired to a capturing `fetch`, used to derive | ||
| * the provider image-generation request body for each batch item without making | ||
| * a network call. Throws for providers without batch image support. | ||
| */ | ||
| export declare const createCaptureImageModel: (resolved: ResolvedModel, credentials: ProviderCredentials, fetchImpl: CapturingFetch) => Promise<ImageModel>; | ||
| //# sourceMappingURL=model.d.ts.map |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"file":"model.d.ts","sourceRoot":"","sources":["../src/model.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,IAAI,CAAC;AAExD,OAAO,EAA0B,wBAAwB,EAAE,MAAM,UAAU,CAAC;AAC5E,OAAO,KAAK,EAAE,aAAa,EAAE,mBAAmB,EAAE,MAAM,SAAS,CAAC;AAElE,iFAAiF;AACjF,MAAM,MAAM,cAAc,GAAG,OAAO,UAAU,CAAC,KAAK,CAAC;AAErD,kFAAkF;AAClF,MAAM,MAAM,eAAe,GAAG,MAAM,GAAG,WAAW,GAAG,YAAY,CAAC;AAElE,MAAM,WAAW,aAAa;IAC5B,8EAA8E;IAC9E,IAAI,EAAE,eAAe,CAAC;IACtB,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,aAAa,CAAC;CACzB;AA4ED;;;;;GAKG;AACH,eAAO,MAAM,YAAY,UAChB,aAAa,GAAG,cAAc,KACpC,aAkBF,CAAC;AA+BF;;;;;GAKG;AACH,eAAO,MAAM,YAAY,GAAU,CAAC,YACxB,aAAa,SACjB,CAAC,MAAM,EAAE,aAAa,KAAK,OAAO,CAAC,OAAO,CAAC,KAChD,OAAO,CAAC,CAAC,CAUX,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,kBAAkB,aACnB,aAAa,eACV,mBAAmB,aACrB,cAAc,KACxB,OAAO,CAAC,aAAa,CAqDvB,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,mBAAmB,oBAI9B,CAAC;AAEH,eAAO,MAAM,4BAA4B,aAC7B,aAAa,KACtB,wBAIA,CAAC;AAEJ;;;;GAIG;AACH,eAAO,MAAM,2BAA2B,aAC5B,aAAa,eACV,mBAAmB,aACrB,cAAc,KACxB,OAAO,CAAC,cAAc,CA8BxB,CAAC"} | ||
| {"version":3,"file":"model.d.ts","sourceRoot":"","sources":["../src/model.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,cAAc,EAAE,UAAU,EAAE,aAAa,EAAE,MAAM,IAAI,CAAC;AAEpE,OAAO,EAA0B,wBAAwB,EAAE,MAAM,UAAU,CAAC;AAC5E,OAAO,KAAK,EAAE,aAAa,EAAE,mBAAmB,EAAE,MAAM,SAAS,CAAC;AAElE,iFAAiF;AACjF,MAAM,MAAM,cAAc,GAAG,OAAO,UAAU,CAAC,KAAK,CAAC;AAErD,kFAAkF;AAClF,MAAM,MAAM,eAAe,GAAG,MAAM,GAAG,WAAW,GAAG,YAAY,CAAC;AAElE,MAAM,WAAW,aAAa;IAC5B,8EAA8E;IAC9E,IAAI,EAAE,eAAe,CAAC;IACtB,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,aAAa,CAAC;CACzB;AA4ED;;;;;GAKG;AACH,eAAO,MAAM,YAAY,UAChB,aAAa,GAAG,cAAc,GAAG,UAAU,KACjD,aAkBF,CAAC;AA+BF;;;;;GAKG;AACH,eAAO,MAAM,YAAY,GAAU,CAAC,YACxB,aAAa,SACjB,CAAC,MAAM,EAAE,aAAa,KAAK,OAAO,CAAC,OAAO,CAAC,KAChD,OAAO,CAAC,CAAC,CAUX,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,kBAAkB,aACnB,aAAa,eACV,mBAAmB,aACrB,cAAc,KACxB,OAAO,CAAC,aAAa,CAqDvB,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,mBAAmB,oBAI9B,CAAC;AAEH,eAAO,MAAM,4BAA4B,aAC7B,aAAa,KACtB,wBAIA,CAAC;AAEJ;;;;GAIG;AACH,eAAO,MAAM,2BAA2B,aAC5B,aAAa,eACV,mBAAmB,aACrB,cAAc,KACxB,OAAO,CAAC,cAAc,CA8BxB,CAAC;AAEF;;;;;;GAMG;AACH,eAAO,MAAM,eAAe,oBAI1B,CAAC;AAEH,eAAO,MAAM,wBAAwB,aACzB,aAAa,KACtB,wBAIA,CAAC;AAEJ;;;;GAIG;AACH,eAAO,MAAM,uBAAuB,aACxB,aAAa,eACV,mBAAmB,aACrB,cAAc,KACxB,OAAO,CAAC,UAAU,CA2BpB,CAAC"} |
| import { | ||
| getBatchResults | ||
| } from "../chunk-yzjd83s9.js"; | ||
| } from "../chunk-bjkbtj1q.js"; | ||
| import { | ||
@@ -8,4 +8,4 @@ createBatchPoller, | ||
| toEvent | ||
| } from "../chunk-d0g9wsxq.js"; | ||
| import"../chunk-xtk2ycy6.js"; | ||
| } from "../chunk-3yqs6x71.js"; | ||
| import"../chunk-h2he3d16.js"; | ||
| import"../chunk-v0bahtg2.js"; | ||
@@ -12,0 +12,0 @@ |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"file":"google.d.ts","sourceRoot":"","sources":["../../src/providers/google.ts"],"names":[],"mappings":"AAmBA,OAAO,KAAK,EAAE,YAAY,EAAe,MAAM,WAAW,CAAC;AAwR3D;;;;GAIG;AACH,eAAO,MAAM,aAAa,EAAE,YAM3B,CAAC"} | ||
| {"version":3,"file":"google.d.ts","sourceRoot":"","sources":["../../src/providers/google.ts"],"names":[],"mappings":"AAoBA,OAAO,KAAK,EAAE,YAAY,EAAe,MAAM,WAAW,CAAC;AA6S3D;;;;GAIG;AACH,eAAO,MAAM,aAAa,EAAE,YAM3B,CAAC"} |
@@ -7,3 +7,3 @@ /** | ||
| */ | ||
| import type { BatchResult, BatchUsage, ProviderCredentials } from "../types"; | ||
| import type { BatchImage, BatchResult, BatchUsage, ProviderCredentials } from "../types"; | ||
| /** Resolve an API key from credentials or the provider's env var, or throw. */ | ||
@@ -14,2 +14,8 @@ export declare const resolveApiKey: (credentials: ProviderCredentials, envVar: string, label: string) => string; | ||
| export declare const embeddingFromBody: (body: unknown) => number[] | undefined; | ||
| /** | ||
| * Read base64 images from an OpenAI-shaped images response body | ||
| * (`{ data: [{ b64_json }], output_format? }`). Returns undefined when the body | ||
| * carries no image data, so chat/embedding results are unaffected. | ||
| */ | ||
| export declare const imagesFromBody: (body: unknown) => BatchImage[] | undefined; | ||
| export declare const usageFromBody: (body: unknown) => BatchUsage | undefined; | ||
@@ -16,0 +22,0 @@ /** Normalize an OpenAI-shaped result line into a {@link BatchResult}. */ |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"file":"shared.d.ts","sourceRoot":"","sources":["../../src/providers/shared.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAKH,OAAO,KAAK,EACV,WAAW,EAEX,UAAU,EACV,mBAAmB,EACpB,MAAM,UAAU,CAAC;AAMlB,+EAA+E;AAC/E,eAAO,MAAM,aAAa,gBACX,mBAAmB,UACxB,MAAM,SACP,MAAM,KACZ,MAQF,CAAC;AAEF,eAAO,MAAM,YAAY,SAAU,OAAO,KAAG,MAAM,GAAG,SAUrD,CAAC;AAEF,gFAAgF;AAChF,eAAO,MAAM,iBAAiB,SAAU,OAAO,KAAG,MAAM,EAAE,GAAG,SAM5D,CAAC;AAEF,eAAO,MAAM,aAAa,SAAU,OAAO,KAAG,UAAU,GAAG,SAmB1D,CAAC;AAaF,yEAAyE;AACzE,eAAO,MAAM,qBAAqB,SAAU,OAAO,KAAG,WAmCrD,CAAC;AAGF,eAAO,MAAM,eAAe,UACnB,MAAM,WACJ,MAAM,WACN,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,YACtB;IAAE,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,KACnC,OAAO,CAAC,MAAM,CAkBhB,CAAC;AAEF;;;;GAIG;AAEH,wBAAuB,gBAAgB,CACrC,MAAM,EAAE,MAAM,EACd,OAAO,EAAE,MAAM,EACf,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAC9B,cAAc,CAAC,WAAW,CAAC,CAQ7B"} | ||
| {"version":3,"file":"shared.d.ts","sourceRoot":"","sources":["../../src/providers/shared.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAKH,OAAO,KAAK,EACV,UAAU,EACV,WAAW,EAEX,UAAU,EACV,mBAAmB,EACpB,MAAM,UAAU,CAAC;AAMlB,+EAA+E;AAC/E,eAAO,MAAM,aAAa,gBACX,mBAAmB,UACxB,MAAM,SACP,MAAM,KACZ,MAQF,CAAC;AAEF,eAAO,MAAM,YAAY,SAAU,OAAO,KAAG,MAAM,GAAG,SAUrD,CAAC;AAEF,gFAAgF;AAChF,eAAO,MAAM,iBAAiB,SAAU,OAAO,KAAG,MAAM,EAAE,GAAG,SAM5D,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,cAAc,SAAU,OAAO,KAAG,UAAU,EAAE,GAAG,SAW7D,CAAC;AAEF,eAAO,MAAM,aAAa,SAAU,OAAO,KAAG,UAAU,GAAG,SAmB1D,CAAC;AAaF,yEAAyE;AACzE,eAAO,MAAM,qBAAqB,SAAU,OAAO,KAAG,WAoCrD,CAAC;AAGF,eAAO,MAAM,eAAe,UACnB,MAAM,WACJ,MAAM,WACN,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,YACtB;IAAE,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,KACnC,OAAO,CAAC,MAAM,CAkBhB,CAAC;AAEF;;;;GAIG;AAEH,wBAAuB,gBAAgB,CACrC,MAAM,EAAE,MAAM,EACd,OAAO,EAAE,MAAM,EACf,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAC9B,cAAc,CAAC,WAAW,CAAC,CAQ7B"} |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"file":"xai.d.ts","sourceRoot":"","sources":["../../src/providers/xai.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAAE,YAAY,EAAe,MAAM,WAAW,CAAC;AAkL3D;;;;GAIG;AACH,eAAO,MAAM,UAAU,EAAE,YAMxB,CAAC"} | ||
| {"version":3,"file":"xai.d.ts","sourceRoot":"","sources":["../../src/providers/xai.ts"],"names":[],"mappings":"AAWA,OAAO,KAAK,EAAE,YAAY,EAAe,MAAM,WAAW,CAAC;AA8M3D;;;;GAIG;AACH,eAAO,MAAM,UAAU,EAAE,YAMxB,CAAC"} |
@@ -7,4 +7,4 @@ import { | ||
| verifyWebhook | ||
| } from "../chunk-d0g9wsxq.js"; | ||
| import"../chunk-xtk2ycy6.js"; | ||
| } from "../chunk-3yqs6x71.js"; | ||
| import"../chunk-h2he3d16.js"; | ||
| import"../chunk-v0bahtg2.js"; | ||
@@ -11,0 +11,0 @@ export { |
+49
-3
@@ -1,2 +0,2 @@ | ||
| import type { EmbeddingModel, JSONValue, LanguageModel, ModelMessage, ToolChoice, ToolSet } from "ai"; | ||
| import type { EmbeddingModel, ImageModel, JSONValue, LanguageModel, ModelMessage, ToolChoice, ToolSet } from "ai"; | ||
| /** Providers with a batch adapter. */ | ||
@@ -53,2 +53,38 @@ export type BatchProvider = "anthropic" | "google" | "groq" | "mistral" | "openai" | "together" | "xai"; | ||
| } | ||
| /** | ||
| * A generated image. Returned inline as base64 (`data` + `mediaType`) by most | ||
| * providers, or as a hosted `url` by some (e.g. xAI batch, whose signed URLs | ||
| * expire ~1h after completion — download promptly). | ||
| */ | ||
| export interface BatchImage { | ||
| /** Base64-encoded image data (no `data:` prefix), when returned inline. */ | ||
| data?: string; | ||
| /** MIME type, e.g. `"image/png"`, when known. */ | ||
| mediaType?: string; | ||
| /** Hosted image URL, when returned instead of inline data. */ | ||
| url?: string; | ||
| } | ||
| /** | ||
| * A single image-generation request within a batch. One `prompt` produces one | ||
| * or more images, correlated to its result by `customId`. Generation only; | ||
| * image editing (input images / masks) is not supported by batch APIs. | ||
| */ | ||
| export interface BatchImageRequest { | ||
| /** Aspect ratio, `"{width}:{height}"` (e.g. Gemini image models). */ | ||
| aspectRatio?: `${number}:${number}`; | ||
| /** Correlates this request to its result. Auto-generated when omitted. */ | ||
| customId?: string; | ||
| /** Number of images to generate. Defaults to 1. */ | ||
| n?: number; | ||
| /** The text prompt describing the image to generate. */ | ||
| prompt: string; | ||
| /** Forwarded to the provider image call (e.g. `{ openai: { quality } }`). */ | ||
| providerOptions?: ProviderOptions; | ||
| /** Seed for deterministic generation, where the provider supports it. */ | ||
| seed?: number; | ||
| /** Image size, `"{width}x{height}"` (e.g. OpenAI image models). */ | ||
| size?: `${number}x${number}`; | ||
| } | ||
| /** Defaults merged into every image request; request-level values take precedence. */ | ||
| export type BatchImageDefaults = Partial<Omit<BatchImageRequest, "customId" | "prompt">>; | ||
| /** Normalized batch lifecycle status, unified across providers. */ | ||
@@ -84,2 +120,4 @@ export type BatchStatus = "validating" | "in_progress" | "finalizing" | "completed" | "failed" | "expired" | "cancelling" | "cancelled"; | ||
| error?: BatchResultError; | ||
| /** Generated images, when the request produced images. */ | ||
| images?: BatchImage[]; | ||
| /** Raw provider response body (OpenAI `response.body` / Anthropic message). */ | ||
@@ -117,3 +155,3 @@ response?: unknown; | ||
| } | ||
| /** Input to `batch()`. */ | ||
| /** Input to `batch()` / `batch.text()`. */ | ||
| export interface BatchOptions extends ProviderCredentials { | ||
@@ -126,3 +164,3 @@ defaults?: BatchDefaults; | ||
| } | ||
| /** Input to `batchEmbeddings()`. */ | ||
| /** Input to `batch.embeddings()`. */ | ||
| export interface BatchEmbeddingsOptions extends ProviderCredentials { | ||
@@ -134,2 +172,10 @@ limits?: BatchLimits; | ||
| } | ||
| /** Input to `batch.images()`. */ | ||
| export interface BatchImageOptions extends ProviderCredentials { | ||
| defaults?: BatchImageDefaults; | ||
| limits?: BatchLimits; | ||
| metadata?: Record<string, string>; | ||
| model: ImageModel; | ||
| requests: BatchImageRequest[]; | ||
| } | ||
| /** | ||
@@ -136,0 +182,0 @@ * Reference to an existing batch, used to rehydrate a handle. Identify the |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"} | ||
| {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"} |
+1
-1
| { | ||
| "name": "batchwork", | ||
| "version": "1.1.0", | ||
| "version": "1.2.0", | ||
| "description": "Unified batch API for AI providers — low-cost LLM batch processing at scale.", | ||
@@ -5,0 +5,0 @@ "keywords": [ |
+32
-4
@@ -49,7 +49,7 @@ # Batchwork | ||
| ```ts | ||
| import { batchEmbeddings } from "batchwork"; | ||
| import { batch } from "batchwork"; | ||
| import { openai } from "@ai-sdk/openai"; | ||
| const job = await batchEmbeddings({ | ||
| model: openai.textEmbeddingModel("text-embedding-3-small"), | ||
| const job = await batch.embeddings({ | ||
| model: openai.embeddingModel("text-embedding-3-small"), | ||
| requests: [ | ||
@@ -69,2 +69,30 @@ { customId: "a", value: "The quick brown fox." }, | ||
| ## Images | ||
| Generate images in bulk — pass an image model and `prompt`s, and get base64 images back on `result.images`: | ||
| ```ts | ||
| import { batch } from "batchwork"; | ||
| import { openai } from "@ai-sdk/openai"; | ||
| const job = await batch.images({ | ||
| model: openai.image("gpt-image-2"), | ||
| requests: [ | ||
| { customId: "a", prompt: "A red bicycle against a brick wall." }, | ||
| { customId: "b", prompt: "A watercolor painting of a sleeping cat." }, | ||
| ], | ||
| }); | ||
| const results = await job.wait().then(() => job.collect()); | ||
| for (const r of results) { | ||
| for (const image of r.images ?? []) { | ||
| // Inline base64 (`image.data` + `image.mediaType`), or a hosted | ||
| // `image.url` for providers that return one (e.g. xAI batch). | ||
| console.log(r.customId, image.mediaType ?? image.url); | ||
| } | ||
| } | ||
| ``` | ||
| Batch image generation is available for **OpenAI** (`/v1/images/generations`, e.g. `gpt-image-2`), **Google Gemini** image models (e.g. `gemini-2.5-flash-image`), and **xAI** (`/v1/images/generations`, e.g. `grok-imagine-image-quality`); other providers throw a clear error. Generation only — image editing, video, and Google's Imagen models aren't batch-supported, and Together AI's batch API is chat/audio only. OpenAI and Google return inline base64 on `image.data`; xAI batch returns signed `image.url`s that **expire ~1h** after completion, so download them promptly. | ||
| ## Features | ||
@@ -74,3 +102,3 @@ | ||
| - **AI SDK native** — author requests in the familiar `generateText` shape. | ||
| - **Chat & embeddings** — `batch()` for completions, `batchEmbeddings()` for vectors. | ||
| - **Chat, embeddings & images** — `batch()` for completions, `batch.embeddings()` for vectors, `batch.images()` for image generation. | ||
| - **~50% cheaper** — every request runs against the provider's batch window. | ||
@@ -77,0 +105,0 @@ - **Normalized results** — unified status, text, usage, and error types regardless of provider. |
| import { | ||
| BatchworkError, | ||
| getAdapter, | ||
| isTerminalStatus | ||
| } from "./chunk-xtk2ycy6.js"; | ||
| // src/server/events.ts | ||
| var EVENT_BY_STATUS = { | ||
| cancelled: "batch.cancelled", | ||
| completed: "batch.completed", | ||
| expired: "batch.expired", | ||
| failed: "batch.failed" | ||
| }; | ||
| var toEvent = (provider, snapshot) => ({ | ||
| completedAt: snapshot.completedAt?.toISOString(), | ||
| createdAt: snapshot.createdAt?.toISOString(), | ||
| id: snapshot.id, | ||
| provider, | ||
| requestCounts: snapshot.requestCounts, | ||
| type: EVENT_BY_STATUS[snapshot.status] ?? "batch.completed" | ||
| }); | ||
| // src/server/signing.ts | ||
| var SIGNATURE_VERSION = "v1"; | ||
| var TOLERANCE_SECONDS = 300; | ||
| var SECRET_PREFIX = "whsec_"; | ||
| var encoder = new TextEncoder; | ||
| var replayCache = new Map; | ||
| var replayLocks = new WeakMap; | ||
| var pruneReplayCache = (now) => { | ||
| for (const [id, expiresAt] of replayCache) { | ||
| if (expiresAt <= now) { | ||
| replayCache.delete(id); | ||
| } | ||
| } | ||
| }; | ||
| var defaultReplayStore = { | ||
| claim: (id, expiresAt, now) => { | ||
| pruneReplayCache(now); | ||
| const existing = replayCache.get(id); | ||
| if (existing && existing > now) { | ||
| return false; | ||
| } | ||
| replayCache.set(id, expiresAt); | ||
| return true; | ||
| }, | ||
| get: (id) => replayCache.get(id), | ||
| set: (id, expiresAt) => { | ||
| replayCache.set(id, expiresAt); | ||
| } | ||
| }; | ||
| var withReplayLock = async (store, id, task) => { | ||
| let locks = replayLocks.get(store); | ||
| if (!locks) { | ||
| locks = new Map; | ||
| replayLocks.set(store, locks); | ||
| } | ||
| const previous = locks.get(id); | ||
| let release; | ||
| const current = new Promise((resolve) => { | ||
| release = resolve; | ||
| }); | ||
| locks.set(id, current); | ||
| if (previous) { | ||
| await previous; | ||
| } | ||
| try { | ||
| return await task(); | ||
| } finally { | ||
| release(); | ||
| if (locks.get(id) === current) { | ||
| locks.delete(id); | ||
| } | ||
| } | ||
| }; | ||
| var base64ToBytes = (value) => Uint8Array.from(atob(value), (char) => char.codePointAt(0) ?? 0); | ||
| var bytesToBase64 = (bytes) => { | ||
| let binary = ""; | ||
| for (const byte of bytes) { | ||
| binary += String.fromCodePoint(byte); | ||
| } | ||
| return btoa(binary); | ||
| }; | ||
| var importKey = (secret) => { | ||
| const raw = secret.startsWith(SECRET_PREFIX) ? base64ToBytes(secret.slice(SECRET_PREFIX.length)) : encoder.encode(secret); | ||
| return crypto.subtle.importKey("raw", raw, { hash: "SHA-256", name: "HMAC" }, false, ["sign", "verify"]); | ||
| }; | ||
| var signContent = async (secret, content) => { | ||
| const key = await importKey(secret); | ||
| const signature = await crypto.subtle.sign("HMAC", key, encoder.encode(content)); | ||
| return bytesToBase64(new Uint8Array(signature)); | ||
| }; | ||
| var verifyContent = async (secret, content, signature) => { | ||
| const key = await importKey(secret); | ||
| return crypto.subtle.verify("HMAC", key, base64ToBytes(signature), encoder.encode(content)); | ||
| }; | ||
| var rememberWebhookId = async (id, timestamp, now, store) => { | ||
| const expiresAt = timestamp + TOLERANCE_SECONDS; | ||
| if (store.claim) { | ||
| const claimed = await store.claim(id, expiresAt, now); | ||
| if (!claimed) { | ||
| throw new BatchworkError("batchwork: webhook replay detected."); | ||
| } | ||
| return; | ||
| } | ||
| await withReplayLock(store, id, async () => { | ||
| if (store === defaultReplayStore) { | ||
| pruneReplayCache(now); | ||
| } | ||
| const existing = await store.get(id); | ||
| if (existing && existing > now) { | ||
| throw new BatchworkError("batchwork: webhook replay detected."); | ||
| } | ||
| await store.set(id, expiresAt); | ||
| }); | ||
| }; | ||
| var signWebhook = async (secret, id, body, timestampSeconds) => { | ||
| const timestamp = Math.floor(timestampSeconds).toString(); | ||
| const signature = await signContent(secret, `${id}.${timestamp}.${body}`); | ||
| return { | ||
| "webhook-id": id, | ||
| "webhook-signature": `${SIGNATURE_VERSION},${signature}`, | ||
| "webhook-timestamp": timestamp | ||
| }; | ||
| }; | ||
| var verifyWebhook = async (request, secret, options) => { | ||
| const id = request.headers.get("webhook-id"); | ||
| const timestamp = request.headers.get("webhook-timestamp"); | ||
| const signatureHeader = request.headers.get("webhook-signature"); | ||
| if (!(id && timestamp && signatureHeader)) { | ||
| throw new BatchworkError("batchwork: missing webhook signature headers."); | ||
| } | ||
| const seconds = Number(timestamp); | ||
| const now = Date.now() / 1000; | ||
| if (!Number.isFinite(seconds) || Math.abs(now - seconds) > TOLERANCE_SECONDS) { | ||
| throw new BatchworkError("batchwork: webhook timestamp outside tolerance."); | ||
| } | ||
| const body = await request.text(); | ||
| const content = `${id}.${timestamp}.${body}`; | ||
| const signatures = signatureHeader.split(" ").map((part) => { | ||
| const comma = part.indexOf(","); | ||
| return comma === -1 ? part : part.slice(comma + 1); | ||
| }); | ||
| let valid = false; | ||
| for (const signature of signatures) { | ||
| if (await verifyContent(secret, content, signature)) { | ||
| valid = true; | ||
| break; | ||
| } | ||
| } | ||
| if (!valid) { | ||
| throw new BatchworkError("batchwork: webhook signature verification failed."); | ||
| } | ||
| await rememberWebhookId(id, seconds, now, options?.replayStore ?? defaultReplayStore); | ||
| return { body, id, timestamp: seconds }; | ||
| }; | ||
| var verifyBatchWebhook = async (request, secret, options) => { | ||
| const { body } = await verifyWebhook(request, secret, options); | ||
| return JSON.parse(body); | ||
| }; | ||
| // src/server/poller.ts | ||
| var parseIpv4 = (host) => { | ||
| if (!/^\d{1,3}(?:\.\d{1,3}){3}$/u.test(host)) { | ||
| return; | ||
| } | ||
| const parts = host.split(".").map(Number); | ||
| const valid = parts.every((part) => Number.isInteger(part) && part >= 0 && part <= 255); | ||
| return valid ? parts : undefined; | ||
| }; | ||
| var isPrivateIpv4 = (parts) => { | ||
| const [a = 0, b = 0] = parts; | ||
| return a === 0 || a === 10 || a === 127 || a === 100 && b >= 64 && b <= 127 || a === 169 && b === 254 || a === 172 && b >= 16 && b <= 31 || a === 192 && b === 168 || a === 198 && (b === 18 || b === 19) || a >= 224; | ||
| }; | ||
| var parseIpv4MappedIpv6 = (host) => { | ||
| const normalized = host.replace(/^\[/u, "").replace(/\]$/u, "").toLowerCase(); | ||
| if (!normalized.startsWith("::ffff:")) { | ||
| return; | ||
| } | ||
| const suffix = normalized.slice("::ffff:".length); | ||
| const dotted = parseIpv4(suffix); | ||
| if (dotted) { | ||
| return dotted; | ||
| } | ||
| const [high, low, extra] = suffix.split(":"); | ||
| if (!(high && low) || extra !== undefined) { | ||
| return; | ||
| } | ||
| const highBits = Number.parseInt(high, 16); | ||
| const lowBits = Number.parseInt(low, 16); | ||
| if (!Number.isInteger(highBits) || !Number.isInteger(lowBits) || highBits < 0 || highBits > 65535 || lowBits < 0 || lowBits > 65535) { | ||
| return; | ||
| } | ||
| return [ | ||
| Math.floor(highBits / 256), | ||
| highBits % 256, | ||
| Math.floor(lowBits / 256), | ||
| lowBits % 256 | ||
| ]; | ||
| }; | ||
| var isPrivateIpv6 = (host) => { | ||
| const normalized = host.replace(/^\[/u, "").replace(/\]$/u, "").toLowerCase(); | ||
| if (!normalized.includes(":")) { | ||
| return false; | ||
| } | ||
| return normalized === "::" || normalized === "::1" || normalized.startsWith("fc") || normalized.startsWith("fd") || normalized.startsWith("fe80:"); | ||
| }; | ||
| var isRedirectStatus = (status) => status >= 300 && status < 400; | ||
| var assertSafeWebhookUrl = (url) => { | ||
| if (url.protocol !== "https:") { | ||
| throw new BatchworkError("batchwork: webhookUrl must use https."); | ||
| } | ||
| if (url.username || url.password) { | ||
| throw new BatchworkError("batchwork: webhookUrl must not include credentials."); | ||
| } | ||
| const host = url.hostname.toLowerCase(); | ||
| const ipv4 = parseIpv4(host); | ||
| const mappedIpv4 = parseIpv4MappedIpv6(host); | ||
| if (host === "localhost" || host.endsWith(".localhost") || host.endsWith(".local") || ipv4 && isPrivateIpv4(ipv4) || mappedIpv4 && isPrivateIpv4(mappedIpv4) || isPrivateIpv6(host)) { | ||
| throw new BatchworkError("batchwork: webhookUrl must not target localhost or private networks."); | ||
| } | ||
| }; | ||
| var validateWebhookUrl = async (rawUrl, validator) => { | ||
| let url; | ||
| try { | ||
| url = new URL(rawUrl); | ||
| } catch (error) { | ||
| throw new BatchworkError("batchwork: webhookUrl must be a valid URL.", { | ||
| cause: error | ||
| }); | ||
| } | ||
| await validator(url); | ||
| return url.toString(); | ||
| }; | ||
| var createWebhookSink = (validator) => async (record, snapshot) => { | ||
| if (!record.webhookUrl) { | ||
| throw new BatchworkError("batchwork: tracked batch has no webhookUrl to deliver to."); | ||
| } | ||
| const webhookUrl = await validateWebhookUrl(record.webhookUrl, validator); | ||
| const body = JSON.stringify(toEvent(record.provider, snapshot)); | ||
| const headers = { | ||
| "content-type": "application/json" | ||
| }; | ||
| if (record.webhookSecret) { | ||
| Object.assign(headers, await signWebhook(record.webhookSecret, record.id, body, Date.now() / 1000)); | ||
| } | ||
| const response = await fetch(webhookUrl, { | ||
| body, | ||
| headers, | ||
| method: "POST", | ||
| redirect: "manual" | ||
| }); | ||
| if (isRedirectStatus(response.status)) { | ||
| throw new BatchworkError(`batchwork: webhook delivery to ${webhookUrl} redirected (${response.status}).`); | ||
| } | ||
| if (!response.ok) { | ||
| throw new BatchworkError(`batchwork: webhook delivery to ${webhookUrl} failed (${response.status}).`); | ||
| } | ||
| }; | ||
| var createBatchPoller = (options) => { | ||
| const resolveCredentials = (provider) => { | ||
| if (typeof options.credentials === "function") { | ||
| return options.credentials(provider); | ||
| } | ||
| return options.credentials ?? {}; | ||
| }; | ||
| const webhookUrlValidator = options.validateWebhookUrl ?? assertSafeWebhookUrl; | ||
| const sink = options.onComplete ?? createWebhookSink(webhookUrlValidator); | ||
| const deliver = async (record, snapshot) => { | ||
| await sink(record, snapshot); | ||
| await options.store.set({ | ||
| ...record, | ||
| deliveredAt: new Date().toISOString(), | ||
| status: snapshot.status | ||
| }); | ||
| }; | ||
| const track = async (target, opts) => { | ||
| const webhookUrl = opts.webhookUrl && !options.onComplete ? await validateWebhookUrl(opts.webhookUrl, webhookUrlValidator) : opts.webhookUrl; | ||
| const record = { | ||
| createdAt: new Date().toISOString(), | ||
| id: target.id, | ||
| provider: target.provider, | ||
| status: target.status ?? "in_progress", | ||
| webhookSecret: opts.secret, | ||
| webhookUrl | ||
| }; | ||
| await options.store.set(record); | ||
| return record; | ||
| }; | ||
| const process = async (record, delivered) => { | ||
| const adapter = getAdapter(record.provider); | ||
| const snapshot = await adapter.retrieve(record.id, resolveCredentials(record.provider)); | ||
| if (isTerminalStatus(snapshot.status)) { | ||
| await deliver(record, snapshot); | ||
| delivered.push(record.id); | ||
| } else if (snapshot.status !== record.status) { | ||
| await options.store.set({ ...record, status: snapshot.status }); | ||
| } | ||
| }; | ||
| const tick = async () => { | ||
| const pending = await options.store.list({ delivered: false }); | ||
| const delivered = []; | ||
| const failed = []; | ||
| for (const record of pending) { | ||
| try { | ||
| await process(record, delivered); | ||
| } catch (error) { | ||
| if (!options.onError) { | ||
| throw error; | ||
| } | ||
| options.onError(record, error); | ||
| failed.push({ | ||
| error: error instanceof Error ? error.message : String(error), | ||
| id: record.id | ||
| }); | ||
| } | ||
| } | ||
| return failed.length > 0 ? { checked: pending.length, delivered, failed } : { checked: pending.length, delivered }; | ||
| }; | ||
| const openaiWebhookHandler = (config) => async (request) => { | ||
| let verified; | ||
| try { | ||
| verified = await verifyWebhook(request, config.signingSecret); | ||
| } catch { | ||
| return new Response("invalid signature", { status: 400 }); | ||
| } | ||
| const payload = JSON.parse(verified.body); | ||
| if (!payload.type?.startsWith("batch.")) { | ||
| return new Response("ignored", { status: 202 }); | ||
| } | ||
| const id = payload.data?.id; | ||
| if (!id) { | ||
| return new Response("missing batch id", { status: 400 }); | ||
| } | ||
| const record = await options.store.get(id); | ||
| if (!record || record.deliveredAt) { | ||
| return new Response("ok", { status: 200 }); | ||
| } | ||
| const snapshot = await getAdapter("openai").retrieve(id, resolveCredentials("openai")); | ||
| if (isTerminalStatus(snapshot.status)) { | ||
| await deliver(record, snapshot); | ||
| } | ||
| return new Response("ok", { status: 200 }); | ||
| }; | ||
| return { deliver, openaiWebhookHandler, tick, track }; | ||
| }; | ||
| // src/server/store.ts | ||
| var createMemoryStore = () => { | ||
| const records = new Map; | ||
| return { | ||
| delete: (id) => { | ||
| records.delete(id); | ||
| return Promise.resolve(); | ||
| }, | ||
| get: (id) => Promise.resolve(records.get(id) ?? null), | ||
| list: (filter) => { | ||
| const all = [...records.values()]; | ||
| if (filter?.delivered === undefined) { | ||
| return Promise.resolve(all); | ||
| } | ||
| const { delivered } = filter; | ||
| return Promise.resolve(all.filter((record) => record.deliveredAt !== undefined === delivered)); | ||
| }, | ||
| set: (record) => { | ||
| records.set(record.id, record); | ||
| return Promise.resolve(); | ||
| } | ||
| }; | ||
| }; | ||
| export { toEvent, signWebhook, verifyWebhook, verifyBatchWebhook, createBatchPoller, createMemoryStore }; | ||
| //# debugId=BAF36CF27957AA5864756E2164756E21 | ||
| //# sourceMappingURL=chunk-d0g9wsxq.js.map |
| { | ||
| "version": 3, | ||
| "sources": ["../src/server/events.ts", "../src/server/signing.ts", "../src/server/poller.ts", "../src/server/store.ts"], | ||
| "sourcesContent": [ | ||
| "import type { BatchProvider, BatchSnapshot, BatchStatus } from \"../types\";\nimport type { BatchWebhookEvent, BatchWebhookEventType } from \"./types\";\n\nconst EVENT_BY_STATUS: Partial<Record<BatchStatus, BatchWebhookEventType>> = {\n cancelled: \"batch.cancelled\",\n completed: \"batch.completed\",\n expired: \"batch.expired\",\n failed: \"batch.failed\",\n};\n\n/** Map a terminal snapshot to the unified webhook event batchwork delivers. */\nexport const toEvent = (\n provider: BatchProvider,\n snapshot: BatchSnapshot\n): BatchWebhookEvent => ({\n completedAt: snapshot.completedAt?.toISOString(),\n createdAt: snapshot.createdAt?.toISOString(),\n id: snapshot.id,\n provider,\n requestCounts: snapshot.requestCounts,\n type: EVENT_BY_STATUS[snapshot.status] ?? \"batch.completed\",\n});\n", | ||
| "import { BatchworkError } from \"../errors\";\nimport type { BatchWebhookEvent } from \"./types\";\n\n// Standard Webhooks-style HMAC-SHA256 signing, compatible with OpenAI's webhook\n// signatures (so the same verifier handles inbound OpenAI events and batchwork's\n// own outbound deliveries). Uses Web Crypto so it runs on edge runtimes.\n\nconst SIGNATURE_VERSION = \"v1\";\nconst TOLERANCE_SECONDS = 300;\nconst SECRET_PREFIX = \"whsec_\";\nconst encoder = new TextEncoder();\n\nexport interface WebhookReplayStore {\n claim?: (\n id: string,\n expiresAt: number,\n now: number\n ) => boolean | Promise<boolean>;\n get: (id: string) => number | Promise<number | undefined> | undefined;\n set: (id: string, expiresAt: number) => Promise<void> | void;\n}\n\nexport interface VerifyWebhookOptions {\n replayStore?: WebhookReplayStore;\n}\n\nconst replayCache = new Map<string, number>();\nconst replayLocks = new WeakMap<\n WebhookReplayStore,\n Map<string, Promise<void>>\n>();\n\nconst pruneReplayCache = (now: number): void => {\n for (const [id, expiresAt] of replayCache) {\n if (expiresAt <= now) {\n replayCache.delete(id);\n }\n }\n};\n\nconst defaultReplayStore: WebhookReplayStore = {\n claim: (id, expiresAt, now) => {\n pruneReplayCache(now);\n const existing = replayCache.get(id);\n if (existing && existing > now) {\n return false;\n }\n replayCache.set(id, expiresAt);\n return true;\n },\n get: (id) => replayCache.get(id),\n set: (id, expiresAt) => {\n replayCache.set(id, expiresAt);\n },\n};\n\nconst withReplayLock = async <Result>(\n store: WebhookReplayStore,\n id: string,\n task: () => Promise<Result>\n): Promise<Result> => {\n let locks = replayLocks.get(store);\n if (!locks) {\n locks = new Map();\n replayLocks.set(store, locks);\n }\n\n const previous = locks.get(id);\n let release!: () => void;\n // oxlint-disable-next-line promise/avoid-new -- a per-id lock needs a deferred release signal.\n const current = new Promise<void>((resolve) => {\n release = resolve;\n });\n locks.set(id, current);\n\n if (previous) {\n await previous;\n }\n try {\n return await task();\n } finally {\n release();\n if (locks.get(id) === current) {\n locks.delete(id);\n }\n }\n};\n\nconst base64ToBytes = (value: string): Uint8Array<ArrayBuffer> =>\n Uint8Array.from(atob(value), (char) => char.codePointAt(0) ?? 0);\n\nconst bytesToBase64 = (bytes: Uint8Array): string => {\n let binary = \"\";\n for (const byte of bytes) {\n binary += String.fromCodePoint(byte);\n }\n return btoa(binary);\n};\n\nconst importKey = (secret: string): Promise<CryptoKey> => {\n const raw = secret.startsWith(SECRET_PREFIX)\n ? base64ToBytes(secret.slice(SECRET_PREFIX.length))\n : encoder.encode(secret);\n return crypto.subtle.importKey(\n \"raw\",\n raw,\n { hash: \"SHA-256\", name: \"HMAC\" },\n false,\n [\"sign\", \"verify\"]\n );\n};\n\nconst signContent = async (\n secret: string,\n content: string\n): Promise<string> => {\n const key = await importKey(secret);\n const signature = await crypto.subtle.sign(\n \"HMAC\",\n key,\n encoder.encode(content)\n );\n return bytesToBase64(new Uint8Array(signature));\n};\n\nconst verifyContent = async (\n secret: string,\n content: string,\n signature: string\n): Promise<boolean> => {\n const key = await importKey(secret);\n return crypto.subtle.verify(\n \"HMAC\",\n key,\n base64ToBytes(signature),\n encoder.encode(content)\n );\n};\n\nconst rememberWebhookId = async (\n id: string,\n timestamp: number,\n now: number,\n store: WebhookReplayStore\n): Promise<void> => {\n const expiresAt = timestamp + TOLERANCE_SECONDS;\n if (store.claim) {\n const claimed = await store.claim(id, expiresAt, now);\n if (!claimed) {\n throw new BatchworkError(\"batchwork: webhook replay detected.\");\n }\n return;\n }\n\n await withReplayLock(store, id, async () => {\n if (store === defaultReplayStore) {\n pruneReplayCache(now);\n }\n const existing = await store.get(id);\n if (existing && existing > now) {\n throw new BatchworkError(\"batchwork: webhook replay detected.\");\n }\n await store.set(id, expiresAt);\n });\n};\n\n/** Build Standard Webhooks signature headers for an outbound delivery. */\nexport const signWebhook = async (\n secret: string,\n id: string,\n body: string,\n timestampSeconds: number\n): Promise<Record<string, string>> => {\n const timestamp = Math.floor(timestampSeconds).toString();\n const signature = await signContent(secret, `${id}.${timestamp}.${body}`);\n return {\n \"webhook-id\": id,\n \"webhook-signature\": `${SIGNATURE_VERSION},${signature}`,\n \"webhook-timestamp\": timestamp,\n };\n};\n\nexport interface VerifiedWebhook {\n id: string;\n timestamp: number;\n body: string;\n}\n\n/**\n * Verify a Standard Webhooks-signed request and return its raw body. Throws if\n * headers are missing, the timestamp is outside tolerance, or no signature\n * matches. Consumes the request body.\n */\nexport const verifyWebhook = async (\n request: Request,\n secret: string,\n options?: VerifyWebhookOptions\n): Promise<VerifiedWebhook> => {\n const id = request.headers.get(\"webhook-id\");\n const timestamp = request.headers.get(\"webhook-timestamp\");\n const signatureHeader = request.headers.get(\"webhook-signature\");\n if (!(id && timestamp && signatureHeader)) {\n throw new BatchworkError(\"batchwork: missing webhook signature headers.\");\n }\n\n const seconds = Number(timestamp);\n const now = Date.now() / 1000;\n if (\n !Number.isFinite(seconds) ||\n Math.abs(now - seconds) > TOLERANCE_SECONDS\n ) {\n throw new BatchworkError(\"batchwork: webhook timestamp outside tolerance.\");\n }\n\n const body = await request.text();\n const content = `${id}.${timestamp}.${body}`;\n const signatures = signatureHeader.split(\" \").map((part) => {\n const comma = part.indexOf(\",\");\n return comma === -1 ? part : part.slice(comma + 1);\n });\n\n let valid = false;\n for (const signature of signatures) {\n // oxlint-disable-next-line no-await-in-loop -- usually a single signature.\n if (await verifyContent(secret, content, signature)) {\n valid = true;\n break;\n }\n }\n if (!valid) {\n throw new BatchworkError(\n \"batchwork: webhook signature verification failed.\"\n );\n }\n\n await rememberWebhookId(\n id,\n seconds,\n now,\n options?.replayStore ?? defaultReplayStore\n );\n\n return { body, id, timestamp: seconds };\n};\n\n/**\n * Verify and parse a batchwork webhook on your receiving endpoint. Returns the\n * unified {@link BatchWebhookEvent}.\n */\nexport const verifyBatchWebhook = async (\n request: Request,\n secret: string,\n options?: VerifyWebhookOptions\n): Promise<BatchWebhookEvent> => {\n const { body } = await verifyWebhook(request, secret, options);\n return JSON.parse(body) as BatchWebhookEvent;\n};\n", | ||
| "import { BatchworkError } from \"../errors\";\nimport { isTerminalStatus } from \"../job\";\nimport { getAdapter } from \"../providers\";\nimport type {\n BatchProvider,\n BatchSnapshot,\n BatchStatus,\n ProviderCredentials,\n} from \"../types\";\nimport { toEvent } from \"./events\";\nimport { signWebhook, verifyWebhook } from \"./signing\";\nimport type { BatchStore, TrackedBatch } from \"./types\";\n\n/** Credentials for polling: a fixed config, or one resolved per provider. */\nexport type CredentialResolver =\n | ProviderCredentials\n | ((provider: BatchProvider) => ProviderCredentials);\n\n/**\n * Handles a batch reaching a terminal status. Replaces the default signed\n * webhook delivery — e.g. to invoke a callback instead (see `batchwork/next`).\n */\nexport type CompletionSink = (\n record: TrackedBatch,\n snapshot: BatchSnapshot\n) => Promise<void>;\n\nexport type WebhookUrlValidator = (url: URL) => void | Promise<void>;\n\nexport interface BatchPollerOptions {\n store: BatchStore;\n /** Falls back to provider env vars (e.g. `OPENAI_API_KEY`) when omitted. */\n credentials?: CredentialResolver;\n /** Replaces signed-webhook delivery when a batch finishes. */\n onComplete?: CompletionSink;\n /** Override the default webhook URL policy with an application allowlist. */\n validateWebhookUrl?: WebhookUrlValidator;\n /**\n * Called when processing a single batch throws during `tick`. When provided,\n * the tick reports the error and continues to the next batch; when omitted,\n * the error propagates out of `tick`.\n */\n onError?: (record: TrackedBatch, error: unknown) => void;\n}\n\nexport interface TrackTarget {\n id: string;\n provider: BatchProvider;\n status?: BatchStatus;\n}\n\nexport interface TrackOptions {\n /** Where to POST the completion webhook. Omit for callback-based delivery. */\n webhookUrl?: string;\n /** Signs the outbound webhook (Standard Webhooks HMAC) when provided. */\n secret?: string;\n}\n\nexport interface TickResult {\n checked: number;\n delivered: string[];\n /** Batches whose processing threw this tick (only when `onError` is set). */\n failed?: { id: string; error: string }[];\n}\n\nexport interface OpenAIWebhookOptions {\n /** The OpenAI webhook signing secret (`whsec_…`). */\n signingSecret: string;\n}\n\nexport interface BatchPoller {\n track: (target: TrackTarget, options: TrackOptions) => Promise<TrackedBatch>;\n tick: () => Promise<TickResult>;\n deliver: (record: TrackedBatch, snapshot: BatchSnapshot) => Promise<void>;\n openaiWebhookHandler: (\n options: OpenAIWebhookOptions\n ) => (request: Request) => Promise<Response>;\n}\n\nconst parseIpv4 = (host: string): number[] | undefined => {\n if (!/^\\d{1,3}(?:\\.\\d{1,3}){3}$/u.test(host)) {\n return;\n }\n const parts = host.split(\".\").map(Number);\n const valid = parts.every(\n (part) => Number.isInteger(part) && part >= 0 && part <= 255\n );\n return valid ? parts : undefined;\n};\n\nconst isPrivateIpv4 = (parts: number[]): boolean => {\n const [a = 0, b = 0] = parts;\n return (\n a === 0 ||\n a === 10 ||\n a === 127 ||\n (a === 100 && b >= 64 && b <= 127) ||\n (a === 169 && b === 254) ||\n (a === 172 && b >= 16 && b <= 31) ||\n (a === 192 && b === 168) ||\n (a === 198 && (b === 18 || b === 19)) ||\n a >= 224\n );\n};\n\nconst parseIpv4MappedIpv6 = (host: string): number[] | undefined => {\n const normalized = host.replace(/^\\[/u, \"\").replace(/\\]$/u, \"\").toLowerCase();\n if (!normalized.startsWith(\"::ffff:\")) {\n return;\n }\n\n const suffix = normalized.slice(\"::ffff:\".length);\n const dotted = parseIpv4(suffix);\n if (dotted) {\n return dotted;\n }\n\n const [high, low, extra] = suffix.split(\":\");\n if (!(high && low) || extra !== undefined) {\n return;\n }\n const highBits = Number.parseInt(high, 16);\n const lowBits = Number.parseInt(low, 16);\n if (\n !Number.isInteger(highBits) ||\n !Number.isInteger(lowBits) ||\n highBits < 0 ||\n highBits > 65_535 ||\n lowBits < 0 ||\n lowBits > 65_535\n ) {\n return;\n }\n return [\n Math.floor(highBits / 256),\n highBits % 256,\n Math.floor(lowBits / 256),\n lowBits % 256,\n ];\n};\n\nconst isPrivateIpv6 = (host: string): boolean => {\n const normalized = host.replace(/^\\[/u, \"\").replace(/\\]$/u, \"\").toLowerCase();\n // Only an IPv6 literal can be a private address, and one always contains a\n // colon — guard so a bare DNS name like `fc2.com` is not mistaken for `fc00::/7`.\n if (!normalized.includes(\":\")) {\n return false;\n }\n return (\n normalized === \"::\" ||\n normalized === \"::1\" ||\n normalized.startsWith(\"fc\") ||\n normalized.startsWith(\"fd\") ||\n normalized.startsWith(\"fe80:\")\n );\n};\n\nconst isRedirectStatus = (status: number): boolean =>\n status >= 300 && status < 400;\n\nconst assertSafeWebhookUrl: WebhookUrlValidator = (url) => {\n if (url.protocol !== \"https:\") {\n throw new BatchworkError(\"batchwork: webhookUrl must use https.\");\n }\n if (url.username || url.password) {\n throw new BatchworkError(\n \"batchwork: webhookUrl must not include credentials.\"\n );\n }\n\n const host = url.hostname.toLowerCase();\n const ipv4 = parseIpv4(host);\n const mappedIpv4 = parseIpv4MappedIpv6(host);\n if (\n host === \"localhost\" ||\n host.endsWith(\".localhost\") ||\n host.endsWith(\".local\") ||\n (ipv4 && isPrivateIpv4(ipv4)) ||\n (mappedIpv4 && isPrivateIpv4(mappedIpv4)) ||\n isPrivateIpv6(host)\n ) {\n throw new BatchworkError(\n \"batchwork: webhookUrl must not target localhost or private networks.\"\n );\n }\n};\n\nconst validateWebhookUrl = async (\n rawUrl: string,\n validator: WebhookUrlValidator\n): Promise<string> => {\n let url: URL;\n try {\n url = new URL(rawUrl);\n } catch (error) {\n throw new BatchworkError(\"batchwork: webhookUrl must be a valid URL.\", {\n cause: error,\n });\n }\n await validator(url);\n return url.toString();\n};\n\n/** The default completion sink: POST a signed webhook to the tracked URL. */\nconst createWebhookSink =\n (validator: WebhookUrlValidator): CompletionSink =>\n async (record, snapshot) => {\n if (!record.webhookUrl) {\n throw new BatchworkError(\n \"batchwork: tracked batch has no webhookUrl to deliver to.\"\n );\n }\n const webhookUrl = await validateWebhookUrl(record.webhookUrl, validator);\n const body = JSON.stringify(toEvent(record.provider, snapshot));\n const headers: Record<string, string> = {\n \"content-type\": \"application/json\",\n };\n if (record.webhookSecret) {\n Object.assign(\n headers,\n await signWebhook(\n record.webhookSecret,\n record.id,\n body,\n Date.now() / 1000\n )\n );\n }\n const response = await fetch(webhookUrl, {\n body,\n headers,\n method: \"POST\",\n redirect: \"manual\",\n });\n if (isRedirectStatus(response.status)) {\n throw new BatchworkError(\n `batchwork: webhook delivery to ${webhookUrl} redirected (${response.status}).`\n );\n }\n if (!response.ok) {\n throw new BatchworkError(\n `batchwork: webhook delivery to ${webhookUrl} failed (${response.status}).`\n );\n }\n };\n\n/**\n * Create a managed poller: register submitted batches with `track`, then run\n * `tick` on a schedule (cron) to poll open batches and deliver one unified,\n * signed webhook per batch when it finishes. For OpenAI, mount\n * `openaiWebhookHandler` to skip polling and react to native webhooks instead.\n */\nexport const createBatchPoller = (options: BatchPollerOptions): BatchPoller => {\n const resolveCredentials = (provider: BatchProvider): ProviderCredentials => {\n if (typeof options.credentials === \"function\") {\n return options.credentials(provider);\n }\n return options.credentials ?? {};\n };\n\n const webhookUrlValidator =\n options.validateWebhookUrl ?? assertSafeWebhookUrl;\n const sink = options.onComplete ?? createWebhookSink(webhookUrlValidator);\n\n const deliver = async (\n record: TrackedBatch,\n snapshot: BatchSnapshot\n ): Promise<void> => {\n // Run the side effect before marking delivered: if it throws, the record\n // stays pending and is retried on the next tick (at-least-once delivery).\n await sink(record, snapshot);\n await options.store.set({\n ...record,\n deliveredAt: new Date().toISOString(),\n status: snapshot.status,\n });\n };\n\n const track = async (\n target: TrackTarget,\n opts: TrackOptions\n ): Promise<TrackedBatch> => {\n const webhookUrl =\n opts.webhookUrl && !options.onComplete\n ? await validateWebhookUrl(opts.webhookUrl, webhookUrlValidator)\n : opts.webhookUrl;\n const record: TrackedBatch = {\n createdAt: new Date().toISOString(),\n id: target.id,\n provider: target.provider,\n status: target.status ?? \"in_progress\",\n webhookSecret: opts.secret,\n webhookUrl,\n };\n await options.store.set(record);\n return record;\n };\n\n const process = async (\n record: TrackedBatch,\n delivered: string[]\n ): Promise<void> => {\n const adapter = getAdapter(record.provider);\n const snapshot = await adapter.retrieve(\n record.id,\n resolveCredentials(record.provider)\n );\n if (isTerminalStatus(snapshot.status)) {\n await deliver(record, snapshot);\n delivered.push(record.id);\n } else if (snapshot.status !== record.status) {\n await options.store.set({ ...record, status: snapshot.status });\n }\n };\n\n const tick = async (): Promise<TickResult> => {\n const pending = await options.store.list({ delivered: false });\n const delivered: string[] = [];\n const failed: { id: string; error: string }[] = [];\n for (const record of pending) {\n // oxlint-disable-next-line no-await-in-loop -- batches are polled serially\n // to avoid hammering provider rate limits; deliver before the next.\n try {\n // oxlint-disable-next-line no-await-in-loop -- see above.\n await process(record, delivered);\n } catch (error) {\n // Without an `onError` handler, preserve the propagate-the-throw\n // behavior; with one, report and continue so a single failing batch\n // can't starve the rest of the queue. Either way the record stays\n // pending (it was never marked delivered) and retries next tick.\n if (!options.onError) {\n throw error;\n }\n options.onError(record, error);\n failed.push({\n error: error instanceof Error ? error.message : String(error),\n id: record.id,\n });\n }\n }\n return failed.length > 0\n ? { checked: pending.length, delivered, failed }\n : { checked: pending.length, delivered };\n };\n\n const openaiWebhookHandler =\n (config: OpenAIWebhookOptions) =>\n async (request: Request): Promise<Response> => {\n let verified: { body: string };\n try {\n verified = await verifyWebhook(request, config.signingSecret);\n } catch {\n return new Response(\"invalid signature\", { status: 400 });\n }\n\n const payload = JSON.parse(verified.body) as {\n type?: string;\n data?: { id?: string };\n };\n if (!payload.type?.startsWith(\"batch.\")) {\n return new Response(\"ignored\", { status: 202 });\n }\n const id = payload.data?.id;\n if (!id) {\n return new Response(\"missing batch id\", { status: 400 });\n }\n const record = await options.store.get(id);\n if (!record || record.deliveredAt) {\n return new Response(\"ok\", { status: 200 });\n }\n const snapshot = await getAdapter(\"openai\").retrieve(\n id,\n resolveCredentials(\"openai\")\n );\n if (isTerminalStatus(snapshot.status)) {\n await deliver(record, snapshot);\n }\n return new Response(\"ok\", { status: 200 });\n };\n\n return { deliver, openaiWebhookHandler, tick, track };\n};\n", | ||
| "import type { BatchStore, TrackedBatch } from \"./types\";\n\n/** An in-memory `BatchStore`. Suitable for development and single-process use. */\nexport const createMemoryStore = (): BatchStore => {\n const records = new Map<string, TrackedBatch>();\n\n return {\n delete: (id) => {\n records.delete(id);\n return Promise.resolve();\n },\n get: (id) => Promise.resolve(records.get(id) ?? null),\n list: (filter) => {\n const all = [...records.values()];\n if (filter?.delivered === undefined) {\n return Promise.resolve(all);\n }\n const { delivered } = filter;\n return Promise.resolve(\n all.filter((record) => (record.deliveredAt !== undefined) === delivered)\n );\n },\n set: (record) => {\n records.set(record.id, record);\n return Promise.resolve();\n },\n };\n};\n" | ||
| ], | ||
| "mappings": "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", | ||
| "debugId": "BAF36CF27957AA5864756E2164756E21", | ||
| "names": [] | ||
| } |
| // src/errors.ts | ||
| class BatchworkError extends Error { | ||
| constructor(message, options) { | ||
| super(message, options); | ||
| this.name = "BatchworkError"; | ||
| } | ||
| } | ||
| class UnsupportedProviderError extends BatchworkError { | ||
| provider; | ||
| constructor(provider, detail) { | ||
| super(detail ?? `batchwork: provider "${provider}" is not supported yet. Supported providers: openai, anthropic, google, groq, mistral, together, xai.`); | ||
| this.name = "UnsupportedProviderError"; | ||
| this.provider = provider; | ||
| } | ||
| } | ||
| class MissingDependencyError extends BatchworkError { | ||
| constructor(pkg, provider) { | ||
| super(`batchwork: install \`${pkg}\` to batch ${provider} models (\`npm install ${pkg}\`).`); | ||
| this.name = "MissingDependencyError"; | ||
| } | ||
| } | ||
| // src/job.ts | ||
| var DEFAULT_POLL_INTERVAL_MS = 15000; | ||
| var TERMINAL_STATUSES = new Set([ | ||
| "completed", | ||
| "failed", | ||
| "expired", | ||
| "cancelled" | ||
| ]); | ||
| var isTerminalStatus = (status) => TERMINAL_STATUSES.has(status); | ||
| var delay = (ms, signal) => new Promise((resolve, reject) => { | ||
| if (signal?.aborted) { | ||
| reject(new BatchworkError("batchwork: wait aborted.")); | ||
| return; | ||
| } | ||
| let timer; | ||
| const onAbort = () => { | ||
| clearTimeout(timer); | ||
| reject(new BatchworkError("batchwork: wait aborted.")); | ||
| }; | ||
| timer = setTimeout(() => { | ||
| signal?.removeEventListener("abort", onAbort); | ||
| resolve(); | ||
| }, ms); | ||
| signal?.addEventListener("abort", onAbort, { once: true }); | ||
| }); | ||
| class BatchJob { | ||
| provider; | ||
| id; | ||
| #adapter; | ||
| #credentials; | ||
| #snapshot; | ||
| constructor(adapter, credentials, snapshot) { | ||
| this.#adapter = adapter; | ||
| this.#credentials = credentials; | ||
| this.#snapshot = snapshot; | ||
| this.id = snapshot.id; | ||
| this.provider = snapshot.provider; | ||
| } | ||
| get status() { | ||
| return this.#snapshot.status; | ||
| } | ||
| get requestCounts() { | ||
| return this.#snapshot.requestCounts; | ||
| } | ||
| get snapshot() { | ||
| return this.#snapshot; | ||
| } | ||
| async poll() { | ||
| this.#snapshot = await this.#adapter.retrieve(this.id, this.#credentials); | ||
| return this.#snapshot; | ||
| } | ||
| async wait(options = {}) { | ||
| const interval = options.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS; | ||
| const deadline = options.timeoutMs ? Date.now() + options.timeoutMs : undefined; | ||
| let snapshot = await this.poll(); | ||
| options.onPoll?.(snapshot); | ||
| while (!isTerminalStatus(snapshot.status)) { | ||
| if (options.signal?.aborted) { | ||
| throw new BatchworkError("batchwork: wait aborted."); | ||
| } | ||
| if (deadline !== undefined && Date.now() > deadline) { | ||
| throw new BatchworkError(`batchwork: timed out waiting for batch "${this.id}".`); | ||
| } | ||
| await delay(interval, options.signal); | ||
| snapshot = await this.poll(); | ||
| options.onPoll?.(snapshot); | ||
| } | ||
| return snapshot; | ||
| } | ||
| results() { | ||
| return this.#adapter.results(this.id, this.#credentials); | ||
| } | ||
| async collect() { | ||
| const out = []; | ||
| for await (const result of this.results()) { | ||
| out.push(result); | ||
| } | ||
| return out; | ||
| } | ||
| async cancel() { | ||
| await this.#adapter.cancel(this.id, this.#credentials); | ||
| return await this.poll(); | ||
| } | ||
| } | ||
| // src/limits.ts | ||
| var DEFAULT_LIMITS = { | ||
| captureConcurrency: 16, | ||
| maxRequestBytes: 20 * 1024 * 1024, | ||
| maxRequests: 50000, | ||
| maxUploadBytes: 200 * 1024 * 1024 | ||
| }; | ||
| var encoder = new TextEncoder; | ||
| var positiveInteger = (name, value) => { | ||
| if (!(Number.isInteger(value) && value > 0)) { | ||
| throw new BatchworkError(`batchwork: limits.${name} must be a positive integer.`); | ||
| } | ||
| return value; | ||
| }; | ||
| var resolveBatchLimits = (limits) => ({ | ||
| captureConcurrency: positiveInteger("captureConcurrency", limits?.captureConcurrency ?? DEFAULT_LIMITS.captureConcurrency), | ||
| maxRequestBytes: positiveInteger("maxRequestBytes", limits?.maxRequestBytes ?? DEFAULT_LIMITS.maxRequestBytes), | ||
| maxRequests: positiveInteger("maxRequests", limits?.maxRequests ?? DEFAULT_LIMITS.maxRequests), | ||
| maxUploadBytes: positiveInteger("maxUploadBytes", limits?.maxUploadBytes ?? DEFAULT_LIMITS.maxUploadBytes) | ||
| }); | ||
| var byteLength = (value) => encoder.encode(value).length; | ||
| var assertByteCount = (label, bytes, maxBytes) => { | ||
| if (bytes > maxBytes) { | ||
| throw new BatchworkError(`batchwork: ${label} is ${bytes} bytes, exceeding the ${maxBytes} byte limit.`); | ||
| } | ||
| }; | ||
| var assertByteLength = (label, value, maxBytes) => { | ||
| assertByteCount(label, byteLength(value), maxBytes); | ||
| }; | ||
| var mapWithConcurrency = async (items, concurrency, mapper) => { | ||
| const results = []; | ||
| results.length = items.length; | ||
| let nextIndex = 0; | ||
| const workerCount = Math.min(concurrency, items.length); | ||
| const runNext = async () => { | ||
| const index = nextIndex; | ||
| nextIndex += 1; | ||
| if (index >= items.length) { | ||
| return; | ||
| } | ||
| results[index] = await mapper(items[index]); | ||
| await runNext(); | ||
| }; | ||
| await Promise.all(Array.from({ length: workerCount }, () => runNext())); | ||
| return results; | ||
| }; | ||
| // src/http.ts | ||
| var assertOk = (url, init, response) => { | ||
| if (!response.ok) { | ||
| throw new BatchworkError(`batchwork: ${init.method ?? "GET"} ${url} failed with ${response.status}.`); | ||
| } | ||
| }; | ||
| var requestJson = async (url, init) => { | ||
| const response = await fetch(url, init); | ||
| assertOk(url, init, response); | ||
| return await response.json(); | ||
| }; | ||
| var requestStream = async (url, init) => { | ||
| const response = await fetch(url, init); | ||
| assertOk(url, init, response); | ||
| if (!response.body) { | ||
| throw new BatchworkError(`batchwork: ${url} returned an empty body.`); | ||
| } | ||
| return response.body; | ||
| }; | ||
| // src/jsonl.ts | ||
| var NEWLINE = ` | ||
| `; | ||
| var DEFAULT_MAX_JSONL_LINE_BYTES = 20 * 1024 * 1024; | ||
| var NEWLINE_BYTES = byteLength(NEWLINE); | ||
| var resolveMaxLineBytes = (options) => { | ||
| const maxLineBytes = options?.maxLineBytes ?? DEFAULT_MAX_JSONL_LINE_BYTES; | ||
| if (!(Number.isInteger(maxLineBytes) && maxLineBytes > 0)) { | ||
| throw new BatchworkError("batchwork: JSONL maxLineBytes must be a positive integer."); | ||
| } | ||
| return maxLineBytes; | ||
| }; | ||
| var assertLineSize = (line, lineNumber, maxLineBytes) => { | ||
| const bytes = byteLength(line); | ||
| if (bytes > maxLineBytes) { | ||
| throw new BatchworkError(`batchwork: JSONL line ${lineNumber} is ${bytes} bytes, exceeding the ${maxLineBytes} byte limit.`); | ||
| } | ||
| }; | ||
| var parseLine = (line, lineNumber, maxLineBytes) => { | ||
| assertLineSize(line, lineNumber, maxLineBytes); | ||
| const trimmed = line.trim(); | ||
| if (trimmed.length === 0) { | ||
| return; | ||
| } | ||
| try { | ||
| return JSON.parse(trimmed); | ||
| } catch (error) { | ||
| throw new BatchworkError(`batchwork: invalid JSONL at line ${lineNumber}.`, { cause: error }); | ||
| } | ||
| }; | ||
| var resolveMaxBytes = (options) => { | ||
| const maxBytes = options?.maxBytes; | ||
| if (maxBytes !== undefined && !(Number.isInteger(maxBytes) && maxBytes > 0)) { | ||
| throw new BatchworkError("batchwork: JSONL maxBytes must be a positive integer."); | ||
| } | ||
| return maxBytes; | ||
| }; | ||
| var encodeJsonl = (items, options) => { | ||
| if (items.length === 0) { | ||
| return ""; | ||
| } | ||
| const lines = []; | ||
| const maxBytes = resolveMaxBytes(options); | ||
| const label = options?.label ?? "JSONL"; | ||
| let bytes = 0; | ||
| for (const item of items) { | ||
| const line = JSON.stringify(item); | ||
| if (line === undefined) { | ||
| throw new BatchworkError(`batchwork: ${label} contains a value that cannot be JSON encoded.`); | ||
| } | ||
| bytes += byteLength(line) + NEWLINE_BYTES; | ||
| if (maxBytes !== undefined) { | ||
| assertByteCount(label, bytes, maxBytes); | ||
| } | ||
| lines.push(line); | ||
| } | ||
| return `${lines.join(NEWLINE)}${NEWLINE}`; | ||
| }; | ||
| var isReadableStream = (source) => ("getReader" in source) && typeof source.getReader === "function"; | ||
| async function* toByteIterable(source) { | ||
| if (isReadableStream(source)) { | ||
| const reader = source.getReader(); | ||
| try { | ||
| let chunk = await reader.read(); | ||
| while (!chunk.done) { | ||
| if (chunk.value) { | ||
| yield chunk.value; | ||
| } | ||
| chunk = await reader.read(); | ||
| } | ||
| } finally { | ||
| reader.releaseLock(); | ||
| } | ||
| return; | ||
| } | ||
| yield* source; | ||
| } | ||
| async function* streamJsonl(source, options) { | ||
| const decoder = new TextDecoder; | ||
| const maxLineBytes = resolveMaxLineBytes(options); | ||
| let buffer = ""; | ||
| let lineNumber = 1; | ||
| for await (const chunk of toByteIterable(source)) { | ||
| buffer += decoder.decode(chunk, { stream: true }); | ||
| let newlineIndex = buffer.indexOf(NEWLINE); | ||
| while (newlineIndex !== -1) { | ||
| const line = buffer.slice(0, newlineIndex); | ||
| buffer = buffer.slice(newlineIndex + 1); | ||
| const parsed2 = parseLine(line, lineNumber, maxLineBytes); | ||
| if (parsed2 !== undefined) { | ||
| yield parsed2; | ||
| } | ||
| lineNumber += 1; | ||
| newlineIndex = buffer.indexOf(NEWLINE); | ||
| } | ||
| assertLineSize(buffer, lineNumber, maxLineBytes); | ||
| } | ||
| buffer += decoder.decode(); | ||
| const parsed = parseLine(buffer, lineNumber, maxLineBytes); | ||
| if (parsed !== undefined) { | ||
| yield parsed; | ||
| } | ||
| } | ||
| // src/payload.ts | ||
| var encodeJsonArrayPayload = ({ | ||
| items, | ||
| label, | ||
| maxBytes, | ||
| prefix, | ||
| suffix | ||
| }) => { | ||
| const encodedItems = []; | ||
| let bytes = byteLength(prefix) + byteLength(suffix); | ||
| assertByteCount(label, bytes, maxBytes); | ||
| for (const [index, item] of items.entries()) { | ||
| const encoded = JSON.stringify(item); | ||
| if (encoded === undefined) { | ||
| throw new BatchworkError(`batchwork: ${label} contains a value that cannot be JSON encoded.`); | ||
| } | ||
| bytes += byteLength(encoded); | ||
| if (index > 0) { | ||
| bytes += 1; | ||
| } | ||
| assertByteCount(label, bytes, maxBytes); | ||
| encodedItems.push(encoded); | ||
| } | ||
| return `${prefix}${encodedItems.join(",")}${suffix}`; | ||
| }; | ||
| // src/util.ts | ||
| var asRecord = (value) => { | ||
| if (typeof value === "object" && value !== null) { | ||
| return value; | ||
| } | ||
| return {}; | ||
| }; | ||
| var asString = (value) => typeof value === "string" ? value : undefined; | ||
| var asNumber = (value) => typeof value === "number" ? value : undefined; | ||
| var asArray = (value) => Array.isArray(value) ? value : []; | ||
| var asNumberArray = (value) => { | ||
| if (!Array.isArray(value) || value.length === 0) { | ||
| return; | ||
| } | ||
| const numbers = value.filter((item) => typeof item === "number"); | ||
| return numbers.length === value.length ? numbers : undefined; | ||
| }; | ||
| var omit = (obj, key) => { | ||
| const result = {}; | ||
| for (const [k, v] of Object.entries(obj)) { | ||
| if (k !== key) { | ||
| result[k] = v; | ||
| } | ||
| } | ||
| return result; | ||
| }; | ||
| var validDate = (date) => Number.isNaN(date.getTime()) ? undefined : date; | ||
| var toDate = (value) => { | ||
| if (typeof value === "string") { | ||
| return validDate(new Date(value)); | ||
| } | ||
| if (typeof value === "number") { | ||
| return validDate(new Date(value * 1000)); | ||
| } | ||
| }; | ||
| // src/providers/ids.ts | ||
| var SIMPLE_PROVIDER_ID = /^[A-Za-z0-9_-]+$/u; | ||
| var assertSimpleProviderId = (label, id) => { | ||
| if (!SIMPLE_PROVIDER_ID.test(id)) { | ||
| throw new BatchworkError(`batchwork: invalid ${label}.`); | ||
| } | ||
| return id; | ||
| }; | ||
| var assertPrefixedProviderId = (label, id, prefix) => { | ||
| const [actualPrefix, value, ...rest] = id.split("/"); | ||
| if (rest.length > 0 || actualPrefix !== prefix || !value || !SIMPLE_PROVIDER_ID.test(value)) { | ||
| throw new BatchworkError(`batchwork: invalid ${label}.`); | ||
| } | ||
| return id; | ||
| }; | ||
| // src/providers/anthropic.ts | ||
| var ANTHROPIC_BASE = "https://api.anthropic.com"; | ||
| var ANTHROPIC_VERSION = "2023-06-01"; | ||
| var apiKey = (credentials) => { | ||
| const key = credentials.apiKey ?? process.env.ANTHROPIC_API_KEY; | ||
| if (!key) { | ||
| throw new BatchworkError("batchwork: missing Anthropic API key. Set ANTHROPIC_API_KEY or pass `apiKey`."); | ||
| } | ||
| return key; | ||
| }; | ||
| var baseUrl = (credentials) => credentials.baseURL ?? ANTHROPIC_BASE; | ||
| var validateResultsUrl = (rawUrl, credentials) => { | ||
| let resultsUrl; | ||
| let expectedBase; | ||
| try { | ||
| resultsUrl = new URL(rawUrl); | ||
| expectedBase = new URL(baseUrl(credentials)); | ||
| } catch (error) { | ||
| throw new BatchworkError("batchwork: invalid Anthropic results_url.", { | ||
| cause: error | ||
| }); | ||
| } | ||
| if (resultsUrl.origin !== expectedBase.origin) { | ||
| throw new BatchworkError("batchwork: Anthropic results_url must match the configured API origin."); | ||
| } | ||
| if (resultsUrl.username || resultsUrl.password) { | ||
| throw new BatchworkError("batchwork: Anthropic results_url must not include credentials."); | ||
| } | ||
| return resultsUrl.toString(); | ||
| }; | ||
| var headers = (credentials) => ({ | ||
| "anthropic-version": ANTHROPIC_VERSION, | ||
| "content-type": "application/json", | ||
| "x-api-key": apiKey(credentials), | ||
| ...credentials.headers | ||
| }); | ||
| var mapStatus = (status) => { | ||
| if (status === "ended") { | ||
| return "completed"; | ||
| } | ||
| if (status === "canceling") { | ||
| return "cancelling"; | ||
| } | ||
| return "in_progress"; | ||
| }; | ||
| var normalizeSnapshot = (raw) => { | ||
| const obj = asRecord(raw); | ||
| const counts = asRecord(obj.request_counts); | ||
| const succeeded = asNumber(counts.succeeded) ?? 0; | ||
| const errored = asNumber(counts.errored) ?? 0; | ||
| const processing = asNumber(counts.processing) ?? 0; | ||
| const canceled = asNumber(counts.canceled) ?? 0; | ||
| const expired = asNumber(counts.expired) ?? 0; | ||
| return { | ||
| completedAt: toDate(obj.ended_at), | ||
| createdAt: toDate(obj.created_at), | ||
| expiresAt: toDate(obj.expires_at), | ||
| id: asString(obj.id) ?? "", | ||
| provider: "anthropic", | ||
| raw, | ||
| requestCounts: { | ||
| canceled, | ||
| completed: succeeded, | ||
| expired, | ||
| failed: errored, | ||
| processing, | ||
| total: succeeded + errored + processing + canceled + expired | ||
| }, | ||
| status: mapStatus(asString(obj.processing_status)) | ||
| }; | ||
| }; | ||
| var textFromMessage = (message) => { | ||
| const text = asArray(asRecord(message).content).map((block) => asRecord(block)).filter((block) => block.type === "text").map((block) => asString(block.text) ?? "").join(""); | ||
| return text.length > 0 ? text : undefined; | ||
| }; | ||
| var usageFromMessage = (message) => { | ||
| const usage = asRecord(asRecord(message).usage); | ||
| const inputTokens = asNumber(usage.input_tokens); | ||
| const outputTokens = asNumber(usage.output_tokens); | ||
| if (inputTokens === undefined && outputTokens === undefined) { | ||
| return; | ||
| } | ||
| return { | ||
| inputTokens, | ||
| outputTokens, | ||
| totalTokens: (inputTokens ?? 0) + (outputTokens ?? 0) | ||
| }; | ||
| }; | ||
| var normalizeResult = (line) => { | ||
| const obj = asRecord(line); | ||
| const customId = asString(obj.custom_id) ?? ""; | ||
| const result = asRecord(obj.result); | ||
| const type = asString(result.type); | ||
| if (type === "succeeded") { | ||
| return { | ||
| customId, | ||
| response: result.message, | ||
| status: "succeeded", | ||
| text: textFromMessage(result.message), | ||
| usage: usageFromMessage(result.message) | ||
| }; | ||
| } | ||
| if (type === "errored") { | ||
| const error = asRecord(result.error); | ||
| const nested = asRecord(error.error); | ||
| const source = Object.keys(nested).length > 0 ? nested : error; | ||
| return { | ||
| customId, | ||
| error: { | ||
| message: asString(source.message) ?? "Request errored.", | ||
| type: asString(source.type) | ||
| }, | ||
| response: result.error, | ||
| status: "errored" | ||
| }; | ||
| } | ||
| if (type === "expired") { | ||
| return { customId, status: "expired" }; | ||
| } | ||
| return { customId, status: "canceled" }; | ||
| }; | ||
| var submit = async (input) => { | ||
| const limits = resolveBatchLimits(input.limits); | ||
| const requests = input.built.map((item) => ({ | ||
| custom_id: item.customId, | ||
| params: omit(item.body, "stream") | ||
| })); | ||
| const body = encodeJsonArrayPayload({ | ||
| items: requests, | ||
| label: "batch upload payload", | ||
| maxBytes: limits.maxUploadBytes, | ||
| prefix: '{"requests":[', | ||
| suffix: "]}" | ||
| }); | ||
| const raw = await requestJson(`${baseUrl(input.credentials)}/v1/messages/batches`, { | ||
| body, | ||
| headers: headers(input.credentials), | ||
| method: "POST" | ||
| }); | ||
| return normalizeSnapshot(raw); | ||
| }; | ||
| var retrieve = async (id, credentials) => { | ||
| const batchId = assertSimpleProviderId("Anthropic batch id", id); | ||
| const raw = await requestJson(`${baseUrl(credentials)}/v1/messages/batches/${batchId}`, { headers: headers(credentials) }); | ||
| return normalizeSnapshot(raw); | ||
| }; | ||
| async function* results(id, credentials) { | ||
| const snapshot = await retrieve(id, credentials); | ||
| const resultsUrl = asString(asRecord(snapshot.raw).results_url); | ||
| if (!resultsUrl) { | ||
| throw new BatchworkError(`batchwork: results are not ready for batch "${id}" (status: ${snapshot.status}).`); | ||
| } | ||
| const stream = await requestStream(validateResultsUrl(resultsUrl, credentials), { | ||
| headers: headers(credentials), | ||
| redirect: "manual" | ||
| }); | ||
| for await (const line of streamJsonl(stream)) { | ||
| yield normalizeResult(line); | ||
| } | ||
| } | ||
| var cancel = async (id, credentials) => { | ||
| const batchId = assertSimpleProviderId("Anthropic batch id", id); | ||
| await requestJson(`${baseUrl(credentials)}/v1/messages/batches/${batchId}/cancel`, { | ||
| headers: headers(credentials), | ||
| method: "POST" | ||
| }); | ||
| }; | ||
| var anthropicAdapter = { | ||
| cancel, | ||
| id: "anthropic", | ||
| results, | ||
| retrieve, | ||
| submit | ||
| }; | ||
| // src/providers/google.ts | ||
| var GOOGLE_BASE = "https://generativelanguage.googleapis.com/v1beta"; | ||
| var GOOGLE_BATCH_PREFIX = "batches"; | ||
| var apiKey2 = (credentials) => { | ||
| const key = credentials.apiKey ?? process.env.GOOGLE_GENERATIVE_AI_API_KEY ?? process.env.GEMINI_API_KEY; | ||
| if (!key) { | ||
| throw new BatchworkError("batchwork: missing Google Gemini API key. Set GOOGLE_GENERATIVE_AI_API_KEY (or GEMINI_API_KEY) or pass `apiKey`."); | ||
| } | ||
| return key; | ||
| }; | ||
| var baseUrl2 = (credentials) => credentials.baseURL ?? GOOGLE_BASE; | ||
| var headers2 = (credentials) => ({ | ||
| "content-type": "application/json", | ||
| "x-goog-api-key": apiKey2(credentials), | ||
| ...credentials.headers | ||
| }); | ||
| var mapState = (state, done) => { | ||
| if (state) { | ||
| if (state.endsWith("SUCCEEDED")) { | ||
| return "completed"; | ||
| } | ||
| if (state.endsWith("FAILED")) { | ||
| return "failed"; | ||
| } | ||
| if (state.endsWith("CANCELLED")) { | ||
| return "cancelled"; | ||
| } | ||
| if (state.endsWith("EXPIRED")) { | ||
| return "expired"; | ||
| } | ||
| if (state.endsWith("PENDING")) { | ||
| return "validating"; | ||
| } | ||
| if (state.endsWith("RUNNING")) { | ||
| return "in_progress"; | ||
| } | ||
| } | ||
| return done ? "completed" : "in_progress"; | ||
| }; | ||
| var inlinedResponses = (raw) => { | ||
| const obj = asRecord(raw); | ||
| const response = asRecord(obj.response); | ||
| const dest = asRecord(obj.dest); | ||
| const responseInline = response.inlinedResponses ?? response.inlined_responses; | ||
| const destInline = dest.inlinedResponses ?? dest.inlined_responses; | ||
| const nestedResponseInline = asRecord(responseInline); | ||
| const nestedDestInline = asRecord(destInline); | ||
| return [ | ||
| ...asArray(responseInline), | ||
| ...asArray(nestedResponseInline.inlinedResponses), | ||
| ...asArray(nestedResponseInline.inlined_responses), | ||
| ...asArray(destInline), | ||
| ...asArray(nestedDestInline.inlinedResponses), | ||
| ...asArray(nestedDestInline.inlined_responses) | ||
| ]; | ||
| }; | ||
| var normalizeSnapshot2 = (raw) => { | ||
| const obj = asRecord(raw); | ||
| const items = inlinedResponses(raw); | ||
| const failed = items.filter((item) => asRecord(item).error).length; | ||
| const id = asString(obj.name) ?? ""; | ||
| return { | ||
| id: id ? assertPrefixedProviderId("Google operation id", id, GOOGLE_BATCH_PREFIX) : "", | ||
| provider: "google", | ||
| raw, | ||
| requestCounts: { | ||
| completed: items.length - failed, | ||
| failed, | ||
| total: items.length | ||
| }, | ||
| status: mapState(asString(obj.state) ?? asString(asRecord(obj.state).name) ?? asString(asRecord(obj.metadata).state), obj.done === true) | ||
| }; | ||
| }; | ||
| var textFromResponse = (response) => { | ||
| const candidate = asRecord(asArray(asRecord(response).candidates)[0]); | ||
| const text = asArray(asRecord(candidate.content).parts).map((part) => asString(asRecord(part).text) ?? "").join(""); | ||
| return text.length > 0 ? text : undefined; | ||
| }; | ||
| var embeddingFromResponse = (response) => asNumberArray(asRecord(asRecord(response).embedding).values); | ||
| var usageFromResponse = (response) => { | ||
| const usage = asRecord(asRecord(response).usageMetadata); | ||
| const inputTokens = asNumber(usage.promptTokenCount); | ||
| const outputTokens = asNumber(usage.candidatesTokenCount); | ||
| const totalTokens = asNumber(usage.totalTokenCount); | ||
| if (inputTokens === undefined && outputTokens === undefined && totalTokens === undefined) { | ||
| return; | ||
| } | ||
| return { | ||
| inputTokens, | ||
| outputTokens, | ||
| totalTokens: totalTokens ?? (inputTokens ?? 0) + (outputTokens ?? 0) | ||
| }; | ||
| }; | ||
| var normalizeResult2 = (item) => { | ||
| const obj = asRecord(item); | ||
| const customId = asString(asRecord(obj.metadata).key) ?? asString(obj.key) ?? asString(obj.custom_id) ?? ""; | ||
| if (obj.error) { | ||
| const error = asRecord(obj.error); | ||
| return { | ||
| customId, | ||
| error: { | ||
| code: asNumber(error.code) ?? asString(error.code), | ||
| message: asString(error.message) ?? "Request errored.", | ||
| type: asString(error.status) | ||
| }, | ||
| response: obj.error, | ||
| status: "errored" | ||
| }; | ||
| } | ||
| return { | ||
| customId, | ||
| embedding: embeddingFromResponse(obj.response), | ||
| response: obj.response, | ||
| status: "succeeded", | ||
| text: textFromResponse(obj.response), | ||
| usage: usageFromResponse(obj.response) | ||
| }; | ||
| }; | ||
| var EMBED_CONFIG_KEYS = new Set([ | ||
| "outputDimensionality", | ||
| "taskType", | ||
| "title" | ||
| ]); | ||
| var toEmbedRequest = (body) => { | ||
| const request = {}; | ||
| const config = {}; | ||
| for (const [key, value] of Object.entries(body)) { | ||
| if (EMBED_CONFIG_KEYS.has(key)) { | ||
| config[key] = value; | ||
| } else { | ||
| request[key] = value; | ||
| } | ||
| } | ||
| if (Object.keys(config).length > 0) { | ||
| request.embedContentConfig = config; | ||
| } | ||
| return request; | ||
| }; | ||
| var submit2 = async (input) => { | ||
| const limits = resolveBatchLimits(input.limits); | ||
| const isEmbedding = input.endpoint.toLowerCase().includes("embedcontent"); | ||
| const method = isEmbedding ? "asyncBatchEmbedContent" : "batchGenerateContent"; | ||
| const requests = input.built.map((item) => { | ||
| const payload = omit(item.body, "stream"); | ||
| return { | ||
| metadata: { key: item.customId }, | ||
| request: isEmbedding ? toEmbedRequest(payload) : payload | ||
| }; | ||
| }); | ||
| const body = encodeJsonArrayPayload({ | ||
| items: requests, | ||
| label: "batch upload payload", | ||
| maxBytes: limits.maxUploadBytes, | ||
| prefix: '{"batch":{"display_name":"batchwork","input_config":{"requests":{"requests":[', | ||
| suffix: "]}}}}" | ||
| }); | ||
| const raw = await requestJson(`${baseUrl2(input.credentials)}/models/${input.modelId}:${method}`, { | ||
| body, | ||
| headers: headers2(input.credentials), | ||
| method: "POST" | ||
| }); | ||
| return normalizeSnapshot2(raw); | ||
| }; | ||
| var retrieve2 = async (id, credentials) => { | ||
| const operationId = assertPrefixedProviderId("Google operation id", id, GOOGLE_BATCH_PREFIX); | ||
| const raw = await requestJson(`${baseUrl2(credentials)}/${operationId}`, { | ||
| headers: headers2(credentials) | ||
| }); | ||
| return normalizeSnapshot2(raw); | ||
| }; | ||
| async function* results2(id, credentials) { | ||
| const snapshot = await retrieve2(id, credentials); | ||
| const raw = asRecord(snapshot.raw); | ||
| const response = asRecord(raw.response); | ||
| const dest = asRecord(raw.dest); | ||
| const responsesFile = asString(asRecord(response.responsesFile).name) ?? asString(response.responsesFile) ?? asString(asRecord(response.responses_file).name) ?? asString(response.responses_file) ?? asString(dest.fileName) ?? asString(dest.file_name); | ||
| if (responsesFile) { | ||
| throw new BatchworkError(`batchwork: batch "${id}" returned file-mode results, which are not supported yet.`); | ||
| } | ||
| const items = inlinedResponses(raw); | ||
| if (items.length === 0) { | ||
| throw new BatchworkError(`batchwork: results are not ready for batch "${id}" (status: ${snapshot.status}).`); | ||
| } | ||
| for (const item of items) { | ||
| yield normalizeResult2(item); | ||
| } | ||
| } | ||
| var cancel2 = async (id, credentials) => { | ||
| const operationId = assertPrefixedProviderId("Google operation id", id, GOOGLE_BATCH_PREFIX); | ||
| await requestJson(`${baseUrl2(credentials)}/${operationId}:cancel`, { | ||
| headers: headers2(credentials), | ||
| method: "POST" | ||
| }); | ||
| }; | ||
| var googleAdapter = { | ||
| cancel: cancel2, | ||
| id: "google", | ||
| results: results2, | ||
| retrieve: retrieve2, | ||
| submit: submit2 | ||
| }; | ||
| // src/providers/shared.ts | ||
| var HTTP_OK_MIN = 200; | ||
| var HTTP_OK_MAX = 300; | ||
| var resolveApiKey = (credentials, envVar, label) => { | ||
| const key = credentials.apiKey ?? process.env[envVar]; | ||
| if (!key) { | ||
| throw new BatchworkError(`batchwork: missing ${label} API key. Set ${envVar} or pass \`apiKey\`.`); | ||
| } | ||
| return key; | ||
| }; | ||
| var textFromBody = (body) => { | ||
| const obj = asRecord(body); | ||
| const choices = asArray(obj.choices); | ||
| if (choices.length > 0) { | ||
| const content = asString(asRecord(asRecord(choices[0]).message).content); | ||
| if (content) { | ||
| return content; | ||
| } | ||
| } | ||
| return asString(obj.output_text); | ||
| }; | ||
| var embeddingFromBody = (body) => { | ||
| const data = asArray(asRecord(body).data); | ||
| if (data.length === 0) { | ||
| return; | ||
| } | ||
| return asNumberArray(asRecord(data[0]).embedding); | ||
| }; | ||
| var usageFromBody = (body) => { | ||
| const usage = asRecord(asRecord(body).usage); | ||
| const inputTokens = asNumber(usage.prompt_tokens) ?? asNumber(usage.input_tokens); | ||
| const outputTokens = asNumber(usage.completion_tokens) ?? asNumber(usage.output_tokens); | ||
| const totalTokens = asNumber(usage.total_tokens); | ||
| if (inputTokens === undefined && outputTokens === undefined && totalTokens === undefined) { | ||
| return; | ||
| } | ||
| return { | ||
| inputTokens, | ||
| outputTokens, | ||
| totalTokens: totalTokens ?? (inputTokens ?? 0) + (outputTokens ?? 0) | ||
| }; | ||
| }; | ||
| var errorFromValue = (value, fallback) => { | ||
| const obj = asRecord(value); | ||
| const nested = asRecord(obj.error); | ||
| const source = nested.message ? nested : obj; | ||
| return { | ||
| code: asNumber(source.code) ?? asString(source.code), | ||
| message: asString(source.message) ?? fallback, | ||
| type: asString(source.type) | ||
| }; | ||
| }; | ||
| var normalizeOpenAIResult = (line) => { | ||
| const obj = asRecord(line); | ||
| const customId = asString(obj.custom_id) ?? ""; | ||
| if (obj.error) { | ||
| return { | ||
| customId, | ||
| error: errorFromValue(obj.error, "Request errored."), | ||
| response: obj.error, | ||
| status: "errored" | ||
| }; | ||
| } | ||
| const response = asRecord(obj.response); | ||
| const statusCode = asNumber(response.status_code) ?? 0; | ||
| if (statusCode >= HTTP_OK_MIN && statusCode < HTTP_OK_MAX) { | ||
| return { | ||
| customId, | ||
| embedding: embeddingFromBody(response.body), | ||
| response: response.body, | ||
| status: "succeeded", | ||
| text: textFromBody(response.body), | ||
| usage: usageFromBody(response.body) | ||
| }; | ||
| } | ||
| return { | ||
| customId, | ||
| error: errorFromValue(response.body, `Request failed with status ${statusCode}.`), | ||
| response: response.body, | ||
| status: "errored" | ||
| }; | ||
| }; | ||
| var uploadInputFile = async (jsonl, baseUrl3, headers3, options = {}) => { | ||
| const form = new FormData; | ||
| const purpose = options.purpose === undefined ? "batch" : options.purpose; | ||
| if (purpose !== null) { | ||
| form.append("purpose", purpose); | ||
| } | ||
| form.append("file", new Blob([jsonl], { type: "application/jsonl" }), "batchwork.jsonl"); | ||
| const raw = await requestJson(`${baseUrl3}/files`, { | ||
| body: form, | ||
| headers: headers3, | ||
| method: "POST", | ||
| redirect: "manual" | ||
| }); | ||
| return raw.id; | ||
| }; | ||
| async function* streamResultFile(fileId, baseUrl3, headers3) { | ||
| const stream = await requestStream(`${baseUrl3}/files/${fileId}/content`, { | ||
| headers: headers3, | ||
| redirect: "manual" | ||
| }); | ||
| for await (const line of streamJsonl(stream)) { | ||
| yield normalizeOpenAIResult(line); | ||
| } | ||
| } | ||
| // src/providers/openai-compatible.ts | ||
| var DEFAULT_COMPLETION_WINDOW = "24h"; | ||
| var mapStatus2 = (status) => { | ||
| const normalized = status?.toLowerCase(); | ||
| switch (normalized) { | ||
| case "validating": | ||
| case "in_progress": | ||
| case "finalizing": | ||
| case "completed": | ||
| case "failed": | ||
| case "expired": | ||
| case "cancelling": | ||
| case "cancelled": { | ||
| return normalized; | ||
| } | ||
| default: { | ||
| return "in_progress"; | ||
| } | ||
| } | ||
| }; | ||
| var normalizeSnapshot3 = (raw, provider) => { | ||
| const outer = asRecord(raw); | ||
| const obj = asRecord(outer.job); | ||
| const source = Object.keys(obj).length > 0 ? obj : outer; | ||
| const counts = asRecord(source.request_counts); | ||
| return { | ||
| completedAt: toDate(source.completed_at), | ||
| createdAt: toDate(source.created_at), | ||
| expiresAt: toDate(source.expires_at), | ||
| id: asString(source.id) ?? "", | ||
| provider, | ||
| raw: source, | ||
| requestCounts: { | ||
| completed: asNumber(counts.completed) ?? 0, | ||
| failed: asNumber(counts.failed) ?? 0, | ||
| total: asNumber(counts.total) ?? 0 | ||
| }, | ||
| status: mapStatus2(asString(source.status)) | ||
| }; | ||
| }; | ||
| var createOpenAICompatibleAdapter = (config) => { | ||
| const completionWindow = config.completionWindow ?? DEFAULT_COMPLETION_WINDOW; | ||
| const lineFormat = config.lineFormat ?? "method-url"; | ||
| const baseUrl3 = (credentials) => credentials.baseURL ?? config.baseUrl; | ||
| const authHeaders = (credentials) => ({ | ||
| Authorization: `Bearer ${resolveApiKey(credentials, config.apiKeyEnv, config.apiKeyLabel)}`, | ||
| ...credentials.headers | ||
| }); | ||
| const submit3 = async (input) => { | ||
| const limits = resolveBatchLimits(input.limits); | ||
| const endpoint = config.normalizeEndpoint ? config.normalizeEndpoint(input.endpoint) : input.endpoint; | ||
| const jsonl = encodeJsonl(input.built.map((item) => { | ||
| const body = omit(item.body, "stream"); | ||
| if (lineFormat === "body-only") { | ||
| return { body, custom_id: item.customId }; | ||
| } | ||
| return { | ||
| body, | ||
| custom_id: item.customId, | ||
| method: "POST", | ||
| url: endpoint | ||
| }; | ||
| }), { label: "batch upload JSONL", maxBytes: limits.maxUploadBytes }); | ||
| const headers3 = authHeaders(input.credentials); | ||
| const url = baseUrl3(input.credentials); | ||
| const purpose = config.filePurpose ?? "batch"; | ||
| const inputFileId = await (config.uploadFile ? config.uploadFile({ baseUrl: url, headers: headers3, jsonl, purpose }) : uploadInputFile(jsonl, url, headers3, { purpose })); | ||
| const raw = await requestJson(`${url}/batches`, { | ||
| body: JSON.stringify({ | ||
| completion_window: completionWindow, | ||
| endpoint, | ||
| input_file_id: inputFileId, | ||
| metadata: input.metadata | ||
| }), | ||
| headers: { ...headers3, "content-type": "application/json" }, | ||
| method: "POST" | ||
| }); | ||
| return normalizeSnapshot3(raw, config.id); | ||
| }; | ||
| const retrieve3 = async (id, credentials) => { | ||
| const batchId = assertSimpleProviderId(`${config.id} batch id`, id); | ||
| const raw = await requestJson(`${baseUrl3(credentials)}/batches/${batchId}`, { | ||
| headers: authHeaders(credentials) | ||
| }); | ||
| return normalizeSnapshot3(raw, config.id); | ||
| }; | ||
| async function* results3(id, credentials) { | ||
| const snapshot = await retrieve3(id, credentials); | ||
| const raw = asRecord(snapshot.raw); | ||
| const outputFileId = asString(raw.output_file_id); | ||
| const errorFileId = asString(raw.error_file_id); | ||
| if (!(outputFileId || errorFileId)) { | ||
| throw new BatchworkError(`batchwork: results are not ready for batch "${id}" (status: ${snapshot.status}).`); | ||
| } | ||
| const headers3 = authHeaders(credentials); | ||
| if (outputFileId) { | ||
| yield* streamResultFile(assertSimpleProviderId(`${config.id} output file id`, outputFileId), baseUrl3(credentials), headers3); | ||
| } | ||
| if (errorFileId) { | ||
| yield* streamResultFile(assertSimpleProviderId(`${config.id} error file id`, errorFileId), baseUrl3(credentials), headers3); | ||
| } | ||
| } | ||
| const cancel3 = async (id, credentials) => { | ||
| const batchId = assertSimpleProviderId(`${config.id} batch id`, id); | ||
| await requestJson(`${baseUrl3(credentials)}/batches/${batchId}/cancel`, { | ||
| headers: authHeaders(credentials), | ||
| method: "POST" | ||
| }); | ||
| }; | ||
| return { cancel: cancel3, id: config.id, results: results3, retrieve: retrieve3, submit: submit3 }; | ||
| }; | ||
| // src/providers/groq.ts | ||
| var groqAdapter = createOpenAICompatibleAdapter({ | ||
| apiKeyEnv: "GROQ_API_KEY", | ||
| apiKeyLabel: "Groq", | ||
| baseUrl: "https://api.groq.com/openai/v1", | ||
| id: "groq", | ||
| lineFormat: "method-url", | ||
| normalizeEndpoint: (endpoint) => endpoint.replace(/^\/openai/u, "") | ||
| }); | ||
| // src/providers/mistral.ts | ||
| var MISTRAL_BASE = "https://api.mistral.ai/v1"; | ||
| var apiKey3 = (credentials) => resolveApiKey(credentials, "MISTRAL_API_KEY", "Mistral"); | ||
| var baseUrl3 = (credentials) => credentials.baseURL ?? MISTRAL_BASE; | ||
| var authHeaders = (credentials) => ({ | ||
| Authorization: `Bearer ${apiKey3(credentials)}`, | ||
| ...credentials.headers | ||
| }); | ||
| var mapStatus3 = (status) => { | ||
| switch (status) { | ||
| case "QUEUED": { | ||
| return "validating"; | ||
| } | ||
| case "SUCCESS": { | ||
| return "completed"; | ||
| } | ||
| case "FAILED": { | ||
| return "failed"; | ||
| } | ||
| case "TIMEOUT_EXCEEDED": { | ||
| return "expired"; | ||
| } | ||
| case "CANCELLATION_REQUESTED": { | ||
| return "cancelling"; | ||
| } | ||
| case "CANCELLED": { | ||
| return "cancelled"; | ||
| } | ||
| default: { | ||
| return "in_progress"; | ||
| } | ||
| } | ||
| }; | ||
| var normalizeSnapshot4 = (raw) => { | ||
| const obj = asRecord(raw); | ||
| const succeeded = asNumber(obj.succeeded_requests) ?? 0; | ||
| const failed = asNumber(obj.failed_requests) ?? 0; | ||
| const id = asString(obj.id) ?? ""; | ||
| return { | ||
| completedAt: toDate(obj.completed_at), | ||
| createdAt: toDate(obj.created_at), | ||
| id: id ? assertSimpleProviderId("Mistral job id", id) : "", | ||
| provider: "mistral", | ||
| raw, | ||
| requestCounts: { | ||
| completed: succeeded, | ||
| failed, | ||
| total: asNumber(obj.total_requests) ?? succeeded + failed | ||
| }, | ||
| status: mapStatus3(asString(obj.status)) | ||
| }; | ||
| }; | ||
| var submit3 = async (input) => { | ||
| const limits = resolveBatchLimits(input.limits); | ||
| const jsonl = encodeJsonl(input.built.map((item) => ({ | ||
| body: omit(omit(item.body, "stream"), "model"), | ||
| custom_id: item.customId | ||
| })), { label: "batch upload JSONL", maxBytes: limits.maxUploadBytes }); | ||
| const inputFileId = await uploadInputFile(jsonl, baseUrl3(input.credentials), authHeaders(input.credentials)); | ||
| const raw = await requestJson(`${baseUrl3(input.credentials)}/batch/jobs`, { | ||
| body: JSON.stringify({ | ||
| endpoint: input.endpoint, | ||
| input_files: [inputFileId], | ||
| metadata: input.metadata, | ||
| model: input.modelId | ||
| }), | ||
| headers: { | ||
| ...authHeaders(input.credentials), | ||
| "content-type": "application/json" | ||
| }, | ||
| method: "POST" | ||
| }); | ||
| return normalizeSnapshot4(raw); | ||
| }; | ||
| var retrieve3 = async (id, credentials) => { | ||
| const jobId = assertSimpleProviderId("Mistral job id", id); | ||
| const raw = await requestJson(`${baseUrl3(credentials)}/batch/jobs/${jobId}`, { | ||
| headers: authHeaders(credentials) | ||
| }); | ||
| return normalizeSnapshot4(raw); | ||
| }; | ||
| async function* results3(id, credentials) { | ||
| const snapshot = await retrieve3(id, credentials); | ||
| const raw = asRecord(snapshot.raw); | ||
| const outputFileId = asString(raw.output_file); | ||
| const errorFileId = asString(raw.error_file); | ||
| const headers3 = authHeaders(credentials); | ||
| if (outputFileId) { | ||
| yield* streamResultFile(assertSimpleProviderId("Mistral output file id", outputFileId), baseUrl3(credentials), headers3); | ||
| } | ||
| if (errorFileId) { | ||
| yield* streamResultFile(assertSimpleProviderId("Mistral error file id", errorFileId), baseUrl3(credentials), headers3); | ||
| } | ||
| if (!(outputFileId || errorFileId)) { | ||
| throw new BatchworkError(`batchwork: results are not ready for batch "${id}" (status: ${snapshot.status}).`); | ||
| } | ||
| } | ||
| var cancel3 = async (id, credentials) => { | ||
| const jobId = assertSimpleProviderId("Mistral job id", id); | ||
| await requestJson(`${baseUrl3(credentials)}/batch/jobs/${jobId}/cancel`, { | ||
| headers: authHeaders(credentials), | ||
| method: "POST" | ||
| }); | ||
| }; | ||
| var mistralAdapter = { | ||
| cancel: cancel3, | ||
| id: "mistral", | ||
| results: results3, | ||
| retrieve: retrieve3, | ||
| submit: submit3 | ||
| }; | ||
| // src/providers/openai.ts | ||
| var openaiAdapter = createOpenAICompatibleAdapter({ | ||
| apiKeyEnv: "OPENAI_API_KEY", | ||
| apiKeyLabel: "OpenAI", | ||
| baseUrl: "https://api.openai.com/v1", | ||
| id: "openai", | ||
| lineFormat: "method-url" | ||
| }); | ||
| // src/providers/together.ts | ||
| var INPUT_FILE_NAME = "batchwork.jsonl"; | ||
| var HTTP_FOUND = 302; | ||
| var parseIpv4 = (host) => { | ||
| if (!/^\d{1,3}(?:\.\d{1,3}){3}$/u.test(host)) { | ||
| return; | ||
| } | ||
| const parts = host.split(".").map(Number); | ||
| const valid = parts.every((part) => Number.isInteger(part) && part >= 0 && part <= 255); | ||
| return valid ? parts : undefined; | ||
| }; | ||
| var isPrivateIpv4 = (parts) => { | ||
| const [a = 0, b = 0] = parts; | ||
| return a === 0 || a === 10 || a === 127 || a === 100 && b >= 64 && b <= 127 || a === 169 && b === 254 || a === 172 && b >= 16 && b <= 31 || a === 192 && b === 168 || a === 198 && (b === 18 || b === 19) || a >= 224; | ||
| }; | ||
| var isPrivateIpv6 = (host) => { | ||
| const normalized = host.replace(/^\[/u, "").replace(/\]$/u, "").toLowerCase(); | ||
| if (!normalized.includes(":")) { | ||
| return false; | ||
| } | ||
| return normalized === "::" || normalized === "::1" || normalized.startsWith("fc") || normalized.startsWith("fd") || normalized.startsWith("fe80:"); | ||
| }; | ||
| var validateUploadLocation = (location) => { | ||
| let url; | ||
| try { | ||
| url = new URL(location); | ||
| } catch (error) { | ||
| throw new BatchworkError("batchwork: Together upload Location must be a valid URL.", { cause: error }); | ||
| } | ||
| if (url.protocol !== "https:") { | ||
| throw new BatchworkError("batchwork: Together upload Location must use https."); | ||
| } | ||
| if (url.username || url.password) { | ||
| throw new BatchworkError("batchwork: Together upload Location must not include credentials."); | ||
| } | ||
| const host = url.hostname.toLowerCase(); | ||
| const ipv4 = parseIpv4(host); | ||
| if (host === "localhost" || host.endsWith(".localhost") || host.endsWith(".local") || ipv4 && isPrivateIpv4(ipv4) || isPrivateIpv6(host)) { | ||
| throw new BatchworkError("batchwork: Together upload Location must not target localhost or private networks."); | ||
| } | ||
| return url.toString(); | ||
| }; | ||
| var uploadTogetherFile = async (args) => { | ||
| const metadata = new FormData; | ||
| metadata.append("purpose", args.purpose); | ||
| metadata.append("file_name", INPUT_FILE_NAME); | ||
| metadata.append("file_type", "jsonl"); | ||
| const init = await fetch(`${args.baseUrl}/files`, { | ||
| body: metadata, | ||
| headers: args.headers, | ||
| method: "POST", | ||
| redirect: "manual" | ||
| }); | ||
| const location = init.headers.get("location"); | ||
| const fileId = init.headers.get("x-together-file-id"); | ||
| if (init.status !== HTTP_FOUND || !(location && fileId)) { | ||
| throw new BatchworkError(`batchwork: Together upload could not be initiated (${init.status}).`); | ||
| } | ||
| const uploadLocation = validateUploadLocation(location); | ||
| const upload = await fetch(uploadLocation, { | ||
| body: args.jsonl, | ||
| method: "PUT", | ||
| redirect: "manual" | ||
| }); | ||
| if (!upload.ok) { | ||
| throw new BatchworkError(`batchwork: Together file upload failed (${upload.status}).`); | ||
| } | ||
| const safeFileId = assertSimpleProviderId("Together file id", fileId); | ||
| await requestJson(`${args.baseUrl}/files/${safeFileId}/preprocess`, { | ||
| headers: args.headers, | ||
| method: "POST" | ||
| }); | ||
| return safeFileId; | ||
| }; | ||
| var togetherAdapter = createOpenAICompatibleAdapter({ | ||
| apiKeyEnv: "TOGETHER_API_KEY", | ||
| apiKeyLabel: "Together AI", | ||
| baseUrl: "https://api.together.xyz/v1", | ||
| filePurpose: "batch-api", | ||
| id: "together", | ||
| lineFormat: "body-only", | ||
| uploadFile: uploadTogetherFile | ||
| }); | ||
| // src/providers/xai.ts | ||
| var XAI_BASE = "https://api.x.ai/v1"; | ||
| var RESULTS_PAGE_SIZE = 100; | ||
| var apiKey4 = (credentials) => resolveApiKey(credentials, "XAI_API_KEY", "xAI"); | ||
| var baseUrl4 = (credentials) => credentials.baseURL ?? XAI_BASE; | ||
| var authHeaders2 = (credentials) => ({ | ||
| Authorization: `Bearer ${apiKey4(credentials)}`, | ||
| ...credentials.headers | ||
| }); | ||
| var deriveStatus = (state) => { | ||
| const pending = asNumber(state.num_pending); | ||
| if (pending === undefined) { | ||
| return "in_progress"; | ||
| } | ||
| const total = asNumber(state.num_requests) ?? 0; | ||
| const cancelled = asNumber(state.num_cancelled) ?? 0; | ||
| if (total === 0) { | ||
| return "in_progress"; | ||
| } | ||
| if (pending > 0) { | ||
| return "in_progress"; | ||
| } | ||
| if (cancelled > 0 && cancelled === total) { | ||
| return "cancelled"; | ||
| } | ||
| return "completed"; | ||
| }; | ||
| var normalizeSnapshot5 = (raw) => { | ||
| const obj = asRecord(raw); | ||
| const state = asRecord(obj.state); | ||
| const id = asString(obj.batch_id) ?? asString(obj.id) ?? ""; | ||
| return { | ||
| completedAt: toDate(obj.finish_time ?? obj.completed_at), | ||
| createdAt: toDate(obj.create_time), | ||
| expiresAt: toDate(obj.expire_time ?? obj.expires_at), | ||
| id: id ? assertSimpleProviderId("xAI batch id", id) : "", | ||
| provider: "xai", | ||
| raw, | ||
| requestCounts: { | ||
| canceled: asNumber(state.num_cancelled) ?? 0, | ||
| completed: asNumber(state.num_success) ?? 0, | ||
| failed: asNumber(state.num_error) ?? 0, | ||
| processing: asNumber(state.num_pending) ?? 0, | ||
| total: asNumber(state.num_requests) ?? 0 | ||
| }, | ||
| status: deriveStatus(state) | ||
| }; | ||
| }; | ||
| var normalizeResult3 = (item) => { | ||
| const obj = asRecord(item); | ||
| const customId = asString(obj.batch_request_id) ?? ""; | ||
| const batchResult = asRecord(obj.batch_result); | ||
| const resultError = batchResult.error; | ||
| const errorMessage = asString(obj.error_message) ?? asString(resultError) ?? asString(asRecord(resultError).message); | ||
| if (errorMessage) { | ||
| return { | ||
| customId, | ||
| error: { | ||
| message: errorMessage, | ||
| type: asString(asRecord(resultError).type) | ||
| }, | ||
| response: obj, | ||
| status: "errored" | ||
| }; | ||
| } | ||
| const response = asRecord(batchResult.response); | ||
| const completion = response.chat_get_completion ?? Object.values(response)[0]; | ||
| return { | ||
| customId, | ||
| response: completion, | ||
| status: "succeeded", | ||
| text: textFromBody(completion), | ||
| usage: usageFromBody(completion) | ||
| }; | ||
| }; | ||
| var submit4 = async (input) => { | ||
| const limits = resolveBatchLimits(input.limits); | ||
| const jsonl = encodeJsonl(input.built.map((item) => ({ | ||
| body: omit(item.body, "stream"), | ||
| custom_id: item.customId, | ||
| method: "POST", | ||
| url: input.endpoint | ||
| })), { label: "batch upload JSONL", maxBytes: limits.maxUploadBytes }); | ||
| const inputFileId = await uploadInputFile(jsonl, baseUrl4(input.credentials), authHeaders2(input.credentials), { purpose: null }); | ||
| const raw = await requestJson(`${baseUrl4(input.credentials)}/batches`, { | ||
| body: JSON.stringify({ input_file_id: inputFileId, name: "batchwork" }), | ||
| headers: { | ||
| ...authHeaders2(input.credentials), | ||
| "content-type": "application/json" | ||
| }, | ||
| method: "POST" | ||
| }); | ||
| return normalizeSnapshot5(raw); | ||
| }; | ||
| var retrieve4 = async (id, credentials) => { | ||
| const batchId = assertSimpleProviderId("xAI batch id", id); | ||
| const raw = await requestJson(`${baseUrl4(credentials)}/batches/${batchId}`, { | ||
| headers: authHeaders2(credentials) | ||
| }); | ||
| return normalizeSnapshot5(raw); | ||
| }; | ||
| async function* results4(id, credentials) { | ||
| const batchId = assertSimpleProviderId("xAI batch id", id); | ||
| const headers3 = authHeaders2(credentials); | ||
| let token; | ||
| do { | ||
| const query = new URLSearchParams({ limit: String(RESULTS_PAGE_SIZE) }); | ||
| if (token) { | ||
| query.set("pagination_token", token); | ||
| } | ||
| const raw = await requestJson(`${baseUrl4(credentials)}/batches/${batchId}/results?${query.toString()}`, { headers: headers3 }); | ||
| const page = asRecord(raw); | ||
| for (const item of Array.isArray(page.results) ? page.results : []) { | ||
| yield normalizeResult3(item); | ||
| } | ||
| token = asString(page.pagination_token); | ||
| } while (token); | ||
| } | ||
| var cancel4 = async (id, credentials) => { | ||
| const batchId = assertSimpleProviderId("xAI batch id", id); | ||
| await requestJson(`${baseUrl4(credentials)}/batches/${batchId}:cancel`, { | ||
| headers: authHeaders2(credentials), | ||
| method: "POST" | ||
| }); | ||
| }; | ||
| var xaiAdapter = { | ||
| cancel: cancel4, | ||
| id: "xai", | ||
| results: results4, | ||
| retrieve: retrieve4, | ||
| submit: submit4 | ||
| }; | ||
| // src/providers/index.ts | ||
| var adapters = { | ||
| anthropic: anthropicAdapter, | ||
| google: googleAdapter, | ||
| groq: groqAdapter, | ||
| mistral: mistralAdapter, | ||
| openai: openaiAdapter, | ||
| together: togetherAdapter, | ||
| xai: xaiAdapter | ||
| }; | ||
| var getAdapter = (provider) => adapters[provider]; | ||
| export { BatchworkError, UnsupportedProviderError, MissingDependencyError, resolveBatchLimits, assertByteLength, mapWithConcurrency, isTerminalStatus, BatchJob, getAdapter }; | ||
| //# debugId=A771CB719D6F2AEA64756E2164756E21 | ||
| //# sourceMappingURL=chunk-xtk2ycy6.js.map |
Sorry, the diff of this file is too big to display
| import { | ||
| BatchJob, | ||
| BatchworkError, | ||
| MissingDependencyError, | ||
| UnsupportedProviderError, | ||
| assertByteLength, | ||
| getAdapter, | ||
| mapWithConcurrency, | ||
| resolveBatchLimits | ||
| } from "./chunk-xtk2ycy6.js"; | ||
| import { | ||
| __require | ||
| } from "./chunk-v0bahtg2.js"; | ||
| // src/model.ts | ||
| var CAPTURE_API_KEY = "batchwork-capture"; | ||
| var PACKAGE_BY_PROVIDER = { | ||
| anthropic: { label: "Anthropic", specifier: "@ai-sdk/anthropic" }, | ||
| google: { label: "Google Gemini", specifier: "@ai-sdk/google" }, | ||
| groq: { label: "Groq", specifier: "@ai-sdk/groq" }, | ||
| mistral: { label: "Mistral", specifier: "@ai-sdk/mistral" }, | ||
| openai: { label: "OpenAI", specifier: "@ai-sdk/openai" }, | ||
| together: { label: "Together AI", specifier: "@ai-sdk/togetherai" }, | ||
| xai: { label: "xAI", specifier: "@ai-sdk/xai" } | ||
| }; | ||
| var PROVIDER_BY_FAMILY = { | ||
| anthropic: "anthropic", | ||
| google: "google", | ||
| groq: "groq", | ||
| mistral: "mistral", | ||
| openai: "openai", | ||
| together: "together", | ||
| togetherai: "together", | ||
| xai: "xai" | ||
| }; | ||
| var PROVIDER_BY_ALIAS = { | ||
| ...PROVIDER_BY_FAMILY, | ||
| gemini: "google" | ||
| }; | ||
| var splitOnce = (value, separator) => { | ||
| const index = value.indexOf(separator); | ||
| if (index === -1) { | ||
| return [value, ""]; | ||
| } | ||
| return [value.slice(0, index), value.slice(index + separator.length)]; | ||
| }; | ||
| var openaiKind = (suffix) => { | ||
| if (suffix === "responses") { | ||
| return "responses"; | ||
| } | ||
| if (suffix === "completion") { | ||
| return "completion"; | ||
| } | ||
| return "chat"; | ||
| }; | ||
| var resolveModelString = (value) => { | ||
| const [providerId, modelId] = splitOnce(value, "/"); | ||
| if (modelId === "") { | ||
| throw new UnsupportedProviderError(value); | ||
| } | ||
| const provider = PROVIDER_BY_ALIAS[providerId]; | ||
| if (!provider) { | ||
| throw new UnsupportedProviderError(providerId); | ||
| } | ||
| return { kind: "chat", modelId, provider }; | ||
| }; | ||
| var resolveModel = (model) => { | ||
| if (typeof model === "string") { | ||
| return resolveModelString(model); | ||
| } | ||
| const [family, suffix] = splitOnce(model.provider, "."); | ||
| const provider = PROVIDER_BY_FAMILY[family]; | ||
| if (provider === "openai") { | ||
| return { kind: openaiKind(suffix), modelId: model.modelId, provider }; | ||
| } | ||
| if (provider) { | ||
| return { kind: "chat", modelId: model.modelId, provider }; | ||
| } | ||
| if (model.modelId.includes("/")) { | ||
| return resolveModelString(model.modelId); | ||
| } | ||
| throw new UnsupportedProviderError(model.provider); | ||
| }; | ||
| var importProvider = (provider) => { | ||
| switch (provider) { | ||
| case "anthropic": { | ||
| return import("@ai-sdk/anthropic"); | ||
| } | ||
| case "google": { | ||
| return import("@ai-sdk/google"); | ||
| } | ||
| case "groq": { | ||
| return import("@ai-sdk/groq"); | ||
| } | ||
| case "mistral": { | ||
| return import("@ai-sdk/mistral"); | ||
| } | ||
| case "openai": { | ||
| return import("@ai-sdk/openai"); | ||
| } | ||
| case "together": { | ||
| return import("@ai-sdk/togetherai"); | ||
| } | ||
| case "xai": { | ||
| return import("@ai-sdk/xai"); | ||
| } | ||
| default: { | ||
| return Promise.reject(new UnsupportedProviderError(provider)); | ||
| } | ||
| } | ||
| }; | ||
| var loadProvider = async (provider, load = importProvider) => { | ||
| try { | ||
| return await load(provider); | ||
| } catch (error) { | ||
| if (error instanceof UnsupportedProviderError) { | ||
| throw error; | ||
| } | ||
| const { specifier, label } = PACKAGE_BY_PROVIDER[provider]; | ||
| throw new MissingDependencyError(specifier, label); | ||
| } | ||
| }; | ||
| var createCaptureModel = async (resolved, credentials, fetchImpl) => { | ||
| const settings = { | ||
| apiKey: credentials.apiKey ?? CAPTURE_API_KEY, | ||
| baseURL: credentials.baseURL, | ||
| fetch: fetchImpl, | ||
| headers: credentials.headers | ||
| }; | ||
| switch (resolved.provider) { | ||
| case "openai": { | ||
| const { createOpenAI } = await loadProvider("openai"); | ||
| const provider = createOpenAI(settings); | ||
| if (resolved.kind === "responses") { | ||
| return provider.responses(resolved.modelId); | ||
| } | ||
| if (resolved.kind === "completion") { | ||
| return provider.completion(resolved.modelId); | ||
| } | ||
| return provider.chat(resolved.modelId); | ||
| } | ||
| case "anthropic": { | ||
| const { createAnthropic } = await loadProvider("anthropic"); | ||
| return createAnthropic(settings).messages(resolved.modelId); | ||
| } | ||
| case "groq": { | ||
| const { createGroq } = await loadProvider("groq"); | ||
| return createGroq(settings).languageModel(resolved.modelId); | ||
| } | ||
| case "mistral": { | ||
| const { createMistral } = await loadProvider("mistral"); | ||
| return createMistral(settings).languageModel(resolved.modelId); | ||
| } | ||
| case "google": { | ||
| const { createGoogleGenerativeAI } = await loadProvider("google"); | ||
| return createGoogleGenerativeAI(settings).languageModel(resolved.modelId); | ||
| } | ||
| case "xai": { | ||
| const { createXai } = await loadProvider("xai"); | ||
| return createXai(settings).languageModel(resolved.modelId); | ||
| } | ||
| case "together": { | ||
| const { createTogetherAI } = await loadProvider("together"); | ||
| return createTogetherAI(settings).languageModel(resolved.modelId); | ||
| } | ||
| default: { | ||
| throw new UnsupportedProviderError(resolved.provider); | ||
| } | ||
| } | ||
| }; | ||
| var EMBEDDING_PROVIDERS = new Set([ | ||
| "google", | ||
| "mistral", | ||
| "openai" | ||
| ]); | ||
| var unsupportedEmbeddingProvider = (provider) => new UnsupportedProviderError(provider, `batchwork: provider "${provider}" does not offer batch embeddings. Embeddings are supported for: openai, mistral, google.`); | ||
| var createCaptureEmbeddingModel = async (resolved, credentials, fetchImpl) => { | ||
| const settings = { | ||
| apiKey: credentials.apiKey ?? CAPTURE_API_KEY, | ||
| baseURL: credentials.baseURL, | ||
| fetch: fetchImpl, | ||
| headers: credentials.headers | ||
| }; | ||
| switch (resolved.provider) { | ||
| case "openai": { | ||
| const { createOpenAI } = await loadProvider("openai"); | ||
| return createOpenAI(settings).textEmbeddingModel(resolved.modelId); | ||
| } | ||
| case "mistral": { | ||
| const { createMistral } = await loadProvider("mistral"); | ||
| return createMistral(settings).textEmbeddingModel(resolved.modelId); | ||
| } | ||
| case "google": { | ||
| const { createGoogleGenerativeAI } = await loadProvider("google"); | ||
| return createGoogleGenerativeAI(settings).textEmbeddingModel(resolved.modelId); | ||
| } | ||
| default: { | ||
| throw unsupportedEmbeddingProvider(resolved.provider); | ||
| } | ||
| } | ||
| }; | ||
| // src/body.ts | ||
| import { embed, generateText } from "ai"; | ||
| var MAX_CAUSE_DEPTH = 10; | ||
| class CaptureSignalError extends Error { | ||
| url; | ||
| rawBody; | ||
| constructor(url, rawBody) { | ||
| super("batchwork:capture"); | ||
| this.name = "CaptureSignalError"; | ||
| this.url = url; | ||
| this.rawBody = rawBody; | ||
| } | ||
| } | ||
| var resolveUrl = (input) => { | ||
| if (typeof input === "string") { | ||
| return input; | ||
| } | ||
| if (input instanceof URL) { | ||
| return input.toString(); | ||
| } | ||
| return input.url; | ||
| }; | ||
| var extractBody = (init) => { | ||
| const body = init?.body; | ||
| if (typeof body === "string") { | ||
| return body; | ||
| } | ||
| if (body instanceof Uint8Array) { | ||
| return new TextDecoder().decode(body); | ||
| } | ||
| throw new BatchworkError("batchwork: unable to read the provider request body during capture."); | ||
| }; | ||
| var captureFetch = (input, init) => Promise.reject(new CaptureSignalError(resolveUrl(input), extractBody(init))); | ||
| var findCapture = (error) => { | ||
| let current = error; | ||
| let depth = 0; | ||
| while (current && depth < MAX_CAUSE_DEPTH) { | ||
| if (current instanceof CaptureSignalError) { | ||
| return current; | ||
| } | ||
| current = current.cause; | ||
| depth += 1; | ||
| } | ||
| }; | ||
| var endpointFromUrl = (url) => { | ||
| try { | ||
| return new URL(url).pathname; | ||
| } catch { | ||
| return url; | ||
| } | ||
| }; | ||
| var mergeDefaults = (request, defaults) => { | ||
| if (!defaults) { | ||
| return request; | ||
| } | ||
| return { ...defaults, ...request }; | ||
| }; | ||
| var toGenerateInput = (model, request) => ({ | ||
| frequencyPenalty: request.frequencyPenalty, | ||
| maxOutputTokens: request.maxOutputTokens, | ||
| maxRetries: 0, | ||
| messages: request.messages, | ||
| model, | ||
| presencePenalty: request.presencePenalty, | ||
| prompt: request.prompt, | ||
| providerOptions: request.providerOptions, | ||
| seed: request.seed, | ||
| stopSequences: request.stopSequences, | ||
| system: request.system, | ||
| temperature: request.temperature, | ||
| toolChoice: request.toolChoice, | ||
| tools: request.tools, | ||
| topK: request.topK, | ||
| topP: request.topP | ||
| }); | ||
| var bodyFromCapture = (error, customId, maxRequestBytes) => { | ||
| const capture = findCapture(error); | ||
| if (capture) { | ||
| assertByteLength(`request "${customId}"`, capture.rawBody, maxRequestBytes); | ||
| return { | ||
| body: JSON.parse(capture.rawBody), | ||
| customId, | ||
| endpoint: endpointFromUrl(capture.url) | ||
| }; | ||
| } | ||
| throw error; | ||
| }; | ||
| var captureOne = async (model, request, customId, maxRequestBytes) => { | ||
| try { | ||
| await generateText(toGenerateInput(model, request)); | ||
| } catch (error) { | ||
| return bodyFromCapture(error, customId, maxRequestBytes); | ||
| } | ||
| throw new BatchworkError("batchwork: the request was not intercepted while building the batch body."); | ||
| }; | ||
| var captureEmbeddingOne = async (model, request, customId, maxRequestBytes) => { | ||
| try { | ||
| await embed({ | ||
| maxRetries: 0, | ||
| model, | ||
| providerOptions: request.providerOptions, | ||
| value: request.value | ||
| }); | ||
| } catch (error) { | ||
| return bodyFromCapture(error, customId, maxRequestBytes); | ||
| } | ||
| throw new BatchworkError("batchwork: the request was not intercepted while building the embedding body."); | ||
| }; | ||
| var assignCustomIds = (requests) => { | ||
| const seen = new Set; | ||
| return requests.map((request, index) => { | ||
| const customId = request.customId ?? `request-${index}`; | ||
| if (seen.has(customId)) { | ||
| throw new BatchworkError(`batchwork: duplicate customId "${customId}". customId values must be unique within a batch.`); | ||
| } | ||
| seen.add(customId); | ||
| return { customId, request }; | ||
| }); | ||
| }; | ||
| var buildRequestBodies = async (resolved, requests, defaults, credentials, rawLimits) => { | ||
| const limits = resolveBatchLimits(rawLimits); | ||
| if (requests.length > limits.maxRequests) { | ||
| throw new BatchworkError(`batchwork: requests length ${requests.length} exceeds the ${limits.maxRequests} request limit.`); | ||
| } | ||
| const model = await createCaptureModel(resolved, credentials, captureFetch); | ||
| const items = assignCustomIds(requests); | ||
| return await mapWithConcurrency(items, limits.captureConcurrency, async (item) => { | ||
| const built = await captureOne(model, mergeDefaults(item.request, defaults), item.customId, limits.maxRequestBytes); | ||
| assertByteLength(`request "${item.customId}"`, JSON.stringify(built.body), limits.maxRequestBytes); | ||
| return built; | ||
| }); | ||
| }; | ||
| var buildEmbeddingBodies = async (resolved, requests, credentials, rawLimits) => { | ||
| const limits = resolveBatchLimits(rawLimits); | ||
| if (requests.length > limits.maxRequests) { | ||
| throw new BatchworkError(`batchwork: requests length ${requests.length} exceeds the ${limits.maxRequests} request limit.`); | ||
| } | ||
| const model = await createCaptureEmbeddingModel(resolved, credentials, captureFetch); | ||
| const items = assignCustomIds(requests); | ||
| return await mapWithConcurrency(items, limits.captureConcurrency, async (item) => { | ||
| const built = await captureEmbeddingOne(model, item.request, item.customId, limits.maxRequestBytes); | ||
| assertByteLength(`request "${item.customId}"`, JSON.stringify(built.body), limits.maxRequestBytes); | ||
| return built; | ||
| }); | ||
| }; | ||
| // src/batch.ts | ||
| var pickCredentials = (source) => ({ | ||
| apiKey: source.apiKey, | ||
| baseURL: source.baseURL, | ||
| headers: source.headers | ||
| }); | ||
| var providerFromRef = (ref) => { | ||
| if (ref.provider) { | ||
| return ref.provider; | ||
| } | ||
| if (ref.model !== undefined) { | ||
| return resolveModel(ref.model).provider; | ||
| } | ||
| throw new BatchworkError("batchwork: provide `provider` or `model` to identify the batch."); | ||
| }; | ||
| var batch = async (options) => { | ||
| if (options.requests.length === 0) { | ||
| throw new BatchworkError("batchwork: `requests` must not be empty."); | ||
| } | ||
| const resolved = resolveModel(options.model); | ||
| const credentials = pickCredentials(options); | ||
| const limits = resolveBatchLimits(options.limits); | ||
| const adapter = getAdapter(resolved.provider); | ||
| const built = await buildRequestBodies(resolved, options.requests, options.defaults, credentials, limits); | ||
| const snapshot = await adapter.submit({ | ||
| built, | ||
| credentials, | ||
| endpoint: built[0]?.endpoint ?? "", | ||
| limits, | ||
| metadata: options.metadata, | ||
| modelId: resolved.modelId | ||
| }); | ||
| return new BatchJob(adapter, credentials, snapshot); | ||
| }; | ||
| var batchEmbeddings = async (options) => { | ||
| if (options.requests.length === 0) { | ||
| throw new BatchworkError("batchwork: `requests` must not be empty."); | ||
| } | ||
| const resolved = resolveModel(options.model); | ||
| if (!EMBEDDING_PROVIDERS.has(resolved.provider)) { | ||
| throw unsupportedEmbeddingProvider(resolved.provider); | ||
| } | ||
| const credentials = pickCredentials(options); | ||
| const limits = resolveBatchLimits(options.limits); | ||
| const adapter = getAdapter(resolved.provider); | ||
| const built = await buildEmbeddingBodies(resolved, options.requests, credentials, limits); | ||
| const snapshot = await adapter.submit({ | ||
| built, | ||
| credentials, | ||
| endpoint: built[0]?.endpoint ?? "", | ||
| limits, | ||
| metadata: options.metadata, | ||
| modelId: resolved.modelId | ||
| }); | ||
| return new BatchJob(adapter, credentials, snapshot); | ||
| }; | ||
| var getBatch = async (ref) => { | ||
| const adapter = getAdapter(providerFromRef(ref)); | ||
| const credentials = pickCredentials(ref); | ||
| const snapshot = await adapter.retrieve(ref.id, credentials); | ||
| return new BatchJob(adapter, credentials, snapshot); | ||
| }; | ||
| var getBatchResults = (ref) => { | ||
| const adapter = getAdapter(providerFromRef(ref)); | ||
| return adapter.results(ref.id, pickCredentials(ref)); | ||
| }; | ||
| var cancelBatch = async (ref) => { | ||
| const adapter = getAdapter(providerFromRef(ref)); | ||
| await adapter.cancel(ref.id, pickCredentials(ref)); | ||
| }; | ||
| export { resolveModel, batch, batchEmbeddings, getBatch, getBatchResults, cancelBatch }; | ||
| //# debugId=FB59477B48377DA164756E2164756E21 | ||
| //# sourceMappingURL=chunk-yzjd83s9.js.map |
| { | ||
| "version": 3, | ||
| "sources": ["../src/model.ts", "../src/body.ts", "../src/batch.ts"], | ||
| "sourcesContent": [ | ||
| "import type * as AnthropicModule from \"@ai-sdk/anthropic\";\nimport type * as GoogleModule from \"@ai-sdk/google\";\nimport type * as GroqModule from \"@ai-sdk/groq\";\nimport type * as MistralModule from \"@ai-sdk/mistral\";\nimport type * as OpenAIModule from \"@ai-sdk/openai\";\nimport type * as TogetherModule from \"@ai-sdk/togetherai\";\nimport type * as XaiModule from \"@ai-sdk/xai\";\nimport type { EmbeddingModel, LanguageModel } from \"ai\";\n\nimport { MissingDependencyError, UnsupportedProviderError } from \"./errors\";\nimport type { BatchProvider, ProviderCredentials } from \"./types\";\n\n/** A fetch implementation compatible with the AI SDK provider `fetch` option. */\nexport type CapturingFetch = typeof globalThis.fetch;\n\n/** OpenAI exposes several request shapes; we mirror the one the model implies. */\nexport type OpenAIModelKind = \"chat\" | \"responses\" | \"completion\";\n\nexport interface ResolvedModel {\n /** Relevant for OpenAI; other providers always use a single chat endpoint. */\n kind: OpenAIModelKind;\n modelId: string;\n provider: BatchProvider;\n}\n\n/**\n * Placeholder API key used only when building request bodies. Body building\n * intercepts the request before it is sent, so no real credential is needed —\n * but the provider refuses to construct a model without one.\n */\nconst CAPTURE_API_KEY = \"batchwork-capture\";\n\n/** The optional `@ai-sdk/*` package backing each provider. */\nconst PACKAGE_BY_PROVIDER: Record<\n BatchProvider,\n { label: string; specifier: string }\n> = {\n anthropic: { label: \"Anthropic\", specifier: \"@ai-sdk/anthropic\" },\n google: { label: \"Google Gemini\", specifier: \"@ai-sdk/google\" },\n groq: { label: \"Groq\", specifier: \"@ai-sdk/groq\" },\n mistral: { label: \"Mistral\", specifier: \"@ai-sdk/mistral\" },\n openai: { label: \"OpenAI\", specifier: \"@ai-sdk/openai\" },\n together: { label: \"Together AI\", specifier: \"@ai-sdk/togetherai\" },\n xai: { label: \"xAI\", specifier: \"@ai-sdk/xai\" },\n};\n\n/**\n * AI SDK provider id prefixes (the part before the first `.` in `model.provider`)\n * mapped to batch providers.\n */\nconst PROVIDER_BY_FAMILY: Record<string, BatchProvider> = {\n anthropic: \"anthropic\",\n google: \"google\",\n groq: \"groq\",\n mistral: \"mistral\",\n openai: \"openai\",\n together: \"together\",\n togetherai: \"together\",\n xai: \"xai\",\n};\n\n/** Aliases accepted in the `\"provider/model\"` string form. */\nconst PROVIDER_BY_ALIAS: Record<string, BatchProvider> = {\n ...PROVIDER_BY_FAMILY,\n gemini: \"google\",\n};\n\nconst splitOnce = (value: string, separator: string): [string, string] => {\n const index = value.indexOf(separator);\n if (index === -1) {\n return [value, \"\"];\n }\n return [value.slice(0, index), value.slice(index + separator.length)];\n};\n\nconst openaiKind = (suffix: string): OpenAIModelKind => {\n if (suffix === \"responses\") {\n return \"responses\";\n }\n if (suffix === \"completion\") {\n return \"completion\";\n }\n // Default to chat completions: the most widely supported batch endpoint.\n return \"chat\";\n};\n\n/** Resolve a `\"provider/model\"` string into a provider + model id. */\nconst resolveModelString = (value: string): ResolvedModel => {\n const [providerId, modelId] = splitOnce(value, \"/\");\n if (modelId === \"\") {\n throw new UnsupportedProviderError(value);\n }\n const provider = PROVIDER_BY_ALIAS[providerId];\n if (!provider) {\n throw new UnsupportedProviderError(providerId);\n }\n return { kind: \"chat\", modelId, provider };\n};\n\n/**\n * Resolve any AI SDK `model` (a `\"provider/model\"` string or a provider model\n * object such as `openai(\"gpt-5.5\")`) to a provider + model id + request\n * shape. Gateway/registry model objects whose `modelId` is itself\n * `\"provider/model\"` are also handled.\n */\nexport const resolveModel = (\n model: LanguageModel | EmbeddingModel\n): ResolvedModel => {\n if (typeof model === \"string\") {\n return resolveModelString(model);\n }\n\n const [family, suffix] = splitOnce(model.provider, \".\");\n const provider = PROVIDER_BY_FAMILY[family];\n if (provider === \"openai\") {\n return { kind: openaiKind(suffix), modelId: model.modelId, provider };\n }\n if (provider) {\n return { kind: \"chat\", modelId: model.modelId, provider };\n }\n // Gateway/registry providers carry the real target in the model id.\n if (model.modelId.includes(\"/\")) {\n return resolveModelString(model.modelId);\n }\n throw new UnsupportedProviderError(model.provider);\n};\n\nconst importProvider = (provider: BatchProvider): Promise<unknown> => {\n switch (provider) {\n case \"anthropic\": {\n return import(\"@ai-sdk/anthropic\");\n }\n case \"google\": {\n return import(\"@ai-sdk/google\");\n }\n case \"groq\": {\n return import(\"@ai-sdk/groq\");\n }\n case \"mistral\": {\n return import(\"@ai-sdk/mistral\");\n }\n case \"openai\": {\n return import(\"@ai-sdk/openai\");\n }\n case \"together\": {\n return import(\"@ai-sdk/togetherai\");\n }\n case \"xai\": {\n return import(\"@ai-sdk/xai\");\n }\n default: {\n return Promise.reject(new UnsupportedProviderError(provider));\n }\n }\n};\n\n/**\n * Import the `@ai-sdk/*` package for a provider, translating a missing optional\n * dependency into a `MissingDependencyError`. The importer is injectable (like\n * the capturing `fetch`) so tests can drive the failure paths without\n * uninstalling a package. Exported for testing; not part of the public API.\n */\nexport const loadProvider = async <T>(\n provider: BatchProvider,\n load: (target: BatchProvider) => Promise<unknown> = importProvider\n): Promise<T> => {\n try {\n return (await load(provider)) as T;\n } catch (error) {\n if (error instanceof UnsupportedProviderError) {\n throw error;\n }\n const { specifier, label } = PACKAGE_BY_PROVIDER[provider];\n throw new MissingDependencyError(specifier, label);\n }\n};\n\n/**\n * Construct an AI SDK model wired to a capturing `fetch`, used to derive the\n * provider request body for each batch item without making a network call.\n */\nexport const createCaptureModel = async (\n resolved: ResolvedModel,\n credentials: ProviderCredentials,\n fetchImpl: CapturingFetch\n): Promise<LanguageModel> => {\n const settings = {\n apiKey: credentials.apiKey ?? CAPTURE_API_KEY,\n baseURL: credentials.baseURL,\n fetch: fetchImpl,\n headers: credentials.headers,\n };\n\n switch (resolved.provider) {\n case \"openai\": {\n const { createOpenAI } =\n await loadProvider<typeof OpenAIModule>(\"openai\");\n const provider = createOpenAI(settings);\n if (resolved.kind === \"responses\") {\n return provider.responses(resolved.modelId);\n }\n if (resolved.kind === \"completion\") {\n return provider.completion(resolved.modelId);\n }\n return provider.chat(resolved.modelId);\n }\n case \"anthropic\": {\n const { createAnthropic } =\n await loadProvider<typeof AnthropicModule>(\"anthropic\");\n return createAnthropic(settings).messages(resolved.modelId);\n }\n case \"groq\": {\n const { createGroq } = await loadProvider<typeof GroqModule>(\"groq\");\n return createGroq(settings).languageModel(resolved.modelId);\n }\n case \"mistral\": {\n const { createMistral } =\n await loadProvider<typeof MistralModule>(\"mistral\");\n return createMistral(settings).languageModel(resolved.modelId);\n }\n case \"google\": {\n const { createGoogleGenerativeAI } =\n await loadProvider<typeof GoogleModule>(\"google\");\n return createGoogleGenerativeAI(settings).languageModel(resolved.modelId);\n }\n case \"xai\": {\n const { createXai } = await loadProvider<typeof XaiModule>(\"xai\");\n return createXai(settings).languageModel(resolved.modelId);\n }\n case \"together\": {\n const { createTogetherAI } =\n await loadProvider<typeof TogetherModule>(\"together\");\n return createTogetherAI(settings).languageModel(resolved.modelId);\n }\n default: {\n throw new UnsupportedProviderError(resolved.provider);\n }\n }\n};\n\n/**\n * Providers whose batch API accepts embeddings. Together is excluded: it exposes\n * an embedding model, but its batch endpoint rejects `/v1/embeddings`. Anthropic,\n * Groq, and xAI have no embedding model at all.\n */\nexport const EMBEDDING_PROVIDERS = new Set<BatchProvider>([\n \"google\",\n \"mistral\",\n \"openai\",\n]);\n\nexport const unsupportedEmbeddingProvider = (\n provider: BatchProvider\n): UnsupportedProviderError =>\n new UnsupportedProviderError(\n provider,\n `batchwork: provider \"${provider}\" does not offer batch embeddings. Embeddings are supported for: openai, mistral, google.`\n );\n\n/**\n * Construct an AI SDK text embedding model wired to a capturing `fetch`, used to\n * derive the provider embedding request body for each batch item without making\n * a network call. Throws for providers without an embedding model.\n */\nexport const createCaptureEmbeddingModel = async (\n resolved: ResolvedModel,\n credentials: ProviderCredentials,\n fetchImpl: CapturingFetch\n): Promise<EmbeddingModel> => {\n const settings = {\n apiKey: credentials.apiKey ?? CAPTURE_API_KEY,\n baseURL: credentials.baseURL,\n fetch: fetchImpl,\n headers: credentials.headers,\n };\n\n switch (resolved.provider) {\n case \"openai\": {\n const { createOpenAI } =\n await loadProvider<typeof OpenAIModule>(\"openai\");\n return createOpenAI(settings).textEmbeddingModel(resolved.modelId);\n }\n case \"mistral\": {\n const { createMistral } =\n await loadProvider<typeof MistralModule>(\"mistral\");\n return createMistral(settings).textEmbeddingModel(resolved.modelId);\n }\n case \"google\": {\n const { createGoogleGenerativeAI } =\n await loadProvider<typeof GoogleModule>(\"google\");\n return createGoogleGenerativeAI(settings).textEmbeddingModel(\n resolved.modelId\n );\n }\n default: {\n throw unsupportedEmbeddingProvider(resolved.provider);\n }\n }\n};\n", | ||
| "import { embed, generateText } from \"ai\";\nimport type { EmbeddingModel, LanguageModel } from \"ai\";\n\nimport { BatchworkError } from \"./errors\";\nimport {\n assertByteLength,\n mapWithConcurrency,\n resolveBatchLimits,\n} from \"./limits\";\nimport type { ResolvedBatchLimits } from \"./limits\";\nimport { createCaptureEmbeddingModel, createCaptureModel } from \"./model\";\nimport type { CapturingFetch, ResolvedModel } from \"./model\";\nimport type {\n BatchDefaults,\n BatchEmbeddingRequest,\n BatchLimits,\n BatchRequest,\n ProviderCredentials,\n} from \"./types\";\n\ntype GenerateTextInput = Parameters<typeof generateText>[0];\n\n/** A provider request body derived from a single batch item. */\nexport interface BuiltRequest {\n /** The serialized provider request body (becomes the batch line). */\n body: Record<string, unknown>;\n customId: string;\n /** API endpoint path the model targets, e.g. `/v1/chat/completions`. */\n endpoint: string;\n}\n\nconst MAX_CAUSE_DEPTH = 10;\n\n/**\n * Thrown by the capturing `fetch` to abort the request after its body has been\n * serialized. The body travels inside the error (not shared state), so capture\n * is correct even under concurrency.\n */\nclass CaptureSignalError extends Error {\n readonly url: string;\n readonly rawBody: string;\n\n constructor(url: string, rawBody: string) {\n super(\"batchwork:capture\");\n this.name = \"CaptureSignalError\";\n this.url = url;\n this.rawBody = rawBody;\n }\n}\n\nconst resolveUrl = (input: string | URL | Request): string => {\n if (typeof input === \"string\") {\n return input;\n }\n if (input instanceof URL) {\n return input.toString();\n }\n return input.url;\n};\n\nconst extractBody = (init?: RequestInit): string => {\n const body = init?.body;\n if (typeof body === \"string\") {\n return body;\n }\n if (body instanceof Uint8Array) {\n return new TextDecoder().decode(body);\n }\n throw new BatchworkError(\n \"batchwork: unable to read the provider request body during capture.\"\n );\n};\n\n// `CapturingFetch` is `typeof fetch`, whose shape varies by runtime types (e.g.\n// Bun adds a required `preconnect` method). We only ever call it as a plain\n// fetch, so cast the bare implementation rather than stub the extra members.\nconst captureFetch = ((input: string | URL | Request, init?: RequestInit) =>\n Promise.reject(\n new CaptureSignalError(resolveUrl(input), extractBody(init))\n )) as unknown as CapturingFetch;\n\nconst findCapture = (error: unknown): CaptureSignalError | undefined => {\n let current: unknown = error;\n let depth = 0;\n while (current && depth < MAX_CAUSE_DEPTH) {\n if (current instanceof CaptureSignalError) {\n return current;\n }\n current = (current as { cause?: unknown }).cause;\n depth += 1;\n }\n};\n\nconst endpointFromUrl = (url: string): string => {\n try {\n return new URL(url).pathname;\n } catch {\n return url;\n }\n};\n\nconst mergeDefaults = (\n request: BatchRequest,\n defaults: BatchDefaults | undefined\n): BatchRequest => {\n if (!defaults) {\n return request;\n }\n return { ...defaults, ...request };\n};\n\n/**\n * Map a batch request to AI SDK `generateText` input. Fields are listed\n * explicitly so `customId` never leaks into the provider request.\n */\nconst toGenerateInput = (\n model: LanguageModel,\n request: BatchRequest\n): GenerateTextInput =>\n // `prompt`/`messages` form a discriminated union in the AI SDK types; we\n // pass both keys and let `generateText` validate the XOR at runtime.\n ({\n frequencyPenalty: request.frequencyPenalty,\n maxOutputTokens: request.maxOutputTokens,\n maxRetries: 0,\n messages: request.messages,\n model,\n presencePenalty: request.presencePenalty,\n prompt: request.prompt,\n providerOptions: request.providerOptions,\n seed: request.seed,\n stopSequences: request.stopSequences,\n system: request.system,\n temperature: request.temperature,\n toolChoice: request.toolChoice,\n tools: request.tools,\n topK: request.topK,\n topP: request.topP,\n }) as GenerateTextInput;\n\n/**\n * Turn a thrown capture into a {@link BuiltRequest}. A genuine failure (one that\n * never reached the capturing `fetch`, e.g. an invalid prompt) is rethrown.\n */\nconst bodyFromCapture = (\n error: unknown,\n customId: string,\n maxRequestBytes: number\n): BuiltRequest => {\n const capture = findCapture(error);\n if (capture) {\n assertByteLength(`request \"${customId}\"`, capture.rawBody, maxRequestBytes);\n return {\n body: JSON.parse(capture.rawBody) as Record<string, unknown>,\n customId,\n endpoint: endpointFromUrl(capture.url),\n };\n }\n throw error;\n};\n\nconst captureOne = async (\n model: LanguageModel,\n request: BatchRequest,\n customId: string,\n maxRequestBytes: number\n): Promise<BuiltRequest> => {\n try {\n await generateText(toGenerateInput(model, request));\n } catch (error) {\n return bodyFromCapture(error, customId, maxRequestBytes);\n }\n throw new BatchworkError(\n \"batchwork: the request was not intercepted while building the batch body.\"\n );\n};\n\nconst captureEmbeddingOne = async (\n model: EmbeddingModel,\n request: BatchEmbeddingRequest,\n customId: string,\n maxRequestBytes: number\n): Promise<BuiltRequest> => {\n try {\n // `maxRetries: 0` is load-bearing: with retries enabled the capture error\n // is wrapped in a `RetryError` (under `.errors`, not `.cause`) and\n // `findCapture`'s cause walk would miss it.\n await embed({\n maxRetries: 0,\n model,\n providerOptions: request.providerOptions,\n value: request.value,\n });\n } catch (error) {\n return bodyFromCapture(error, customId, maxRequestBytes);\n }\n throw new BatchworkError(\n \"batchwork: the request was not intercepted while building the embedding body.\"\n );\n};\n\n/**\n * Assign and validate a unique `customId` for each request (sequentially, so\n * duplicates are reported deterministically before bodies are captured in\n * parallel). Auto-generates `request-{index}` when omitted.\n */\nconst assignCustomIds = <T extends { customId?: string }>(\n requests: readonly T[]\n): { customId: string; request: T }[] => {\n const seen = new Set<string>();\n return requests.map((request, index) => {\n const customId = request.customId ?? `request-${index}`;\n if (seen.has(customId)) {\n throw new BatchworkError(\n `batchwork: duplicate customId \"${customId}\". customId values must be unique within a batch.`\n );\n }\n seen.add(customId);\n return { customId, request };\n });\n};\n\n/**\n * Derive provider request bodies for every batch item by running each through\n * the AI SDK with a capturing `fetch`. This reuses the AI SDK's full message,\n * tool, and multimodal conversion, so the body matches what `generateText`\n * would send — minus the network call.\n */\nexport const buildRequestBodies = async (\n resolved: ResolvedModel,\n requests: readonly BatchRequest[],\n defaults: BatchDefaults | undefined,\n credentials: ProviderCredentials,\n rawLimits?: BatchLimits | ResolvedBatchLimits\n): Promise<BuiltRequest[]> => {\n const limits = resolveBatchLimits(rawLimits);\n if (requests.length > limits.maxRequests) {\n throw new BatchworkError(\n `batchwork: requests length ${requests.length} exceeds the ${limits.maxRequests} request limit.`\n );\n }\n const model = await createCaptureModel(resolved, credentials, captureFetch);\n const items = assignCustomIds(requests);\n\n return await mapWithConcurrency(\n items,\n limits.captureConcurrency,\n async (item) => {\n const built = await captureOne(\n model,\n mergeDefaults(item.request, defaults),\n item.customId,\n limits.maxRequestBytes\n );\n assertByteLength(\n `request \"${item.customId}\"`,\n JSON.stringify(built.body),\n limits.maxRequestBytes\n );\n return built;\n }\n );\n};\n\n/**\n * Derive provider embedding request bodies for every batch item by running each\n * through the AI SDK `embed` with a capturing `fetch`. Mirrors\n * {@link buildRequestBodies} for the embedding endpoint; each item maps to a\n * single embedding (`input: [value]`), correlated by `customId`.\n */\nexport const buildEmbeddingBodies = async (\n resolved: ResolvedModel,\n requests: readonly BatchEmbeddingRequest[],\n credentials: ProviderCredentials,\n rawLimits?: BatchLimits | ResolvedBatchLimits\n): Promise<BuiltRequest[]> => {\n const limits = resolveBatchLimits(rawLimits);\n if (requests.length > limits.maxRequests) {\n throw new BatchworkError(\n `batchwork: requests length ${requests.length} exceeds the ${limits.maxRequests} request limit.`\n );\n }\n const model = await createCaptureEmbeddingModel(\n resolved,\n credentials,\n captureFetch\n );\n const items = assignCustomIds(requests);\n\n return await mapWithConcurrency(\n items,\n limits.captureConcurrency,\n async (item) => {\n const built = await captureEmbeddingOne(\n model,\n item.request,\n item.customId,\n limits.maxRequestBytes\n );\n assertByteLength(\n `request \"${item.customId}\"`,\n JSON.stringify(built.body),\n limits.maxRequestBytes\n );\n return built;\n }\n );\n};\n", | ||
| "import { buildEmbeddingBodies, buildRequestBodies } from \"./body\";\nimport { BatchworkError } from \"./errors\";\nimport { BatchJob } from \"./job\";\nimport { resolveBatchLimits } from \"./limits\";\nimport {\n EMBEDDING_PROVIDERS,\n resolveModel,\n unsupportedEmbeddingProvider,\n} from \"./model\";\nimport { getAdapter } from \"./providers\";\nimport type {\n BatchEmbeddingsOptions,\n BatchOptions,\n BatchProvider,\n BatchRef,\n BatchResult,\n ProviderCredentials,\n} from \"./types\";\n\nconst pickCredentials = (source: ProviderCredentials): ProviderCredentials => ({\n apiKey: source.apiKey,\n baseURL: source.baseURL,\n headers: source.headers,\n});\n\nconst providerFromRef = (ref: BatchRef): BatchProvider => {\n if (ref.provider) {\n return ref.provider;\n }\n if (ref.model !== undefined) {\n return resolveModel(ref.model).provider;\n }\n throw new BatchworkError(\n \"batchwork: provide `provider` or `model` to identify the batch.\"\n );\n};\n\n/**\n * Submit a batch of requests to the model's provider and return a handle.\n *\n * Resolves immediately once the batch is accepted — it does not wait for\n * processing. Use the returned {@link BatchJob} to poll, wait, or stream\n * results.\n *\n * @example\n * const job = await batch({\n * model: openai(\"gpt-5.5\"),\n * requests: [{ customId: \"a\", prompt: \"Say hi\" }],\n * });\n * const results = await job.wait().then(() => job.collect());\n */\nexport const batch = async (options: BatchOptions): Promise<BatchJob> => {\n if (options.requests.length === 0) {\n throw new BatchworkError(\"batchwork: `requests` must not be empty.\");\n }\n\n const resolved = resolveModel(options.model);\n const credentials = pickCredentials(options);\n const limits = resolveBatchLimits(options.limits);\n const adapter = getAdapter(resolved.provider);\n\n const built = await buildRequestBodies(\n resolved,\n options.requests,\n options.defaults,\n credentials,\n limits\n );\n const snapshot = await adapter.submit({\n built,\n credentials,\n endpoint: built[0]?.endpoint ?? \"\",\n limits,\n metadata: options.metadata,\n modelId: resolved.modelId,\n });\n\n return new BatchJob(adapter, credentials, snapshot);\n};\n\n/**\n * Submit a batch of embedding requests to the model's provider and return a\n * handle. Each request's `value` produces one vector, correlated by `customId`\n * via {@link BatchJob.results}. Supported for OpenAI, Mistral, Together, and\n * Google; other providers throw {@link UnsupportedProviderError}.\n *\n * @example\n * const job = await batchEmbeddings({\n * model: openai.textEmbeddingModel(\"text-embedding-3-small\"),\n * requests: [{ customId: \"a\", value: \"hello world\" }],\n * });\n * const results = await job.wait().then(() => job.collect());\n * for (const r of results) {\n * console.log(r.customId, r.embedding?.length);\n * }\n */\nexport const batchEmbeddings = async (\n options: BatchEmbeddingsOptions\n): Promise<BatchJob> => {\n if (options.requests.length === 0) {\n throw new BatchworkError(\"batchwork: `requests` must not be empty.\");\n }\n\n const resolved = resolveModel(options.model);\n if (!EMBEDDING_PROVIDERS.has(resolved.provider)) {\n throw unsupportedEmbeddingProvider(resolved.provider);\n }\n const credentials = pickCredentials(options);\n const limits = resolveBatchLimits(options.limits);\n const adapter = getAdapter(resolved.provider);\n\n const built = await buildEmbeddingBodies(\n resolved,\n options.requests,\n credentials,\n limits\n );\n const snapshot = await adapter.submit({\n built,\n credentials,\n endpoint: built[0]?.endpoint ?? \"\",\n limits,\n metadata: options.metadata,\n modelId: resolved.modelId,\n });\n\n return new BatchJob(adapter, credentials, snapshot);\n};\n\n/**\n * Rehydrate a {@link BatchJob} for an existing batch id (e.g. one persisted\n * after submission). Identify the provider with `provider` or `model`.\n */\nexport const getBatch = async (ref: BatchRef): Promise<BatchJob> => {\n const adapter = getAdapter(providerFromRef(ref));\n const credentials = pickCredentials(ref);\n const snapshot = await adapter.retrieve(ref.id, credentials);\n return new BatchJob(adapter, credentials, snapshot);\n};\n\n/** Stream the results of an existing batch by id, without a handle. */\nexport const getBatchResults = (ref: BatchRef): AsyncGenerator<BatchResult> => {\n const adapter = getAdapter(providerFromRef(ref));\n return adapter.results(ref.id, pickCredentials(ref));\n};\n\n/** Request cancellation of an existing batch by id. */\nexport const cancelBatch = async (ref: BatchRef): Promise<void> => {\n const adapter = getAdapter(providerFromRef(ref));\n await adapter.cancel(ref.id, pickCredentials(ref));\n};\n" | ||
| ], | ||
| "mappings": "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", | ||
| "debugId": "FB59477B48377DA164756E2164756E21", | ||
| "names": [] | ||
| } |
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
360676
8.09%3361
7.07%111
33.73%10
11.11%