batchwork 1.0.1 → 1.2.0

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 (45) hide show
  1. package/README.md +53 -0
  2. package/dist/batch.d.ts +26 -7
  3. package/dist/batch.d.ts.map +1 -1
  4. package/dist/body.d.ts +15 -1
  5. package/dist/body.d.ts.map +1 -1
  6. package/dist/{chunk-e6qn48qa.js → chunk-3yqs6x71.js} +89 -15
  7. package/dist/chunk-3yqs6x71.js.map +13 -0
  8. package/dist/{chunk-m4n610nm.js → chunk-bjkbtj1q.js} +206 -23
  9. package/dist/chunk-bjkbtj1q.js.map +12 -0
  10. package/dist/{chunk-g481f961.js → chunk-h2he3d16.js} +192 -38
  11. package/dist/chunk-h2he3d16.js.map +27 -0
  12. package/dist/errors.d.ts +1 -1
  13. package/dist/errors.d.ts.map +1 -1
  14. package/dist/index.d.ts +2 -2
  15. package/dist/index.d.ts.map +1 -1
  16. package/dist/index.js +7 -3
  17. package/dist/index.js.map +1 -1
  18. package/dist/job.d.ts.map +1 -1
  19. package/dist/jsonl.d.ts +5 -1
  20. package/dist/jsonl.d.ts.map +1 -1
  21. package/dist/limits.d.ts +1 -0
  22. package/dist/limits.d.ts.map +1 -1
  23. package/dist/model.d.ts +31 -2
  24. package/dist/model.d.ts.map +1 -1
  25. package/dist/next/index.js +3 -3
  26. package/dist/payload.d.ts +10 -0
  27. package/dist/payload.d.ts.map +1 -0
  28. package/dist/providers/anthropic.d.ts.map +1 -1
  29. package/dist/providers/google.d.ts.map +1 -1
  30. package/dist/providers/shared.d.ts +9 -1
  31. package/dist/providers/shared.d.ts.map +1 -1
  32. package/dist/providers/together.d.ts.map +1 -1
  33. package/dist/providers/xai.d.ts.map +1 -1
  34. package/dist/server/index.js +2 -2
  35. package/dist/server/poller.d.ts.map +1 -1
  36. package/dist/server/signing.d.ts +1 -0
  37. package/dist/server/signing.d.ts.map +1 -1
  38. package/dist/types.d.ts +71 -2
  39. package/dist/types.d.ts.map +1 -1
  40. package/dist/util.d.ts +2 -0
  41. package/dist/util.d.ts.map +1 -1
  42. package/package.json +1 -1
  43. package/dist/chunk-e6qn48qa.js.map +0 -13
  44. package/dist/chunk-g481f961.js.map +0 -26
  45. package/dist/chunk-m4n610nm.js.map +0 -12
@@ -8,8 +8,8 @@ class BatchworkError extends Error {
8
8
 
9
9
  class UnsupportedProviderError extends BatchworkError {
10
10
  provider;
11
- constructor(provider) {
12
- super(`batchwork: provider "${provider}" is not supported yet. Supported providers: openai, anthropic, google, groq, mistral, together, xai.`);
11
+ constructor(provider, detail) {
12
+ super(detail ?? `batchwork: provider "${provider}" is not supported yet. Supported providers: openai, anthropic, google, groq, mistral, together, xai.`);
13
13
  this.name = "UnsupportedProviderError";
14
14
  this.provider = provider;
15
15
  }
@@ -36,11 +36,16 @@ var delay = (ms, signal) => new Promise((resolve, reject) => {
36
36
  reject(new BatchworkError("batchwork: wait aborted."));
37
37
  return;
38
38
  }
39
- const timer = setTimeout(resolve, ms);
40
- signal?.addEventListener("abort", () => {
39
+ let timer;
40
+ const onAbort = () => {
41
41
  clearTimeout(timer);
42
42
  reject(new BatchworkError("batchwork: wait aborted."));
43
- }, { once: true });
43
+ };
44
+ timer = setTimeout(() => {
45
+ signal?.removeEventListener("abort", onAbort);
46
+ resolve();
47
+ }, ms);
48
+ signal?.addEventListener("abort", onAbort, { once: true });
44
49
  });
45
50
 
46
51
  class BatchJob {
@@ -124,12 +129,14 @@ var resolveBatchLimits = (limits) => ({
124
129
  maxUploadBytes: positiveInteger("maxUploadBytes", limits?.maxUploadBytes ?? DEFAULT_LIMITS.maxUploadBytes)
125
130
  });
126
131
  var byteLength = (value) => encoder.encode(value).length;
127
- var assertByteLength = (label, value, maxBytes) => {
128
- const bytes = byteLength(value);
132
+ var assertByteCount = (label, bytes, maxBytes) => {
129
133
  if (bytes > maxBytes) {
130
134
  throw new BatchworkError(`batchwork: ${label} is ${bytes} bytes, exceeding the ${maxBytes} byte limit.`);
131
135
  }
132
136
  };
137
+ var assertByteLength = (label, value, maxBytes) => {
138
+ assertByteCount(label, byteLength(value), maxBytes);
139
+ };
133
140
  var mapWithConcurrency = async (items, concurrency, mapper) => {
134
141
  const results = [];
135
142
  results.length = items.length;
@@ -172,6 +179,7 @@ var requestStream = async (url, init) => {
172
179
  var NEWLINE = `
173
180
  `;
174
181
  var DEFAULT_MAX_JSONL_LINE_BYTES = 20 * 1024 * 1024;
182
+ var NEWLINE_BYTES = byteLength(NEWLINE);
175
183
  var resolveMaxLineBytes = (options) => {
176
184
  const maxLineBytes = options?.maxLineBytes ?? DEFAULT_MAX_JSONL_LINE_BYTES;
177
185
  if (!(Number.isInteger(maxLineBytes) && maxLineBytes > 0)) {
@@ -197,12 +205,33 @@ var parseLine = (line, lineNumber, maxLineBytes) => {
197
205
  throw new BatchworkError(`batchwork: invalid JSONL at line ${lineNumber}.`, { cause: error });
198
206
  }
199
207
  };
200
- var encodeJsonl = (items) => {
208
+ var resolveMaxBytes = (options) => {
209
+ const maxBytes = options?.maxBytes;
210
+ if (maxBytes !== undefined && !(Number.isInteger(maxBytes) && maxBytes > 0)) {
211
+ throw new BatchworkError("batchwork: JSONL maxBytes must be a positive integer.");
212
+ }
213
+ return maxBytes;
214
+ };
215
+ var encodeJsonl = (items, options) => {
201
216
  if (items.length === 0) {
202
217
  return "";
203
218
  }
204
- const body = items.map((item) => JSON.stringify(item)).join(NEWLINE);
205
- return `${body}${NEWLINE}`;
219
+ const lines = [];
220
+ const maxBytes = resolveMaxBytes(options);
221
+ const label = options?.label ?? "JSONL";
222
+ let bytes = 0;
223
+ for (const item of items) {
224
+ const line = JSON.stringify(item);
225
+ if (line === undefined) {
226
+ throw new BatchworkError(`batchwork: ${label} contains a value that cannot be JSON encoded.`);
227
+ }
228
+ bytes += byteLength(line) + NEWLINE_BYTES;
229
+ if (maxBytes !== undefined) {
230
+ assertByteCount(label, bytes, maxBytes);
231
+ }
232
+ lines.push(line);
233
+ }
234
+ return `${lines.join(NEWLINE)}${NEWLINE}`;
206
235
  };
207
236
  var isReadableStream = (source) => ("getReader" in source) && typeof source.getReader === "function";
208
237
  async function* toByteIterable(source) {
@@ -250,6 +279,32 @@ async function* streamJsonl(source, options) {
250
279
  }
251
280
  }
252
281
 
282
+ // src/payload.ts
283
+ var encodeJsonArrayPayload = ({
284
+ items,
285
+ label,
286
+ maxBytes,
287
+ prefix,
288
+ suffix
289
+ }) => {
290
+ const encodedItems = [];
291
+ let bytes = byteLength(prefix) + byteLength(suffix);
292
+ assertByteCount(label, bytes, maxBytes);
293
+ for (const [index, item] of items.entries()) {
294
+ const encoded = JSON.stringify(item);
295
+ if (encoded === undefined) {
296
+ throw new BatchworkError(`batchwork: ${label} contains a value that cannot be JSON encoded.`);
297
+ }
298
+ bytes += byteLength(encoded);
299
+ if (index > 0) {
300
+ bytes += 1;
301
+ }
302
+ assertByteCount(label, bytes, maxBytes);
303
+ encodedItems.push(encoded);
304
+ }
305
+ return `${prefix}${encodedItems.join(",")}${suffix}`;
306
+ };
307
+
253
308
  // src/util.ts
254
309
  var asRecord = (value) => {
255
310
  if (typeof value === "object" && value !== null) {
@@ -260,6 +315,13 @@ var asRecord = (value) => {
260
315
  var asString = (value) => typeof value === "string" ? value : undefined;
261
316
  var asNumber = (value) => typeof value === "number" ? value : undefined;
262
317
  var asArray = (value) => Array.isArray(value) ? value : [];
318
+ var asNumberArray = (value) => {
319
+ if (!Array.isArray(value) || value.length === 0) {
320
+ return;
321
+ }
322
+ const numbers = value.filter((item) => typeof item === "number");
323
+ return numbers.length === value.length ? numbers : undefined;
324
+ };
263
325
  var omit = (obj, key) => {
264
326
  const result = {};
265
327
  for (const [k, v] of Object.entries(obj)) {
@@ -269,12 +331,13 @@ var omit = (obj, key) => {
269
331
  }
270
332
  return result;
271
333
  };
334
+ var validDate = (date) => Number.isNaN(date.getTime()) ? undefined : date;
272
335
  var toDate = (value) => {
273
336
  if (typeof value === "string") {
274
- return new Date(value);
337
+ return validDate(new Date(value));
275
338
  }
276
339
  if (typeof value === "number") {
277
- return new Date(value * 1000);
340
+ return validDate(new Date(value * 1000));
278
341
  }
279
342
  };
280
343
 
@@ -421,8 +484,13 @@ var submit = async (input) => {
421
484
  custom_id: item.customId,
422
485
  params: omit(item.body, "stream")
423
486
  }));
424
- const body = JSON.stringify({ requests });
425
- assertByteLength("batch upload payload", body, limits.maxUploadBytes);
487
+ const body = encodeJsonArrayPayload({
488
+ items: requests,
489
+ label: "batch upload payload",
490
+ maxBytes: limits.maxUploadBytes,
491
+ prefix: '{"requests":[',
492
+ suffix: "]}"
493
+ });
426
494
  const raw = await requestJson(`${baseUrl(input.credentials)}/v1/messages/batches`, {
427
495
  body,
428
496
  headers: headers(input.credentials),
@@ -442,7 +510,8 @@ async function* results(id, credentials) {
442
510
  throw new BatchworkError(`batchwork: results are not ready for batch "${id}" (status: ${snapshot.status}).`);
443
511
  }
444
512
  const stream = await requestStream(validateResultsUrl(resultsUrl, credentials), {
445
- headers: headers(credentials)
513
+ headers: headers(credentials),
514
+ redirect: "manual"
446
515
  });
447
516
  for await (const line of streamJsonl(stream)) {
448
517
  yield normalizeResult(line);
@@ -541,6 +610,21 @@ var textFromResponse = (response) => {
541
610
  const text = asArray(asRecord(candidate.content).parts).map((part) => asString(asRecord(part).text) ?? "").join("");
542
611
  return text.length > 0 ? text : undefined;
543
612
  };
613
+ var embeddingFromResponse = (response) => asNumberArray(asRecord(asRecord(response).embedding).values);
614
+ var imagesFromResponse = (response) => {
615
+ const candidate = asRecord(asArray(asRecord(response).candidates)[0]);
616
+ const images = [];
617
+ for (const part of asArray(asRecord(candidate.content).parts)) {
618
+ const partObj = asRecord(part);
619
+ const inline = asRecord(partObj.inlineData ?? partObj.inline_data);
620
+ const data = asString(inline.data);
621
+ const mediaType = asString(inline.mimeType) ?? asString(inline.mime_type);
622
+ if (data && mediaType?.startsWith("image/")) {
623
+ images.push({ data, mediaType });
624
+ }
625
+ }
626
+ return images.length > 0 ? images : undefined;
627
+ };
544
628
  var usageFromResponse = (response) => {
545
629
  const usage = asRecord(asRecord(response).usageMetadata);
546
630
  const inputTokens = asNumber(usage.promptTokenCount);
@@ -573,26 +657,53 @@ var normalizeResult2 = (item) => {
573
657
  }
574
658
  return {
575
659
  customId,
660
+ embedding: embeddingFromResponse(obj.response),
661
+ images: imagesFromResponse(obj.response),
576
662
  response: obj.response,
577
663
  status: "succeeded",
578
664
  text: textFromResponse(obj.response),
579
665
  usage: usageFromResponse(obj.response)
580
666
  };
581
667
  };
668
+ var EMBED_CONFIG_KEYS = new Set([
669
+ "outputDimensionality",
670
+ "taskType",
671
+ "title"
672
+ ]);
673
+ var toEmbedRequest = (body) => {
674
+ const request = {};
675
+ const config = {};
676
+ for (const [key, value] of Object.entries(body)) {
677
+ if (EMBED_CONFIG_KEYS.has(key)) {
678
+ config[key] = value;
679
+ } else {
680
+ request[key] = value;
681
+ }
682
+ }
683
+ if (Object.keys(config).length > 0) {
684
+ request.embedContentConfig = config;
685
+ }
686
+ return request;
687
+ };
582
688
  var submit2 = async (input) => {
583
689
  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
- }
690
+ const isEmbedding = input.endpoint.toLowerCase().includes("embedcontent");
691
+ const method = isEmbedding ? "asyncBatchEmbedContent" : "batchGenerateContent";
692
+ const requests = input.built.map((item) => {
693
+ const payload = omit(item.body, "stream");
694
+ return {
695
+ metadata: { key: item.customId },
696
+ request: isEmbedding ? toEmbedRequest(payload) : payload
697
+ };
698
+ });
699
+ const body = encodeJsonArrayPayload({
700
+ items: requests,
701
+ label: "batch upload payload",
702
+ maxBytes: limits.maxUploadBytes,
703
+ prefix: '{"batch":{"display_name":"batchwork","input_config":{"requests":{"requests":[',
704
+ suffix: "]}}}}"
593
705
  });
594
- assertByteLength("batch upload payload", body, limits.maxUploadBytes);
595
- const raw = await requestJson(`${baseUrl2(input.credentials)}/models/${input.modelId}:batchGenerateContent`, {
706
+ const raw = await requestJson(`${baseUrl2(input.credentials)}/models/${input.modelId}:${method}`, {
596
707
  body,
597
708
  headers: headers2(input.credentials),
598
709
  method: "POST"
@@ -659,6 +770,25 @@ var textFromBody = (body) => {
659
770
  }
660
771
  return asString(obj.output_text);
661
772
  };
773
+ var embeddingFromBody = (body) => {
774
+ const data = asArray(asRecord(body).data);
775
+ if (data.length === 0) {
776
+ return;
777
+ }
778
+ return asNumberArray(asRecord(data[0]).embedding);
779
+ };
780
+ var imagesFromBody = (body) => {
781
+ const obj = asRecord(body);
782
+ const mediaType = `image/${asString(obj.output_format) ?? "png"}`;
783
+ const images = [];
784
+ for (const item of asArray(obj.data)) {
785
+ const b64 = asString(asRecord(item).b64_json);
786
+ if (b64) {
787
+ images.push({ data: b64, mediaType });
788
+ }
789
+ }
790
+ return images.length > 0 ? images : undefined;
791
+ };
662
792
  var usageFromBody = (body) => {
663
793
  const usage = asRecord(asRecord(body).usage);
664
794
  const inputTokens = asNumber(usage.prompt_tokens) ?? asNumber(usage.input_tokens);
@@ -699,6 +829,8 @@ var normalizeOpenAIResult = (line) => {
699
829
  if (statusCode >= HTTP_OK_MIN && statusCode < HTTP_OK_MAX) {
700
830
  return {
701
831
  customId,
832
+ embedding: embeddingFromBody(response.body),
833
+ images: imagesFromBody(response.body),
702
834
  response: response.body,
703
835
  status: "succeeded",
704
836
  text: textFromBody(response.body),
@@ -722,13 +854,15 @@ var uploadInputFile = async (jsonl, baseUrl3, headers3, options = {}) => {
722
854
  const raw = await requestJson(`${baseUrl3}/files`, {
723
855
  body: form,
724
856
  headers: headers3,
725
- method: "POST"
857
+ method: "POST",
858
+ redirect: "manual"
726
859
  });
727
860
  return raw.id;
728
861
  };
729
862
  async function* streamResultFile(fileId, baseUrl3, headers3) {
730
863
  const stream = await requestStream(`${baseUrl3}/files/${fileId}/content`, {
731
- headers: headers3
864
+ headers: headers3,
865
+ redirect: "manual"
732
866
  });
733
867
  for await (const line of streamJsonl(stream)) {
734
868
  yield normalizeOpenAIResult(line);
@@ -797,8 +931,7 @@ var createOpenAICompatibleAdapter = (config) => {
797
931
  method: "POST",
798
932
  url: endpoint
799
933
  };
800
- }));
801
- assertByteLength("batch upload JSONL", jsonl, limits.maxUploadBytes);
934
+ }), { label: "batch upload JSONL", maxBytes: limits.maxUploadBytes });
802
935
  const headers3 = authHeaders(input.credentials);
803
936
  const url = baseUrl3(input.credentials);
804
937
  const purpose = config.filePurpose ?? "batch";
@@ -915,8 +1048,7 @@ var submit3 = async (input) => {
915
1048
  const jsonl = encodeJsonl(input.built.map((item) => ({
916
1049
  body: omit(omit(item.body, "stream"), "model"),
917
1050
  custom_id: item.customId
918
- })));
919
- assertByteLength("batch upload JSONL", jsonl, limits.maxUploadBytes);
1051
+ })), { label: "batch upload JSONL", maxBytes: limits.maxUploadBytes });
920
1052
  const inputFileId = await uploadInputFile(jsonl, baseUrl3(input.credentials), authHeaders(input.credentials));
921
1053
  const raw = await requestJson(`${baseUrl3(input.credentials)}/batch/jobs`, {
922
1054
  body: JSON.stringify({
@@ -997,6 +1129,9 @@ var isPrivateIpv4 = (parts) => {
997
1129
  };
998
1130
  var isPrivateIpv6 = (host) => {
999
1131
  const normalized = host.replace(/^\[/u, "").replace(/\]$/u, "").toLowerCase();
1132
+ if (!normalized.includes(":")) {
1133
+ return false;
1134
+ }
1000
1135
  return normalized === "::" || normalized === "::1" || normalized.startsWith("fc") || normalized.startsWith("fd") || normalized.startsWith("fe80:");
1001
1136
  };
1002
1137
  var validateUploadLocation = (location) => {
@@ -1038,7 +1173,8 @@ var uploadTogetherFile = async (args) => {
1038
1173
  const uploadLocation = validateUploadLocation(location);
1039
1174
  const upload = await fetch(uploadLocation, {
1040
1175
  body: args.jsonl,
1041
- method: "PUT"
1176
+ method: "PUT",
1177
+ redirect: "manual"
1042
1178
  });
1043
1179
  if (!upload.ok) {
1044
1180
  throw new BatchworkError(`batchwork: Together file upload failed (${upload.status}).`);
@@ -1092,7 +1228,7 @@ var normalizeSnapshot5 = (raw) => {
1092
1228
  const state = asRecord(obj.state);
1093
1229
  const id = asString(obj.batch_id) ?? asString(obj.id) ?? "";
1094
1230
  return {
1095
- completedAt: toDate(obj.cancel_time),
1231
+ completedAt: toDate(obj.finish_time ?? obj.completed_at),
1096
1232
  createdAt: toDate(obj.create_time),
1097
1233
  expiresAt: toDate(obj.expire_time ?? obj.expires_at),
1098
1234
  id: id ? assertSimpleProviderId("xAI batch id", id) : "",
@@ -1108,6 +1244,24 @@ var normalizeSnapshot5 = (raw) => {
1108
1244
  status: deriveStatus(state)
1109
1245
  };
1110
1246
  };
1247
+ var imagesFromXaiCompletion = (completion) => {
1248
+ const obj = asRecord(completion);
1249
+ const entries = asArray(obj.data);
1250
+ const sources = entries.length > 0 ? entries : [obj];
1251
+ const images = [];
1252
+ for (const source of sources) {
1253
+ const record = asRecord(source);
1254
+ const data = asString(record.base64) ?? asString(record.b64_json);
1255
+ const url = asString(record.url);
1256
+ if (data || url) {
1257
+ images.push({
1258
+ ...data ? { data } : {},
1259
+ ...url ? { url } : {}
1260
+ });
1261
+ }
1262
+ }
1263
+ return images.length > 0 ? images : undefined;
1264
+ };
1111
1265
  var normalizeResult3 = (item) => {
1112
1266
  const obj = asRecord(item);
1113
1267
  const customId = asString(obj.batch_request_id) ?? "";
@@ -1129,6 +1283,7 @@ var normalizeResult3 = (item) => {
1129
1283
  const completion = response.chat_get_completion ?? Object.values(response)[0];
1130
1284
  return {
1131
1285
  customId,
1286
+ images: imagesFromXaiCompletion(completion),
1132
1287
  response: completion,
1133
1288
  status: "succeeded",
1134
1289
  text: textFromBody(completion),
@@ -1142,8 +1297,7 @@ var submit4 = async (input) => {
1142
1297
  custom_id: item.customId,
1143
1298
  method: "POST",
1144
1299
  url: input.endpoint
1145
- })));
1146
- assertByteLength("batch upload JSONL", jsonl, limits.maxUploadBytes);
1300
+ })), { label: "batch upload JSONL", maxBytes: limits.maxUploadBytes });
1147
1301
  const inputFileId = await uploadInputFile(jsonl, baseUrl4(input.credentials), authHeaders2(input.credentials), { purpose: null });
1148
1302
  const raw = await requestJson(`${baseUrl4(input.credentials)}/batches`, {
1149
1303
  body: JSON.stringify({ input_file_id: inputFileId, name: "batchwork" }),
@@ -1208,5 +1362,5 @@ var getAdapter = (provider) => adapters[provider];
1208
1362
 
1209
1363
  export { BatchworkError, UnsupportedProviderError, MissingDependencyError, resolveBatchLimits, assertByteLength, mapWithConcurrency, isTerminalStatus, BatchJob, getAdapter };
1210
1364
 
1211
- //# debugId=A38719FEA6433ECA64756E2164756E21
1212
- //# sourceMappingURL=chunk-g481f961.js.map
1365
+ //# debugId=308C7FBEE03E10E564756E2164756E21
1366
+ //# sourceMappingURL=chunk-h2he3d16.js.map