🚀 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
0.0.0
to
1.0.0
+27
dist/batch.d.ts
import { BatchJob } from "./job";
import type { BatchOptions, BatchRef, BatchResult } from "./types";
/**
* Submit a batch of requests to the model's provider and return a 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.
*
* @example
* const job = await batch({
* model: openai("gpt-5.5"),
* requests: [{ customId: "a", prompt: "Say hi" }],
* });
* const results = await job.wait().then(() => job.collect());
*/
export declare const batch: (options: BatchOptions) => Promise<BatchJob>;
/**
* Rehydrate a {@link BatchJob} for an existing batch id (e.g. one persisted
* after submission). Identify the provider with `provider` or `model`.
*/
export declare const getBatch: (ref: BatchRef) => Promise<BatchJob>;
/** Stream the results of an existing batch by id, without a handle. */
export declare const getBatchResults: (ref: BatchRef) => AsyncGenerator<BatchResult>;
/** Request cancellation of an existing batch by id. */
export declare const cancelBatch: (ref: BatchRef) => Promise<void>;
//# sourceMappingURL=batch.d.ts.map
{"version":3,"file":"batch.d.ts","sourceRoot":"","sources":["../src/batch.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAC;AAGjC,OAAO,KAAK,EACV,YAAY,EAEZ,QAAQ,EACR,WAAW,EAEZ,MAAM,SAAS,CAAC;AAoBjB;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,KAAK,YAAmB,YAAY,KAAG,OAAO,CAAC,QAAQ,CAwBnE,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"}
import type { ResolvedModel } from "./model";
import type { BatchDefaults, BatchRequest, ProviderCredentials } from "./types";
/** A provider request body derived from a single batch item. */
export interface BuiltRequest {
/** The serialized provider request body (becomes the batch line). */
body: Record<string, unknown>;
customId: string;
/** API endpoint path the model targets, e.g. `/v1/chat/completions`. */
endpoint: string;
}
/**
* Derive provider request bodies for every batch item by running each through
* the AI SDK with a capturing `fetch`. This reuses the AI SDK's full message,
* tool, and multimodal conversion, so the body matches what `generateText`
* would send — minus the network call.
*/
export declare const buildRequestBodies: (resolved: ResolvedModel, requests: readonly BatchRequest[], defaults: BatchDefaults | undefined, credentials: ProviderCredentials) => Promise<BuiltRequest[]>;
//# sourceMappingURL=body.d.ts.map
{"version":3,"file":"body.d.ts","sourceRoot":"","sources":["../src/body.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAkB,aAAa,EAAE,MAAM,SAAS,CAAC;AAC7D,OAAO,KAAK,EAAE,aAAa,EAAE,YAAY,EAAE,mBAAmB,EAAE,MAAM,SAAS,CAAC;AAIhF,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;AAuID;;;;;GAKG;AACH,eAAO,MAAM,kBAAkB,aACnB,aAAa,YACb,SAAS,YAAY,EAAE,YACvB,aAAa,GAAG,SAAS,eACtB,mBAAmB,KAC/B,OAAO,CAAC,YAAY,EAAE,CAsBxB,CAAC"}
import {
BatchJob,
BatchworkError,
MissingDependencyError,
UnsupportedProviderError,
getAdapter
} from "./chunk-kv3847wy.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);
}
}
};
// src/body.ts
import { 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 captureOne = async (model, request, customId) => {
try {
await generateText(toGenerateInput(model, request));
} catch (error) {
const capture = findCapture(error);
if (capture) {
return {
body: JSON.parse(capture.rawBody),
customId,
endpoint: endpointFromUrl(capture.url)
};
}
throw error;
}
throw new BatchworkError("batchwork: the request was not intercepted while building the batch body.");
};
var buildRequestBodies = async (resolved, requests, defaults, credentials) => {
const model = await createCaptureModel(resolved, credentials, captureFetch);
const seen = new Set;
const items = 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 };
});
return await Promise.all(items.map((item) => captureOne(model, mergeDefaults(item.request, defaults), item.customId)));
};
// 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 adapter = getAdapter(resolved.provider);
const built = await buildRequestBodies(resolved, options.requests, options.defaults, credentials);
const snapshot = await adapter.submit({
built,
credentials,
endpoint: built[0]?.endpoint ?? "",
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, getBatch, getBatchResults, cancelBatch };
//# debugId=A3005BE7078C61CE64756E2164756E21
//# sourceMappingURL=chunk-ab2d71gk.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 { 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 = (model: LanguageModel): 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",
"import { generateText } from \"ai\";\nimport type { LanguageModel } from \"ai\";\n\nimport { BatchworkError } from \"./errors\";\nimport { createCaptureModel } from \"./model\";\nimport type { CapturingFetch, ResolvedModel } from \"./model\";\nimport type { BatchDefaults, BatchRequest, ProviderCredentials } 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\nconst captureOne = async (\n model: LanguageModel,\n request: BatchRequest,\n customId: string\n): Promise<BuiltRequest> => {\n try {\n await generateText(toGenerateInput(model, request));\n } catch (error) {\n const capture = findCapture(error);\n if (capture) {\n return {\n body: JSON.parse(capture.rawBody) as Record<string, unknown>,\n customId,\n endpoint: endpointFromUrl(capture.url),\n };\n }\n // A genuine failure (e.g. invalid prompt) — surface it to the caller.\n throw error;\n }\n throw new BatchworkError(\n \"batchwork: the request was not intercepted while building the batch body.\"\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): Promise<BuiltRequest[]> => {\n const model = await createCaptureModel(resolved, credentials, captureFetch);\n const seen = new Set<string>();\n\n // Assign and validate customIds up front (sequentially, so duplicates are\n // reported deterministically) before capturing bodies in parallel.\n const items = 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 return await Promise.all(\n items.map((item) =>\n captureOne(model, mergeDefaults(item.request, defaults), item.customId)\n )\n );\n};\n",
"import { buildRequestBodies } from \"./body\";\nimport { BatchworkError } from \"./errors\";\nimport { BatchJob } from \"./job\";\nimport { resolveModel } from \"./model\";\nimport { getAdapter } from \"./providers\";\nimport type {\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 adapter = getAdapter(resolved.provider);\n\n const built = await buildRequestBodies(\n resolved,\n options.requests,\n options.defaults,\n credentials\n );\n const snapshot = await adapter.submit({\n built,\n credentials,\n endpoint: built[0]?.endpoint ?? \"\",\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": "A3005BE7078C61CE64756E2164756E21",
"names": []
}
// src/errors.ts
class BatchworkError extends Error {
constructor(message, options) {
super(message, options);
this.name = "BatchworkError";
}
}
class UnsupportedProviderError extends BatchworkError {
provider;
constructor(provider) {
super(`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;
}
const timer = setTimeout(resolve, ms);
signal?.addEventListener("abort", () => {
clearTimeout(timer);
reject(new BatchworkError("batchwork: wait aborted."));
}, { 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/http.ts
var safeText = async (response) => {
try {
return await response.text();
} catch {
return "<no body>";
}
};
var assertOk = async (url, init, response) => {
if (!response.ok) {
const detail = await safeText(response);
throw new BatchworkError(`batchwork: ${init.method ?? "GET"} ${url} failed with ${response.status}: ${detail}`);
}
};
var requestJson = async (url, init) => {
const response = await fetch(url, init);
await assertOk(url, init, response);
return await response.json();
};
var requestStream = async (url, init) => {
const response = await fetch(url, init);
await 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 encodeJsonl = (items) => {
if (items.length === 0) {
return "";
}
const body = items.map((item) => JSON.stringify(item)).join(NEWLINE);
return `${body}${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) {
const decoder = new TextDecoder;
let buffer = "";
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).trim();
buffer = buffer.slice(newlineIndex + 1);
if (line.length > 0) {
yield JSON.parse(line);
}
newlineIndex = buffer.indexOf(NEWLINE);
}
}
buffer += decoder.decode();
const tail = buffer.trim();
if (tail.length > 0) {
yield JSON.parse(tail);
}
}
// 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 omit = (obj, key) => {
const result = {};
for (const [k, v] of Object.entries(obj)) {
if (k !== key) {
result[k] = v;
}
}
return result;
};
var toDate = (value) => {
if (typeof value === "string") {
return new Date(value);
}
if (typeof value === "number") {
return new Date(value * 1000);
}
};
// 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 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 requests = input.built.map((item) => ({
custom_id: item.customId,
params: omit(item.body, "stream")
}));
const raw = await requestJson(`${baseUrl(input.credentials)}/v1/messages/batches`, {
body: JSON.stringify({ requests }),
headers: headers(input.credentials),
method: "POST"
});
return normalizeSnapshot(raw);
};
var retrieve = async (id, credentials) => {
const raw = await requestJson(`${baseUrl(credentials)}/v1/messages/batches/${id}`, { 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(resultsUrl, {
headers: headers(credentials)
});
for await (const line of streamJsonl(stream)) {
yield normalizeResult(line);
}
}
var cancel = async (id, credentials) => {
await requestJson(`${baseUrl(credentials)}/v1/messages/batches/${id}/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 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;
return {
id: asString(obj.name) ?? "",
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 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,
response: obj.response,
status: "succeeded",
text: textFromResponse(obj.response),
usage: usageFromResponse(obj.response)
};
};
var submit2 = async (input) => {
const requests = input.built.map((item) => ({
metadata: { key: item.customId },
request: omit(item.body, "stream")
}));
const raw = await requestJson(`${baseUrl2(input.credentials)}/models/${input.modelId}:batchGenerateContent`, {
body: JSON.stringify({
batch: {
display_name: "batchwork",
input_config: { requests: { requests } }
}
}),
headers: headers2(input.credentials),
method: "POST"
});
return normalizeSnapshot2(raw);
};
var retrieve2 = async (id, credentials) => {
const raw = await requestJson(`${baseUrl2(credentials)}/${id}`, {
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) => {
await requestJson(`${baseUrl2(credentials)}/${id}: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 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,
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"
});
return raw.id;
};
async function* streamResultFile(fileId, baseUrl3, headers3) {
const stream = await requestStream(`${baseUrl3}/files/${fileId}/content`, {
headers: headers3
});
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 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
};
}));
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 raw = await requestJson(`${baseUrl3(credentials)}/batches/${id}`, {
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(outputFileId, baseUrl3(credentials), headers3);
}
if (errorFileId) {
yield* streamResultFile(errorFileId, baseUrl3(credentials), headers3);
}
}
const cancel3 = async (id, credentials) => {
await requestJson(`${baseUrl3(credentials)}/batches/${id}/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;
return {
completedAt: toDate(obj.completed_at),
createdAt: toDate(obj.created_at),
id: asString(obj.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 jsonl = encodeJsonl(input.built.map((item) => ({
body: omit(omit(item.body, "stream"), "model"),
custom_id: item.customId
})));
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 raw = await requestJson(`${baseUrl3(credentials)}/batch/jobs/${id}`, {
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(outputFileId, baseUrl3(credentials), headers3);
}
if (errorFileId) {
yield* streamResultFile(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) => {
await requestJson(`${baseUrl3(credentials)}/batch/jobs/${id}/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 safeText2 = async (response) => {
try {
return await response.text();
} catch {
return "<no body>";
}
};
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}): ${await safeText2(init)}`);
}
const upload = await fetch(location, { body: args.jsonl, method: "PUT" });
if (!upload.ok) {
throw new BatchworkError(`batchwork: Together file upload failed (${upload.status}): ${await safeText2(upload)}`);
}
await requestJson(`${args.baseUrl}/files/${fileId}/preprocess`, {
headers: args.headers,
method: "POST"
});
return fileId;
};
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);
return {
completedAt: toDate(obj.cancel_time),
createdAt: toDate(obj.create_time),
expiresAt: toDate(obj.expire_time ?? obj.expires_at),
id: asString(obj.batch_id) ?? asString(obj.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 jsonl = encodeJsonl(input.built.map((item) => ({
body: omit(item.body, "stream"),
custom_id: item.customId,
method: "POST",
url: input.endpoint
})));
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 raw = await requestJson(`${baseUrl4(credentials)}/batches/${id}`, {
headers: authHeaders2(credentials)
});
return normalizeSnapshot5(raw);
};
async function* results4(id, credentials) {
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/${id}/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) => {
await requestJson(`${baseUrl4(credentials)}/batches/${id}: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, isTerminalStatus, BatchJob, getAdapter };
//# debugId=DA60AE45A8F12B3C64756E2164756E21
//# sourceMappingURL=chunk-kv3847wy.js.map

Sorry, the diff of this file is too big to display

import { createRequire } from "node:module";
var __require = /* @__PURE__ */ createRequire(import.meta.url);
export { __require };
//# debugId=5F0D92CF7AD4DB9164756E2164756E21
//# sourceMappingURL=chunk-v0bahtg2.js.map
{
"version": 3,
"sources": [],
"sourcesContent": [
],
"mappings": "",
"debugId": "5F0D92CF7AD4DB9164756E2164756E21",
"names": []
}
import {
BatchworkError,
getAdapter,
isTerminalStatus
} from "./chunk-kv3847wy.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 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 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) => {
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);
if (!Number.isFinite(seconds) || Math.abs(Date.now() / 1000 - 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.");
}
return { body, id, timestamp: seconds };
};
var verifyBatchWebhook = async (request, secret) => {
const { body } = await verifyWebhook(request, secret);
return JSON.parse(body);
};
// src/server/poller.ts
var sendWebhook = async (record, snapshot) => {
if (!record.webhookUrl) {
throw new BatchworkError("batchwork: tracked batch has no webhookUrl to deliver to.");
}
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(record.webhookUrl, {
body,
headers,
method: "POST"
});
if (!response.ok) {
throw new BatchworkError(`batchwork: webhook delivery to ${record.webhookUrl} failed (${response.status}).`);
}
};
var createBatchPoller = (options) => {
const resolveCredentials = (provider) => {
if (typeof options.credentials === "function") {
return options.credentials(provider);
}
return options.credentials ?? {};
};
const sink = options.onComplete ?? sendWebhook;
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 record = {
createdAt: new Date().toISOString(),
id: target.id,
provider: target.provider,
status: target.status ?? "in_progress",
webhookSecret: opts.secret,
webhookUrl: opts.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=344750DFEF66E25964756E2164756E21
//# sourceMappingURL=chunk-zp2cxkyb.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\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\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): 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 if (\n !Number.isFinite(seconds) ||\n Math.abs(Date.now() / 1000 - 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 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): Promise<BatchWebhookEvent> => {\n const { body } = await verifyWebhook(request, secret);\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 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 /**\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\n/** The default completion sink: POST a signed webhook to the tracked URL. */\nconst sendWebhook: CompletionSink = 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 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(record.webhookUrl, {\n body,\n headers,\n method: \"POST\",\n });\n if (!response.ok) {\n throw new BatchworkError(\n `batchwork: webhook delivery to ${record.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 sink = options.onComplete ?? sendWebhook;\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 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: opts.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": "344750DFEF66E25964756E2164756E21",
"names": []
}
/** Base error for all batchwork failures. */
export declare class BatchworkError extends Error {
constructor(message: string, options?: {
cause?: unknown;
});
}
/** Thrown when a model resolves to a provider without a batch adapter. */
export declare class UnsupportedProviderError extends BatchworkError {
readonly provider: string;
constructor(provider: string);
}
/** Thrown when an optional provider package is not installed. */
export declare class MissingDependencyError extends BatchworkError {
constructor(pkg: string, provider: string);
}
//# sourceMappingURL=errors.d.ts.map
{"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAEA,6CAA6C;AAC7C,qBAAa,cAAe,SAAQ,KAAK;IACvC,YAAY,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE;QAAE,KAAK,CAAC,EAAE,OAAO,CAAA;KAAE,EAGzD;CACF;AAED,0EAA0E;AAC1E,qBAAa,wBAAyB,SAAQ,cAAc;IAC1D,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAE1B,YAAY,QAAQ,EAAE,MAAM,EAM3B;CACF;AAED,iEAAiE;AACjE,qBAAa,sBAAuB,SAAQ,cAAc;IACxD,YAAY,GAAG,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAKxC;CACF"}
/** Make a request and parse a JSON response, throwing on non-2xx. */
export declare const requestJson: <T>(url: string, init: RequestInit) => Promise<T>;
/** Make a request and return the raw body stream, throwing on non-2xx. */
export declare const requestStream: (url: string, init: RequestInit) => Promise<ReadableStream<Uint8Array>>;
//# sourceMappingURL=http.d.ts.map
{"version":3,"file":"http.d.ts","sourceRoot":"","sources":["../src/http.ts"],"names":[],"mappings":"AAmBA,qEAAqE;AACrE,eAAO,MAAM,WAAW,GAAU,CAAC,OAC5B,MAAM,QACL,WAAW,KAChB,OAAO,CAAC,CAAC,CAIX,CAAC;AAEF,0EAA0E;AAC1E,eAAO,MAAM,aAAa,QACnB,MAAM,QACL,WAAW,KAChB,OAAO,CAAC,cAAc,CAAC,UAAU,CAAC,CAOpC,CAAC"}
export { batch, cancelBatch, getBatch, getBatchResults } from "./batch";
export { BatchworkError, MissingDependencyError, UnsupportedProviderError, } from "./errors";
export { BatchJob, isTerminalStatus } from "./job";
export { resolveModel } from "./model";
export type { BatchDefaults, BatchOptions, BatchProvider, BatchRef, BatchRequest, BatchRequestCounts, BatchRequestSettings, BatchResult, BatchResultError, BatchResultStatus, BatchSnapshot, BatchStatus, BatchUsage, ProviderCredentials, ProviderOptions, WaitOptions, } from "./types";
//# sourceMappingURL=index.d.ts.map
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,WAAW,EAAE,QAAQ,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC;AACxE,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,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,
cancelBatch,
getBatch,
getBatchResults,
resolveModel
} from "./chunk-ab2d71gk.js";
import {
BatchJob,
BatchworkError,
MissingDependencyError,
UnsupportedProviderError,
isTerminalStatus
} from "./chunk-kv3847wy.js";
import"./chunk-v0bahtg2.js";
export {
resolveModel,
isTerminalStatus,
getBatchResults,
getBatch,
cancelBatch,
batch,
UnsupportedProviderError,
MissingDependencyError,
BatchworkError,
BatchJob
};
//# debugId=503CE7D732E9F8F664756E2164756E21
//# sourceMappingURL=index.js.map
{
"version": 3,
"sources": [],
"sourcesContent": [
],
"mappings": "",
"debugId": "503CE7D732E9F8F664756E2164756E21",
"names": []
}
import type { BatchAdapter } from "./providers/adapter";
import type { BatchProvider, BatchRequestCounts, BatchResult, BatchSnapshot, BatchStatus, ProviderCredentials, WaitOptions } from "./types";
/** Whether a status means the batch has finished processing. */
export declare const isTerminalStatus: (status: BatchStatus) => boolean;
/**
* A handle to a submitted batch. Returned by `batch()` and `getBatch()`. Use it
* to poll status, wait for completion, stream results, or cancel.
*/
export declare class BatchJob {
#private;
readonly provider: BatchProvider;
readonly id: string;
constructor(adapter: BatchAdapter, credentials: ProviderCredentials, snapshot: BatchSnapshot);
/** The most recently observed status. */
get status(): BatchStatus;
/** The most recently observed per-request tallies. */
get requestCounts(): BatchRequestCounts;
/** The most recently observed snapshot. */
get snapshot(): BatchSnapshot;
/** Refresh the status from the provider and return the new snapshot. */
poll(): Promise<BatchSnapshot>;
/** Poll until the batch reaches a terminal status, then return the snapshot. */
wait(options?: WaitOptions): Promise<BatchSnapshot>;
/** Stream normalized results as they are read. Order is not guaranteed. */
results(): AsyncGenerator<BatchResult>;
/** Collect all results into an array, keyed by `customId`. */
collect(): Promise<BatchResult[]>;
/** Request cancellation, then refresh status. */
cancel(): Promise<BatchSnapshot>;
}
//# sourceMappingURL=job.d.ts.map
{"version":3,"file":"job.d.ts","sourceRoot":"","sources":["../src/job.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AACxD,OAAO,KAAK,EACV,aAAa,EACb,kBAAkB,EAClB,WAAW,EACX,aAAa,EACb,WAAW,EACX,mBAAmB,EACnB,WAAW,EACZ,MAAM,SAAS,CAAC;AAWjB,gEAAgE;AAChE,eAAO,MAAM,gBAAgB,WAAY,WAAW,KAAG,OACxB,CAAC;AAoBhC;;;GAGG;AACH,qBAAa,QAAQ;;IACnB,QAAQ,CAAC,QAAQ,EAAE,aAAa,CAAC;IACjC,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IAMpB,YACE,OAAO,EAAE,YAAY,EACrB,WAAW,EAAE,mBAAmB,EAChC,QAAQ,EAAE,aAAa,EAOxB;IAED,yCAAyC;IACzC,IAAI,MAAM,IAAI,WAAW,CAExB;IAED,sDAAsD;IACtD,IAAI,aAAa,IAAI,kBAAkB,CAEtC;IAED,2CAA2C;IAC3C,IAAI,QAAQ,IAAI,aAAa,CAE5B;IAED,wEAAwE;IAClE,IAAI,IAAI,OAAO,CAAC,aAAa,CAAC,CAGnC;IAED,gFAAgF;IAC1E,IAAI,CAAC,OAAO,GAAE,WAAgB,GAAG,OAAO,CAAC,aAAa,CAAC,CA0B5D;IAED,2EAA2E;IAC3E,OAAO,IAAI,cAAc,CAAC,WAAW,CAAC,CAErC;IAED,8DAA8D;IACxD,OAAO,IAAI,OAAO,CAAC,WAAW,EAAE,CAAC,CAMtC;IAED,iDAAiD;IAC3C,MAAM,IAAI,OAAO,CAAC,aAAa,CAAC,CAGrC;CACF"}
/**
* JSONL (newline-delimited JSON) helpers. OpenAI batch input/output and
* Anthropic batch results are all JSONL, so batchwork builds and parses it for
* the user.
*/
/** Serialize an array of values to a JSONL string (trailing newline included). */
export declare const encodeJsonl: (items: readonly unknown[]) => string;
/** Parse a complete JSONL string into an array, skipping blank lines. */
export declare const parseJsonl: <T = unknown>(text: string) => T[];
/**
* Stream-parse JSONL from a byte stream, yielding one parsed value per line as
* it arrives. Memory-efficient for large result files.
*
* @yields {T} the parsed value for each non-empty line.
*/
export declare function streamJsonl<T = unknown>(source: ReadableStream<Uint8Array> | AsyncIterable<Uint8Array>): AsyncGenerator<T>;
//# sourceMappingURL=jsonl.d.ts.map
{"version":3,"file":"jsonl.d.ts","sourceRoot":"","sources":["../src/jsonl.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAIH,kFAAkF;AAClF,eAAO,MAAM,WAAW,UAAW,SAAS,OAAO,EAAE,KAAG,MAMvD,CAAC;AAEF,yEAAyE;AACzE,eAAO,MAAM,UAAU,GAAI,CAAC,GAAG,OAAO,QAAQ,MAAM,KAAG,CAAC,EASvD,CAAC;AAkCF;;;;;GAKG;AAEH,wBAAuB,WAAW,CAAC,CAAC,GAAG,OAAO,EAC5C,MAAM,EAAE,cAAc,CAAC,UAAU,CAAC,GAAG,aAAa,CAAC,UAAU,CAAC,GAC7D,cAAc,CAAC,CAAC,CAAC,CAsBnB"}
import type { LanguageModel } from "ai";
import type { BatchProvider, ProviderCredentials } from "./types";
/** A fetch implementation compatible with the AI SDK provider `fetch` option. */
export type CapturingFetch = typeof globalThis.fetch;
/** OpenAI exposes several request shapes; we mirror the one the model implies. */
export type OpenAIModelKind = "chat" | "responses" | "completion";
export interface ResolvedModel {
/** Relevant for OpenAI; other providers always use a single chat endpoint. */
kind: OpenAIModelKind;
modelId: string;
provider: BatchProvider;
}
/**
* Resolve any AI SDK `model` (a `"provider/model"` string or a provider model
* object such as `openai("gpt-5.5")`) to a provider + model id + request
* shape. Gateway/registry model objects whose `modelId` is itself
* `"provider/model"` are also handled.
*/
export declare const resolveModel: (model: LanguageModel) => ResolvedModel;
/**
* Import the `@ai-sdk/*` package for a provider, translating a missing optional
* dependency into a `MissingDependencyError`. The importer is injectable (like
* the capturing `fetch`) so tests can drive the failure paths without
* uninstalling a package. Exported for testing; not part of the public API.
*/
export declare const loadProvider: <T>(provider: BatchProvider, load?: (target: BatchProvider) => Promise<unknown>) => Promise<T>;
/**
* Construct an AI SDK model wired to a capturing `fetch`, used to derive the
* provider request body for each batch item without making a network call.
*/
export declare const createCaptureModel: (resolved: ResolvedModel, credentials: ProviderCredentials, fetchImpl: CapturingFetch) => Promise<LanguageModel>;
//# sourceMappingURL=model.d.ts.map
{"version":3,"file":"model.d.ts","sourceRoot":"","sources":["../src/model.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,IAAI,CAAC;AAGxC,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,UAAW,aAAa,KAAG,aAkBnD,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"}
import type { CredentialResolver, TrackTarget } from "../server/poller";
import { createMemoryStore } from "../server/store";
import type { BatchStore, BatchWebhookEvent, TrackedBatch } from "../server/types";
import type { BatchResult } from "../types";
export { createMemoryStore };
export type { BatchStore, BatchWebhookEvent, BatchResult, TrackedBatch, TrackTarget, };
/**
* Invoked once per batch when it reaches a terminal status. Persist the results
* to your database here. `results` streams the parsed result lines for a
* completed batch and is empty for failure events (`batch.failed` /
* `batch.expired` / `batch.cancelled`) — inspect `event.type`.
*
* May fire more than once for the same batch (a cron tick racing the OpenAI
* native webhook, or a retry after a partial save), so make persistence
* idempotent — upsert keyed by `(provider, batchId, customId)`.
*/
export type OnBatchComplete = (event: BatchWebhookEvent, results: AsyncIterable<BatchResult>) => void | Promise<void>;
export interface BatchRoutesOptions {
store: BatchStore;
/** Called when each batch finishes; persist results here. */
onComplete: OnBatchComplete;
/** Falls back to provider env vars (e.g. `OPENAI_API_KEY`) when omitted. */
credentials?: CredentialResolver;
/** When set, the cron `GET` requires `Authorization: Bearer <cronSecret>`. */
cronSecret?: string;
/** When set, mounts an OpenAI native-webhook handler on `POST`. */
openaiSigningSecret?: string;
/** Observe per-batch processing errors during a tick; the tick continues. */
onError?: (record: TrackedBatch, error: unknown) => void;
}
export interface BatchRoutes {
/** Cron-triggered poll tick. Wire to Vercel Cron (or any scheduler). */
GET: (request: Request) => Promise<Response>;
/** OpenAI native-webhook handler. Present only with `openaiSigningSecret`. */
POST?: (request: Request) => Promise<Response>;
/** Register a submitted batch so the cron polls it (a `BatchJob` works). */
track: (target: TrackTarget) => Promise<TrackedBatch>;
}
/**
* Build Next.js App Router route handlers that poll your in-flight batches on a
* cron tick and invoke `onComplete` directly when each finishes — persist
* results to your DB without round-tripping an HTTP webhook back to your app.
*
* @example
* import { createBatchRoutes, createMemoryStore } from "batchwork/next";
*
* export const { GET, POST } = createBatchRoutes({
* store: createMemoryStore(),
* cronSecret: process.env.CRON_SECRET,
* openaiSigningSecret: process.env.OPENAI_WEBHOOK_SECRET,
* onComplete: async (event, results) => {
* for await (const r of results) {
* await db.insert({ id: r.customId, status: r.status, text: r.text });
* }
* },
* });
*/
export declare const createBatchRoutes: (options: BatchRoutesOptions) => BatchRoutes;
//# sourceMappingURL=index.d.ts.map
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/next/index.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAEV,kBAAkB,EAClB,WAAW,EACZ,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAC;AACpD,OAAO,KAAK,EACV,UAAU,EACV,iBAAiB,EACjB,YAAY,EACb,MAAM,iBAAiB,CAAC;AACzB,OAAO,KAAK,EAAiB,WAAW,EAAuB,MAAM,UAAU,CAAC;AAEhF,OAAO,EAAE,iBAAiB,EAAE,CAAC;AAC7B,YAAY,EACV,UAAU,EACV,iBAAiB,EACjB,WAAW,EACX,YAAY,EACZ,WAAW,GACZ,CAAC;AAEF;;;;;;;;;GASG;AACH,MAAM,MAAM,eAAe,GAAG,CAC5B,KAAK,EAAE,iBAAiB,EACxB,OAAO,EAAE,aAAa,CAAC,WAAW,CAAC,KAChC,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;AAE1B,MAAM,WAAW,kBAAkB;IACjC,KAAK,EAAE,UAAU,CAAC;IAClB,6DAA6D;IAC7D,UAAU,EAAE,eAAe,CAAC;IAC5B,4EAA4E;IAC5E,WAAW,CAAC,EAAE,kBAAkB,CAAC;IACjC,8EAA8E;IAC9E,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,mEAAmE;IACnE,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,6EAA6E;IAC7E,OAAO,CAAC,EAAE,CAAC,MAAM,EAAE,YAAY,EAAE,KAAK,EAAE,OAAO,KAAK,IAAI,CAAC;CAC1D;AAED,MAAM,WAAW,WAAW;IAC1B,wEAAwE;IACxE,GAAG,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,OAAO,CAAC,QAAQ,CAAC,CAAC;IAC7C,8EAA8E;IAC9E,IAAI,CAAC,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,OAAO,CAAC,QAAQ,CAAC,CAAC;IAC/C,4EAA4E;IAC5E,KAAK,EAAE,CAAC,MAAM,EAAE,WAAW,KAAK,OAAO,CAAC,YAAY,CAAC,CAAC;CACvD;AASD;;;;;;;;;;;;;;;;;;GAkBG;AACH,eAAO,MAAM,iBAAiB,YAAa,kBAAkB,KAAG,WAoD/D,CAAC"}
import {
getBatchResults
} from "../chunk-ab2d71gk.js";
import {
createBatchPoller,
createMemoryStore,
toEvent
} from "../chunk-zp2cxkyb.js";
import"../chunk-kv3847wy.js";
import"../chunk-v0bahtg2.js";
// src/next/index.ts
var EMPTY_RESULTS = {
[Symbol.asyncIterator]: () => ({
next: () => Promise.resolve({ done: true, value: undefined })
})
};
var createBatchRoutes = (options) => {
const resolveCredentials = (provider) => {
if (typeof options.credentials === "function") {
return options.credentials(provider);
}
return options.credentials ?? {};
};
const sink = async (record, snapshot) => {
const event = toEvent(record.provider, snapshot);
const results = event.type === "batch.completed" ? getBatchResults({
id: record.id,
provider: record.provider,
...resolveCredentials(record.provider)
}) : EMPTY_RESULTS;
await options.onComplete(event, results);
};
const poller = createBatchPoller({
credentials: options.credentials,
onComplete: sink,
onError: (record, error) => options.onError?.(record, error),
store: options.store
});
const GET = (request) => {
if (options.cronSecret && request.headers.get("authorization") !== `Bearer ${options.cronSecret}`) {
return Promise.resolve(new Response("unauthorized", { status: 401 }));
}
return poller.tick().then((result) => Response.json(result));
};
const track = (target) => poller.track(target, {});
const routes = { GET, track };
if (options.openaiSigningSecret) {
routes.POST = poller.openaiWebhookHandler({
signingSecret: options.openaiSigningSecret
});
}
return routes;
};
export {
createMemoryStore,
createBatchRoutes
};
//# debugId=2EB8C739158F3A9164756E2164756E21
//# sourceMappingURL=index.js.map
{
"version": 3,
"sources": ["../src/next/index.ts"],
"sourcesContent": [
"import { getBatchResults } from \"../batch\";\nimport { toEvent } from \"../server/events\";\nimport { createBatchPoller } from \"../server/poller\";\nimport type {\n CompletionSink,\n CredentialResolver,\n TrackTarget,\n} from \"../server/poller\";\nimport { createMemoryStore } from \"../server/store\";\nimport type {\n BatchStore,\n BatchWebhookEvent,\n TrackedBatch,\n} from \"../server/types\";\nimport type { BatchProvider, BatchResult, ProviderCredentials } from \"../types\";\n\nexport { createMemoryStore };\nexport type {\n BatchStore,\n BatchWebhookEvent,\n BatchResult,\n TrackedBatch,\n TrackTarget,\n};\n\n/**\n * Invoked once per batch when it reaches a terminal status. Persist the results\n * to your database here. `results` streams the parsed result lines for a\n * completed batch and is empty for failure events (`batch.failed` /\n * `batch.expired` / `batch.cancelled`) — inspect `event.type`.\n *\n * May fire more than once for the same batch (a cron tick racing the OpenAI\n * native webhook, or a retry after a partial save), so make persistence\n * idempotent — upsert keyed by `(provider, batchId, customId)`.\n */\nexport type OnBatchComplete = (\n event: BatchWebhookEvent,\n results: AsyncIterable<BatchResult>\n) => void | Promise<void>;\n\nexport interface BatchRoutesOptions {\n store: BatchStore;\n /** Called when each batch finishes; persist results here. */\n onComplete: OnBatchComplete;\n /** Falls back to provider env vars (e.g. `OPENAI_API_KEY`) when omitted. */\n credentials?: CredentialResolver;\n /** When set, the cron `GET` requires `Authorization: Bearer <cronSecret>`. */\n cronSecret?: string;\n /** When set, mounts an OpenAI native-webhook handler on `POST`. */\n openaiSigningSecret?: string;\n /** Observe per-batch processing errors during a tick; the tick continues. */\n onError?: (record: TrackedBatch, error: unknown) => void;\n}\n\nexport interface BatchRoutes {\n /** Cron-triggered poll tick. Wire to Vercel Cron (or any scheduler). */\n GET: (request: Request) => Promise<Response>;\n /** OpenAI native-webhook handler. Present only with `openaiSigningSecret`. */\n POST?: (request: Request) => Promise<Response>;\n /** Register a submitted batch so the cron polls it (a `BatchJob` works). */\n track: (target: TrackTarget) => Promise<TrackedBatch>;\n}\n\n/** An already-exhausted async iterable, for non-completed events. */\nconst EMPTY_RESULTS: AsyncIterable<BatchResult> = {\n [Symbol.asyncIterator]: () => ({\n next: () => Promise.resolve({ done: true, value: undefined }),\n }),\n};\n\n/**\n * Build Next.js App Router route handlers that poll your in-flight batches on a\n * cron tick and invoke `onComplete` directly when each finishes — persist\n * results to your DB without round-tripping an HTTP webhook back to your app.\n *\n * @example\n * import { createBatchRoutes, createMemoryStore } from \"batchwork/next\";\n *\n * export const { GET, POST } = createBatchRoutes({\n * store: createMemoryStore(),\n * cronSecret: process.env.CRON_SECRET,\n * openaiSigningSecret: process.env.OPENAI_WEBHOOK_SECRET,\n * onComplete: async (event, results) => {\n * for await (const r of results) {\n * await db.insert({ id: r.customId, status: r.status, text: r.text });\n * }\n * },\n * });\n */\nexport const createBatchRoutes = (options: BatchRoutesOptions): BatchRoutes => {\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 sink: CompletionSink = async (record, snapshot) => {\n const event = toEvent(record.provider, snapshot);\n // Only completed batches have results to fetch — the adapter throws when a\n // terminal batch has no output/error file (failed/expired/cancelled).\n const results =\n event.type === \"batch.completed\"\n ? getBatchResults({\n id: record.id,\n provider: record.provider,\n ...resolveCredentials(record.provider),\n })\n : EMPTY_RESULTS;\n await options.onComplete(event, results);\n };\n\n const poller = createBatchPoller({\n credentials: options.credentials,\n onComplete: sink,\n // Always supply an `onError` so one failing batch can't abort the whole\n // tick; forward to the caller's handler when they provided one.\n onError: (record, error) => options.onError?.(record, error),\n store: options.store,\n });\n\n const GET = (request: Request): Promise<Response> => {\n if (\n options.cronSecret &&\n request.headers.get(\"authorization\") !== `Bearer ${options.cronSecret}`\n ) {\n return Promise.resolve(new Response(\"unauthorized\", { status: 401 }));\n }\n return poller.tick().then((result) => Response.json(result));\n };\n\n const track = (target: TrackTarget): Promise<TrackedBatch> =>\n poller.track(target, {});\n\n const routes: BatchRoutes = { GET, track };\n if (options.openaiSigningSecret) {\n routes.POST = poller.openaiWebhookHandler({\n signingSecret: options.openaiSigningSecret,\n });\n }\n return routes;\n};\n"
],
"mappings": ";;;;;;;;;;;;AAgEA,IAAM,gBAA4C;AAAA,GAC/C,OAAO,gBAAgB,OAAO;AAAA,IAC7B,MAAM,MAAM,QAAQ,QAAQ,EAAE,MAAM,MAAM,OAAO,UAAU,CAAC;AAAA,EAC9D;AACF;AAqBO,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,OAAuB,OAAO,QAAQ,aAAa;AAAA,IACvD,MAAM,QAAQ,QAAQ,OAAO,UAAU,QAAQ;AAAA,IAG/C,MAAM,UACJ,MAAM,SAAS,oBACX,gBAAgB;AAAA,MACd,IAAI,OAAO;AAAA,MACX,UAAU,OAAO;AAAA,SACd,mBAAmB,OAAO,QAAQ;AAAA,IACvC,CAAC,IACD;AAAA,IACN,MAAM,QAAQ,WAAW,OAAO,OAAO;AAAA;AAAA,EAGzC,MAAM,SAAS,kBAAkB;AAAA,IAC/B,aAAa,QAAQ;AAAA,IACrB,YAAY;AAAA,IAGZ,SAAS,CAAC,QAAQ,UAAU,QAAQ,UAAU,QAAQ,KAAK;AAAA,IAC3D,OAAO,QAAQ;AAAA,EACjB,CAAC;AAAA,EAED,MAAM,MAAM,CAAC,YAAwC;AAAA,IACnD,IACE,QAAQ,cACR,QAAQ,QAAQ,IAAI,eAAe,MAAM,UAAU,QAAQ,cAC3D;AAAA,MACA,OAAO,QAAQ,QAAQ,IAAI,SAAS,gBAAgB,EAAE,QAAQ,IAAI,CAAC,CAAC;AAAA,IACtE;AAAA,IACA,OAAO,OAAO,KAAK,EAAE,KAAK,CAAC,WAAW,SAAS,KAAK,MAAM,CAAC;AAAA;AAAA,EAG7D,MAAM,QAAQ,CAAC,WACb,OAAO,MAAM,QAAQ,CAAC,CAAC;AAAA,EAEzB,MAAM,SAAsB,EAAE,KAAK,MAAM;AAAA,EACzC,IAAI,QAAQ,qBAAqB;AAAA,IAC/B,OAAO,OAAO,OAAO,qBAAqB;AAAA,MACxC,eAAe,QAAQ;AAAA,IACzB,CAAC;AAAA,EACH;AAAA,EACA,OAAO;AAAA;",
"debugId": "2EB8C739158F3A9164756E2164756E21",
"names": []
}
import type { BatchStore } from "../server/types";
import type { PostgresStoreOptions } from "./types";
/** Options for {@link createPostgresStore}. */
export interface PostgresBatchStoreOptions extends PostgresStoreOptions {
/** Table to store tracked batches in. Defaults to `batchwork_batches`. */
table?: string;
}
/** `CREATE TABLE`/index DDL for the tracked-batch table (run by `migratePostgres`). */
export declare const batchTableDdl: (table?: string) => string;
/**
* A Postgres-backed {@link BatchStore} for the poller. Run {@link migratePostgres}
* (or {@link batchTableDdl}) once to create the table.
*
* @example
* import { Pool } from "pg";
* const store = createPostgresStore({ client: new Pool({ connectionString }) });
*/
export declare const createPostgresStore: (options: PostgresBatchStoreOptions) => BatchStore;
//# sourceMappingURL=batch-store.d.ts.map
{"version":3,"file":"batch-store.d.ts","sourceRoot":"","sources":["../../src/postgres/batch-store.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAgB,MAAM,iBAAiB,CAAC;AAGhE,OAAO,KAAK,EAAE,oBAAoB,EAAe,MAAM,SAAS,CAAC;AAEjE,+CAA+C;AAC/C,MAAM,WAAW,yBAA0B,SAAQ,oBAAoB;IACrE,0EAA0E;IAC1E,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAyBD,uFAAuF;AACvF,eAAO,MAAM,aAAa,sBAAkC,MAc3D,CAAC;AAEF;;;;;;;GAOG;AACH,eAAO,MAAM,mBAAmB,YACrB,yBAAyB,KACjC,UAkDF,CAAC"}
export { batchTableDdl, createPostgresStore } from "./batch-store";
export type { PostgresBatchStoreOptions } from "./batch-store";
export { migratePostgres } from "./migrate";
export type { MigrateOptions } from "./migrate";
export { DEFAULT_BATCH_TABLE, type PostgresStoreOptions, type SqlExecutor, } from "./types";
//# sourceMappingURL=index.d.ts.map
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/postgres/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,mBAAmB,EAAE,MAAM,eAAe,CAAC;AACnE,YAAY,EAAE,yBAAyB,EAAE,MAAM,eAAe,CAAC;AAC/D,OAAO,EAAE,eAAe,EAAE,MAAM,WAAW,CAAC;AAC5C,YAAY,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAChD,OAAO,EACL,mBAAmB,EACnB,KAAK,oBAAoB,EACzB,KAAK,WAAW,GACjB,MAAM,SAAS,CAAC"}
import"../chunk-v0bahtg2.js";
// src/postgres/types.ts
var DEFAULT_BATCH_TABLE = "batchwork_batches";
var assertSafeTable = (table) => {
if (!/^[A-Za-z_][A-Za-z0-9_]*(?<schema>\.[A-Za-z_][A-Za-z0-9_]*)?$/u.test(table)) {
throw new Error(`batchwork: unsafe Postgres table name ${JSON.stringify(table)}.`);
}
return table;
};
// src/postgres/batch-store.ts
var toBatch = (row) => ({
createdAt: row.created_at,
id: row.id,
provider: row.provider,
status: row.status,
...row.webhook_url === null ? {} : { webhookUrl: row.webhook_url },
...row.webhook_secret === null ? {} : { webhookSecret: row.webhook_secret },
...row.delivered_at === null ? {} : { deliveredAt: row.delivered_at }
});
var batchTableDdl = (table = DEFAULT_BATCH_TABLE) => {
const name = assertSafeTable(table);
return `
CREATE TABLE IF NOT EXISTS ${name} (
id text PRIMARY KEY,
provider text NOT NULL,
webhook_url text,
webhook_secret text,
status text NOT NULL,
created_at text NOT NULL,
delivered_at text
);
CREATE INDEX IF NOT EXISTS ${name}_undelivered_idx
ON ${name} (created_at) WHERE delivered_at IS NULL;`;
};
var createPostgresStore = (options) => {
const { client } = options;
const table = assertSafeTable(options.table ?? DEFAULT_BATCH_TABLE);
return {
delete: async (id) => {
await client.query(`DELETE FROM ${table} WHERE id = $1`, [id]);
},
get: async (id) => {
const { rows } = await client.query(`SELECT * FROM ${table} WHERE id = $1`, [id]);
const [row] = rows;
return row ? toBatch(row) : null;
},
list: async (filter) => {
let text = `SELECT * FROM ${table}`;
if (filter?.delivered === true) {
text += " WHERE delivered_at IS NOT NULL";
} else if (filter?.delivered === false) {
text += " WHERE delivered_at IS NULL";
}
const { rows } = await client.query(text);
return rows.map(toBatch);
},
set: async (record) => {
await client.query(`INSERT INTO ${table}
(id, provider, webhook_url, webhook_secret, status, created_at, delivered_at)
VALUES ($1, $2, $3, $4, $5, $6, $7)
ON CONFLICT (id) DO UPDATE SET
provider = EXCLUDED.provider,
webhook_url = EXCLUDED.webhook_url,
webhook_secret = EXCLUDED.webhook_secret,
status = EXCLUDED.status,
created_at = EXCLUDED.created_at,
delivered_at = EXCLUDED.delivered_at`, [
record.id,
record.provider,
record.webhookUrl ?? null,
record.webhookSecret ?? null,
record.status,
record.createdAt,
record.deliveredAt ?? null
]);
}
};
};
// src/postgres/migrate.ts
var runDdl = async (client, ddl) => {
for (const statement of ddl.split(";")) {
const trimmed = statement.trim();
if (trimmed) {
await client.query(trimmed);
}
}
};
var migratePostgres = async (client, options = {}) => {
await runDdl(client, batchTableDdl(assertSafeTable(options.batchTable ?? DEFAULT_BATCH_TABLE)));
};
export {
migratePostgres,
createPostgresStore,
batchTableDdl,
DEFAULT_BATCH_TABLE
};
//# debugId=119F35EA4B39BF6464756E2164756E21
//# sourceMappingURL=index.js.map
{
"version": 3,
"sources": ["../src/postgres/types.ts", "../src/postgres/batch-store.ts", "../src/postgres/migrate.ts"],
"sourcesContent": [
"/**\n * The minimal slice of a Postgres client batchwork's adapters use: a\n * node-postgres-style `query(text, params)` returning `{ rows }`. This is\n * deliberately structural so you can pass a `pg.Pool`/`pg.Client`, a\n * `@neondatabase/serverless` client, or any thin wrapper that exposes `query` —\n * batchwork never imports a driver, so it adds no dependency of its own.\n */\nexport interface SqlExecutor {\n query: <Row = Record<string, unknown>>(\n text: string,\n params?: unknown[]\n ) => Promise<{ rows: Row[] }>;\n}\n\n/** Options for the Postgres-backed batch store. */\nexport interface PostgresStoreOptions {\n /** A `pg.Pool`-compatible client (anything with `query(text, params)`). */\n client: SqlExecutor;\n}\n\n/** Table name defaults to this; override for a custom schema. */\nexport const DEFAULT_BATCH_TABLE = \"batchwork_batches\";\n\n/**\n * Guard against SQL injection through a caller-supplied table name: identifiers\n * can't be parameterized, so they're interpolated directly and must be a plain\n * unquoted identifier (optionally schema-qualified).\n */\nexport const assertSafeTable = (table: string): string => {\n if (\n !/^[A-Za-z_][A-Za-z0-9_]*(?<schema>\\.[A-Za-z_][A-Za-z0-9_]*)?$/u.test(table)\n ) {\n throw new Error(\n `batchwork: unsafe Postgres table name ${JSON.stringify(table)}.`\n );\n }\n return table;\n};\n",
"import type { BatchStore, TrackedBatch } from \"../server/types\";\nimport type { BatchProvider, BatchStatus } from \"../types\";\nimport { assertSafeTable, DEFAULT_BATCH_TABLE } from \"./types\";\nimport type { PostgresStoreOptions, SqlExecutor } from \"./types\";\n\n/** Options for {@link createPostgresStore}. */\nexport interface PostgresBatchStoreOptions extends PostgresStoreOptions {\n /** Table to store tracked batches in. Defaults to `batchwork_batches`. */\n table?: string;\n}\n\n/** The raw row shape returned by the tracked-batch table. */\ninterface BatchRow {\n id: string;\n provider: BatchProvider;\n webhook_url: string | null;\n webhook_secret: string | null;\n status: BatchStatus;\n created_at: string;\n delivered_at: string | null;\n}\n\nconst toBatch = (row: BatchRow): TrackedBatch => ({\n createdAt: row.created_at,\n id: row.id,\n provider: row.provider,\n status: row.status,\n // Re-add optional fields only when set, so the shape matches `createMemoryStore`\n // (and `list`'s `deliveredAt !== undefined` delivery filter stays correct).\n ...(row.webhook_url === null ? {} : { webhookUrl: row.webhook_url }),\n ...(row.webhook_secret === null ? {} : { webhookSecret: row.webhook_secret }),\n ...(row.delivered_at === null ? {} : { deliveredAt: row.delivered_at }),\n});\n\n/** `CREATE TABLE`/index DDL for the tracked-batch table (run by `migratePostgres`). */\nexport const batchTableDdl = (table = DEFAULT_BATCH_TABLE): string => {\n const name = assertSafeTable(table);\n return `\nCREATE TABLE IF NOT EXISTS ${name} (\n id text PRIMARY KEY,\n provider text NOT NULL,\n webhook_url text,\n webhook_secret text,\n status text NOT NULL,\n created_at text NOT NULL,\n delivered_at text\n);\nCREATE INDEX IF NOT EXISTS ${name}_undelivered_idx\n ON ${name} (created_at) WHERE delivered_at IS NULL;`;\n};\n\n/**\n * A Postgres-backed {@link BatchStore} for the poller. Run {@link migratePostgres}\n * (or {@link batchTableDdl}) once to create the table.\n *\n * @example\n * import { Pool } from \"pg\";\n * const store = createPostgresStore({ client: new Pool({ connectionString }) });\n */\nexport const createPostgresStore = (\n options: PostgresBatchStoreOptions\n): BatchStore => {\n const { client }: { client: SqlExecutor } = options;\n const table = assertSafeTable(options.table ?? DEFAULT_BATCH_TABLE);\n\n return {\n delete: async (id) => {\n await client.query(`DELETE FROM ${table} WHERE id = $1`, [id]);\n },\n get: async (id) => {\n const { rows } = await client.query<BatchRow>(\n `SELECT * FROM ${table} WHERE id = $1`,\n [id]\n );\n const [row] = rows;\n return row ? toBatch(row) : null;\n },\n list: async (filter) => {\n let text = `SELECT * FROM ${table}`;\n if (filter?.delivered === true) {\n text += \" WHERE delivered_at IS NOT NULL\";\n } else if (filter?.delivered === false) {\n text += \" WHERE delivered_at IS NULL\";\n }\n const { rows } = await client.query<BatchRow>(text);\n return rows.map(toBatch);\n },\n set: async (record) => {\n await client.query(\n `INSERT INTO ${table}\n (id, provider, webhook_url, webhook_secret, status, created_at, delivered_at)\n VALUES ($1, $2, $3, $4, $5, $6, $7)\n ON CONFLICT (id) DO UPDATE SET\n provider = EXCLUDED.provider,\n webhook_url = EXCLUDED.webhook_url,\n webhook_secret = EXCLUDED.webhook_secret,\n status = EXCLUDED.status,\n created_at = EXCLUDED.created_at,\n delivered_at = EXCLUDED.delivered_at`,\n [\n record.id,\n record.provider,\n record.webhookUrl ?? null,\n record.webhookSecret ?? null,\n record.status,\n record.createdAt,\n record.deliveredAt ?? null,\n ]\n );\n },\n };\n};\n",
"import { batchTableDdl } from \"./batch-store\";\nimport { assertSafeTable, DEFAULT_BATCH_TABLE } from \"./types\";\nimport type { SqlExecutor } from \"./types\";\n\n/** Options for {@link migratePostgres}. */\nexport interface MigrateOptions {\n /** Tracked-batch table name. Defaults to `batchwork_batches`. */\n batchTable?: string;\n}\n\n// Run a multi-statement DDL string one statement at a time: some drivers\n// (notably PGlite's extended protocol) reject multiple statements per query.\nconst runDdl = async (client: SqlExecutor, ddl: string): Promise<void> => {\n for (const statement of ddl.split(\";\")) {\n const trimmed = statement.trim();\n if (trimmed) {\n // oxlint-disable-next-line no-await-in-loop -- DDL must run in order.\n await client.query(trimmed);\n }\n }\n};\n\n/**\n * Create the table the batch store needs. Idempotent (`CREATE TABLE IF NOT\n * EXISTS`), so it's safe to run on every boot. Call once before using the store.\n *\n * @example\n * await migratePostgres(pool);\n */\nexport const migratePostgres = async (\n client: SqlExecutor,\n options: MigrateOptions = {}\n): Promise<void> => {\n await runDdl(\n client,\n batchTableDdl(assertSafeTable(options.batchTable ?? DEFAULT_BATCH_TABLE))\n );\n};\n"
],
"mappings": ";;;AAqBO,IAAM,sBAAsB;AAO5B,IAAM,kBAAkB,CAAC,UAA0B;AAAA,EACxD,IACE,CAAC,gEAAgE,KAAK,KAAK,GAC3E;AAAA,IACA,MAAM,IAAI,MACR,yCAAyC,KAAK,UAAU,KAAK,IAC/D;AAAA,EACF;AAAA,EACA,OAAO;AAAA;;;ACdT,IAAM,UAAU,CAAC,SAAiC;AAAA,EAChD,WAAW,IAAI;AAAA,EACf,IAAI,IAAI;AAAA,EACR,UAAU,IAAI;AAAA,EACd,QAAQ,IAAI;AAAA,KAGR,IAAI,gBAAgB,OAAO,CAAC,IAAI,EAAE,YAAY,IAAI,YAAY;AAAA,KAC9D,IAAI,mBAAmB,OAAO,CAAC,IAAI,EAAE,eAAe,IAAI,eAAe;AAAA,KACvE,IAAI,iBAAiB,OAAO,CAAC,IAAI,EAAE,aAAa,IAAI,aAAa;AACvE;AAGO,IAAM,gBAAgB,CAAC,QAAQ,wBAAgC;AAAA,EACpE,MAAM,OAAO,gBAAgB,KAAK;AAAA,EAClC,OAAO;AAAA,6BACoB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,6BASA;AAAA,OACtB;AAAA;AAWA,IAAM,sBAAsB,CACjC,YACe;AAAA,EACf,QAAQ,WAAoC;AAAA,EAC5C,MAAM,QAAQ,gBAAgB,QAAQ,SAAS,mBAAmB;AAAA,EAElE,OAAO;AAAA,IACL,QAAQ,OAAO,OAAO;AAAA,MACpB,MAAM,OAAO,MAAM,eAAe,uBAAuB,CAAC,EAAE,CAAC;AAAA;AAAA,IAE/D,KAAK,OAAO,OAAO;AAAA,MACjB,QAAQ,SAAS,MAAM,OAAO,MAC5B,iBAAiB,uBACjB,CAAC,EAAE,CACL;AAAA,MACA,OAAO,OAAO;AAAA,MACd,OAAO,MAAM,QAAQ,GAAG,IAAI;AAAA;AAAA,IAE9B,MAAM,OAAO,WAAW;AAAA,MACtB,IAAI,OAAO,iBAAiB;AAAA,MAC5B,IAAI,QAAQ,cAAc,MAAM;AAAA,QAC9B,QAAQ;AAAA,MACV,EAAO,SAAI,QAAQ,cAAc,OAAO;AAAA,QACtC,QAAQ;AAAA,MACV;AAAA,MACA,QAAQ,SAAS,MAAM,OAAO,MAAgB,IAAI;AAAA,MAClD,OAAO,KAAK,IAAI,OAAO;AAAA;AAAA,IAEzB,KAAK,OAAO,WAAW;AAAA,MACrB,MAAM,OAAO,MACX,eAAe;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,kDAUf;AAAA,QACE,OAAO;AAAA,QACP,OAAO;AAAA,QACP,OAAO,cAAc;AAAA,QACrB,OAAO,iBAAiB;AAAA,QACxB,OAAO;AAAA,QACP,OAAO;AAAA,QACP,OAAO,eAAe;AAAA,MACxB,CACF;AAAA;AAAA,EAEJ;AAAA;;AClGF,IAAM,SAAS,OAAO,QAAqB,QAA+B;AAAA,EACxE,WAAW,aAAa,IAAI,MAAM,GAAG,GAAG;AAAA,IACtC,MAAM,UAAU,UAAU,KAAK;AAAA,IAC/B,IAAI,SAAS;AAAA,MAEX,MAAM,OAAO,MAAM,OAAO;AAAA,IAC5B;AAAA,EACF;AAAA;AAUK,IAAM,kBAAkB,OAC7B,QACA,UAA0B,CAAC,MACT;AAAA,EAClB,MAAM,OACJ,QACA,cAAc,gBAAgB,QAAQ,cAAc,mBAAmB,CAAC,CAC1E;AAAA;",
"debugId": "119F35EA4B39BF6464756E2164756E21",
"names": []
}
import type { SqlExecutor } from "./types";
/** Options for {@link migratePostgres}. */
export interface MigrateOptions {
/** Tracked-batch table name. Defaults to `batchwork_batches`. */
batchTable?: string;
}
/**
* Create the table the batch store needs. Idempotent (`CREATE TABLE IF NOT
* EXISTS`), so it's safe to run on every boot. Call once before using the store.
*
* @example
* await migratePostgres(pool);
*/
export declare const migratePostgres: (client: SqlExecutor, options?: MigrateOptions) => Promise<void>;
//# sourceMappingURL=migrate.d.ts.map
{"version":3,"file":"migrate.d.ts","sourceRoot":"","sources":["../../src/postgres/migrate.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AAE3C,2CAA2C;AAC3C,MAAM,WAAW,cAAc;IAC7B,iEAAiE;IACjE,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAcD;;;;;;GAMG;AACH,eAAO,MAAM,eAAe,WAClB,WAAW,YACV,cAAc,KACtB,OAAO,CAAC,IAAI,CAKd,CAAC"}
/**
* The minimal slice of a Postgres client batchwork's adapters use: a
* node-postgres-style `query(text, params)` returning `{ rows }`. This is
* deliberately structural so you can pass a `pg.Pool`/`pg.Client`, a
* `@neondatabase/serverless` client, or any thin wrapper that exposes `query` —
* batchwork never imports a driver, so it adds no dependency of its own.
*/
export interface SqlExecutor {
query: <Row = Record<string, unknown>>(text: string, params?: unknown[]) => Promise<{
rows: Row[];
}>;
}
/** Options for the Postgres-backed batch store. */
export interface PostgresStoreOptions {
/** A `pg.Pool`-compatible client (anything with `query(text, params)`). */
client: SqlExecutor;
}
/** Table name defaults to this; override for a custom schema. */
export declare const DEFAULT_BATCH_TABLE = "batchwork_batches";
/**
* Guard against SQL injection through a caller-supplied table name: identifiers
* can't be parameterized, so they're interpolated directly and must be a plain
* unquoted identifier (optionally schema-qualified).
*/
export declare const assertSafeTable: (table: string) => string;
//# sourceMappingURL=types.d.ts.map
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/postgres/types.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,MAAM,WAAW,WAAW;IAC1B,KAAK,EAAE,CAAC,GAAG,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EACnC,IAAI,EAAE,MAAM,EACZ,MAAM,CAAC,EAAE,OAAO,EAAE,KACf,OAAO,CAAC;QAAE,IAAI,EAAE,GAAG,EAAE,CAAA;KAAE,CAAC,CAAC;CAC/B;AAED,mDAAmD;AACnD,MAAM,WAAW,oBAAoB;IACnC,2EAA2E;IAC3E,MAAM,EAAE,WAAW,CAAC;CACrB;AAED,iEAAiE;AACjE,eAAO,MAAM,mBAAmB,sBAAsB,CAAC;AAEvD;;;;GAIG;AACH,eAAO,MAAM,eAAe,UAAW,MAAM,KAAG,MAS/C,CAAC"}
import type { BuiltRequest } from "../body";
import type { BatchProvider, BatchResult, BatchSnapshot, ProviderCredentials } from "../types";
export interface SubmitInput {
built: BuiltRequest[];
credentials: ProviderCredentials;
/** Endpoint path the captured requests target (e.g. `/v1/chat/completions`). */
endpoint: string;
metadata?: Record<string, string>;
/**
* Resolved model id. Needed by providers that set the model on the batch/job
* (Gemini puts it in the create URL, Mistral on the job); ignored by providers
* that carry the model in each request line (OpenAI, Anthropic).
*/
modelId: string;
}
/** A provider's batch lifecycle: submit, poll, stream results, cancel. */
export interface BatchAdapter {
cancel: (id: string, credentials: ProviderCredentials) => Promise<void>;
readonly id: BatchProvider;
results: (id: string, credentials: ProviderCredentials) => AsyncGenerator<BatchResult>;
retrieve: (id: string, credentials: ProviderCredentials) => Promise<BatchSnapshot>;
submit: (input: SubmitInput) => Promise<BatchSnapshot>;
}
//# sourceMappingURL=adapter.d.ts.map
{"version":3,"file":"adapter.d.ts","sourceRoot":"","sources":["../../src/providers/adapter.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAC5C,OAAO,KAAK,EACV,aAAa,EACb,WAAW,EACX,aAAa,EACb,mBAAmB,EACpB,MAAM,UAAU,CAAC;AAElB,MAAM,WAAW,WAAW;IAC1B,KAAK,EAAE,YAAY,EAAE,CAAC;IACtB,WAAW,EAAE,mBAAmB,CAAC;IACjC,gFAAgF;IAChF,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAClC;;;;OAIG;IACH,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,0EAA0E;AAC1E,MAAM,WAAW,YAAY;IAC3B,MAAM,EAAE,CAAC,EAAE,EAAE,MAAM,EAAE,WAAW,EAAE,mBAAmB,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACxE,QAAQ,CAAC,EAAE,EAAE,aAAa,CAAC;IAC3B,OAAO,EAAE,CACP,EAAE,EAAE,MAAM,EACV,WAAW,EAAE,mBAAmB,KAC7B,cAAc,CAAC,WAAW,CAAC,CAAC;IACjC,QAAQ,EAAE,CACR,EAAE,EAAE,MAAM,EACV,WAAW,EAAE,mBAAmB,KAC7B,OAAO,CAAC,aAAa,CAAC,CAAC;IAC5B,MAAM,EAAE,CAAC,KAAK,EAAE,WAAW,KAAK,OAAO,CAAC,aAAa,CAAC,CAAC;CACxD"}
import type { BatchAdapter } from "./adapter";
export declare const anthropicAdapter: BatchAdapter;
//# sourceMappingURL=anthropic.d.ts.map
{"version":3,"file":"anthropic.d.ts","sourceRoot":"","sources":["../../src/providers/anthropic.ts"],"names":[],"mappings":"AAWA,OAAO,KAAK,EAAE,YAAY,EAAe,MAAM,WAAW,CAAC;AAqL3D,eAAO,MAAM,gBAAgB,EAAE,YAM9B,CAAC"}
import type { BatchAdapter } from "./adapter";
/**
* Google Gemini (Developer API) batch adapter: submits inline requests via
* `:batchGenerateContent`, polls the returned long-running operation, then reads
* the inline responses keyed by each request's `metadata.key`.
*/
export declare const googleAdapter: BatchAdapter;
//# sourceMappingURL=google.d.ts.map
{"version":3,"file":"google.d.ts","sourceRoot":"","sources":["../../src/providers/google.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAAE,YAAY,EAAe,MAAM,WAAW,CAAC;AA6N3D;;;;GAIG;AACH,eAAO,MAAM,aAAa,EAAE,YAM3B,CAAC"}
/**
* Groq batch adapter. Groq's batch API is OpenAI-compatible (Files API +
* `/batches`), so it reuses the OpenAI-compatible flow. Groq is served under
* `/openai/v1`, but its batch `url` must be `/v1/chat/completions`, so the
* captured endpoint's `/openai` prefix is stripped.
*/
export declare const groqAdapter: import("./adapter").BatchAdapter;
//# sourceMappingURL=groq.d.ts.map
{"version":3,"file":"groq.d.ts","sourceRoot":"","sources":["../../src/providers/groq.ts"],"names":[],"mappings":"AAEA;;;;;GAKG;AACH,eAAO,MAAM,WAAW,kCAOtB,CAAC"}
import type { BatchProvider } from "../types";
import type { BatchAdapter } from "./adapter";
export declare const getAdapter: (provider: BatchProvider) => BatchAdapter;
export type { BatchAdapter, SubmitInput } from "./adapter";
//# sourceMappingURL=index.d.ts.map
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/providers/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAC9C,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,WAAW,CAAC;AAmB9C,eAAO,MAAM,UAAU,aAAc,aAAa,KAAG,YACjC,CAAC;AAErB,YAAY,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC"}
import type { BatchAdapter } from "./adapter";
/**
* Mistral batch adapter: uploads JSONL (`purpose=batch`), creates a job with the
* model/endpoint set on the job, polls `status`, then downloads the OpenAI-shaped
* output/error files.
*/
export declare const mistralAdapter: BatchAdapter;
//# sourceMappingURL=mistral.d.ts.map
{"version":3,"file":"mistral.d.ts","sourceRoot":"","sources":["../../src/providers/mistral.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAAE,YAAY,EAAe,MAAM,WAAW,CAAC;AAuI3D;;;;GAIG;AACH,eAAO,MAAM,cAAc,EAAE,YAM5B,CAAC"}
/**
* Factory for OpenAI-compatible batch adapters. OpenAI, Groq, and Together AI
* all share the same lifecycle — upload JSONL via the Files API, create a batch
* referencing the file, poll a `status` field, then download output/error files
* — differing only in base URL, credentials, and minor request-line shape.
*/
import type { BatchProvider } from "../types";
import type { BatchAdapter } from "./adapter";
export type BatchLineFormat = "body-only" | "method-url";
export interface OpenAICompatibleConfig {
apiKeyEnv: string;
apiKeyLabel: string;
baseUrl: string;
completionWindow?: string;
filePurpose?: string;
id: BatchProvider;
/**
* Override the JSONL upload. Defaults to a direct multipart POST to the Files
* API (OpenAI, Groq); Together replaces it with its presigned-URL flow.
* Returns the uploaded file id.
*/
uploadFile?: (args: {
baseUrl: string;
headers: Record<string, string>;
jsonl: string;
purpose: string;
}) => Promise<string>;
/**
* Input-line shape. `method-url` writes `{ custom_id, method, url, body }`
* (OpenAI, Groq); `body-only` writes `{ custom_id, body }` (Together).
*/
lineFormat?: BatchLineFormat;
/**
* Map the captured endpoint path to the value the provider expects in the
* batch `url`/`endpoint` field (e.g. Groq serves under `/openai/v1` but its
* batch `url` must be `/v1/chat/completions`).
*/
normalizeEndpoint?: (endpoint: string) => string;
}
export declare const createOpenAICompatibleAdapter: (config: OpenAICompatibleConfig) => BatchAdapter;
//# sourceMappingURL=openai-compatible.d.ts.map
{"version":3,"file":"openai-compatible.d.ts","sourceRoot":"","sources":["../../src/providers/openai-compatible.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAKH,OAAO,KAAK,EACV,aAAa,EAKd,MAAM,UAAU,CAAC;AAElB,OAAO,KAAK,EAAE,YAAY,EAAe,MAAM,WAAW,CAAC;AAI3D,MAAM,MAAM,eAAe,GAAG,WAAW,GAAG,YAAY,CAAC;AAEzD,MAAM,WAAW,sBAAsB;IACrC,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,MAAM,CAAC;IAEhB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAE1B,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,EAAE,EAAE,aAAa,CAAC;IAClB;;;;OAIG;IACH,UAAU,CAAC,EAAE,CAAC,IAAI,EAAE;QAClB,OAAO,EAAE,MAAM,CAAC;QAChB,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;QAChC,KAAK,EAAE,MAAM,CAAC;QACd,OAAO,EAAE,MAAM,CAAC;KACjB,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;IACtB;;;OAGG;IACH,UAAU,CAAC,EAAE,eAAe,CAAC;IAC7B;;;;OAIG;IACH,iBAAiB,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,MAAM,CAAC;CAClD;AAgDD,eAAO,MAAM,6BAA6B,WAChC,sBAAsB,KAC7B,YAgGF,CAAC"}
/**
* OpenAI batch adapter: builds JSONL, uploads it via the Files API
* (`purpose=batch`), creates the batch, polls `status`, then downloads and
* parses the output and error files.
*/
export declare const openaiAdapter: import("./adapter").BatchAdapter;
//# sourceMappingURL=openai.d.ts.map
{"version":3,"file":"openai.d.ts","sourceRoot":"","sources":["../../src/providers/openai.ts"],"names":[],"mappings":"AAEA;;;;GAIG;AACH,eAAO,MAAM,aAAa,kCAMxB,CAAC"}
/**
* Helpers shared across the OpenAI-shaped adapters (OpenAI, Groq, Together,
* Mistral). These providers all upload JSONL via a Files API, return results as
* JSONL keyed by `custom_id`, and shape each result line like OpenAI's
* (`{ custom_id, response: { status_code, body }, error }`).
*/
import type { BatchResult, BatchUsage, ProviderCredentials } from "../types";
/** Resolve an API key from credentials or the provider's env var, or throw. */
export declare const resolveApiKey: (credentials: ProviderCredentials, envVar: string, label: string) => string;
export declare const textFromBody: (body: unknown) => string | undefined;
export declare const usageFromBody: (body: unknown) => BatchUsage | undefined;
/** Normalize an OpenAI-shaped result line into a {@link BatchResult}. */
export declare const normalizeOpenAIResult: (line: unknown) => BatchResult;
export declare const uploadInputFile: (jsonl: string, baseUrl: string, headers: Record<string, string>, options?: {
purpose?: string | null;
}) => Promise<string>;
/**
* Stream a JSONL result file's content as normalized OpenAI-shaped results.
*
* @yields {BatchResult} the normalized result for each line.
*/
export declare function streamResultFile(fileId: string, baseUrl: string, headers: Record<string, string>): AsyncGenerator<BatchResult>;
//# sourceMappingURL=shared.d.ts.map
{"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,eAAO,MAAM,aAAa,SAAU,OAAO,KAAG,UAAU,GAAG,SAmB1D,CAAC;AAaF,yEAAyE;AACzE,eAAO,MAAM,qBAAqB,SAAU,OAAO,KAAG,WAkCrD,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,CAiBhB,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,CAO7B"}
/**
* Together AI batch adapter. Together's batch API mirrors OpenAI's (Files API +
* `/batches`) but uses the leaner `{ custom_id, body }` input-line shape, with
* the endpoint declared on the batch rather than per line, and a presigned-URL
* file upload in place of OpenAI's direct multipart POST.
*/
export declare const togetherAdapter: import("./adapter").BatchAdapter;
//# sourceMappingURL=together.d.ts.map
{"version":3,"file":"together.d.ts","sourceRoot":"","sources":["../../src/providers/together.ts"],"names":[],"mappings":"AAkEA;;;;;GAKG;AACH,eAAO,MAAM,eAAe,kCAQ1B,CAAC"}
import type { BatchAdapter } from "./adapter";
/**
* xAI (Grok) batch adapter. Uses xAI's OpenAI-compatible file-upload path
* (`/v1/files` + `/v1/batches` with `input_file_id`), but its status, paginated
* `/results`, and `:cancel` shapes are proprietary.
*/
export declare const xaiAdapter: BatchAdapter;
//# sourceMappingURL=xai.d.ts.map
{"version":3,"file":"xai.d.ts","sourceRoot":"","sources":["../../src/providers/xai.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,YAAY,EAAe,MAAM,WAAW,CAAC;AAyK3D;;;;GAIG;AACH,eAAO,MAAM,UAAU,EAAE,YAMxB,CAAC"}
import type { BatchStore } from "../server/types";
import type { RedisStoreOptions } from "./types";
/** Options for {@link createRedisStore}. */
export type RedisBatchStoreOptions = RedisStoreOptions;
/**
* A Redis-backed {@link BatchStore} for the poller, over `@upstash/redis`. Each
* tracked batch is a JSON value at `{prefix}:batch:{id}`, with a set index at
* `{prefix}:batches` so `list` can enumerate them.
*
* @example
* import { Redis } from "@upstash/redis";
* const store = createRedisStore({ redis: Redis.fromEnv() });
*/
export declare const createRedisStore: (options: RedisBatchStoreOptions) => BatchStore;
//# sourceMappingURL=batch-store.d.ts.map
{"version":3,"file":"batch-store.d.ts","sourceRoot":"","sources":["../../src/redis/batch-store.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAgB,MAAM,iBAAiB,CAAC;AAEhE,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,SAAS,CAAC;AAEjD,4CAA4C;AAC5C,MAAM,MAAM,sBAAsB,GAAG,iBAAiB,CAAC;AAEvD;;;;;;;;GAQG;AACH,eAAO,MAAM,gBAAgB,YAClB,sBAAsB,KAC9B,UAsCF,CAAC"}
export { createRedisStore } from "./batch-store";
export type { RedisBatchStoreOptions } from "./batch-store";
export { DEFAULT_PREFIX, type RedisClient, type RedisStoreOptions, } from "./types";
//# sourceMappingURL=index.d.ts.map
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/redis/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC;AACjD,YAAY,EAAE,sBAAsB,EAAE,MAAM,eAAe,CAAC;AAC5D,OAAO,EACL,cAAc,EACd,KAAK,WAAW,EAChB,KAAK,iBAAiB,GACvB,MAAM,SAAS,CAAC"}
import"../chunk-v0bahtg2.js";
// src/redis/types.ts
var DEFAULT_PREFIX = "batchwork";
var coerce = (value) => {
if (value === null || value === undefined) {
return null;
}
return typeof value === "string" ? JSON.parse(value) : value;
};
// src/redis/batch-store.ts
var createRedisStore = (options) => {
const { redis } = options;
const prefix = options.prefix ?? DEFAULT_PREFIX;
const batchKey = (id) => `${prefix}:batch:${id}`;
const indexKey = `${prefix}:batches`;
return {
delete: async (id) => {
await redis.del(batchKey(id));
await redis.srem(indexKey, id);
},
get: async (id) => coerce(await redis.get(batchKey(id))),
list: async (filter) => {
const ids = await redis.smembers(indexKey);
if (ids.length === 0) {
return [];
}
const raws = await redis.mget(...ids.map(batchKey));
const records = [];
for (const raw of raws) {
const record = coerce(raw);
if (record) {
records.push(record);
}
}
if (filter?.delivered === undefined) {
return records;
}
const { delivered } = filter;
return records.filter((record) => record.deliveredAt !== undefined === delivered);
},
set: async (record) => {
await redis.set(batchKey(record.id), JSON.stringify(record));
await redis.sadd(indexKey, record.id);
}
};
};
export {
createRedisStore,
DEFAULT_PREFIX
};
//# debugId=80E91990D446837F64756E2164756E21
//# sourceMappingURL=index.js.map
{
"version": 3,
"sources": ["../src/redis/types.ts", "../src/redis/batch-store.ts"],
"sourcesContent": [
"import type { Redis } from \"@upstash/redis\";\n\n/** The Upstash Redis client batchwork's adapters drive. */\nexport type RedisClient = Redis;\n\n/** Options for the Redis-backed batch store. */\nexport interface RedisStoreOptions {\n /** A connected `@upstash/redis` client. */\n redis: Redis;\n /** Key namespace, so one Redis can back many apps. Defaults to `batchwork`. */\n prefix?: string;\n}\n\nexport const DEFAULT_PREFIX = \"batchwork\";\n\n/**\n * Read a stored value back into a record. `@upstash/redis` may or may not\n * auto-deserialize depending on the client's config, and values returned from\n * Lua (`eval`) are always raw strings — so tolerate both an object and a JSON\n * string, and treat a missing key as `null`.\n */\nexport const coerce = <T>(value: unknown): T | null => {\n if (value === null || value === undefined) {\n return null;\n }\n return (typeof value === \"string\" ? JSON.parse(value) : value) as T;\n};\n",
"import type { BatchStore, TrackedBatch } from \"../server/types\";\nimport { coerce, DEFAULT_PREFIX } from \"./types\";\nimport type { RedisStoreOptions } from \"./types\";\n\n/** Options for {@link createRedisStore}. */\nexport type RedisBatchStoreOptions = RedisStoreOptions;\n\n/**\n * A Redis-backed {@link BatchStore} for the poller, over `@upstash/redis`. Each\n * tracked batch is a JSON value at `{prefix}:batch:{id}`, with a set index at\n * `{prefix}:batches` so `list` can enumerate them.\n *\n * @example\n * import { Redis } from \"@upstash/redis\";\n * const store = createRedisStore({ redis: Redis.fromEnv() });\n */\nexport const createRedisStore = (\n options: RedisBatchStoreOptions\n): BatchStore => {\n const { redis } = options;\n const prefix = options.prefix ?? DEFAULT_PREFIX;\n const batchKey = (id: string): string => `${prefix}:batch:${id}`;\n const indexKey = `${prefix}:batches`;\n\n return {\n delete: async (id) => {\n await redis.del(batchKey(id));\n await redis.srem(indexKey, id);\n },\n get: async (id) => coerce<TrackedBatch>(await redis.get(batchKey(id))),\n list: async (filter) => {\n const ids = await redis.smembers(indexKey);\n if (ids.length === 0) {\n return [];\n }\n const raws = await redis.mget<unknown[]>(...ids.map(batchKey));\n const records: TrackedBatch[] = [];\n for (const raw of raws) {\n const record = coerce<TrackedBatch>(raw);\n if (record) {\n records.push(record);\n }\n }\n if (filter?.delivered === undefined) {\n return records;\n }\n const { delivered } = filter;\n return records.filter(\n (record) => (record.deliveredAt !== undefined) === delivered\n );\n },\n set: async (record) => {\n await redis.set(batchKey(record.id), JSON.stringify(record));\n await redis.sadd(indexKey, record.id);\n },\n };\n};\n"
],
"mappings": ";;;AAaO,IAAM,iBAAiB;AAQvB,IAAM,SAAS,CAAI,UAA6B;AAAA,EACrD,IAAI,UAAU,QAAQ,UAAU,WAAW;AAAA,IACzC,OAAO;AAAA,EACT;AAAA,EACA,OAAQ,OAAO,UAAU,WAAW,KAAK,MAAM,KAAK,IAAI;AAAA;;;ACTnD,IAAM,mBAAmB,CAC9B,YACe;AAAA,EACf,QAAQ,UAAU;AAAA,EAClB,MAAM,SAAS,QAAQ,UAAU;AAAA,EACjC,MAAM,WAAW,CAAC,OAAuB,GAAG,gBAAgB;AAAA,EAC5D,MAAM,WAAW,GAAG;AAAA,EAEpB,OAAO;AAAA,IACL,QAAQ,OAAO,OAAO;AAAA,MACpB,MAAM,MAAM,IAAI,SAAS,EAAE,CAAC;AAAA,MAC5B,MAAM,MAAM,KAAK,UAAU,EAAE;AAAA;AAAA,IAE/B,KAAK,OAAO,OAAO,OAAqB,MAAM,MAAM,IAAI,SAAS,EAAE,CAAC,CAAC;AAAA,IACrE,MAAM,OAAO,WAAW;AAAA,MACtB,MAAM,MAAM,MAAM,MAAM,SAAS,QAAQ;AAAA,MACzC,IAAI,IAAI,WAAW,GAAG;AAAA,QACpB,OAAO,CAAC;AAAA,MACV;AAAA,MACA,MAAM,OAAO,MAAM,MAAM,KAAgB,GAAG,IAAI,IAAI,QAAQ,CAAC;AAAA,MAC7D,MAAM,UAA0B,CAAC;AAAA,MACjC,WAAW,OAAO,MAAM;AAAA,QACtB,MAAM,SAAS,OAAqB,GAAG;AAAA,QACvC,IAAI,QAAQ;AAAA,UACV,QAAQ,KAAK,MAAM;AAAA,QACrB;AAAA,MACF;AAAA,MACA,IAAI,QAAQ,cAAc,WAAW;AAAA,QACnC,OAAO;AAAA,MACT;AAAA,MACA,QAAQ,cAAc;AAAA,MACtB,OAAO,QAAQ,OACb,CAAC,WAAY,OAAO,gBAAgB,cAAe,SACrD;AAAA;AAAA,IAEF,KAAK,OAAO,WAAW;AAAA,MACrB,MAAM,MAAM,IAAI,SAAS,OAAO,EAAE,GAAG,KAAK,UAAU,MAAM,CAAC;AAAA,MAC3D,MAAM,MAAM,KAAK,UAAU,OAAO,EAAE;AAAA;AAAA,EAExC;AAAA;",
"debugId": "80E91990D446837F64756E2164756E21",
"names": []
}
import type { Redis } from "@upstash/redis";
/** The Upstash Redis client batchwork's adapters drive. */
export type RedisClient = Redis;
/** Options for the Redis-backed batch store. */
export interface RedisStoreOptions {
/** A connected `@upstash/redis` client. */
redis: Redis;
/** Key namespace, so one Redis can back many apps. Defaults to `batchwork`. */
prefix?: string;
}
export declare const DEFAULT_PREFIX = "batchwork";
/**
* Read a stored value back into a record. `@upstash/redis` may or may not
* auto-deserialize depending on the client's config, and values returned from
* Lua (`eval`) are always raw strings — so tolerate both an object and a JSON
* string, and treat a missing key as `null`.
*/
export declare const coerce: <T>(value: unknown) => T | null;
//# sourceMappingURL=types.d.ts.map
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/redis/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,gBAAgB,CAAC;AAE5C,2DAA2D;AAC3D,MAAM,MAAM,WAAW,GAAG,KAAK,CAAC;AAEhC,gDAAgD;AAChD,MAAM,WAAW,iBAAiB;IAChC,2CAA2C;IAC3C,KAAK,EAAE,KAAK,CAAC;IACb,+EAA+E;IAC/E,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,eAAO,MAAM,cAAc,cAAc,CAAC;AAE1C;;;;;GAKG;AACH,eAAO,MAAM,MAAM,GAAI,CAAC,SAAS,OAAO,KAAG,CAAC,GAAG,IAK9C,CAAC"}
import type { BatchProvider, BatchSnapshot } from "../types";
import type { BatchWebhookEvent } from "./types";
/** Map a terminal snapshot to the unified webhook event batchwork delivers. */
export declare const toEvent: (provider: BatchProvider, snapshot: BatchSnapshot) => BatchWebhookEvent;
//# sourceMappingURL=events.d.ts.map
{"version":3,"file":"events.d.ts","sourceRoot":"","sources":["../../src/server/events.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,aAAa,EAAe,MAAM,UAAU,CAAC;AAC1E,OAAO,KAAK,EAAE,iBAAiB,EAAyB,MAAM,SAAS,CAAC;AASxE,+EAA+E;AAC/E,eAAO,MAAM,OAAO,aACR,aAAa,YACb,aAAa,KACtB,iBAOD,CAAC"}
export { createBatchPoller } from "./poller";
export type { BatchPoller, BatchPollerOptions, CompletionSink, CredentialResolver, OpenAIWebhookOptions, TickResult, TrackOptions, TrackTarget, } from "./poller";
export { signWebhook, verifyBatchWebhook, verifyWebhook } from "./signing";
export type { VerifiedWebhook } from "./signing";
export { createMemoryStore } from "./store";
export type { BatchStore, BatchWebhookEvent, BatchWebhookEventType, TrackedBatch, } from "./types";
//# sourceMappingURL=index.d.ts.map
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/server/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,UAAU,CAAC;AAC7C,YAAY,EACV,WAAW,EACX,kBAAkB,EAClB,cAAc,EACd,kBAAkB,EAClB,oBAAoB,EACpB,UAAU,EACV,YAAY,EACZ,WAAW,GACZ,MAAM,UAAU,CAAC;AAClB,OAAO,EAAE,WAAW,EAAE,kBAAkB,EAAE,aAAa,EAAE,MAAM,WAAW,CAAC;AAC3E,YAAY,EAAE,eAAe,EAAE,MAAM,WAAW,CAAC;AACjD,OAAO,EAAE,iBAAiB,EAAE,MAAM,SAAS,CAAC;AAC5C,YAAY,EACV,UAAU,EACV,iBAAiB,EACjB,qBAAqB,EACrB,YAAY,GACb,MAAM,SAAS,CAAC"}
import {
createBatchPoller,
createMemoryStore,
signWebhook,
verifyBatchWebhook,
verifyWebhook
} from "../chunk-zp2cxkyb.js";
import"../chunk-kv3847wy.js";
import"../chunk-v0bahtg2.js";
export {
verifyWebhook,
verifyBatchWebhook,
signWebhook,
createMemoryStore,
createBatchPoller
};
//# debugId=C585C9F747001A8C64756E2164756E21
//# sourceMappingURL=index.js.map
{
"version": 3,
"sources": [],
"sourcesContent": [
],
"mappings": "",
"debugId": "C585C9F747001A8C64756E2164756E21",
"names": []
}
import type { BatchProvider, BatchSnapshot, BatchStatus, ProviderCredentials } from "../types";
import type { BatchStore, TrackedBatch } from "./types";
/** Credentials for polling: a fixed config, or one resolved per provider. */
export type CredentialResolver = ProviderCredentials | ((provider: BatchProvider) => ProviderCredentials);
/**
* Handles a batch reaching a terminal status. Replaces the default signed
* webhook delivery — e.g. to invoke a callback instead (see `batchwork/next`).
*/
export type CompletionSink = (record: TrackedBatch, snapshot: BatchSnapshot) => Promise<void>;
export interface BatchPollerOptions {
store: BatchStore;
/** Falls back to provider env vars (e.g. `OPENAI_API_KEY`) when omitted. */
credentials?: CredentialResolver;
/** Replaces signed-webhook delivery when a batch finishes. */
onComplete?: CompletionSink;
/**
* Called when processing a single batch throws during `tick`. When provided,
* the tick reports the error and continues to the next batch; when omitted,
* the error propagates out of `tick`.
*/
onError?: (record: TrackedBatch, error: unknown) => void;
}
export interface TrackTarget {
id: string;
provider: BatchProvider;
status?: BatchStatus;
}
export interface TrackOptions {
/** Where to POST the completion webhook. Omit for callback-based delivery. */
webhookUrl?: string;
/** Signs the outbound webhook (Standard Webhooks HMAC) when provided. */
secret?: string;
}
export interface TickResult {
checked: number;
delivered: string[];
/** Batches whose processing threw this tick (only when `onError` is set). */
failed?: {
id: string;
error: string;
}[];
}
export interface OpenAIWebhookOptions {
/** The OpenAI webhook signing secret (`whsec_…`). */
signingSecret: string;
}
export interface BatchPoller {
track: (target: TrackTarget, options: TrackOptions) => Promise<TrackedBatch>;
tick: () => Promise<TickResult>;
deliver: (record: TrackedBatch, snapshot: BatchSnapshot) => Promise<void>;
openaiWebhookHandler: (options: OpenAIWebhookOptions) => (request: Request) => Promise<Response>;
}
/**
* Create a managed poller: register submitted batches with `track`, then run
* `tick` on a schedule (cron) to poll open batches and deliver one unified,
* signed webhook per batch when it finishes. For OpenAI, mount
* `openaiWebhookHandler` to skip polling and react to native webhooks instead.
*/
export declare const createBatchPoller: (options: BatchPollerOptions) => BatchPoller;
//# sourceMappingURL=poller.d.ts.map
{"version":3,"file":"poller.d.ts","sourceRoot":"","sources":["../../src/server/poller.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EACV,aAAa,EACb,aAAa,EACb,WAAW,EACX,mBAAmB,EACpB,MAAM,UAAU,CAAC;AAGlB,OAAO,KAAK,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAExD,6EAA6E;AAC7E,MAAM,MAAM,kBAAkB,GAC1B,mBAAmB,GACnB,CAAC,CAAC,QAAQ,EAAE,aAAa,KAAK,mBAAmB,CAAC,CAAC;AAEvD;;;GAGG;AACH,MAAM,MAAM,cAAc,GAAG,CAC3B,MAAM,EAAE,YAAY,EACpB,QAAQ,EAAE,aAAa,KACpB,OAAO,CAAC,IAAI,CAAC,CAAC;AAEnB,MAAM,WAAW,kBAAkB;IACjC,KAAK,EAAE,UAAU,CAAC;IAClB,4EAA4E;IAC5E,WAAW,CAAC,EAAE,kBAAkB,CAAC;IACjC,8DAA8D;IAC9D,UAAU,CAAC,EAAE,cAAc,CAAC;IAC5B;;;;OAIG;IACH,OAAO,CAAC,EAAE,CAAC,MAAM,EAAE,YAAY,EAAE,KAAK,EAAE,OAAO,KAAK,IAAI,CAAC;CAC1D;AAED,MAAM,WAAW,WAAW;IAC1B,EAAE,EAAE,MAAM,CAAC;IACX,QAAQ,EAAE,aAAa,CAAC;IACxB,MAAM,CAAC,EAAE,WAAW,CAAC;CACtB;AAED,MAAM,WAAW,YAAY;IAC3B,8EAA8E;IAC9E,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,yEAAyE;IACzE,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,UAAU;IACzB,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,EAAE,CAAC;IACpB,6EAA6E;IAC7E,MAAM,CAAC,EAAE;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;CAC1C;AAED,MAAM,WAAW,oBAAoB;IACnC,qDAAqD;IACrD,aAAa,EAAE,MAAM,CAAC;CACvB;AAED,MAAM,WAAW,WAAW;IAC1B,KAAK,EAAE,CAAC,MAAM,EAAE,WAAW,EAAE,OAAO,EAAE,YAAY,KAAK,OAAO,CAAC,YAAY,CAAC,CAAC;IAC7E,IAAI,EAAE,MAAM,OAAO,CAAC,UAAU,CAAC,CAAC;IAChC,OAAO,EAAE,CAAC,MAAM,EAAE,YAAY,EAAE,QAAQ,EAAE,aAAa,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAC1E,oBAAoB,EAAE,CACpB,OAAO,EAAE,oBAAoB,KAC1B,CAAC,OAAO,EAAE,OAAO,KAAK,OAAO,CAAC,QAAQ,CAAC,CAAC;CAC9C;AAoCD;;;;;GAKG;AACH,eAAO,MAAM,iBAAiB,YAAa,kBAAkB,KAAG,WA2H/D,CAAC"}
import type { BatchWebhookEvent } from "./types";
/** Build Standard Webhooks signature headers for an outbound delivery. */
export declare const signWebhook: (secret: string, id: string, body: string, timestampSeconds: number) => Promise<Record<string, string>>;
export interface VerifiedWebhook {
id: string;
timestamp: number;
body: string;
}
/**
* Verify a Standard Webhooks-signed request and return its raw body. Throws if
* headers are missing, the timestamp is outside tolerance, or no signature
* matches. Consumes the request body.
*/
export declare const verifyWebhook: (request: Request, secret: string) => Promise<VerifiedWebhook>;
/**
* Verify and parse a batchwork webhook on your receiving endpoint. Returns the
* unified {@link BatchWebhookEvent}.
*/
export declare const verifyBatchWebhook: (request: Request, secret: string) => Promise<BatchWebhookEvent>;
//# sourceMappingURL=signing.d.ts.map
{"version":3,"file":"signing.d.ts","sourceRoot":"","sources":["../../src/server/signing.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,SAAS,CAAC;AA8DjD,0EAA0E;AAC1E,eAAO,MAAM,WAAW,WACd,MAAM,MACV,MAAM,QACJ,MAAM,oBACM,MAAM,KACvB,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAQhC,CAAC;AAEF,MAAM,WAAW,eAAe;IAC9B,EAAE,EAAE,MAAM,CAAC;IACX,SAAS,EAAE,MAAM,CAAC;IAClB,IAAI,EAAE,MAAM,CAAC;CACd;AAED;;;;GAIG;AACH,eAAO,MAAM,aAAa,YACf,OAAO,UACR,MAAM,KACb,OAAO,CAAC,eAAe,CAsCzB,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,kBAAkB,YACpB,OAAO,UACR,MAAM,KACb,OAAO,CAAC,iBAAiB,CAG3B,CAAC"}
import type { BatchStore } from "./types";
/** An in-memory `BatchStore`. Suitable for development and single-process use. */
export declare const createMemoryStore: () => BatchStore;
//# sourceMappingURL=store.d.ts.map
{"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../../src/server/store.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAgB,MAAM,SAAS,CAAC;AAExD,kFAAkF;AAClF,eAAO,MAAM,iBAAiB,QAAO,UAwBpC,CAAC"}
import type { BatchProvider, BatchRequestCounts, BatchStatus } from "../types";
/** The unified webhook event batchwork delivers when a batch finishes. */
export type BatchWebhookEventType = "batch.completed" | "batch.failed" | "batch.expired" | "batch.cancelled";
export interface BatchWebhookEvent {
type: BatchWebhookEventType;
id: string;
provider: BatchProvider;
requestCounts: BatchRequestCounts;
createdAt?: string;
completedAt?: string;
}
/** A batch being watched by the poller, plus its delivery target. */
export interface TrackedBatch {
id: string;
provider: BatchProvider;
/** Where to POST the completion webhook. Omitted for callback-based flows. */
webhookUrl?: string;
webhookSecret?: string;
status: BatchStatus;
createdAt: string;
/** Set once the completion webhook has been delivered. */
deliveredAt?: string;
}
/**
* Persistence for tracked batches. Implement against any KV/DB (Vercel KV,
* Upstash Redis, Cloudflare KV, Postgres). `createMemoryStore` is provided for
* development and single-process use.
*/
export interface BatchStore {
get: (id: string) => Promise<TrackedBatch | null>;
set: (record: TrackedBatch) => Promise<void>;
delete: (id: string) => Promise<void>;
list: (filter?: {
delivered?: boolean;
}) => Promise<TrackedBatch[]>;
}
//# sourceMappingURL=types.d.ts.map
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/server/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,kBAAkB,EAAE,WAAW,EAAE,MAAM,UAAU,CAAC;AAE/E,0EAA0E;AAC1E,MAAM,MAAM,qBAAqB,GAC7B,iBAAiB,GACjB,cAAc,GACd,eAAe,GACf,iBAAiB,CAAC;AAEtB,MAAM,WAAW,iBAAiB;IAChC,IAAI,EAAE,qBAAqB,CAAC;IAC5B,EAAE,EAAE,MAAM,CAAC;IACX,QAAQ,EAAE,aAAa,CAAC;IACxB,aAAa,EAAE,kBAAkB,CAAC;IAClC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,qEAAqE;AACrE,MAAM,WAAW,YAAY;IAC3B,EAAE,EAAE,MAAM,CAAC;IACX,QAAQ,EAAE,aAAa,CAAC;IACxB,8EAA8E;IAC9E,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,MAAM,EAAE,WAAW,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;IAClB,0DAA0D;IAC1D,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED;;;;GAIG;AACH,MAAM,WAAW,UAAU;IACzB,GAAG,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,OAAO,CAAC,YAAY,GAAG,IAAI,CAAC,CAAC;IAClD,GAAG,EAAE,CAAC,MAAM,EAAE,YAAY,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAC7C,MAAM,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACtC,IAAI,EAAE,CAAC,MAAM,CAAC,EAAE;QAAE,SAAS,CAAC,EAAE,OAAO,CAAA;KAAE,KAAK,OAAO,CAAC,YAAY,EAAE,CAAC,CAAC;CACrE"}
import type { JSONValue, LanguageModel, ModelMessage, ToolChoice, ToolSet } from "ai";
/** Providers with a batch adapter. */
export type BatchProvider = "anthropic" | "google" | "groq" | "mistral" | "openai" | "together" | "xai";
/**
* Provider-specific options, forwarded verbatim into the underlying request
* body. Structurally identical to the AI SDK's `ProviderOptions`.
*/
export type ProviderOptions = Record<string, Record<string, JSONValue>>;
/**
* Sampling and prompt-shaping fields shared with the AI SDK's `generateText`.
* Used both per request and as batch-wide defaults.
*/
export interface BatchRequestSettings {
frequencyPenalty?: number;
maxOutputTokens?: number;
presencePenalty?: number;
providerOptions?: ProviderOptions;
seed?: number;
stopSequences?: string[];
system?: string;
temperature?: number;
toolChoice?: ToolChoice<ToolSet>;
tools?: ToolSet;
topK?: number;
topP?: number;
}
/**
* A single request within a batch. Mirrors the AI SDK `generateText` input
* (minus `model`), plus a `customId` used to correlate the result.
*/
export interface BatchRequest extends BatchRequestSettings {
/** Correlates this request to its result. Auto-generated when omitted. */
customId?: string;
messages?: ModelMessage[];
prompt?: string;
}
/** Defaults merged into every request; request-level values take precedence. */
export type BatchDefaults = BatchRequestSettings;
/** Normalized batch lifecycle status, unified across providers. */
export type BatchStatus = "validating" | "in_progress" | "finalizing" | "completed" | "failed" | "expired" | "cancelling" | "cancelled";
/** Normalized per-request tallies. `completed` = succeeded, `failed` = errored. */
export interface BatchRequestCounts {
canceled?: number;
completed: number;
expired?: number;
failed: number;
processing?: number;
total: number;
}
/** Outcome of a single request within a batch. */
export type BatchResultStatus = "succeeded" | "errored" | "expired" | "canceled";
export interface BatchUsage {
inputTokens?: number;
outputTokens?: number;
totalTokens?: number;
}
export interface BatchResultError {
code?: string | number;
message: string;
type?: string;
}
/** A normalized result line, correlated by `customId`. */
export interface BatchResult {
customId: string;
/** Normalized error, present when `status` is `"errored"`. */
error?: BatchResultError;
/** Raw provider response body (OpenAI `response.body` / Anthropic message). */
response?: unknown;
status: BatchResultStatus;
/** Normalized text output, when the request produced a message. */
text?: string;
usage?: BatchUsage;
}
/** A point-in-time, normalized view of a batch's status. */
export interface BatchSnapshot {
completedAt?: Date;
createdAt?: Date;
expiresAt?: Date;
id: string;
provider: BatchProvider;
/** Raw provider status object. */
raw: unknown;
requestCounts: BatchRequestCounts;
status: BatchStatus;
}
/** Provider credentials and connection config. Falls back to env when omitted. */
export interface ProviderCredentials {
apiKey?: string;
baseURL?: string;
/** Extra provider-level HTTP headers (e.g. beta flags). */
headers?: Record<string, string>;
}
/** Input to `batch()`. */
export interface BatchOptions extends ProviderCredentials {
defaults?: BatchDefaults;
metadata?: Record<string, string>;
model: LanguageModel;
requests: BatchRequest[];
}
/**
* Reference to an existing batch, used to rehydrate a handle. Identify the
* provider with either `model` or `provider` (e.g. from a webhook event).
*/
export interface BatchRef extends ProviderCredentials {
id: string;
model?: LanguageModel;
provider?: BatchProvider;
}
export interface WaitOptions {
/** Invoked after every poll with the latest snapshot. */
onPoll?: (snapshot: BatchSnapshot) => void;
/** Delay between polls. Default 15000ms. */
pollIntervalMs?: number;
signal?: AbortSignal;
/** Give up after this long. Default: no timeout. */
timeoutMs?: number;
}
//# sourceMappingURL=types.d.ts.map
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
/** Small, defensive helpers for reading loosely-typed provider JSON. */
export declare const asRecord: (value: unknown) => Record<string, unknown>;
export declare const asString: (value: unknown) => string | undefined;
export declare const asNumber: (value: unknown) => number | undefined;
export declare const asArray: (value: unknown) => unknown[];
/** Return a shallow copy of `obj` without `key`. */
export declare const omit: (obj: Record<string, unknown>, key: string) => Record<string, unknown>;
/** Coerce a provider timestamp (ISO string or unix seconds) to a `Date`. */
export declare const toDate: (value: unknown) => Date | undefined;
//# sourceMappingURL=util.d.ts.map
{"version":3,"file":"util.d.ts","sourceRoot":"","sources":["../src/util.ts"],"names":[],"mappings":"AAAA,wEAAwE;AAExE,eAAO,MAAM,QAAQ,UAAW,OAAO,KAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAK/D,CAAC;AAEF,eAAO,MAAM,QAAQ,UAAW,OAAO,KAAG,MAAM,GAAG,SACJ,CAAC;AAEhD,eAAO,MAAM,QAAQ,UAAW,OAAO,KAAG,MAAM,GAAG,SACJ,CAAC;AAEhD,eAAO,MAAM,OAAO,UAAW,OAAO,KAAG,OAAO,EACb,CAAC;AAEpC,oDAAoD;AACpD,eAAO,MAAM,IAAI,QACV,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,OACvB,MAAM,KACV,MAAM,CAAC,MAAM,EAAE,OAAO,CAQxB,CAAC;AAEF,4EAA4E;AAC5E,eAAO,MAAM,MAAM,UAAW,OAAO,KAAG,IAAI,GAAG,SAO9C,CAAC"}
MIT License
Copyright (c) 2026 Hayden Bleasel
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
# Batchwork
A unified **batch API** for AI providers. Submit thousands of LLM requests at roughly half the cost with a single call — `batchwork` handles JSONL, file uploads, inline submission, polling, and result parsing across every major provider.
[![npm version](https://img.shields.io/npm/v/batchwork.svg)](https://www.npmjs.com/package/batchwork)
[![npm downloads](https://img.shields.io/npm/dm/batchwork.svg)](https://www.npmjs.com/package/batchwork)
[![license](https://img.shields.io/npm/l/batchwork.svg)](#license)
[![Socket Badge](https://socket.dev/api/badge/npm/package/batchwork)](https://socket.dev/npm/package/batchwork)
📖 **Full documentation: [batchwork.dev](https://batchwork.dev)**
## Install
```bash
npm install batchwork
# plus the provider package(s) you use:
npm install @ai-sdk/openai @ai-sdk/anthropic
```
`batchwork` depends only on `ai`. The `@ai-sdk/*` provider packages are **optional peer dependencies** — install only the ones you batch with. Requires Node.js 20 or newer.
## Usage
```ts
import { batch } from "batchwork";
import { openai } from "@ai-sdk/openai";
const job = await batch({
model: openai.chat("gpt-5.5"),
requests: [
{ customId: "a", prompt: "Summarize: …" },
{ customId: "b", messages: [{ role: "user", content: "Translate: …" }] },
],
});
const results = await job.wait().then(() => job.collect());
for (const r of results) {
console.log(r.customId, r.status, r.text);
}
```
Author requests in the same `generateText` shape you already use, pass the AI SDK models you already use, and get back one normalized result type correlated by `customId`.
## Features
- **One API, many providers** — OpenAI, Anthropic, Google Gemini, Groq, Mistral, Together AI, and xAI.
- **AI SDK native** — author requests in the familiar `generateText` shape.
- **~50% cheaper** — every request runs against the provider's batch window.
- **Normalized results** — unified status, text, usage, and error types regardless of provider.
- **Server-ready** — optional layers for managed polling, unified webhooks, and Next.js route handlers.
- **Durable stores** — drop-in Postgres (`batchwork/postgres`) and Upstash Redis (`batchwork/redis`) adapters for the poller, or bring your own.
Guides for models, the job handle, rehydration, the server layer, and Next.js handlers all live at **[batchwork.dev](https://batchwork.dev)**.
## License
[MIT](https://opensource.org/licenses/MIT) © [Hayden Bleasel](https://github.com/haydenbleasel)
+119
-7
{
"name": "batchwork",
"version": "0.0.0",
"description": "",
"license": "ISC",
"author": "",
"type": "commonjs",
"main": "index.js",
"version": "1.0.0",
"description": "Unified batch API for AI providers — low-cost LLM batch processing at scale.",
"keywords": [
"ai",
"ai-sdk",
"anthropic",
"batch",
"llm",
"openai",
"vercel"
],
"homepage": "https://github.com/haydenbleasel/batchwork#readme",
"bugs": {
"url": "https://github.com/haydenbleasel/batchwork/issues"
},
"license": "MIT",
"author": "Hayden Bleasel",
"repository": {
"type": "git",
"url": "git+https://github.com/haydenbleasel/batchwork.git",
"directory": "packages/batchwork"
},
"files": [
"dist"
],
"type": "module",
"main": "./dist/index.js",
"module": "./dist/index.js",
"types": "./dist/index.d.ts",
"exports": {
".": {
"types": "./dist/index.d.ts",
"import": "./dist/index.js"
},
"./server": {
"types": "./dist/server/index.d.ts",
"import": "./dist/server/index.js"
},
"./next": {
"types": "./dist/next/index.d.ts",
"import": "./dist/next/index.js"
},
"./postgres": {
"types": "./dist/postgres/index.d.ts",
"import": "./dist/postgres/index.js"
},
"./redis": {
"types": "./dist/redis/index.d.ts",
"import": "./dist/redis/index.js"
}
},
"publishConfig": {
"access": "public"
},
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
"build": "bun run scripts/build.ts",
"dev": "bun run scripts/build.ts --watch",
"typecheck": "tsgo --noEmit && tsgo --noEmit -p tsconfig.bun.json",
"test": "bun test ./test",
"test:coverage": "bun test ./test --coverage",
"test:live": "bun test --env-file=.env.local ./live"
},
"dependencies": {
"ai": "^6.0.204"
},
"devDependencies": {
"@ai-sdk/anthropic": "^3.0.84",
"@ai-sdk/google": "^3.0.82",
"@ai-sdk/groq": "^3.0.41",
"@ai-sdk/mistral": "^3.0.39",
"@ai-sdk/openai": "^3.0.71",
"@ai-sdk/togetherai": "^2.0.55",
"@ai-sdk/xai": "^3.0.95",
"@electric-sql/pglite": "^0.5.2",
"@types/bun": "^1.3.14",
"@types/node": "^25.9.3",
"@types/pg": "^8.20.0",
"@typescript/native-preview": "^7.0.0-dev.20260613.1",
"@upstash/redis": "^1.38.0",
"pg": "^8.21.0",
"typescript": "^6.0.3"
},
"peerDependencies": {
"@ai-sdk/anthropic": ">=3",
"@ai-sdk/google": ">=3",
"@ai-sdk/groq": ">=3",
"@ai-sdk/mistral": ">=3",
"@ai-sdk/openai": ">=3",
"@ai-sdk/togetherai": ">=2",
"@ai-sdk/xai": ">=3",
"@upstash/redis": ">=1"
},
"peerDependenciesMeta": {
"@upstash/redis": {
"optional": true
},
"@ai-sdk/anthropic": {
"optional": true
},
"@ai-sdk/google": {
"optional": true
},
"@ai-sdk/groq": {
"optional": true
},
"@ai-sdk/mistral": {
"optional": true
},
"@ai-sdk/openai": {
"optional": true
},
"@ai-sdk/togetherai": {
"optional": true
},
"@ai-sdk/xai": {
"optional": true
}
},
"engines": {
"node": ">=20"
}
}