| 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"} |
+118
| 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"} |
+21
| 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. |
+57
| # 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. | ||
| [](https://www.npmjs.com/package/batchwork) | ||
| [](https://www.npmjs.com/package/batchwork) | ||
| [](#license) | ||
| [](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" | ||
| } | ||
| } |
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
Network access
Supply chain riskThis module accesses the network.
Found 1 instance in 1 package
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
Found 4 instances in 1 package
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
Found 1 instance in 1 package
Empty package
Supply chain riskPackage does not contain any code. It may be removed, is name squatting, or the result of a faulty package publish.
Found 1 instance in 1 package
No README
QualityPackage does not have a README. This may indicate a failed publish or a low quality package.
Found 1 instance in 1 package
No contributors or author data
MaintenancePackage does not specify a list of contributors or an author in package.json.
Found 1 instance in 1 package
No bug tracker
MaintenancePackage does not have a linked bug tracker in package.json.
Found 1 instance in 1 package
No repository
Supply chain riskPackage does not have a linked source code repository. Without this field, a package will have no reference to the location of the source code use to generate the package.
Found 1 instance in 1 package
No tests
QualityPackage does not have any tests. This is a strong signal of a poorly maintained or low quality package.
Found 1 instance in 1 package
No v1
QualityPackage is not semver >=1. This means it is not stable and does not support ^ ranges.
Found 1 instance in 1 package
No website
QualityPackage does not have a website.
Found 1 instance in 1 package
258517
113784.14%91
9000%2514
Infinity%0
-100%0
-100%0
-100%58
Infinity%Yes
NaN9
Infinity%15
Infinity%5
400%8
700%+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added