batchwork 0.0.0 → 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (95) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +57 -0
  3. package/dist/batch.d.ts +27 -0
  4. package/dist/batch.d.ts.map +1 -0
  5. package/dist/body.d.ts +19 -0
  6. package/dist/body.d.ts.map +1 -0
  7. package/dist/chunk-e6qn48qa.js +301 -0
  8. package/dist/chunk-e6qn48qa.js.map +13 -0
  9. package/dist/chunk-g481f961.js +1212 -0
  10. package/dist/chunk-g481f961.js.map +26 -0
  11. package/dist/chunk-m4n610nm.js +338 -0
  12. package/dist/chunk-m4n610nm.js.map +12 -0
  13. package/dist/chunk-v0bahtg2.js +7 -0
  14. package/dist/chunk-v0bahtg2.js.map +9 -0
  15. package/dist/errors.d.ts +16 -0
  16. package/dist/errors.d.ts.map +1 -0
  17. package/dist/http.d.ts +5 -0
  18. package/dist/http.d.ts.map +1 -0
  19. package/dist/index.d.ts +6 -0
  20. package/dist/index.d.ts.map +1 -0
  21. package/dist/index.js +30 -0
  22. package/dist/index.js.map +9 -0
  23. package/dist/job.d.ts +31 -0
  24. package/dist/job.d.ts.map +1 -0
  25. package/dist/jsonl.d.ts +7 -0
  26. package/dist/jsonl.d.ts.map +1 -0
  27. package/dist/limits.d.ts +12 -0
  28. package/dist/limits.d.ts.map +1 -0
  29. package/dist/model.d.ts +32 -0
  30. package/dist/model.d.ts.map +1 -0
  31. package/dist/next/index.d.ts +61 -0
  32. package/dist/next/index.d.ts.map +1 -0
  33. package/dist/next/index.js +64 -0
  34. package/dist/next/index.js.map +10 -0
  35. package/dist/postgres/batch-store.d.ts +19 -0
  36. package/dist/postgres/batch-store.d.ts.map +1 -0
  37. package/dist/postgres/index.d.ts +6 -0
  38. package/dist/postgres/index.d.ts.map +1 -0
  39. package/dist/postgres/index.js +101 -0
  40. package/dist/postgres/index.js.map +12 -0
  41. package/dist/postgres/migrate.d.ts +15 -0
  42. package/dist/postgres/migrate.d.ts.map +1 -0
  43. package/dist/postgres/types.d.ts +26 -0
  44. package/dist/postgres/types.d.ts.map +1 -0
  45. package/dist/providers/adapter.d.ts +25 -0
  46. package/dist/providers/adapter.d.ts.map +1 -0
  47. package/dist/providers/anthropic.d.ts +3 -0
  48. package/dist/providers/anthropic.d.ts.map +1 -0
  49. package/dist/providers/google.d.ts +8 -0
  50. package/dist/providers/google.d.ts.map +1 -0
  51. package/dist/providers/groq.d.ts +8 -0
  52. package/dist/providers/groq.d.ts.map +1 -0
  53. package/dist/providers/ids.d.ts +3 -0
  54. package/dist/providers/ids.d.ts.map +1 -0
  55. package/dist/providers/index.d.ts +5 -0
  56. package/dist/providers/index.d.ts.map +1 -0
  57. package/dist/providers/mistral.d.ts +8 -0
  58. package/dist/providers/mistral.d.ts.map +1 -0
  59. package/dist/providers/openai-compatible.d.ts +41 -0
  60. package/dist/providers/openai-compatible.d.ts.map +1 -0
  61. package/dist/providers/openai.d.ts +7 -0
  62. package/dist/providers/openai.d.ts.map +1 -0
  63. package/dist/providers/shared.d.ts +23 -0
  64. package/dist/providers/shared.d.ts.map +1 -0
  65. package/dist/providers/together.d.ts +8 -0
  66. package/dist/providers/together.d.ts.map +1 -0
  67. package/dist/providers/xai.d.ts +8 -0
  68. package/dist/providers/xai.d.ts.map +1 -0
  69. package/dist/redis/batch-store.d.ts +15 -0
  70. package/dist/redis/batch-store.d.ts.map +1 -0
  71. package/dist/redis/index.d.ts +4 -0
  72. package/dist/redis/index.d.ts.map +1 -0
  73. package/dist/redis/index.js +55 -0
  74. package/dist/redis/index.js.map +11 -0
  75. package/dist/redis/types.d.ts +19 -0
  76. package/dist/redis/types.d.ts.map +1 -0
  77. package/dist/server/events.d.ts +5 -0
  78. package/dist/server/events.d.ts.map +1 -0
  79. package/dist/server/index.d.ts +7 -0
  80. package/dist/server/index.d.ts.map +1 -0
  81. package/dist/server/index.js +19 -0
  82. package/dist/server/index.js.map +9 -0
  83. package/dist/server/poller.d.ts +63 -0
  84. package/dist/server/poller.d.ts.map +1 -0
  85. package/dist/server/signing.d.ts +27 -0
  86. package/dist/server/signing.d.ts.map +1 -0
  87. package/dist/server/store.d.ts +4 -0
  88. package/dist/server/store.d.ts.map +1 -0
  89. package/dist/server/types.d.ts +37 -0
  90. package/dist/server/types.d.ts.map +1 -0
  91. package/dist/types.d.ts +125 -0
  92. package/dist/types.d.ts.map +1 -0
  93. package/dist/util.d.ts +10 -0
  94. package/dist/util.d.ts.map +1 -0
  95. package/package.json +119 -7
@@ -0,0 +1,1212 @@
1
+ // src/errors.ts
2
+ class BatchworkError extends Error {
3
+ constructor(message, options) {
4
+ super(message, options);
5
+ this.name = "BatchworkError";
6
+ }
7
+ }
8
+
9
+ class UnsupportedProviderError extends BatchworkError {
10
+ provider;
11
+ constructor(provider) {
12
+ super(`batchwork: provider "${provider}" is not supported yet. Supported providers: openai, anthropic, google, groq, mistral, together, xai.`);
13
+ this.name = "UnsupportedProviderError";
14
+ this.provider = provider;
15
+ }
16
+ }
17
+
18
+ class MissingDependencyError extends BatchworkError {
19
+ constructor(pkg, provider) {
20
+ super(`batchwork: install \`${pkg}\` to batch ${provider} models (\`npm install ${pkg}\`).`);
21
+ this.name = "MissingDependencyError";
22
+ }
23
+ }
24
+
25
+ // src/job.ts
26
+ var DEFAULT_POLL_INTERVAL_MS = 15000;
27
+ var TERMINAL_STATUSES = new Set([
28
+ "completed",
29
+ "failed",
30
+ "expired",
31
+ "cancelled"
32
+ ]);
33
+ var isTerminalStatus = (status) => TERMINAL_STATUSES.has(status);
34
+ var delay = (ms, signal) => new Promise((resolve, reject) => {
35
+ if (signal?.aborted) {
36
+ reject(new BatchworkError("batchwork: wait aborted."));
37
+ return;
38
+ }
39
+ const timer = setTimeout(resolve, ms);
40
+ signal?.addEventListener("abort", () => {
41
+ clearTimeout(timer);
42
+ reject(new BatchworkError("batchwork: wait aborted."));
43
+ }, { once: true });
44
+ });
45
+
46
+ class BatchJob {
47
+ provider;
48
+ id;
49
+ #adapter;
50
+ #credentials;
51
+ #snapshot;
52
+ constructor(adapter, credentials, snapshot) {
53
+ this.#adapter = adapter;
54
+ this.#credentials = credentials;
55
+ this.#snapshot = snapshot;
56
+ this.id = snapshot.id;
57
+ this.provider = snapshot.provider;
58
+ }
59
+ get status() {
60
+ return this.#snapshot.status;
61
+ }
62
+ get requestCounts() {
63
+ return this.#snapshot.requestCounts;
64
+ }
65
+ get snapshot() {
66
+ return this.#snapshot;
67
+ }
68
+ async poll() {
69
+ this.#snapshot = await this.#adapter.retrieve(this.id, this.#credentials);
70
+ return this.#snapshot;
71
+ }
72
+ async wait(options = {}) {
73
+ const interval = options.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS;
74
+ const deadline = options.timeoutMs ? Date.now() + options.timeoutMs : undefined;
75
+ let snapshot = await this.poll();
76
+ options.onPoll?.(snapshot);
77
+ while (!isTerminalStatus(snapshot.status)) {
78
+ if (options.signal?.aborted) {
79
+ throw new BatchworkError("batchwork: wait aborted.");
80
+ }
81
+ if (deadline !== undefined && Date.now() > deadline) {
82
+ throw new BatchworkError(`batchwork: timed out waiting for batch "${this.id}".`);
83
+ }
84
+ await delay(interval, options.signal);
85
+ snapshot = await this.poll();
86
+ options.onPoll?.(snapshot);
87
+ }
88
+ return snapshot;
89
+ }
90
+ results() {
91
+ return this.#adapter.results(this.id, this.#credentials);
92
+ }
93
+ async collect() {
94
+ const out = [];
95
+ for await (const result of this.results()) {
96
+ out.push(result);
97
+ }
98
+ return out;
99
+ }
100
+ async cancel() {
101
+ await this.#adapter.cancel(this.id, this.#credentials);
102
+ return await this.poll();
103
+ }
104
+ }
105
+
106
+ // src/limits.ts
107
+ var DEFAULT_LIMITS = {
108
+ captureConcurrency: 16,
109
+ maxRequestBytes: 20 * 1024 * 1024,
110
+ maxRequests: 50000,
111
+ maxUploadBytes: 200 * 1024 * 1024
112
+ };
113
+ var encoder = new TextEncoder;
114
+ var positiveInteger = (name, value) => {
115
+ if (!(Number.isInteger(value) && value > 0)) {
116
+ throw new BatchworkError(`batchwork: limits.${name} must be a positive integer.`);
117
+ }
118
+ return value;
119
+ };
120
+ var resolveBatchLimits = (limits) => ({
121
+ captureConcurrency: positiveInteger("captureConcurrency", limits?.captureConcurrency ?? DEFAULT_LIMITS.captureConcurrency),
122
+ maxRequestBytes: positiveInteger("maxRequestBytes", limits?.maxRequestBytes ?? DEFAULT_LIMITS.maxRequestBytes),
123
+ maxRequests: positiveInteger("maxRequests", limits?.maxRequests ?? DEFAULT_LIMITS.maxRequests),
124
+ maxUploadBytes: positiveInteger("maxUploadBytes", limits?.maxUploadBytes ?? DEFAULT_LIMITS.maxUploadBytes)
125
+ });
126
+ var byteLength = (value) => encoder.encode(value).length;
127
+ var assertByteLength = (label, value, maxBytes) => {
128
+ const bytes = byteLength(value);
129
+ if (bytes > maxBytes) {
130
+ throw new BatchworkError(`batchwork: ${label} is ${bytes} bytes, exceeding the ${maxBytes} byte limit.`);
131
+ }
132
+ };
133
+ var mapWithConcurrency = async (items, concurrency, mapper) => {
134
+ const results = [];
135
+ results.length = items.length;
136
+ let nextIndex = 0;
137
+ const workerCount = Math.min(concurrency, items.length);
138
+ const runNext = async () => {
139
+ const index = nextIndex;
140
+ nextIndex += 1;
141
+ if (index >= items.length) {
142
+ return;
143
+ }
144
+ results[index] = await mapper(items[index]);
145
+ await runNext();
146
+ };
147
+ await Promise.all(Array.from({ length: workerCount }, () => runNext()));
148
+ return results;
149
+ };
150
+
151
+ // src/http.ts
152
+ var assertOk = (url, init, response) => {
153
+ if (!response.ok) {
154
+ throw new BatchworkError(`batchwork: ${init.method ?? "GET"} ${url} failed with ${response.status}.`);
155
+ }
156
+ };
157
+ var requestJson = async (url, init) => {
158
+ const response = await fetch(url, init);
159
+ assertOk(url, init, response);
160
+ return await response.json();
161
+ };
162
+ var requestStream = async (url, init) => {
163
+ const response = await fetch(url, init);
164
+ assertOk(url, init, response);
165
+ if (!response.body) {
166
+ throw new BatchworkError(`batchwork: ${url} returned an empty body.`);
167
+ }
168
+ return response.body;
169
+ };
170
+
171
+ // src/jsonl.ts
172
+ var NEWLINE = `
173
+ `;
174
+ var DEFAULT_MAX_JSONL_LINE_BYTES = 20 * 1024 * 1024;
175
+ var resolveMaxLineBytes = (options) => {
176
+ const maxLineBytes = options?.maxLineBytes ?? DEFAULT_MAX_JSONL_LINE_BYTES;
177
+ if (!(Number.isInteger(maxLineBytes) && maxLineBytes > 0)) {
178
+ throw new BatchworkError("batchwork: JSONL maxLineBytes must be a positive integer.");
179
+ }
180
+ return maxLineBytes;
181
+ };
182
+ var assertLineSize = (line, lineNumber, maxLineBytes) => {
183
+ const bytes = byteLength(line);
184
+ if (bytes > maxLineBytes) {
185
+ throw new BatchworkError(`batchwork: JSONL line ${lineNumber} is ${bytes} bytes, exceeding the ${maxLineBytes} byte limit.`);
186
+ }
187
+ };
188
+ var parseLine = (line, lineNumber, maxLineBytes) => {
189
+ assertLineSize(line, lineNumber, maxLineBytes);
190
+ const trimmed = line.trim();
191
+ if (trimmed.length === 0) {
192
+ return;
193
+ }
194
+ try {
195
+ return JSON.parse(trimmed);
196
+ } catch (error) {
197
+ throw new BatchworkError(`batchwork: invalid JSONL at line ${lineNumber}.`, { cause: error });
198
+ }
199
+ };
200
+ var encodeJsonl = (items) => {
201
+ if (items.length === 0) {
202
+ return "";
203
+ }
204
+ const body = items.map((item) => JSON.stringify(item)).join(NEWLINE);
205
+ return `${body}${NEWLINE}`;
206
+ };
207
+ var isReadableStream = (source) => ("getReader" in source) && typeof source.getReader === "function";
208
+ async function* toByteIterable(source) {
209
+ if (isReadableStream(source)) {
210
+ const reader = source.getReader();
211
+ try {
212
+ let chunk = await reader.read();
213
+ while (!chunk.done) {
214
+ if (chunk.value) {
215
+ yield chunk.value;
216
+ }
217
+ chunk = await reader.read();
218
+ }
219
+ } finally {
220
+ reader.releaseLock();
221
+ }
222
+ return;
223
+ }
224
+ yield* source;
225
+ }
226
+ async function* streamJsonl(source, options) {
227
+ const decoder = new TextDecoder;
228
+ const maxLineBytes = resolveMaxLineBytes(options);
229
+ let buffer = "";
230
+ let lineNumber = 1;
231
+ for await (const chunk of toByteIterable(source)) {
232
+ buffer += decoder.decode(chunk, { stream: true });
233
+ let newlineIndex = buffer.indexOf(NEWLINE);
234
+ while (newlineIndex !== -1) {
235
+ const line = buffer.slice(0, newlineIndex);
236
+ buffer = buffer.slice(newlineIndex + 1);
237
+ const parsed2 = parseLine(line, lineNumber, maxLineBytes);
238
+ if (parsed2 !== undefined) {
239
+ yield parsed2;
240
+ }
241
+ lineNumber += 1;
242
+ newlineIndex = buffer.indexOf(NEWLINE);
243
+ }
244
+ assertLineSize(buffer, lineNumber, maxLineBytes);
245
+ }
246
+ buffer += decoder.decode();
247
+ const parsed = parseLine(buffer, lineNumber, maxLineBytes);
248
+ if (parsed !== undefined) {
249
+ yield parsed;
250
+ }
251
+ }
252
+
253
+ // src/util.ts
254
+ var asRecord = (value) => {
255
+ if (typeof value === "object" && value !== null) {
256
+ return value;
257
+ }
258
+ return {};
259
+ };
260
+ var asString = (value) => typeof value === "string" ? value : undefined;
261
+ var asNumber = (value) => typeof value === "number" ? value : undefined;
262
+ var asArray = (value) => Array.isArray(value) ? value : [];
263
+ var omit = (obj, key) => {
264
+ const result = {};
265
+ for (const [k, v] of Object.entries(obj)) {
266
+ if (k !== key) {
267
+ result[k] = v;
268
+ }
269
+ }
270
+ return result;
271
+ };
272
+ var toDate = (value) => {
273
+ if (typeof value === "string") {
274
+ return new Date(value);
275
+ }
276
+ if (typeof value === "number") {
277
+ return new Date(value * 1000);
278
+ }
279
+ };
280
+
281
+ // src/providers/ids.ts
282
+ var SIMPLE_PROVIDER_ID = /^[A-Za-z0-9_-]+$/u;
283
+ var assertSimpleProviderId = (label, id) => {
284
+ if (!SIMPLE_PROVIDER_ID.test(id)) {
285
+ throw new BatchworkError(`batchwork: invalid ${label}.`);
286
+ }
287
+ return id;
288
+ };
289
+ var assertPrefixedProviderId = (label, id, prefix) => {
290
+ const [actualPrefix, value, ...rest] = id.split("/");
291
+ if (rest.length > 0 || actualPrefix !== prefix || !value || !SIMPLE_PROVIDER_ID.test(value)) {
292
+ throw new BatchworkError(`batchwork: invalid ${label}.`);
293
+ }
294
+ return id;
295
+ };
296
+
297
+ // src/providers/anthropic.ts
298
+ var ANTHROPIC_BASE = "https://api.anthropic.com";
299
+ var ANTHROPIC_VERSION = "2023-06-01";
300
+ var apiKey = (credentials) => {
301
+ const key = credentials.apiKey ?? process.env.ANTHROPIC_API_KEY;
302
+ if (!key) {
303
+ throw new BatchworkError("batchwork: missing Anthropic API key. Set ANTHROPIC_API_KEY or pass `apiKey`.");
304
+ }
305
+ return key;
306
+ };
307
+ var baseUrl = (credentials) => credentials.baseURL ?? ANTHROPIC_BASE;
308
+ var validateResultsUrl = (rawUrl, credentials) => {
309
+ let resultsUrl;
310
+ let expectedBase;
311
+ try {
312
+ resultsUrl = new URL(rawUrl);
313
+ expectedBase = new URL(baseUrl(credentials));
314
+ } catch (error) {
315
+ throw new BatchworkError("batchwork: invalid Anthropic results_url.", {
316
+ cause: error
317
+ });
318
+ }
319
+ if (resultsUrl.origin !== expectedBase.origin) {
320
+ throw new BatchworkError("batchwork: Anthropic results_url must match the configured API origin.");
321
+ }
322
+ if (resultsUrl.username || resultsUrl.password) {
323
+ throw new BatchworkError("batchwork: Anthropic results_url must not include credentials.");
324
+ }
325
+ return resultsUrl.toString();
326
+ };
327
+ var headers = (credentials) => ({
328
+ "anthropic-version": ANTHROPIC_VERSION,
329
+ "content-type": "application/json",
330
+ "x-api-key": apiKey(credentials),
331
+ ...credentials.headers
332
+ });
333
+ var mapStatus = (status) => {
334
+ if (status === "ended") {
335
+ return "completed";
336
+ }
337
+ if (status === "canceling") {
338
+ return "cancelling";
339
+ }
340
+ return "in_progress";
341
+ };
342
+ var normalizeSnapshot = (raw) => {
343
+ const obj = asRecord(raw);
344
+ const counts = asRecord(obj.request_counts);
345
+ const succeeded = asNumber(counts.succeeded) ?? 0;
346
+ const errored = asNumber(counts.errored) ?? 0;
347
+ const processing = asNumber(counts.processing) ?? 0;
348
+ const canceled = asNumber(counts.canceled) ?? 0;
349
+ const expired = asNumber(counts.expired) ?? 0;
350
+ return {
351
+ completedAt: toDate(obj.ended_at),
352
+ createdAt: toDate(obj.created_at),
353
+ expiresAt: toDate(obj.expires_at),
354
+ id: asString(obj.id) ?? "",
355
+ provider: "anthropic",
356
+ raw,
357
+ requestCounts: {
358
+ canceled,
359
+ completed: succeeded,
360
+ expired,
361
+ failed: errored,
362
+ processing,
363
+ total: succeeded + errored + processing + canceled + expired
364
+ },
365
+ status: mapStatus(asString(obj.processing_status))
366
+ };
367
+ };
368
+ var textFromMessage = (message) => {
369
+ const text = asArray(asRecord(message).content).map((block) => asRecord(block)).filter((block) => block.type === "text").map((block) => asString(block.text) ?? "").join("");
370
+ return text.length > 0 ? text : undefined;
371
+ };
372
+ var usageFromMessage = (message) => {
373
+ const usage = asRecord(asRecord(message).usage);
374
+ const inputTokens = asNumber(usage.input_tokens);
375
+ const outputTokens = asNumber(usage.output_tokens);
376
+ if (inputTokens === undefined && outputTokens === undefined) {
377
+ return;
378
+ }
379
+ return {
380
+ inputTokens,
381
+ outputTokens,
382
+ totalTokens: (inputTokens ?? 0) + (outputTokens ?? 0)
383
+ };
384
+ };
385
+ var normalizeResult = (line) => {
386
+ const obj = asRecord(line);
387
+ const customId = asString(obj.custom_id) ?? "";
388
+ const result = asRecord(obj.result);
389
+ const type = asString(result.type);
390
+ if (type === "succeeded") {
391
+ return {
392
+ customId,
393
+ response: result.message,
394
+ status: "succeeded",
395
+ text: textFromMessage(result.message),
396
+ usage: usageFromMessage(result.message)
397
+ };
398
+ }
399
+ if (type === "errored") {
400
+ const error = asRecord(result.error);
401
+ const nested = asRecord(error.error);
402
+ const source = Object.keys(nested).length > 0 ? nested : error;
403
+ return {
404
+ customId,
405
+ error: {
406
+ message: asString(source.message) ?? "Request errored.",
407
+ type: asString(source.type)
408
+ },
409
+ response: result.error,
410
+ status: "errored"
411
+ };
412
+ }
413
+ if (type === "expired") {
414
+ return { customId, status: "expired" };
415
+ }
416
+ return { customId, status: "canceled" };
417
+ };
418
+ var submit = async (input) => {
419
+ const limits = resolveBatchLimits(input.limits);
420
+ const requests = input.built.map((item) => ({
421
+ custom_id: item.customId,
422
+ params: omit(item.body, "stream")
423
+ }));
424
+ const body = JSON.stringify({ requests });
425
+ assertByteLength("batch upload payload", body, limits.maxUploadBytes);
426
+ const raw = await requestJson(`${baseUrl(input.credentials)}/v1/messages/batches`, {
427
+ body,
428
+ headers: headers(input.credentials),
429
+ method: "POST"
430
+ });
431
+ return normalizeSnapshot(raw);
432
+ };
433
+ var retrieve = async (id, credentials) => {
434
+ const batchId = assertSimpleProviderId("Anthropic batch id", id);
435
+ const raw = await requestJson(`${baseUrl(credentials)}/v1/messages/batches/${batchId}`, { headers: headers(credentials) });
436
+ return normalizeSnapshot(raw);
437
+ };
438
+ async function* results(id, credentials) {
439
+ const snapshot = await retrieve(id, credentials);
440
+ const resultsUrl = asString(asRecord(snapshot.raw).results_url);
441
+ if (!resultsUrl) {
442
+ throw new BatchworkError(`batchwork: results are not ready for batch "${id}" (status: ${snapshot.status}).`);
443
+ }
444
+ const stream = await requestStream(validateResultsUrl(resultsUrl, credentials), {
445
+ headers: headers(credentials)
446
+ });
447
+ for await (const line of streamJsonl(stream)) {
448
+ yield normalizeResult(line);
449
+ }
450
+ }
451
+ var cancel = async (id, credentials) => {
452
+ const batchId = assertSimpleProviderId("Anthropic batch id", id);
453
+ await requestJson(`${baseUrl(credentials)}/v1/messages/batches/${batchId}/cancel`, {
454
+ headers: headers(credentials),
455
+ method: "POST"
456
+ });
457
+ };
458
+ var anthropicAdapter = {
459
+ cancel,
460
+ id: "anthropic",
461
+ results,
462
+ retrieve,
463
+ submit
464
+ };
465
+
466
+ // src/providers/google.ts
467
+ var GOOGLE_BASE = "https://generativelanguage.googleapis.com/v1beta";
468
+ var GOOGLE_BATCH_PREFIX = "batches";
469
+ var apiKey2 = (credentials) => {
470
+ const key = credentials.apiKey ?? process.env.GOOGLE_GENERATIVE_AI_API_KEY ?? process.env.GEMINI_API_KEY;
471
+ if (!key) {
472
+ throw new BatchworkError("batchwork: missing Google Gemini API key. Set GOOGLE_GENERATIVE_AI_API_KEY (or GEMINI_API_KEY) or pass `apiKey`.");
473
+ }
474
+ return key;
475
+ };
476
+ var baseUrl2 = (credentials) => credentials.baseURL ?? GOOGLE_BASE;
477
+ var headers2 = (credentials) => ({
478
+ "content-type": "application/json",
479
+ "x-goog-api-key": apiKey2(credentials),
480
+ ...credentials.headers
481
+ });
482
+ var mapState = (state, done) => {
483
+ if (state) {
484
+ if (state.endsWith("SUCCEEDED")) {
485
+ return "completed";
486
+ }
487
+ if (state.endsWith("FAILED")) {
488
+ return "failed";
489
+ }
490
+ if (state.endsWith("CANCELLED")) {
491
+ return "cancelled";
492
+ }
493
+ if (state.endsWith("EXPIRED")) {
494
+ return "expired";
495
+ }
496
+ if (state.endsWith("PENDING")) {
497
+ return "validating";
498
+ }
499
+ if (state.endsWith("RUNNING")) {
500
+ return "in_progress";
501
+ }
502
+ }
503
+ return done ? "completed" : "in_progress";
504
+ };
505
+ var inlinedResponses = (raw) => {
506
+ const obj = asRecord(raw);
507
+ const response = asRecord(obj.response);
508
+ const dest = asRecord(obj.dest);
509
+ const responseInline = response.inlinedResponses ?? response.inlined_responses;
510
+ const destInline = dest.inlinedResponses ?? dest.inlined_responses;
511
+ const nestedResponseInline = asRecord(responseInline);
512
+ const nestedDestInline = asRecord(destInline);
513
+ return [
514
+ ...asArray(responseInline),
515
+ ...asArray(nestedResponseInline.inlinedResponses),
516
+ ...asArray(nestedResponseInline.inlined_responses),
517
+ ...asArray(destInline),
518
+ ...asArray(nestedDestInline.inlinedResponses),
519
+ ...asArray(nestedDestInline.inlined_responses)
520
+ ];
521
+ };
522
+ var normalizeSnapshot2 = (raw) => {
523
+ const obj = asRecord(raw);
524
+ const items = inlinedResponses(raw);
525
+ const failed = items.filter((item) => asRecord(item).error).length;
526
+ const id = asString(obj.name) ?? "";
527
+ return {
528
+ id: id ? assertPrefixedProviderId("Google operation id", id, GOOGLE_BATCH_PREFIX) : "",
529
+ provider: "google",
530
+ raw,
531
+ requestCounts: {
532
+ completed: items.length - failed,
533
+ failed,
534
+ total: items.length
535
+ },
536
+ status: mapState(asString(obj.state) ?? asString(asRecord(obj.state).name) ?? asString(asRecord(obj.metadata).state), obj.done === true)
537
+ };
538
+ };
539
+ var textFromResponse = (response) => {
540
+ const candidate = asRecord(asArray(asRecord(response).candidates)[0]);
541
+ const text = asArray(asRecord(candidate.content).parts).map((part) => asString(asRecord(part).text) ?? "").join("");
542
+ return text.length > 0 ? text : undefined;
543
+ };
544
+ var usageFromResponse = (response) => {
545
+ const usage = asRecord(asRecord(response).usageMetadata);
546
+ const inputTokens = asNumber(usage.promptTokenCount);
547
+ const outputTokens = asNumber(usage.candidatesTokenCount);
548
+ const totalTokens = asNumber(usage.totalTokenCount);
549
+ if (inputTokens === undefined && outputTokens === undefined && totalTokens === undefined) {
550
+ return;
551
+ }
552
+ return {
553
+ inputTokens,
554
+ outputTokens,
555
+ totalTokens: totalTokens ?? (inputTokens ?? 0) + (outputTokens ?? 0)
556
+ };
557
+ };
558
+ var normalizeResult2 = (item) => {
559
+ const obj = asRecord(item);
560
+ const customId = asString(asRecord(obj.metadata).key) ?? asString(obj.key) ?? asString(obj.custom_id) ?? "";
561
+ if (obj.error) {
562
+ const error = asRecord(obj.error);
563
+ return {
564
+ customId,
565
+ error: {
566
+ code: asNumber(error.code) ?? asString(error.code),
567
+ message: asString(error.message) ?? "Request errored.",
568
+ type: asString(error.status)
569
+ },
570
+ response: obj.error,
571
+ status: "errored"
572
+ };
573
+ }
574
+ return {
575
+ customId,
576
+ response: obj.response,
577
+ status: "succeeded",
578
+ text: textFromResponse(obj.response),
579
+ usage: usageFromResponse(obj.response)
580
+ };
581
+ };
582
+ var submit2 = async (input) => {
583
+ const limits = resolveBatchLimits(input.limits);
584
+ const requests = input.built.map((item) => ({
585
+ metadata: { key: item.customId },
586
+ request: omit(item.body, "stream")
587
+ }));
588
+ const body = JSON.stringify({
589
+ batch: {
590
+ display_name: "batchwork",
591
+ input_config: { requests: { requests } }
592
+ }
593
+ });
594
+ assertByteLength("batch upload payload", body, limits.maxUploadBytes);
595
+ const raw = await requestJson(`${baseUrl2(input.credentials)}/models/${input.modelId}:batchGenerateContent`, {
596
+ body,
597
+ headers: headers2(input.credentials),
598
+ method: "POST"
599
+ });
600
+ return normalizeSnapshot2(raw);
601
+ };
602
+ var retrieve2 = async (id, credentials) => {
603
+ const operationId = assertPrefixedProviderId("Google operation id", id, GOOGLE_BATCH_PREFIX);
604
+ const raw = await requestJson(`${baseUrl2(credentials)}/${operationId}`, {
605
+ headers: headers2(credentials)
606
+ });
607
+ return normalizeSnapshot2(raw);
608
+ };
609
+ async function* results2(id, credentials) {
610
+ const snapshot = await retrieve2(id, credentials);
611
+ const raw = asRecord(snapshot.raw);
612
+ const response = asRecord(raw.response);
613
+ const dest = asRecord(raw.dest);
614
+ 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);
615
+ if (responsesFile) {
616
+ throw new BatchworkError(`batchwork: batch "${id}" returned file-mode results, which are not supported yet.`);
617
+ }
618
+ const items = inlinedResponses(raw);
619
+ if (items.length === 0) {
620
+ throw new BatchworkError(`batchwork: results are not ready for batch "${id}" (status: ${snapshot.status}).`);
621
+ }
622
+ for (const item of items) {
623
+ yield normalizeResult2(item);
624
+ }
625
+ }
626
+ var cancel2 = async (id, credentials) => {
627
+ const operationId = assertPrefixedProviderId("Google operation id", id, GOOGLE_BATCH_PREFIX);
628
+ await requestJson(`${baseUrl2(credentials)}/${operationId}:cancel`, {
629
+ headers: headers2(credentials),
630
+ method: "POST"
631
+ });
632
+ };
633
+ var googleAdapter = {
634
+ cancel: cancel2,
635
+ id: "google",
636
+ results: results2,
637
+ retrieve: retrieve2,
638
+ submit: submit2
639
+ };
640
+
641
+ // src/providers/shared.ts
642
+ var HTTP_OK_MIN = 200;
643
+ var HTTP_OK_MAX = 300;
644
+ var resolveApiKey = (credentials, envVar, label) => {
645
+ const key = credentials.apiKey ?? process.env[envVar];
646
+ if (!key) {
647
+ throw new BatchworkError(`batchwork: missing ${label} API key. Set ${envVar} or pass \`apiKey\`.`);
648
+ }
649
+ return key;
650
+ };
651
+ var textFromBody = (body) => {
652
+ const obj = asRecord(body);
653
+ const choices = asArray(obj.choices);
654
+ if (choices.length > 0) {
655
+ const content = asString(asRecord(asRecord(choices[0]).message).content);
656
+ if (content) {
657
+ return content;
658
+ }
659
+ }
660
+ return asString(obj.output_text);
661
+ };
662
+ var usageFromBody = (body) => {
663
+ const usage = asRecord(asRecord(body).usage);
664
+ const inputTokens = asNumber(usage.prompt_tokens) ?? asNumber(usage.input_tokens);
665
+ const outputTokens = asNumber(usage.completion_tokens) ?? asNumber(usage.output_tokens);
666
+ const totalTokens = asNumber(usage.total_tokens);
667
+ if (inputTokens === undefined && outputTokens === undefined && totalTokens === undefined) {
668
+ return;
669
+ }
670
+ return {
671
+ inputTokens,
672
+ outputTokens,
673
+ totalTokens: totalTokens ?? (inputTokens ?? 0) + (outputTokens ?? 0)
674
+ };
675
+ };
676
+ var errorFromValue = (value, fallback) => {
677
+ const obj = asRecord(value);
678
+ const nested = asRecord(obj.error);
679
+ const source = nested.message ? nested : obj;
680
+ return {
681
+ code: asNumber(source.code) ?? asString(source.code),
682
+ message: asString(source.message) ?? fallback,
683
+ type: asString(source.type)
684
+ };
685
+ };
686
+ var normalizeOpenAIResult = (line) => {
687
+ const obj = asRecord(line);
688
+ const customId = asString(obj.custom_id) ?? "";
689
+ if (obj.error) {
690
+ return {
691
+ customId,
692
+ error: errorFromValue(obj.error, "Request errored."),
693
+ response: obj.error,
694
+ status: "errored"
695
+ };
696
+ }
697
+ const response = asRecord(obj.response);
698
+ const statusCode = asNumber(response.status_code) ?? 0;
699
+ if (statusCode >= HTTP_OK_MIN && statusCode < HTTP_OK_MAX) {
700
+ return {
701
+ customId,
702
+ response: response.body,
703
+ status: "succeeded",
704
+ text: textFromBody(response.body),
705
+ usage: usageFromBody(response.body)
706
+ };
707
+ }
708
+ return {
709
+ customId,
710
+ error: errorFromValue(response.body, `Request failed with status ${statusCode}.`),
711
+ response: response.body,
712
+ status: "errored"
713
+ };
714
+ };
715
+ var uploadInputFile = async (jsonl, baseUrl3, headers3, options = {}) => {
716
+ const form = new FormData;
717
+ const purpose = options.purpose === undefined ? "batch" : options.purpose;
718
+ if (purpose !== null) {
719
+ form.append("purpose", purpose);
720
+ }
721
+ form.append("file", new Blob([jsonl], { type: "application/jsonl" }), "batchwork.jsonl");
722
+ const raw = await requestJson(`${baseUrl3}/files`, {
723
+ body: form,
724
+ headers: headers3,
725
+ method: "POST"
726
+ });
727
+ return raw.id;
728
+ };
729
+ async function* streamResultFile(fileId, baseUrl3, headers3) {
730
+ const stream = await requestStream(`${baseUrl3}/files/${fileId}/content`, {
731
+ headers: headers3
732
+ });
733
+ for await (const line of streamJsonl(stream)) {
734
+ yield normalizeOpenAIResult(line);
735
+ }
736
+ }
737
+
738
+ // src/providers/openai-compatible.ts
739
+ var DEFAULT_COMPLETION_WINDOW = "24h";
740
+ var mapStatus2 = (status) => {
741
+ const normalized = status?.toLowerCase();
742
+ switch (normalized) {
743
+ case "validating":
744
+ case "in_progress":
745
+ case "finalizing":
746
+ case "completed":
747
+ case "failed":
748
+ case "expired":
749
+ case "cancelling":
750
+ case "cancelled": {
751
+ return normalized;
752
+ }
753
+ default: {
754
+ return "in_progress";
755
+ }
756
+ }
757
+ };
758
+ var normalizeSnapshot3 = (raw, provider) => {
759
+ const outer = asRecord(raw);
760
+ const obj = asRecord(outer.job);
761
+ const source = Object.keys(obj).length > 0 ? obj : outer;
762
+ const counts = asRecord(source.request_counts);
763
+ return {
764
+ completedAt: toDate(source.completed_at),
765
+ createdAt: toDate(source.created_at),
766
+ expiresAt: toDate(source.expires_at),
767
+ id: asString(source.id) ?? "",
768
+ provider,
769
+ raw: source,
770
+ requestCounts: {
771
+ completed: asNumber(counts.completed) ?? 0,
772
+ failed: asNumber(counts.failed) ?? 0,
773
+ total: asNumber(counts.total) ?? 0
774
+ },
775
+ status: mapStatus2(asString(source.status))
776
+ };
777
+ };
778
+ var createOpenAICompatibleAdapter = (config) => {
779
+ const completionWindow = config.completionWindow ?? DEFAULT_COMPLETION_WINDOW;
780
+ const lineFormat = config.lineFormat ?? "method-url";
781
+ const baseUrl3 = (credentials) => credentials.baseURL ?? config.baseUrl;
782
+ const authHeaders = (credentials) => ({
783
+ Authorization: `Bearer ${resolveApiKey(credentials, config.apiKeyEnv, config.apiKeyLabel)}`,
784
+ ...credentials.headers
785
+ });
786
+ const submit3 = async (input) => {
787
+ const limits = resolveBatchLimits(input.limits);
788
+ const endpoint = config.normalizeEndpoint ? config.normalizeEndpoint(input.endpoint) : input.endpoint;
789
+ const jsonl = encodeJsonl(input.built.map((item) => {
790
+ const body = omit(item.body, "stream");
791
+ if (lineFormat === "body-only") {
792
+ return { body, custom_id: item.customId };
793
+ }
794
+ return {
795
+ body,
796
+ custom_id: item.customId,
797
+ method: "POST",
798
+ url: endpoint
799
+ };
800
+ }));
801
+ assertByteLength("batch upload JSONL", jsonl, limits.maxUploadBytes);
802
+ const headers3 = authHeaders(input.credentials);
803
+ const url = baseUrl3(input.credentials);
804
+ const purpose = config.filePurpose ?? "batch";
805
+ const inputFileId = await (config.uploadFile ? config.uploadFile({ baseUrl: url, headers: headers3, jsonl, purpose }) : uploadInputFile(jsonl, url, headers3, { purpose }));
806
+ const raw = await requestJson(`${url}/batches`, {
807
+ body: JSON.stringify({
808
+ completion_window: completionWindow,
809
+ endpoint,
810
+ input_file_id: inputFileId,
811
+ metadata: input.metadata
812
+ }),
813
+ headers: { ...headers3, "content-type": "application/json" },
814
+ method: "POST"
815
+ });
816
+ return normalizeSnapshot3(raw, config.id);
817
+ };
818
+ const retrieve3 = async (id, credentials) => {
819
+ const batchId = assertSimpleProviderId(`${config.id} batch id`, id);
820
+ const raw = await requestJson(`${baseUrl3(credentials)}/batches/${batchId}`, {
821
+ headers: authHeaders(credentials)
822
+ });
823
+ return normalizeSnapshot3(raw, config.id);
824
+ };
825
+ async function* results3(id, credentials) {
826
+ const snapshot = await retrieve3(id, credentials);
827
+ const raw = asRecord(snapshot.raw);
828
+ const outputFileId = asString(raw.output_file_id);
829
+ const errorFileId = asString(raw.error_file_id);
830
+ if (!(outputFileId || errorFileId)) {
831
+ throw new BatchworkError(`batchwork: results are not ready for batch "${id}" (status: ${snapshot.status}).`);
832
+ }
833
+ const headers3 = authHeaders(credentials);
834
+ if (outputFileId) {
835
+ yield* streamResultFile(assertSimpleProviderId(`${config.id} output file id`, outputFileId), baseUrl3(credentials), headers3);
836
+ }
837
+ if (errorFileId) {
838
+ yield* streamResultFile(assertSimpleProviderId(`${config.id} error file id`, errorFileId), baseUrl3(credentials), headers3);
839
+ }
840
+ }
841
+ const cancel3 = async (id, credentials) => {
842
+ const batchId = assertSimpleProviderId(`${config.id} batch id`, id);
843
+ await requestJson(`${baseUrl3(credentials)}/batches/${batchId}/cancel`, {
844
+ headers: authHeaders(credentials),
845
+ method: "POST"
846
+ });
847
+ };
848
+ return { cancel: cancel3, id: config.id, results: results3, retrieve: retrieve3, submit: submit3 };
849
+ };
850
+
851
+ // src/providers/groq.ts
852
+ var groqAdapter = createOpenAICompatibleAdapter({
853
+ apiKeyEnv: "GROQ_API_KEY",
854
+ apiKeyLabel: "Groq",
855
+ baseUrl: "https://api.groq.com/openai/v1",
856
+ id: "groq",
857
+ lineFormat: "method-url",
858
+ normalizeEndpoint: (endpoint) => endpoint.replace(/^\/openai/u, "")
859
+ });
860
+
861
+ // src/providers/mistral.ts
862
+ var MISTRAL_BASE = "https://api.mistral.ai/v1";
863
+ var apiKey3 = (credentials) => resolveApiKey(credentials, "MISTRAL_API_KEY", "Mistral");
864
+ var baseUrl3 = (credentials) => credentials.baseURL ?? MISTRAL_BASE;
865
+ var authHeaders = (credentials) => ({
866
+ Authorization: `Bearer ${apiKey3(credentials)}`,
867
+ ...credentials.headers
868
+ });
869
+ var mapStatus3 = (status) => {
870
+ switch (status) {
871
+ case "QUEUED": {
872
+ return "validating";
873
+ }
874
+ case "SUCCESS": {
875
+ return "completed";
876
+ }
877
+ case "FAILED": {
878
+ return "failed";
879
+ }
880
+ case "TIMEOUT_EXCEEDED": {
881
+ return "expired";
882
+ }
883
+ case "CANCELLATION_REQUESTED": {
884
+ return "cancelling";
885
+ }
886
+ case "CANCELLED": {
887
+ return "cancelled";
888
+ }
889
+ default: {
890
+ return "in_progress";
891
+ }
892
+ }
893
+ };
894
+ var normalizeSnapshot4 = (raw) => {
895
+ const obj = asRecord(raw);
896
+ const succeeded = asNumber(obj.succeeded_requests) ?? 0;
897
+ const failed = asNumber(obj.failed_requests) ?? 0;
898
+ const id = asString(obj.id) ?? "";
899
+ return {
900
+ completedAt: toDate(obj.completed_at),
901
+ createdAt: toDate(obj.created_at),
902
+ id: id ? assertSimpleProviderId("Mistral job id", id) : "",
903
+ provider: "mistral",
904
+ raw,
905
+ requestCounts: {
906
+ completed: succeeded,
907
+ failed,
908
+ total: asNumber(obj.total_requests) ?? succeeded + failed
909
+ },
910
+ status: mapStatus3(asString(obj.status))
911
+ };
912
+ };
913
+ var submit3 = async (input) => {
914
+ const limits = resolveBatchLimits(input.limits);
915
+ const jsonl = encodeJsonl(input.built.map((item) => ({
916
+ body: omit(omit(item.body, "stream"), "model"),
917
+ custom_id: item.customId
918
+ })));
919
+ assertByteLength("batch upload JSONL", jsonl, limits.maxUploadBytes);
920
+ const inputFileId = await uploadInputFile(jsonl, baseUrl3(input.credentials), authHeaders(input.credentials));
921
+ const raw = await requestJson(`${baseUrl3(input.credentials)}/batch/jobs`, {
922
+ body: JSON.stringify({
923
+ endpoint: input.endpoint,
924
+ input_files: [inputFileId],
925
+ metadata: input.metadata,
926
+ model: input.modelId
927
+ }),
928
+ headers: {
929
+ ...authHeaders(input.credentials),
930
+ "content-type": "application/json"
931
+ },
932
+ method: "POST"
933
+ });
934
+ return normalizeSnapshot4(raw);
935
+ };
936
+ var retrieve3 = async (id, credentials) => {
937
+ const jobId = assertSimpleProviderId("Mistral job id", id);
938
+ const raw = await requestJson(`${baseUrl3(credentials)}/batch/jobs/${jobId}`, {
939
+ headers: authHeaders(credentials)
940
+ });
941
+ return normalizeSnapshot4(raw);
942
+ };
943
+ async function* results3(id, credentials) {
944
+ const snapshot = await retrieve3(id, credentials);
945
+ const raw = asRecord(snapshot.raw);
946
+ const outputFileId = asString(raw.output_file);
947
+ const errorFileId = asString(raw.error_file);
948
+ const headers3 = authHeaders(credentials);
949
+ if (outputFileId) {
950
+ yield* streamResultFile(assertSimpleProviderId("Mistral output file id", outputFileId), baseUrl3(credentials), headers3);
951
+ }
952
+ if (errorFileId) {
953
+ yield* streamResultFile(assertSimpleProviderId("Mistral error file id", errorFileId), baseUrl3(credentials), headers3);
954
+ }
955
+ if (!(outputFileId || errorFileId)) {
956
+ throw new BatchworkError(`batchwork: results are not ready for batch "${id}" (status: ${snapshot.status}).`);
957
+ }
958
+ }
959
+ var cancel3 = async (id, credentials) => {
960
+ const jobId = assertSimpleProviderId("Mistral job id", id);
961
+ await requestJson(`${baseUrl3(credentials)}/batch/jobs/${jobId}/cancel`, {
962
+ headers: authHeaders(credentials),
963
+ method: "POST"
964
+ });
965
+ };
966
+ var mistralAdapter = {
967
+ cancel: cancel3,
968
+ id: "mistral",
969
+ results: results3,
970
+ retrieve: retrieve3,
971
+ submit: submit3
972
+ };
973
+
974
+ // src/providers/openai.ts
975
+ var openaiAdapter = createOpenAICompatibleAdapter({
976
+ apiKeyEnv: "OPENAI_API_KEY",
977
+ apiKeyLabel: "OpenAI",
978
+ baseUrl: "https://api.openai.com/v1",
979
+ id: "openai",
980
+ lineFormat: "method-url"
981
+ });
982
+
983
+ // src/providers/together.ts
984
+ var INPUT_FILE_NAME = "batchwork.jsonl";
985
+ var HTTP_FOUND = 302;
986
+ var parseIpv4 = (host) => {
987
+ if (!/^\d{1,3}(?:\.\d{1,3}){3}$/u.test(host)) {
988
+ return;
989
+ }
990
+ const parts = host.split(".").map(Number);
991
+ const valid = parts.every((part) => Number.isInteger(part) && part >= 0 && part <= 255);
992
+ return valid ? parts : undefined;
993
+ };
994
+ var isPrivateIpv4 = (parts) => {
995
+ const [a = 0, b = 0] = parts;
996
+ return a === 0 || a === 10 || a === 127 || a === 100 && b >= 64 && b <= 127 || a === 169 && b === 254 || a === 172 && b >= 16 && b <= 31 || a === 192 && b === 168 || a === 198 && (b === 18 || b === 19) || a >= 224;
997
+ };
998
+ var isPrivateIpv6 = (host) => {
999
+ const normalized = host.replace(/^\[/u, "").replace(/\]$/u, "").toLowerCase();
1000
+ return normalized === "::" || normalized === "::1" || normalized.startsWith("fc") || normalized.startsWith("fd") || normalized.startsWith("fe80:");
1001
+ };
1002
+ var validateUploadLocation = (location) => {
1003
+ let url;
1004
+ try {
1005
+ url = new URL(location);
1006
+ } catch (error) {
1007
+ throw new BatchworkError("batchwork: Together upload Location must be a valid URL.", { cause: error });
1008
+ }
1009
+ if (url.protocol !== "https:") {
1010
+ throw new BatchworkError("batchwork: Together upload Location must use https.");
1011
+ }
1012
+ if (url.username || url.password) {
1013
+ throw new BatchworkError("batchwork: Together upload Location must not include credentials.");
1014
+ }
1015
+ const host = url.hostname.toLowerCase();
1016
+ const ipv4 = parseIpv4(host);
1017
+ if (host === "localhost" || host.endsWith(".localhost") || host.endsWith(".local") || ipv4 && isPrivateIpv4(ipv4) || isPrivateIpv6(host)) {
1018
+ throw new BatchworkError("batchwork: Together upload Location must not target localhost or private networks.");
1019
+ }
1020
+ return url.toString();
1021
+ };
1022
+ var uploadTogetherFile = async (args) => {
1023
+ const metadata = new FormData;
1024
+ metadata.append("purpose", args.purpose);
1025
+ metadata.append("file_name", INPUT_FILE_NAME);
1026
+ metadata.append("file_type", "jsonl");
1027
+ const init = await fetch(`${args.baseUrl}/files`, {
1028
+ body: metadata,
1029
+ headers: args.headers,
1030
+ method: "POST",
1031
+ redirect: "manual"
1032
+ });
1033
+ const location = init.headers.get("location");
1034
+ const fileId = init.headers.get("x-together-file-id");
1035
+ if (init.status !== HTTP_FOUND || !(location && fileId)) {
1036
+ throw new BatchworkError(`batchwork: Together upload could not be initiated (${init.status}).`);
1037
+ }
1038
+ const uploadLocation = validateUploadLocation(location);
1039
+ const upload = await fetch(uploadLocation, {
1040
+ body: args.jsonl,
1041
+ method: "PUT"
1042
+ });
1043
+ if (!upload.ok) {
1044
+ throw new BatchworkError(`batchwork: Together file upload failed (${upload.status}).`);
1045
+ }
1046
+ const safeFileId = assertSimpleProviderId("Together file id", fileId);
1047
+ await requestJson(`${args.baseUrl}/files/${safeFileId}/preprocess`, {
1048
+ headers: args.headers,
1049
+ method: "POST"
1050
+ });
1051
+ return safeFileId;
1052
+ };
1053
+ var togetherAdapter = createOpenAICompatibleAdapter({
1054
+ apiKeyEnv: "TOGETHER_API_KEY",
1055
+ apiKeyLabel: "Together AI",
1056
+ baseUrl: "https://api.together.xyz/v1",
1057
+ filePurpose: "batch-api",
1058
+ id: "together",
1059
+ lineFormat: "body-only",
1060
+ uploadFile: uploadTogetherFile
1061
+ });
1062
+
1063
+ // src/providers/xai.ts
1064
+ var XAI_BASE = "https://api.x.ai/v1";
1065
+ var RESULTS_PAGE_SIZE = 100;
1066
+ var apiKey4 = (credentials) => resolveApiKey(credentials, "XAI_API_KEY", "xAI");
1067
+ var baseUrl4 = (credentials) => credentials.baseURL ?? XAI_BASE;
1068
+ var authHeaders2 = (credentials) => ({
1069
+ Authorization: `Bearer ${apiKey4(credentials)}`,
1070
+ ...credentials.headers
1071
+ });
1072
+ var deriveStatus = (state) => {
1073
+ const pending = asNumber(state.num_pending);
1074
+ if (pending === undefined) {
1075
+ return "in_progress";
1076
+ }
1077
+ const total = asNumber(state.num_requests) ?? 0;
1078
+ const cancelled = asNumber(state.num_cancelled) ?? 0;
1079
+ if (total === 0) {
1080
+ return "in_progress";
1081
+ }
1082
+ if (pending > 0) {
1083
+ return "in_progress";
1084
+ }
1085
+ if (cancelled > 0 && cancelled === total) {
1086
+ return "cancelled";
1087
+ }
1088
+ return "completed";
1089
+ };
1090
+ var normalizeSnapshot5 = (raw) => {
1091
+ const obj = asRecord(raw);
1092
+ const state = asRecord(obj.state);
1093
+ const id = asString(obj.batch_id) ?? asString(obj.id) ?? "";
1094
+ return {
1095
+ completedAt: toDate(obj.cancel_time),
1096
+ createdAt: toDate(obj.create_time),
1097
+ expiresAt: toDate(obj.expire_time ?? obj.expires_at),
1098
+ id: id ? assertSimpleProviderId("xAI batch id", id) : "",
1099
+ provider: "xai",
1100
+ raw,
1101
+ requestCounts: {
1102
+ canceled: asNumber(state.num_cancelled) ?? 0,
1103
+ completed: asNumber(state.num_success) ?? 0,
1104
+ failed: asNumber(state.num_error) ?? 0,
1105
+ processing: asNumber(state.num_pending) ?? 0,
1106
+ total: asNumber(state.num_requests) ?? 0
1107
+ },
1108
+ status: deriveStatus(state)
1109
+ };
1110
+ };
1111
+ var normalizeResult3 = (item) => {
1112
+ const obj = asRecord(item);
1113
+ const customId = asString(obj.batch_request_id) ?? "";
1114
+ const batchResult = asRecord(obj.batch_result);
1115
+ const resultError = batchResult.error;
1116
+ const errorMessage = asString(obj.error_message) ?? asString(resultError) ?? asString(asRecord(resultError).message);
1117
+ if (errorMessage) {
1118
+ return {
1119
+ customId,
1120
+ error: {
1121
+ message: errorMessage,
1122
+ type: asString(asRecord(resultError).type)
1123
+ },
1124
+ response: obj,
1125
+ status: "errored"
1126
+ };
1127
+ }
1128
+ const response = asRecord(batchResult.response);
1129
+ const completion = response.chat_get_completion ?? Object.values(response)[0];
1130
+ return {
1131
+ customId,
1132
+ response: completion,
1133
+ status: "succeeded",
1134
+ text: textFromBody(completion),
1135
+ usage: usageFromBody(completion)
1136
+ };
1137
+ };
1138
+ var submit4 = async (input) => {
1139
+ const limits = resolveBatchLimits(input.limits);
1140
+ const jsonl = encodeJsonl(input.built.map((item) => ({
1141
+ body: omit(item.body, "stream"),
1142
+ custom_id: item.customId,
1143
+ method: "POST",
1144
+ url: input.endpoint
1145
+ })));
1146
+ assertByteLength("batch upload JSONL", jsonl, limits.maxUploadBytes);
1147
+ const inputFileId = await uploadInputFile(jsonl, baseUrl4(input.credentials), authHeaders2(input.credentials), { purpose: null });
1148
+ const raw = await requestJson(`${baseUrl4(input.credentials)}/batches`, {
1149
+ body: JSON.stringify({ input_file_id: inputFileId, name: "batchwork" }),
1150
+ headers: {
1151
+ ...authHeaders2(input.credentials),
1152
+ "content-type": "application/json"
1153
+ },
1154
+ method: "POST"
1155
+ });
1156
+ return normalizeSnapshot5(raw);
1157
+ };
1158
+ var retrieve4 = async (id, credentials) => {
1159
+ const batchId = assertSimpleProviderId("xAI batch id", id);
1160
+ const raw = await requestJson(`${baseUrl4(credentials)}/batches/${batchId}`, {
1161
+ headers: authHeaders2(credentials)
1162
+ });
1163
+ return normalizeSnapshot5(raw);
1164
+ };
1165
+ async function* results4(id, credentials) {
1166
+ const batchId = assertSimpleProviderId("xAI batch id", id);
1167
+ const headers3 = authHeaders2(credentials);
1168
+ let token;
1169
+ do {
1170
+ const query = new URLSearchParams({ limit: String(RESULTS_PAGE_SIZE) });
1171
+ if (token) {
1172
+ query.set("pagination_token", token);
1173
+ }
1174
+ const raw = await requestJson(`${baseUrl4(credentials)}/batches/${batchId}/results?${query.toString()}`, { headers: headers3 });
1175
+ const page = asRecord(raw);
1176
+ for (const item of Array.isArray(page.results) ? page.results : []) {
1177
+ yield normalizeResult3(item);
1178
+ }
1179
+ token = asString(page.pagination_token);
1180
+ } while (token);
1181
+ }
1182
+ var cancel4 = async (id, credentials) => {
1183
+ const batchId = assertSimpleProviderId("xAI batch id", id);
1184
+ await requestJson(`${baseUrl4(credentials)}/batches/${batchId}:cancel`, {
1185
+ headers: authHeaders2(credentials),
1186
+ method: "POST"
1187
+ });
1188
+ };
1189
+ var xaiAdapter = {
1190
+ cancel: cancel4,
1191
+ id: "xai",
1192
+ results: results4,
1193
+ retrieve: retrieve4,
1194
+ submit: submit4
1195
+ };
1196
+
1197
+ // src/providers/index.ts
1198
+ var adapters = {
1199
+ anthropic: anthropicAdapter,
1200
+ google: googleAdapter,
1201
+ groq: groqAdapter,
1202
+ mistral: mistralAdapter,
1203
+ openai: openaiAdapter,
1204
+ together: togetherAdapter,
1205
+ xai: xaiAdapter
1206
+ };
1207
+ var getAdapter = (provider) => adapters[provider];
1208
+
1209
+ export { BatchworkError, UnsupportedProviderError, MissingDependencyError, resolveBatchLimits, assertByteLength, mapWithConcurrency, isTerminalStatus, BatchJob, getAdapter };
1210
+
1211
+ //# debugId=A38719FEA6433ECA64756E2164756E21
1212
+ //# sourceMappingURL=chunk-g481f961.js.map