🚀 Socket Launch Week Day 5:Introducing Repository Access Permissions and Custom Roles.Learn more
Sign In

batchwork

Package Overview
Dependencies
Maintainers
1
Versions
5
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

batchwork - npm Package Compare versions

Comparing version
1.1.0
to
1.2.0
+375
dist/chunk-3yqs6x71.js
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"}
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"}

@@ -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"}
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": []
}

@@ -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 {

@@ -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":"AAAA,OAAO,KAAK,EACV,cAAc,EACd,SAAS,EACT,aAAa,EACb,YAAY,EACZ,UAAU,EACV,OAAO,EACR,MAAM,IAAI,CAAC;AAEZ,sCAAsC;AACtC,MAAM,MAAM,aAAa,GACrB,WAAW,GACX,QAAQ,GACR,MAAM,GACN,SAAS,GACT,QAAQ,GACR,UAAU,GACV,KAAK,CAAC;AAEV;;;GAGG;AACH,MAAM,MAAM,eAAe,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,SAAS,CAAC,CAAC,CAAC;AAExE;;;GAGG;AACH,MAAM,WAAW,oBAAoB;IACnC,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,eAAe,CAAC,EAAE,eAAe,CAAC;IAClC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,aAAa,CAAC,EAAE,MAAM,EAAE,CAAC;IACzB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,UAAU,CAAC,EAAE,UAAU,CAAC,OAAO,CAAC,CAAC;IACjC,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED;;;GAGG;AACH,MAAM,WAAW,YAAa,SAAQ,oBAAoB;IACxD,0EAA0E;IAC1E,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,YAAY,EAAE,CAAC;IAC1B,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,gFAAgF;AAChF,MAAM,MAAM,aAAa,GAAG,oBAAoB,CAAC;AAEjD;;;;;GAKG;AACH,MAAM,WAAW,qBAAqB;IACpC,0EAA0E;IAC1E,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,oFAAoF;IACpF,eAAe,CAAC,EAAE,eAAe,CAAC;IAClC,yBAAyB;IACzB,KAAK,EAAE,MAAM,CAAC;CACf;AAED,mEAAmE;AACnE,MAAM,MAAM,WAAW,GACnB,YAAY,GACZ,aAAa,GACb,YAAY,GACZ,WAAW,GACX,QAAQ,GACR,SAAS,GACT,YAAY,GACZ,WAAW,CAAC;AAEhB,mFAAmF;AACnF,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,KAAK,EAAE,MAAM,CAAC;CACf;AAED,kDAAkD;AAClD,MAAM,MAAM,iBAAiB,GACzB,WAAW,GACX,SAAS,GACT,SAAS,GACT,UAAU,CAAC;AAEf,MAAM,WAAW,UAAU;IACzB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,gBAAgB;IAC/B,IAAI,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IACvB,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,0DAA0D;AAC1D,MAAM,WAAW,WAAW;IAC1B,QAAQ,EAAE,MAAM,CAAC;IACjB,2EAA2E;IAC3E,SAAS,CAAC,EAAE,MAAM,EAAE,CAAC;IACrB,8DAA8D;IAC9D,KAAK,CAAC,EAAE,gBAAgB,CAAC;IACzB,+EAA+E;IAC/E,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,MAAM,EAAE,iBAAiB,CAAC;IAC1B,mEAAmE;IACnE,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,UAAU,CAAC;CACpB;AAED,4DAA4D;AAC5D,MAAM,WAAW,aAAa;IAC5B,WAAW,CAAC,EAAE,IAAI,CAAC;IACnB,SAAS,CAAC,EAAE,IAAI,CAAC;IACjB,SAAS,CAAC,EAAE,IAAI,CAAC;IACjB,EAAE,EAAE,MAAM,CAAC;IACX,QAAQ,EAAE,aAAa,CAAC;IACxB,kCAAkC;IAClC,GAAG,EAAE,OAAO,CAAC;IACb,aAAa,EAAE,kBAAkB,CAAC;IAClC,MAAM,EAAE,WAAW,CAAC;CACrB;AAED,kFAAkF;AAClF,MAAM,WAAW,mBAAmB;IAClC,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,2DAA2D;IAC3D,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAClC;AAED,MAAM,WAAW,WAAW;IAC1B,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAED,0BAA0B;AAC1B,MAAM,WAAW,YAAa,SAAQ,mBAAmB;IACvD,QAAQ,CAAC,EAAE,aAAa,CAAC;IACzB,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAClC,KAAK,EAAE,aAAa,CAAC;IACrB,QAAQ,EAAE,YAAY,EAAE,CAAC;CAC1B;AAED,oCAAoC;AACpC,MAAM,WAAW,sBAAuB,SAAQ,mBAAmB;IACjE,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAClC,KAAK,EAAE,cAAc,CAAC;IACtB,QAAQ,EAAE,qBAAqB,EAAE,CAAC;CACnC;AAED;;;GAGG;AACH,MAAM,WAAW,QAAS,SAAQ,mBAAmB;IACnD,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,CAAC,EAAE,aAAa,CAAC;IACtB,QAAQ,CAAC,EAAE,aAAa,CAAC;CAC1B;AAED,MAAM,WAAW,WAAW;IAC1B,yDAAyD;IACzD,MAAM,CAAC,EAAE,CAAC,QAAQ,EAAE,aAAa,KAAK,IAAI,CAAC;IAC3C,4CAA4C;IAC5C,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB,oDAAoD;IACpD,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB"}
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
{
"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": [

@@ -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": ";;;;;;;AAGA,IAAM,kBAAuE;AAAA,EAC3E,WAAW;AAAA,EACX,WAAW;AAAA,EACX,SAAS;AAAA,EACT,QAAQ;AACV;AAGO,IAAM,UAAU,CACrB,UACA,cACuB;AAAA,EACvB,aAAa,SAAS,aAAa,YAAY;AAAA,EAC/C,WAAW,SAAS,WAAW,YAAY;AAAA,EAC3C,IAAI,SAAS;AAAA,EACb;AAAA,EACA,eAAe,SAAS;AAAA,EACxB,MAAM,gBAAgB,SAAS,WAAW;AAC5C;;;ACdA,IAAM,oBAAoB;AAC1B,IAAM,oBAAoB;AAC1B,IAAM,gBAAgB;AACtB,IAAM,UAAU,IAAI;AAgBpB,IAAM,cAAc,IAAI;AACxB,IAAM,cAAc,IAAI;AAKxB,IAAM,mBAAmB,CAAC,QAAsB;AAAA,EAC9C,YAAY,IAAI,cAAc,aAAa;AAAA,IACzC,IAAI,aAAa,KAAK;AAAA,MACpB,YAAY,OAAO,EAAE;AAAA,IACvB;AAAA,EACF;AAAA;AAGF,IAAM,qBAAyC;AAAA,EAC7C,OAAO,CAAC,IAAI,WAAW,QAAQ;AAAA,IAC7B,iBAAiB,GAAG;AAAA,IACpB,MAAM,WAAW,YAAY,IAAI,EAAE;AAAA,IACnC,IAAI,YAAY,WAAW,KAAK;AAAA,MAC9B,OAAO;AAAA,IACT;AAAA,IACA,YAAY,IAAI,IAAI,SAAS;AAAA,IAC7B,OAAO;AAAA;AAAA,EAET,KAAK,CAAC,OAAO,YAAY,IAAI,EAAE;AAAA,EAC/B,KAAK,CAAC,IAAI,cAAc;AAAA,IACtB,YAAY,IAAI,IAAI,SAAS;AAAA;AAEjC;AAEA,IAAM,iBAAiB,OACrB,OACA,IACA,SACoB;AAAA,EACpB,IAAI,QAAQ,YAAY,IAAI,KAAK;AAAA,EACjC,IAAI,CAAC,OAAO;AAAA,IACV,QAAQ,IAAI;AAAA,IACZ,YAAY,IAAI,OAAO,KAAK;AAAA,EAC9B;AAAA,EAEA,MAAM,WAAW,MAAM,IAAI,EAAE;AAAA,EAC7B,IAAI;AAAA,EAEJ,MAAM,UAAU,IAAI,QAAc,CAAC,YAAY;AAAA,IAC7C,UAAU;AAAA,GACX;AAAA,EACD,MAAM,IAAI,IAAI,OAAO;AAAA,EAErB,IAAI,UAAU;AAAA,IACZ,MAAM;AAAA,EACR;AAAA,EACA,IAAI;AAAA,IACF,OAAO,MAAM,KAAK;AAAA,YAClB;AAAA,IACA,QAAQ;AAAA,IACR,IAAI,MAAM,IAAI,EAAE,MAAM,SAAS;AAAA,MAC7B,MAAM,OAAO,EAAE;AAAA,IACjB;AAAA;AAAA;AAIJ,IAAM,gBAAgB,CAAC,UACrB,WAAW,KAAK,KAAK,KAAK,GAAG,CAAC,SAAS,KAAK,YAAY,CAAC,KAAK,CAAC;AAEjE,IAAM,gBAAgB,CAAC,UAA8B;AAAA,EACnD,IAAI,SAAS;AAAA,EACb,WAAW,QAAQ,OAAO;AAAA,IACxB,UAAU,OAAO,cAAc,IAAI;AAAA,EACrC;AAAA,EACA,OAAO,KAAK,MAAM;AAAA;AAGpB,IAAM,YAAY,CAAC,WAAuC;AAAA,EACxD,MAAM,MAAM,OAAO,WAAW,aAAa,IACvC,cAAc,OAAO,MAAM,cAAc,MAAM,CAAC,IAChD,QAAQ,OAAO,MAAM;AAAA,EACzB,OAAO,OAAO,OAAO,UACnB,OACA,KACA,EAAE,MAAM,WAAW,MAAM,OAAO,GAChC,OACA,CAAC,QAAQ,QAAQ,CACnB;AAAA;AAGF,IAAM,cAAc,OAClB,QACA,YACoB;AAAA,EACpB,MAAM,MAAM,MAAM,UAAU,MAAM;AAAA,EAClC,MAAM,YAAY,MAAM,OAAO,OAAO,KACpC,QACA,KACA,QAAQ,OAAO,OAAO,CACxB;AAAA,EACA,OAAO,cAAc,IAAI,WAAW,SAAS,CAAC;AAAA;AAGhD,IAAM,gBAAgB,OACpB,QACA,SACA,cACqB;AAAA,EACrB,MAAM,MAAM,MAAM,UAAU,MAAM;AAAA,EAClC,OAAO,OAAO,OAAO,OACnB,QACA,KACA,cAAc,SAAS,GACvB,QAAQ,OAAO,OAAO,CACxB;AAAA;AAGF,IAAM,oBAAoB,OACxB,IACA,WACA,KACA,UACkB;AAAA,EAClB,MAAM,YAAY,YAAY;AAAA,EAC9B,IAAI,MAAM,OAAO;AAAA,IACf,MAAM,UAAU,MAAM,MAAM,MAAM,IAAI,WAAW,GAAG;AAAA,IACpD,IAAI,CAAC,SAAS;AAAA,MACZ,MAAM,IAAI,eAAe,qCAAqC;AAAA,IAChE;AAAA,IACA;AAAA,EACF;AAAA,EAEA,MAAM,eAAe,OAAO,IAAI,YAAY;AAAA,IAC1C,IAAI,UAAU,oBAAoB;AAAA,MAChC,iBAAiB,GAAG;AAAA,IACtB;AAAA,IACA,MAAM,WAAW,MAAM,MAAM,IAAI,EAAE;AAAA,IACnC,IAAI,YAAY,WAAW,KAAK;AAAA,MAC9B,MAAM,IAAI,eAAe,qCAAqC;AAAA,IAChE;AAAA,IACA,MAAM,MAAM,IAAI,IAAI,SAAS;AAAA,GAC9B;AAAA;AAII,IAAM,cAAc,OACzB,QACA,IACA,MACA,qBACoC;AAAA,EACpC,MAAM,YAAY,KAAK,MAAM,gBAAgB,EAAE,SAAS;AAAA,EACxD,MAAM,YAAY,MAAM,YAAY,QAAQ,GAAG,MAAM,aAAa,MAAM;AAAA,EACxE,OAAO;AAAA,IACL,cAAc;AAAA,IACd,qBAAqB,GAAG,qBAAqB;AAAA,IAC7C,qBAAqB;AAAA,EACvB;AAAA;AAcK,IAAM,gBAAgB,OAC3B,SACA,QACA,YAC6B;AAAA,EAC7B,MAAM,KAAK,QAAQ,QAAQ,IAAI,YAAY;AAAA,EAC3C,MAAM,YAAY,QAAQ,QAAQ,IAAI,mBAAmB;AAAA,EACzD,MAAM,kBAAkB,QAAQ,QAAQ,IAAI,mBAAmB;AAAA,EAC/D,IAAI,EAAE,MAAM,aAAa,kBAAkB;AAAA,IACzC,MAAM,IAAI,eAAe,+CAA+C;AAAA,EAC1E;AAAA,EAEA,MAAM,UAAU,OAAO,SAAS;AAAA,EAChC,MAAM,MAAM,KAAK,IAAI,IAAI;AAAA,EACzB,IACE,CAAC,OAAO,SAAS,OAAO,KACxB,KAAK,IAAI,MAAM,OAAO,IAAI,mBAC1B;AAAA,IACA,MAAM,IAAI,eAAe,iDAAiD;AAAA,EAC5E;AAAA,EAEA,MAAM,OAAO,MAAM,QAAQ,KAAK;AAAA,EAChC,MAAM,UAAU,GAAG,MAAM,aAAa;AAAA,EACtC,MAAM,aAAa,gBAAgB,MAAM,GAAG,EAAE,IAAI,CAAC,SAAS;AAAA,IAC1D,MAAM,QAAQ,KAAK,QAAQ,GAAG;AAAA,IAC9B,OAAO,UAAU,KAAK,OAAO,KAAK,MAAM,QAAQ,CAAC;AAAA,GAClD;AAAA,EAED,IAAI,QAAQ;AAAA,EACZ,WAAW,aAAa,YAAY;AAAA,IAElC,IAAI,MAAM,cAAc,QAAQ,SAAS,SAAS,GAAG;AAAA,MACnD,QAAQ;AAAA,MACR;AAAA,IACF;AAAA,EACF;AAAA,EACA,IAAI,CAAC,OAAO;AAAA,IACV,MAAM,IAAI,eACR,mDACF;AAAA,EACF;AAAA,EAEA,MAAM,kBACJ,IACA,SACA,KACA,SAAS,eAAe,kBAC1B;AAAA,EAEA,OAAO,EAAE,MAAM,IAAI,WAAW,QAAQ;AAAA;AAOjC,IAAM,qBAAqB,OAChC,SACA,QACA,YAC+B;AAAA,EAC/B,QAAQ,SAAS,MAAM,cAAc,SAAS,QAAQ,OAAO;AAAA,EAC7D,OAAO,KAAK,MAAM,IAAI;AAAA;;;AChLxB,IAAM,YAAY,CAAC,SAAuC;AAAA,EACxD,IAAI,CAAC,6BAA6B,KAAK,IAAI,GAAG;AAAA,IAC5C;AAAA,EACF;AAAA,EACA,MAAM,QAAQ,KAAK,MAAM,GAAG,EAAE,IAAI,MAAM;AAAA,EACxC,MAAM,QAAQ,MAAM,MAClB,CAAC,SAAS,OAAO,UAAU,IAAI,KAAK,QAAQ,KAAK,QAAQ,GAC3D;AAAA,EACA,OAAO,QAAQ,QAAQ;AAAA;AAGzB,IAAM,gBAAgB,CAAC,UAA6B;AAAA,EAClD,OAAO,IAAI,GAAG,IAAI,KAAK;AAAA,EACvB,OACE,MAAM,KACN,MAAM,MACN,MAAM,OACL,MAAM,OAAO,KAAK,MAAM,KAAK,OAC7B,MAAM,OAAO,MAAM,OACnB,MAAM,OAAO,KAAK,MAAM,KAAK,MAC7B,MAAM,OAAO,MAAM,OACnB,MAAM,QAAQ,MAAM,MAAM,MAAM,OACjC,KAAK;AAAA;AAIT,IAAM,sBAAsB,CAAC,SAAuC;AAAA,EAClE,MAAM,aAAa,KAAK,QAAQ,QAAQ,EAAE,EAAE,QAAQ,QAAQ,EAAE,EAAE,YAAY;AAAA,EAC5E,IAAI,CAAC,WAAW,WAAW,SAAS,GAAG;AAAA,IACrC;AAAA,EACF;AAAA,EAEA,MAAM,SAAS,WAAW,MAAM,UAAU,MAAM;AAAA,EAChD,MAAM,SAAS,UAAU,MAAM;AAAA,EAC/B,IAAI,QAAQ;AAAA,IACV,OAAO;AAAA,EACT;AAAA,EAEA,OAAO,MAAM,KAAK,SAAS,OAAO,MAAM,GAAG;AAAA,EAC3C,IAAI,EAAE,QAAQ,QAAQ,UAAU,WAAW;AAAA,IACzC;AAAA,EACF;AAAA,EACA,MAAM,WAAW,OAAO,SAAS,MAAM,EAAE;AAAA,EACzC,MAAM,UAAU,OAAO,SAAS,KAAK,EAAE;AAAA,EACvC,IACE,CAAC,OAAO,UAAU,QAAQ,KAC1B,CAAC,OAAO,UAAU,OAAO,KACzB,WAAW,KACX,WAAW,SACX,UAAU,KACV,UAAU,OACV;AAAA,IACA;AAAA,EACF;AAAA,EACA,OAAO;AAAA,IACL,KAAK,MAAM,WAAW,GAAG;AAAA,IACzB,WAAW;AAAA,IACX,KAAK,MAAM,UAAU,GAAG;AAAA,IACxB,UAAU;AAAA,EACZ;AAAA;AAGF,IAAM,gBAAgB,CAAC,SAA0B;AAAA,EAC/C,MAAM,aAAa,KAAK,QAAQ,QAAQ,EAAE,EAAE,QAAQ,QAAQ,EAAE,EAAE,YAAY;AAAA,EAG5E,IAAI,CAAC,WAAW,SAAS,GAAG,GAAG;AAAA,IAC7B,OAAO;AAAA,EACT;AAAA,EACA,OACE,eAAe,QACf,eAAe,SACf,WAAW,WAAW,IAAI,KAC1B,WAAW,WAAW,IAAI,KAC1B,WAAW,WAAW,OAAO;AAAA;AAIjC,IAAM,mBAAmB,CAAC,WACxB,UAAU,OAAO,SAAS;AAE5B,IAAM,uBAA4C,CAAC,QAAQ;AAAA,EACzD,IAAI,IAAI,aAAa,UAAU;AAAA,IAC7B,MAAM,IAAI,eAAe,uCAAuC;AAAA,EAClE;AAAA,EACA,IAAI,IAAI,YAAY,IAAI,UAAU;AAAA,IAChC,MAAM,IAAI,eACR,qDACF;AAAA,EACF;AAAA,EAEA,MAAM,OAAO,IAAI,SAAS,YAAY;AAAA,EACtC,MAAM,OAAO,UAAU,IAAI;AAAA,EAC3B,MAAM,aAAa,oBAAoB,IAAI;AAAA,EAC3C,IACE,SAAS,eACT,KAAK,SAAS,YAAY,KAC1B,KAAK,SAAS,QAAQ,KACrB,QAAQ,cAAc,IAAI,KAC1B,cAAc,cAAc,UAAU,KACvC,cAAc,IAAI,GAClB;AAAA,IACA,MAAM,IAAI,eACR,sEACF;AAAA,EACF;AAAA;AAGF,IAAM,qBAAqB,OACzB,QACA,cACoB;AAAA,EACpB,IAAI;AAAA,EACJ,IAAI;AAAA,IACF,MAAM,IAAI,IAAI,MAAM;AAAA,IACpB,OAAO,OAAO;AAAA,IACd,MAAM,IAAI,eAAe,8CAA8C;AAAA,MACrE,OAAO;AAAA,IACT,CAAC;AAAA;AAAA,EAEH,MAAM,UAAU,GAAG;AAAA,EACnB,OAAO,IAAI,SAAS;AAAA;AAItB,IAAM,oBACJ,CAAC,cACD,OAAO,QAAQ,aAAa;AAAA,EAC1B,IAAI,CAAC,OAAO,YAAY;AAAA,IACtB,MAAM,IAAI,eACR,2DACF;AAAA,EACF;AAAA,EACA,MAAM,aAAa,MAAM,mBAAmB,OAAO,YAAY,SAAS;AAAA,EACxE,MAAM,OAAO,KAAK,UAAU,QAAQ,OAAO,UAAU,QAAQ,CAAC;AAAA,EAC9D,MAAM,UAAkC;AAAA,IACtC,gBAAgB;AAAA,EAClB;AAAA,EACA,IAAI,OAAO,eAAe;AAAA,IACxB,OAAO,OACL,SACA,MAAM,YACJ,OAAO,eACP,OAAO,IACP,MACA,KAAK,IAAI,IAAI,IACf,CACF;AAAA,EACF;AAAA,EACA,MAAM,WAAW,MAAM,MAAM,YAAY;AAAA,IACvC;AAAA,IACA;AAAA,IACA,QAAQ;AAAA,IACR,UAAU;AAAA,EACZ,CAAC;AAAA,EACD,IAAI,iBAAiB,SAAS,MAAM,GAAG;AAAA,IACrC,MAAM,IAAI,eACR,kCAAkC,0BAA0B,SAAS,UACvE;AAAA,EACF;AAAA,EACA,IAAI,CAAC,SAAS,IAAI;AAAA,IAChB,MAAM,IAAI,eACR,kCAAkC,sBAAsB,SAAS,UACnE;AAAA,EACF;AAAA;AASG,IAAM,oBAAoB,CAAC,YAA6C;AAAA,EAC7E,MAAM,qBAAqB,CAAC,aAAiD;AAAA,IAC3E,IAAI,OAAO,QAAQ,gBAAgB,YAAY;AAAA,MAC7C,OAAO,QAAQ,YAAY,QAAQ;AAAA,IACrC;AAAA,IACA,OAAO,QAAQ,eAAe,CAAC;AAAA;AAAA,EAGjC,MAAM,sBACJ,QAAQ,sBAAsB;AAAA,EAChC,MAAM,OAAO,QAAQ,cAAc,kBAAkB,mBAAmB;AAAA,EAExE,MAAM,UAAU,OACd,QACA,aACkB;AAAA,IAGlB,MAAM,KAAK,QAAQ,QAAQ;AAAA,IAC3B,MAAM,QAAQ,MAAM,IAAI;AAAA,SACnB;AAAA,MACH,aAAa,IAAI,KAAK,EAAE,YAAY;AAAA,MACpC,QAAQ,SAAS;AAAA,IACnB,CAAC;AAAA;AAAA,EAGH,MAAM,QAAQ,OACZ,QACA,SAC0B;AAAA,IAC1B,MAAM,aACJ,KAAK,cAAc,CAAC,QAAQ,aACxB,MAAM,mBAAmB,KAAK,YAAY,mBAAmB,IAC7D,KAAK;AAAA,IACX,MAAM,SAAuB;AAAA,MAC3B,WAAW,IAAI,KAAK,EAAE,YAAY;AAAA,MAClC,IAAI,OAAO;AAAA,MACX,UAAU,OAAO;AAAA,MACjB,QAAQ,OAAO,UAAU;AAAA,MACzB,eAAe,KAAK;AAAA,MACpB;AAAA,IACF;AAAA,IACA,MAAM,QAAQ,MAAM,IAAI,MAAM;AAAA,IAC9B,OAAO;AAAA;AAAA,EAGT,MAAM,UAAU,OACd,QACA,cACkB;AAAA,IAClB,MAAM,UAAU,WAAW,OAAO,QAAQ;AAAA,IAC1C,MAAM,WAAW,MAAM,QAAQ,SAC7B,OAAO,IACP,mBAAmB,OAAO,QAAQ,CACpC;AAAA,IACA,IAAI,iBAAiB,SAAS,MAAM,GAAG;AAAA,MACrC,MAAM,QAAQ,QAAQ,QAAQ;AAAA,MAC9B,UAAU,KAAK,OAAO,EAAE;AAAA,IAC1B,EAAO,SAAI,SAAS,WAAW,OAAO,QAAQ;AAAA,MAC5C,MAAM,QAAQ,MAAM,IAAI,KAAK,QAAQ,QAAQ,SAAS,OAAO,CAAC;AAAA,IAChE;AAAA;AAAA,EAGF,MAAM,OAAO,YAAiC;AAAA,IAC5C,MAAM,UAAU,MAAM,QAAQ,MAAM,KAAK,EAAE,WAAW,MAAM,CAAC;AAAA,IAC7D,MAAM,YAAsB,CAAC;AAAA,IAC7B,MAAM,SAA0C,CAAC;AAAA,IACjD,WAAW,UAAU,SAAS;AAAA,MAG5B,IAAI;AAAA,QAEF,MAAM,QAAQ,QAAQ,SAAS;AAAA,QAC/B,OAAO,OAAO;AAAA,QAKd,IAAI,CAAC,QAAQ,SAAS;AAAA,UACpB,MAAM;AAAA,QACR;AAAA,QACA,QAAQ,QAAQ,QAAQ,KAAK;AAAA,QAC7B,OAAO,KAAK;AAAA,UACV,OAAO,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;AAAA,UAC5D,IAAI,OAAO;AAAA,QACb,CAAC;AAAA;AAAA,IAEL;AAAA,IACA,OAAO,OAAO,SAAS,IACnB,EAAE,SAAS,QAAQ,QAAQ,WAAW,OAAO,IAC7C,EAAE,SAAS,QAAQ,QAAQ,UAAU;AAAA;AAAA,EAG3C,MAAM,uBACJ,CAAC,WACD,OAAO,YAAwC;AAAA,IAC7C,IAAI;AAAA,IACJ,IAAI;AAAA,MACF,WAAW,MAAM,cAAc,SAAS,OAAO,aAAa;AAAA,MAC5D,MAAM;AAAA,MACN,OAAO,IAAI,SAAS,qBAAqB,EAAE,QAAQ,IAAI,CAAC;AAAA;AAAA,IAG1D,MAAM,UAAU,KAAK,MAAM,SAAS,IAAI;AAAA,IAIxC,IAAI,CAAC,QAAQ,MAAM,WAAW,QAAQ,GAAG;AAAA,MACvC,OAAO,IAAI,SAAS,WAAW,EAAE,QAAQ,IAAI,CAAC;AAAA,IAChD;AAAA,IACA,MAAM,KAAK,QAAQ,MAAM;AAAA,IACzB,IAAI,CAAC,IAAI;AAAA,MACP,OAAO,IAAI,SAAS,oBAAoB,EAAE,QAAQ,IAAI,CAAC;AAAA,IACzD;AAAA,IACA,MAAM,SAAS,MAAM,QAAQ,MAAM,IAAI,EAAE;AAAA,IACzC,IAAI,CAAC,UAAU,OAAO,aAAa;AAAA,MACjC,OAAO,IAAI,SAAS,MAAM,EAAE,QAAQ,IAAI,CAAC;AAAA,IAC3C;AAAA,IACA,MAAM,WAAW,MAAM,WAAW,QAAQ,EAAE,SAC1C,IACA,mBAAmB,QAAQ,CAC7B;AAAA,IACA,IAAI,iBAAiB,SAAS,MAAM,GAAG;AAAA,MACrC,MAAM,QAAQ,QAAQ,QAAQ;AAAA,IAChC;AAAA,IACA,OAAO,IAAI,SAAS,MAAM,EAAE,QAAQ,IAAI,CAAC;AAAA;AAAA,EAG7C,OAAO,EAAE,SAAS,sBAAsB,MAAM,MAAM;AAAA;;;ACzX/C,IAAM,oBAAoB,MAAkB;AAAA,EACjD,MAAM,UAAU,IAAI;AAAA,EAEpB,OAAO;AAAA,IACL,QAAQ,CAAC,OAAO;AAAA,MACd,QAAQ,OAAO,EAAE;AAAA,MACjB,OAAO,QAAQ,QAAQ;AAAA;AAAA,IAEzB,KAAK,CAAC,OAAO,QAAQ,QAAQ,QAAQ,IAAI,EAAE,KAAK,IAAI;AAAA,IACpD,MAAM,CAAC,WAAW;AAAA,MAChB,MAAM,MAAM,CAAC,GAAG,QAAQ,OAAO,CAAC;AAAA,MAChC,IAAI,QAAQ,cAAc,WAAW;AAAA,QACnC,OAAO,QAAQ,QAAQ,GAAG;AAAA,MAC5B;AAAA,MACA,QAAQ,cAAc;AAAA,MACtB,OAAO,QAAQ,QACb,IAAI,OAAO,CAAC,WAAY,OAAO,gBAAgB,cAAe,SAAS,CACzE;AAAA;AAAA,IAEF,KAAK,CAAC,WAAW;AAAA,MACf,QAAQ,IAAI,OAAO,IAAI,MAAM;AAAA,MAC7B,OAAO,QAAQ,QAAQ;AAAA;AAAA,EAE3B;AAAA;",
"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": []
}