batchwork 1.3.0 → 1.4.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.
@@ -9,7 +9,7 @@ class BatchworkError extends Error {
9
9
  class UnsupportedProviderError extends BatchworkError {
10
10
  provider;
11
11
  constructor(provider, detail) {
12
- super(detail ?? `batchwork: provider "${provider}" is not supported yet. Supported providers: openai, anthropic, google, groq, mistral, together, xai.`);
12
+ super(detail ?? `batchwork: provider "${provider}" is not supported yet. Supported providers: openai, azure, anthropic, google, groq, mistral, together, xai.`);
13
13
  this.name = "UnsupportedProviderError";
14
14
  this.provider = provider;
15
15
  }
@@ -138,22 +138,48 @@ var assertByteCount = (label, bytes, maxBytes) => {
138
138
  var assertByteLength = (label, value, maxBytes) => {
139
139
  assertByteCount(label, byteLength(value), maxBytes);
140
140
  };
141
- var mapWithConcurrency = async (items, concurrency, mapper) => {
142
- const results = [];
143
- results.length = items.length;
144
- let nextIndex = 0;
145
- const workerCount = Math.min(concurrency, items.length);
146
- const runNext = async () => {
147
- const index = nextIndex;
148
- nextIndex += 1;
149
- if (index >= items.length) {
150
- return;
141
+
142
+ // src/util.ts
143
+ var trimTrailingSlashes = (value) => {
144
+ let end = value.length;
145
+ while (end > 0 && value[end - 1] === "/") {
146
+ end -= 1;
147
+ }
148
+ return value.slice(0, end);
149
+ };
150
+ var asRecord = (value) => {
151
+ if (typeof value === "object" && value !== null) {
152
+ return value;
153
+ }
154
+ return {};
155
+ };
156
+ var asString = (value) => typeof value === "string" ? value : undefined;
157
+ var asNumber = (value) => typeof value === "number" ? value : undefined;
158
+ var asArray = (value) => Array.isArray(value) ? value : [];
159
+ var asNumberArray = (value) => {
160
+ if (!Array.isArray(value) || value.length === 0) {
161
+ return;
162
+ }
163
+ const numbers = value.filter((item) => typeof item === "number");
164
+ return numbers.length === value.length ? numbers : undefined;
165
+ };
166
+ var omit = (obj, key) => {
167
+ const result = {};
168
+ for (const [k, v] of Object.entries(obj)) {
169
+ if (k !== key) {
170
+ result[k] = v;
151
171
  }
152
- results[index] = await mapper(items[index]);
153
- await runNext();
154
- };
155
- await Promise.all(Array.from({ length: workerCount }, () => runNext()));
156
- return results;
172
+ }
173
+ return result;
174
+ };
175
+ var validDate = (date) => Number.isNaN(date.getTime()) ? undefined : date;
176
+ var toDate = (value) => {
177
+ if (typeof value === "string") {
178
+ return validDate(new Date(value));
179
+ }
180
+ if (typeof value === "number") {
181
+ return validDate(new Date(value * 1000));
182
+ }
157
183
  };
158
184
 
159
185
  // src/http.ts
@@ -306,42 +332,6 @@ var encodeJsonArrayPayload = ({
306
332
  return `${prefix}${encodedItems.join(",")}${suffix}`;
307
333
  };
308
334
 
309
- // src/util.ts
310
- var asRecord = (value) => {
311
- if (typeof value === "object" && value !== null) {
312
- return value;
313
- }
314
- return {};
315
- };
316
- var asString = (value) => typeof value === "string" ? value : undefined;
317
- var asNumber = (value) => typeof value === "number" ? value : undefined;
318
- var asArray = (value) => Array.isArray(value) ? value : [];
319
- var asNumberArray = (value) => {
320
- if (!Array.isArray(value) || value.length === 0) {
321
- return;
322
- }
323
- const numbers = value.filter((item) => typeof item === "number");
324
- return numbers.length === value.length ? numbers : undefined;
325
- };
326
- var omit = (obj, key) => {
327
- const result = {};
328
- for (const [k, v] of Object.entries(obj)) {
329
- if (k !== key) {
330
- result[k] = v;
331
- }
332
- }
333
- return result;
334
- };
335
- var validDate = (date) => Number.isNaN(date.getTime()) ? undefined : date;
336
- var toDate = (value) => {
337
- if (typeof value === "string") {
338
- return validDate(new Date(value));
339
- }
340
- if (typeof value === "number") {
341
- return validDate(new Date(value * 1000));
342
- }
343
- };
344
-
345
335
  // src/providers/ids.ts
346
336
  var SIMPLE_PROVIDER_ID = /^[A-Za-z0-9_-]+$/u;
347
337
  var assertSimpleProviderId = (label, id) => {
@@ -525,237 +515,18 @@ async function* results(id, credentials) {
525
515
  }
526
516
  }
527
517
  var cancel = async (id, credentials) => {
528
- const batchId = assertSimpleProviderId("Anthropic batch id", id);
529
- await requestJson(`${baseUrl(credentials)}/v1/messages/batches/${batchId}/cancel`, {
530
- headers: headers(credentials),
531
- method: "POST"
532
- });
533
- };
534
- var anthropicAdapter = {
535
- cancel,
536
- id: "anthropic",
537
- results,
538
- retrieve,
539
- submit
540
- };
541
-
542
- // src/providers/google.ts
543
- var GOOGLE_BASE = "https://generativelanguage.googleapis.com/v1beta";
544
- var OPERATION_ID_LABEL = "Google operation id";
545
- var GOOGLE_BATCH_PREFIX = "batches";
546
- var apiKey2 = (credentials) => {
547
- const key = credentials.apiKey ?? process.env.GOOGLE_GENERATIVE_AI_API_KEY ?? process.env.GEMINI_API_KEY;
548
- if (!key) {
549
- throw new BatchworkError("batchwork: missing Google Gemini API key. Set GOOGLE_GENERATIVE_AI_API_KEY (or GEMINI_API_KEY) or pass `apiKey`.");
550
- }
551
- return key;
552
- };
553
- var baseUrl2 = (credentials) => credentials.baseURL ?? GOOGLE_BASE;
554
- var headers2 = (credentials) => ({
555
- "content-type": "application/json",
556
- "x-goog-api-key": apiKey2(credentials),
557
- ...credentials.headers
558
- });
559
- var mapState = (state, done) => {
560
- if (state) {
561
- if (state.endsWith("SUCCEEDED")) {
562
- return "completed";
563
- }
564
- if (state.endsWith("FAILED")) {
565
- return "failed";
566
- }
567
- if (state.endsWith("CANCELLED")) {
568
- return "cancelled";
569
- }
570
- if (state.endsWith("EXPIRED")) {
571
- return "expired";
572
- }
573
- if (state.endsWith("PENDING")) {
574
- return "validating";
575
- }
576
- if (state.endsWith("RUNNING")) {
577
- return "in_progress";
578
- }
579
- }
580
- return done ? "completed" : "in_progress";
581
- };
582
- var inlinedResponses = (raw) => {
583
- const obj = asRecord(raw);
584
- const response = asRecord(obj.response);
585
- const dest = asRecord(obj.dest);
586
- const responseInline = response.inlinedResponses ?? response.inlined_responses;
587
- const destInline = dest.inlinedResponses ?? dest.inlined_responses;
588
- const nestedResponseInline = asRecord(responseInline);
589
- const nestedDestInline = asRecord(destInline);
590
- return [
591
- ...asArray(responseInline),
592
- ...asArray(nestedResponseInline.inlinedResponses),
593
- ...asArray(nestedResponseInline.inlined_responses),
594
- ...asArray(destInline),
595
- ...asArray(nestedDestInline.inlinedResponses),
596
- ...asArray(nestedDestInline.inlined_responses)
597
- ];
598
- };
599
- var normalizeSnapshot2 = (raw) => {
600
- const obj = asRecord(raw);
601
- const items = inlinedResponses(raw);
602
- const failed = items.filter((item) => asRecord(item).error).length;
603
- const id = asString(obj.name) ?? "";
604
- return {
605
- id: id ? assertPrefixedProviderId(OPERATION_ID_LABEL, id, GOOGLE_BATCH_PREFIX) : "",
606
- provider: "google",
607
- raw,
608
- requestCounts: {
609
- completed: items.length - failed,
610
- failed,
611
- total: items.length
612
- },
613
- status: mapState(asString(obj.state) ?? asString(asRecord(obj.state).name) ?? asString(asRecord(obj.metadata).state), obj.done === true)
614
- };
615
- };
616
- var textFromResponse = (response) => {
617
- const candidate = asRecord(asArray(asRecord(response).candidates)[0]);
618
- const text = asArray(asRecord(candidate.content).parts).map((part) => asString(asRecord(part).text) ?? "").join("");
619
- return text.length > 0 ? text : undefined;
620
- };
621
- var embeddingFromResponse = (response) => asNumberArray(asRecord(asRecord(response).embedding).values);
622
- var imagesFromResponse = (response) => {
623
- const candidate = asRecord(asArray(asRecord(response).candidates)[0]);
624
- const images = [];
625
- for (const part of asArray(asRecord(candidate.content).parts)) {
626
- const partObj = asRecord(part);
627
- const inline = asRecord(partObj.inlineData ?? partObj.inline_data);
628
- const data = asString(inline.data);
629
- const mediaType = asString(inline.mimeType) ?? asString(inline.mime_type);
630
- if (data && mediaType?.startsWith("image/")) {
631
- images.push({ data, mediaType });
632
- }
633
- }
634
- return images.length > 0 ? images : undefined;
635
- };
636
- var usageFromResponse = (response) => {
637
- const usage = asRecord(asRecord(response).usageMetadata);
638
- const inputTokens = asNumber(usage.promptTokenCount);
639
- const outputTokens = asNumber(usage.candidatesTokenCount);
640
- const totalTokens = asNumber(usage.totalTokenCount);
641
- if (inputTokens === undefined && outputTokens === undefined && totalTokens === undefined) {
642
- return;
643
- }
644
- return {
645
- inputTokens,
646
- outputTokens,
647
- totalTokens: totalTokens ?? (inputTokens ?? 0) + (outputTokens ?? 0)
648
- };
649
- };
650
- var normalizeResult2 = (item) => {
651
- const obj = asRecord(item);
652
- const customId = asString(asRecord(obj.metadata).key) ?? asString(obj.key) ?? asString(obj.custom_id) ?? "";
653
- if (obj.error) {
654
- const error = asRecord(obj.error);
655
- return {
656
- customId,
657
- error: {
658
- code: asNumber(error.code) ?? asString(error.code),
659
- message: asString(error.message) ?? "Request errored.",
660
- type: asString(error.status)
661
- },
662
- response: obj.error,
663
- status: "errored"
664
- };
665
- }
666
- return {
667
- customId,
668
- embedding: embeddingFromResponse(obj.response),
669
- images: imagesFromResponse(obj.response),
670
- response: obj.response,
671
- status: "succeeded",
672
- text: textFromResponse(obj.response),
673
- usage: usageFromResponse(obj.response)
674
- };
675
- };
676
- var EMBED_CONFIG_KEYS = new Set([
677
- "outputDimensionality",
678
- "taskType",
679
- "title"
680
- ]);
681
- var toEmbedRequest = (body) => {
682
- const request = {};
683
- const config = {};
684
- for (const [key, value] of Object.entries(body)) {
685
- if (EMBED_CONFIG_KEYS.has(key)) {
686
- config[key] = value;
687
- } else {
688
- request[key] = value;
689
- }
690
- }
691
- if (Object.keys(config).length > 0) {
692
- request.embedContentConfig = config;
693
- }
694
- return request;
695
- };
696
- var submit2 = async (input) => {
697
- const limits = resolveBatchLimits(input.limits);
698
- const isEmbedding = input.endpoint.toLowerCase().includes("embedcontent");
699
- const method = isEmbedding ? "asyncBatchEmbedContent" : "batchGenerateContent";
700
- const requests = input.built.map((item) => {
701
- const payload = omit(item.body, "stream");
702
- return {
703
- metadata: { key: item.customId },
704
- request: isEmbedding ? toEmbedRequest(payload) : payload
705
- };
706
- });
707
- const body = encodeJsonArrayPayload({
708
- items: requests,
709
- label: "batch upload payload",
710
- maxBytes: limits.maxUploadBytes,
711
- prefix: '{"batch":{"display_name":"batchwork","input_config":{"requests":{"requests":[',
712
- suffix: "]}}}}"
713
- });
714
- const raw = await requestJson(`${baseUrl2(input.credentials)}/models/${input.modelId}:${method}`, {
715
- body,
716
- headers: headers2(input.credentials),
717
- method: "POST"
718
- });
719
- return normalizeSnapshot2(raw);
720
- };
721
- var retrieve2 = async (id, credentials) => {
722
- const operationId = assertPrefixedProviderId(OPERATION_ID_LABEL, id, GOOGLE_BATCH_PREFIX);
723
- const raw = await requestJson(`${baseUrl2(credentials)}/${operationId}`, {
724
- headers: headers2(credentials)
725
- });
726
- return normalizeSnapshot2(raw);
727
- };
728
- var fileNameFrom = (value) => asString(asRecord(value).name) ?? asString(value);
729
- async function* results2(id, credentials) {
730
- const snapshot = await retrieve2(id, credentials);
731
- const raw = asRecord(snapshot.raw);
732
- const response = asRecord(raw.response);
733
- const dest = asRecord(raw.dest);
734
- const responsesFile = fileNameFrom(response.responsesFile) ?? fileNameFrom(response.responses_file) ?? asString(dest.fileName) ?? asString(dest.file_name);
735
- if (responsesFile) {
736
- throw new BatchworkError(`batchwork: batch "${id}" returned file-mode results, which are not supported yet.`);
737
- }
738
- const items = inlinedResponses(raw);
739
- if (items.length === 0) {
740
- throw new BatchworkError(`batchwork: results are not ready for batch "${id}" (status: ${snapshot.status}).`);
741
- }
742
- for (const item of items) {
743
- yield normalizeResult2(item);
744
- }
745
- }
746
- var cancel2 = async (id, credentials) => {
747
- const operationId = assertPrefixedProviderId(OPERATION_ID_LABEL, id, GOOGLE_BATCH_PREFIX);
748
- await requestJson(`${baseUrl2(credentials)}/${operationId}:cancel`, {
749
- headers: headers2(credentials),
518
+ const batchId = assertSimpleProviderId("Anthropic batch id", id);
519
+ await requestJson(`${baseUrl(credentials)}/v1/messages/batches/${batchId}/cancel`, {
520
+ headers: headers(credentials),
750
521
  method: "POST"
751
522
  });
752
523
  };
753
- var googleAdapter = {
754
- cancel: cancel2,
755
- id: "google",
756
- results: results2,
757
- retrieve: retrieve2,
758
- submit: submit2
524
+ var anthropicAdapter = {
525
+ cancel,
526
+ id: "anthropic",
527
+ results,
528
+ retrieve,
529
+ submit
759
530
  };
760
531
 
761
532
  // src/providers/shared.ts
@@ -777,7 +548,30 @@ var textFromBody = (body) => {
777
548
  return content;
778
549
  }
779
550
  }
780
- return asString(obj.output_text) ?? asString(obj.text);
551
+ const directText = asString(obj.output_text) ?? asString(obj.text);
552
+ if (directText !== undefined) {
553
+ return directText;
554
+ }
555
+ const parts = [];
556
+ for (const output of asArray(obj.output)) {
557
+ const item = asRecord(output);
558
+ if (item.type !== "message") {
559
+ continue;
560
+ }
561
+ for (const content of asArray(item.content)) {
562
+ const part = asRecord(content);
563
+ if (part.type !== "output_text") {
564
+ continue;
565
+ }
566
+ const text = asString(part.text);
567
+ if (text !== undefined) {
568
+ parts.push(text);
569
+ }
570
+ }
571
+ }
572
+ if (parts.length > 0) {
573
+ return parts.join("");
574
+ }
781
575
  };
782
576
  var segmentsFromBody = (body) => {
783
577
  const obj = asRecord(body);
@@ -818,11 +612,11 @@ var imagesFromBody = (body) => {
818
612
  return images.length > 0 ? images : undefined;
819
613
  };
820
614
  var moderationFromBody = (body) => {
821
- const results3 = asArray(asRecord(body).results);
822
- if (results3.length === 0) {
615
+ const results2 = asArray(asRecord(body).results);
616
+ if (results2.length === 0) {
823
617
  return;
824
618
  }
825
- const first = asRecord(results3[0]);
619
+ const first = asRecord(results2[0]);
826
620
  const categories = {};
827
621
  for (const [key, value] of Object.entries(asRecord(first.categories))) {
828
622
  if (typeof value === "boolean") {
@@ -899,24 +693,24 @@ var normalizeOpenAIResult = (line) => {
899
693
  status: "errored"
900
694
  };
901
695
  };
902
- var uploadInputFile = async (jsonl, baseUrl3, headers3, options = {}) => {
696
+ var uploadInputFile = async (jsonl, baseUrl2, headers2, options = {}) => {
903
697
  const form = new FormData;
904
698
  const purpose = options.purpose === undefined ? "batch" : options.purpose;
905
699
  if (purpose !== null) {
906
700
  form.append("purpose", purpose);
907
701
  }
908
702
  form.append("file", new Blob([jsonl], { type: "application/jsonl" }), "batchwork.jsonl");
909
- const raw = await requestJson(`${baseUrl3}/files`, {
703
+ const raw = await requestJson(`${baseUrl2}/files`, {
910
704
  body: form,
911
- headers: headers3,
705
+ headers: headers2,
912
706
  method: "POST",
913
707
  redirect: "manual"
914
708
  });
915
709
  return raw.id;
916
710
  };
917
- async function* streamResultFile(fileId, baseUrl3, headers3) {
918
- const stream = await requestStream(`${baseUrl3}/files/${fileId}/content`, {
919
- headers: headers3,
711
+ async function* streamResultFile(fileId, baseUrl2, headers2) {
712
+ const stream = await requestStream(`${baseUrl2}/files/${fileId}/content`, {
713
+ headers: headers2,
920
714
  redirect: "manual"
921
715
  });
922
716
  for await (const line of streamJsonl(stream)) {
@@ -944,7 +738,7 @@ var mapStatus2 = (status) => {
944
738
  }
945
739
  }
946
740
  };
947
- var normalizeSnapshot3 = (raw, provider) => {
741
+ var normalizeSnapshot2 = (raw, provider) => {
948
742
  const outer = asRecord(raw);
949
743
  const obj = asRecord(outer.job);
950
744
  const source = Object.keys(obj).length > 0 ? obj : outer;
@@ -967,14 +761,19 @@ var normalizeSnapshot3 = (raw, provider) => {
967
761
  var createOpenAICompatibleAdapter = (config) => {
968
762
  const completionWindow = config.completionWindow ?? DEFAULT_COMPLETION_WINDOW;
969
763
  const lineFormat = config.lineFormat ?? "method-url";
970
- const baseUrl3 = (credentials) => credentials.baseURL ?? config.baseUrl;
764
+ const baseUrl2 = (credentials) => config.resolveBaseUrl?.(credentials) ?? credentials.baseURL ?? config.baseUrl ?? (() => {
765
+ throw new BatchworkError(`batchwork: missing ${config.apiKeyLabel} base URL. Pass \`baseURL\`.`);
766
+ })();
971
767
  const authHeaders = (credentials) => ({
972
- Authorization: `Bearer ${resolveApiKey(credentials, config.apiKeyEnv, config.apiKeyLabel)}`,
768
+ ...config.authHeaders ? config.authHeaders(credentials) : {
769
+ Authorization: `Bearer ${resolveApiKey(credentials, config.apiKeyEnv, config.apiKeyLabel)}`
770
+ },
973
771
  ...credentials.headers
974
772
  });
975
- const submit3 = async (input) => {
773
+ const submit2 = async (input) => {
976
774
  const limits = resolveBatchLimits(input.limits);
977
775
  const endpoint = config.normalizeEndpoint ? config.normalizeEndpoint(input.endpoint) : input.endpoint;
776
+ const batchEndpoint = config.batchEndpoint?.(endpoint) ?? endpoint;
978
777
  const jsonl = encodeJsonl(input.built.map((item) => {
979
778
  const body = omit(item.body, "stream");
980
779
  if (lineFormat === "body-only") {
@@ -991,53 +790,321 @@ var createOpenAICompatibleAdapter = (config) => {
991
790
  url: endpoint
992
791
  };
993
792
  }), { label: "batch upload JSONL", maxBytes: limits.maxUploadBytes });
994
- const headers3 = authHeaders(input.credentials);
995
- const url = baseUrl3(input.credentials);
793
+ const headers2 = authHeaders(input.credentials);
794
+ const url = baseUrl2(input.credentials);
996
795
  const purpose = config.filePurpose ?? "batch";
997
- const inputFileId = await (config.uploadFile ? config.uploadFile({ baseUrl: url, headers: headers3, jsonl, purpose }) : uploadInputFile(jsonl, url, headers3, { purpose }));
796
+ const inputFileId = await (config.uploadFile ? config.uploadFile({ baseUrl: url, headers: headers2, jsonl, purpose }) : uploadInputFile(jsonl, url, headers2, { purpose }));
998
797
  const raw = await requestJson(`${url}/batches`, {
999
798
  body: JSON.stringify({
1000
799
  completion_window: completionWindow,
1001
- endpoint,
800
+ endpoint: batchEndpoint,
1002
801
  input_file_id: inputFileId,
1003
802
  metadata: input.metadata
1004
803
  }),
1005
- headers: { ...headers3, "content-type": "application/json" },
804
+ headers: { ...headers2, "content-type": "application/json" },
1006
805
  method: "POST"
1007
806
  });
1008
- return normalizeSnapshot3(raw, config.id);
807
+ return normalizeSnapshot2(raw, config.id);
1009
808
  };
1010
- const retrieve3 = async (id, credentials) => {
809
+ const retrieve2 = async (id, credentials) => {
1011
810
  const batchId = assertSimpleProviderId(`${config.id} batch id`, id);
1012
- const raw = await requestJson(`${baseUrl3(credentials)}/batches/${batchId}`, {
811
+ const raw = await requestJson(`${baseUrl2(credentials)}/batches/${batchId}`, {
1013
812
  headers: authHeaders(credentials)
1014
813
  });
1015
- return normalizeSnapshot3(raw, config.id);
814
+ return normalizeSnapshot2(raw, config.id);
1016
815
  };
1017
- async function* results3(id, credentials) {
1018
- const snapshot = await retrieve3(id, credentials);
816
+ async function* results2(id, credentials) {
817
+ const snapshot = await retrieve2(id, credentials);
1019
818
  const raw = asRecord(snapshot.raw);
1020
819
  const outputFileId = asString(raw.output_file_id);
1021
820
  const errorFileId = asString(raw.error_file_id);
1022
821
  if (!(outputFileId || errorFileId)) {
1023
822
  throw new BatchworkError(`batchwork: results are not ready for batch "${id}" (status: ${snapshot.status}).`);
1024
823
  }
1025
- const headers3 = authHeaders(credentials);
824
+ const headers2 = authHeaders(credentials);
1026
825
  if (outputFileId) {
1027
- yield* streamResultFile(assertSimpleProviderId(`${config.id} output file id`, outputFileId), baseUrl3(credentials), headers3);
826
+ yield* streamResultFile(assertSimpleProviderId(`${config.id} output file id`, outputFileId), baseUrl2(credentials), headers2);
1028
827
  }
1029
828
  if (errorFileId) {
1030
- yield* streamResultFile(assertSimpleProviderId(`${config.id} error file id`, errorFileId), baseUrl3(credentials), headers3);
829
+ yield* streamResultFile(assertSimpleProviderId(`${config.id} error file id`, errorFileId), baseUrl2(credentials), headers2);
1031
830
  }
1032
831
  }
1033
- const cancel3 = async (id, credentials) => {
832
+ const cancel2 = async (id, credentials) => {
1034
833
  const batchId = assertSimpleProviderId(`${config.id} batch id`, id);
1035
- await requestJson(`${baseUrl3(credentials)}/batches/${batchId}/cancel`, {
834
+ await requestJson(`${baseUrl2(credentials)}/batches/${batchId}/cancel`, {
1036
835
  headers: authHeaders(credentials),
1037
836
  method: "POST"
1038
837
  });
1039
838
  };
1040
- return { cancel: cancel3, id: config.id, results: results3, retrieve: retrieve3, submit: submit3 };
839
+ return { cancel: cancel2, id: config.id, results: results2, retrieve: retrieve2, submit: submit2 };
840
+ };
841
+
842
+ // src/providers/azure.ts
843
+ var isAzureOpenAIUrl = (value) => {
844
+ try {
845
+ return new URL(value).hostname.endsWith(".openai.azure.com");
846
+ } catch {
847
+ return false;
848
+ }
849
+ };
850
+ var baseUrl2 = (credentials) => {
851
+ const configured = credentials.baseURL ?? (process.env.AZURE_RESOURCE_NAME ? `https://${process.env.AZURE_RESOURCE_NAME}.openai.azure.com/openai` : undefined);
852
+ if (!configured) {
853
+ throw new BatchworkError("batchwork: missing Azure OpenAI resource. Set AZURE_RESOURCE_NAME or pass `baseURL`.");
854
+ }
855
+ const normalized = trimTrailingSlashes(configured);
856
+ if (!isAzureOpenAIUrl(normalized)) {
857
+ return normalized;
858
+ }
859
+ if (normalized.endsWith("/v1")) {
860
+ return normalized;
861
+ }
862
+ return normalized.endsWith("/openai") ? `${normalized}/v1` : `${normalized}/openai/v1`;
863
+ };
864
+ var hasCallerAuth = (headers2) => Object.keys(headers2 ?? {}).some((name) => {
865
+ const normalized = name.toLowerCase();
866
+ return normalized === "api-key" || normalized === "authorization";
867
+ });
868
+ var authHeaders = (credentials) => {
869
+ if (credentials.apiKey) {
870
+ return { "api-key": credentials.apiKey };
871
+ }
872
+ if (hasCallerAuth(credentials.headers)) {
873
+ return {};
874
+ }
875
+ const apiKey2 = process.env.AZURE_API_KEY ?? process.env.AZURE_OPENAI_API_KEY;
876
+ if (apiKey2) {
877
+ return { "api-key": apiKey2 };
878
+ }
879
+ throw new BatchworkError("batchwork: missing Azure OpenAI API key. Set AZURE_API_KEY or pass `apiKey`.");
880
+ };
881
+ var azureAdapter = createOpenAICompatibleAdapter({
882
+ apiKeyEnv: "AZURE_API_KEY",
883
+ apiKeyLabel: "Azure OpenAI",
884
+ authHeaders,
885
+ batchEndpoint: () => "/v1/chat/completions",
886
+ id: "azure",
887
+ normalizeEndpoint: (endpoint) => endpoint.replace(/^\/openai/u, ""),
888
+ resolveBaseUrl: baseUrl2
889
+ });
890
+
891
+ // src/providers/google.ts
892
+ var GOOGLE_BASE = "https://generativelanguage.googleapis.com/v1beta";
893
+ var OPERATION_ID_LABEL = "Google operation id";
894
+ var GOOGLE_BATCH_PREFIX = "batches";
895
+ var apiKey2 = (credentials) => {
896
+ const key = credentials.apiKey ?? process.env.GOOGLE_GENERATIVE_AI_API_KEY ?? process.env.GEMINI_API_KEY;
897
+ if (!key) {
898
+ throw new BatchworkError("batchwork: missing Google Gemini API key. Set GOOGLE_GENERATIVE_AI_API_KEY (or GEMINI_API_KEY) or pass `apiKey`.");
899
+ }
900
+ return key;
901
+ };
902
+ var baseUrl3 = (credentials) => credentials.baseURL ?? GOOGLE_BASE;
903
+ var headers2 = (credentials) => ({
904
+ "content-type": "application/json",
905
+ "x-goog-api-key": apiKey2(credentials),
906
+ ...credentials.headers
907
+ });
908
+ var mapState = (state, done) => {
909
+ if (state) {
910
+ if (state.endsWith("SUCCEEDED")) {
911
+ return "completed";
912
+ }
913
+ if (state.endsWith("FAILED")) {
914
+ return "failed";
915
+ }
916
+ if (state.endsWith("CANCELLED")) {
917
+ return "cancelled";
918
+ }
919
+ if (state.endsWith("EXPIRED")) {
920
+ return "expired";
921
+ }
922
+ if (state.endsWith("PENDING")) {
923
+ return "validating";
924
+ }
925
+ if (state.endsWith("RUNNING")) {
926
+ return "in_progress";
927
+ }
928
+ }
929
+ return done ? "completed" : "in_progress";
930
+ };
931
+ var inlinedResponses = (raw) => {
932
+ const obj = asRecord(raw);
933
+ const response = asRecord(obj.response);
934
+ const dest = asRecord(obj.dest);
935
+ const responseInline = response.inlinedResponses ?? response.inlined_responses;
936
+ const destInline = dest.inlinedResponses ?? dest.inlined_responses;
937
+ const nestedResponseInline = asRecord(responseInline);
938
+ const nestedDestInline = asRecord(destInline);
939
+ return [
940
+ ...asArray(responseInline),
941
+ ...asArray(nestedResponseInline.inlinedResponses),
942
+ ...asArray(nestedResponseInline.inlined_responses),
943
+ ...asArray(destInline),
944
+ ...asArray(nestedDestInline.inlinedResponses),
945
+ ...asArray(nestedDestInline.inlined_responses)
946
+ ];
947
+ };
948
+ var normalizeSnapshot3 = (raw) => {
949
+ const obj = asRecord(raw);
950
+ const items = inlinedResponses(raw);
951
+ const failed = items.filter((item) => asRecord(item).error).length;
952
+ const id = asString(obj.name) ?? "";
953
+ return {
954
+ id: id ? assertPrefixedProviderId(OPERATION_ID_LABEL, id, GOOGLE_BATCH_PREFIX) : "",
955
+ provider: "google",
956
+ raw,
957
+ requestCounts: {
958
+ completed: items.length - failed,
959
+ failed,
960
+ total: items.length
961
+ },
962
+ status: mapState(asString(obj.state) ?? asString(asRecord(obj.state).name) ?? asString(asRecord(obj.metadata).state), obj.done === true)
963
+ };
964
+ };
965
+ var textFromResponse = (response) => {
966
+ const candidate = asRecord(asArray(asRecord(response).candidates)[0]);
967
+ const text = asArray(asRecord(candidate.content).parts).map((part) => asString(asRecord(part).text) ?? "").join("");
968
+ return text.length > 0 ? text : undefined;
969
+ };
970
+ var embeddingFromResponse = (response) => asNumberArray(asRecord(asRecord(response).embedding).values);
971
+ var imagesFromResponse = (response) => {
972
+ const candidate = asRecord(asArray(asRecord(response).candidates)[0]);
973
+ const images = [];
974
+ for (const part of asArray(asRecord(candidate.content).parts)) {
975
+ const partObj = asRecord(part);
976
+ const inline = asRecord(partObj.inlineData ?? partObj.inline_data);
977
+ const data = asString(inline.data);
978
+ const mediaType = asString(inline.mimeType) ?? asString(inline.mime_type);
979
+ if (data && mediaType?.startsWith("image/")) {
980
+ images.push({ data, mediaType });
981
+ }
982
+ }
983
+ return images.length > 0 ? images : undefined;
984
+ };
985
+ var usageFromResponse = (response) => {
986
+ const usage = asRecord(asRecord(response).usageMetadata);
987
+ const inputTokens = asNumber(usage.promptTokenCount);
988
+ const outputTokens = asNumber(usage.candidatesTokenCount);
989
+ const totalTokens = asNumber(usage.totalTokenCount);
990
+ if (inputTokens === undefined && outputTokens === undefined && totalTokens === undefined) {
991
+ return;
992
+ }
993
+ return {
994
+ inputTokens,
995
+ outputTokens,
996
+ totalTokens: totalTokens ?? (inputTokens ?? 0) + (outputTokens ?? 0)
997
+ };
998
+ };
999
+ var normalizeResult2 = (item) => {
1000
+ const obj = asRecord(item);
1001
+ const customId = asString(asRecord(obj.metadata).key) ?? asString(obj.key) ?? asString(obj.custom_id) ?? "";
1002
+ if (obj.error) {
1003
+ const error = asRecord(obj.error);
1004
+ return {
1005
+ customId,
1006
+ error: {
1007
+ code: asNumber(error.code) ?? asString(error.code),
1008
+ message: asString(error.message) ?? "Request errored.",
1009
+ type: asString(error.status)
1010
+ },
1011
+ response: obj.error,
1012
+ status: "errored"
1013
+ };
1014
+ }
1015
+ return {
1016
+ customId,
1017
+ embedding: embeddingFromResponse(obj.response),
1018
+ images: imagesFromResponse(obj.response),
1019
+ response: obj.response,
1020
+ status: "succeeded",
1021
+ text: textFromResponse(obj.response),
1022
+ usage: usageFromResponse(obj.response)
1023
+ };
1024
+ };
1025
+ var EMBED_CONFIG_KEYS = new Set([
1026
+ "outputDimensionality",
1027
+ "taskType",
1028
+ "title"
1029
+ ]);
1030
+ var toEmbedRequest = (body) => {
1031
+ const request = {};
1032
+ const config = {};
1033
+ for (const [key, value] of Object.entries(body)) {
1034
+ if (EMBED_CONFIG_KEYS.has(key)) {
1035
+ config[key] = value;
1036
+ } else {
1037
+ request[key] = value;
1038
+ }
1039
+ }
1040
+ if (Object.keys(config).length > 0) {
1041
+ request.embedContentConfig = config;
1042
+ }
1043
+ return request;
1044
+ };
1045
+ var submit2 = async (input) => {
1046
+ const limits = resolveBatchLimits(input.limits);
1047
+ const isEmbedding = input.endpoint.toLowerCase().includes("embedcontent");
1048
+ const method = isEmbedding ? "asyncBatchEmbedContent" : "batchGenerateContent";
1049
+ const requests = input.built.map((item) => {
1050
+ const payload = omit(item.body, "stream");
1051
+ return {
1052
+ metadata: { key: item.customId },
1053
+ request: isEmbedding ? toEmbedRequest(payload) : payload
1054
+ };
1055
+ });
1056
+ const body = encodeJsonArrayPayload({
1057
+ items: requests,
1058
+ label: "batch upload payload",
1059
+ maxBytes: limits.maxUploadBytes,
1060
+ prefix: '{"batch":{"display_name":"batchwork","input_config":{"requests":{"requests":[',
1061
+ suffix: "]}}}}"
1062
+ });
1063
+ const raw = await requestJson(`${baseUrl3(input.credentials)}/models/${input.modelId}:${method}`, {
1064
+ body,
1065
+ headers: headers2(input.credentials),
1066
+ method: "POST"
1067
+ });
1068
+ return normalizeSnapshot3(raw);
1069
+ };
1070
+ var retrieve2 = async (id, credentials) => {
1071
+ const operationId = assertPrefixedProviderId(OPERATION_ID_LABEL, id, GOOGLE_BATCH_PREFIX);
1072
+ const raw = await requestJson(`${baseUrl3(credentials)}/${operationId}`, {
1073
+ headers: headers2(credentials)
1074
+ });
1075
+ return normalizeSnapshot3(raw);
1076
+ };
1077
+ var fileNameFrom = (value) => asString(asRecord(value).name) ?? asString(value);
1078
+ async function* results2(id, credentials) {
1079
+ const snapshot = await retrieve2(id, credentials);
1080
+ const raw = asRecord(snapshot.raw);
1081
+ const response = asRecord(raw.response);
1082
+ const dest = asRecord(raw.dest);
1083
+ const responsesFile = fileNameFrom(response.responsesFile) ?? fileNameFrom(response.responses_file) ?? asString(dest.fileName) ?? asString(dest.file_name);
1084
+ if (responsesFile) {
1085
+ throw new BatchworkError(`batchwork: batch "${id}" returned file-mode results, which are not supported yet.`);
1086
+ }
1087
+ const items = inlinedResponses(raw);
1088
+ if (items.length === 0) {
1089
+ throw new BatchworkError(`batchwork: results are not ready for batch "${id}" (status: ${snapshot.status}).`);
1090
+ }
1091
+ for (const item of items) {
1092
+ yield normalizeResult2(item);
1093
+ }
1094
+ }
1095
+ var cancel2 = async (id, credentials) => {
1096
+ const operationId = assertPrefixedProviderId(OPERATION_ID_LABEL, id, GOOGLE_BATCH_PREFIX);
1097
+ await requestJson(`${baseUrl3(credentials)}/${operationId}:cancel`, {
1098
+ headers: headers2(credentials),
1099
+ method: "POST"
1100
+ });
1101
+ };
1102
+ var googleAdapter = {
1103
+ cancel: cancel2,
1104
+ id: "google",
1105
+ results: results2,
1106
+ retrieve: retrieve2,
1107
+ submit: submit2
1041
1108
  };
1042
1109
 
1043
1110
  // src/providers/groq.ts
@@ -1054,8 +1121,8 @@ var groqAdapter = createOpenAICompatibleAdapter({
1054
1121
  var MISTRAL_BASE = "https://api.mistral.ai/v1";
1055
1122
  var JOB_ID_LABEL = "Mistral job id";
1056
1123
  var apiKey3 = (credentials) => resolveApiKey(credentials, "MISTRAL_API_KEY", "Mistral");
1057
- var baseUrl3 = (credentials) => credentials.baseURL ?? MISTRAL_BASE;
1058
- var authHeaders = (credentials) => ({
1124
+ var baseUrl4 = (credentials) => credentials.baseURL ?? MISTRAL_BASE;
1125
+ var authHeaders2 = (credentials) => ({
1059
1126
  Authorization: `Bearer ${apiKey3(credentials)}`,
1060
1127
  ...credentials.headers
1061
1128
  });
@@ -1109,8 +1176,8 @@ var submit3 = async (input) => {
1109
1176
  body: omit(omit(item.body, "stream"), "model"),
1110
1177
  custom_id: item.customId
1111
1178
  })), { label: "batch upload JSONL", maxBytes: limits.maxUploadBytes });
1112
- const inputFileId = await uploadInputFile(jsonl, baseUrl3(input.credentials), authHeaders(input.credentials));
1113
- const raw = await requestJson(`${baseUrl3(input.credentials)}/batch/jobs`, {
1179
+ const inputFileId = await uploadInputFile(jsonl, baseUrl4(input.credentials), authHeaders2(input.credentials));
1180
+ const raw = await requestJson(`${baseUrl4(input.credentials)}/batch/jobs`, {
1114
1181
  body: JSON.stringify({
1115
1182
  endpoint: input.endpoint,
1116
1183
  input_files: [inputFileId],
@@ -1118,7 +1185,7 @@ var submit3 = async (input) => {
1118
1185
  model: input.modelId
1119
1186
  }),
1120
1187
  headers: {
1121
- ...authHeaders(input.credentials),
1188
+ ...authHeaders2(input.credentials),
1122
1189
  "content-type": "application/json"
1123
1190
  },
1124
1191
  method: "POST"
@@ -1127,8 +1194,8 @@ var submit3 = async (input) => {
1127
1194
  };
1128
1195
  var retrieve3 = async (id, credentials) => {
1129
1196
  const jobId = assertSimpleProviderId(JOB_ID_LABEL, id);
1130
- const raw = await requestJson(`${baseUrl3(credentials)}/batch/jobs/${jobId}`, {
1131
- headers: authHeaders(credentials)
1197
+ const raw = await requestJson(`${baseUrl4(credentials)}/batch/jobs/${jobId}`, {
1198
+ headers: authHeaders2(credentials)
1132
1199
  });
1133
1200
  return normalizeSnapshot4(raw);
1134
1201
  };
@@ -1137,12 +1204,12 @@ async function* results3(id, credentials) {
1137
1204
  const raw = asRecord(snapshot.raw);
1138
1205
  const outputFileId = asString(raw.output_file);
1139
1206
  const errorFileId = asString(raw.error_file);
1140
- const headers3 = authHeaders(credentials);
1207
+ const headers3 = authHeaders2(credentials);
1141
1208
  if (outputFileId) {
1142
- yield* streamResultFile(assertSimpleProviderId("Mistral output file id", outputFileId), baseUrl3(credentials), headers3);
1209
+ yield* streamResultFile(assertSimpleProviderId("Mistral output file id", outputFileId), baseUrl4(credentials), headers3);
1143
1210
  }
1144
1211
  if (errorFileId) {
1145
- yield* streamResultFile(assertSimpleProviderId("Mistral error file id", errorFileId), baseUrl3(credentials), headers3);
1212
+ yield* streamResultFile(assertSimpleProviderId("Mistral error file id", errorFileId), baseUrl4(credentials), headers3);
1146
1213
  }
1147
1214
  if (!(outputFileId || errorFileId)) {
1148
1215
  throw new BatchworkError(`batchwork: results are not ready for batch "${id}" (status: ${snapshot.status}).`);
@@ -1150,8 +1217,8 @@ async function* results3(id, credentials) {
1150
1217
  }
1151
1218
  var cancel3 = async (id, credentials) => {
1152
1219
  const jobId = assertSimpleProviderId(JOB_ID_LABEL, id);
1153
- await requestJson(`${baseUrl3(credentials)}/batch/jobs/${jobId}/cancel`, {
1154
- headers: authHeaders(credentials),
1220
+ await requestJson(`${baseUrl4(credentials)}/batch/jobs/${jobId}/cancel`, {
1221
+ headers: authHeaders2(credentials),
1155
1222
  method: "POST"
1156
1223
  });
1157
1224
  };
@@ -1292,8 +1359,8 @@ var XAI_BASE = "https://api.x.ai/v1";
1292
1359
  var BATCH_ID_LABEL = "xAI batch id";
1293
1360
  var RESULTS_PAGE_SIZE = 100;
1294
1361
  var apiKey4 = (credentials) => resolveApiKey(credentials, "XAI_API_KEY", "xAI");
1295
- var baseUrl4 = (credentials) => credentials.baseURL ?? XAI_BASE;
1296
- var authHeaders2 = (credentials) => ({
1362
+ var baseUrl5 = (credentials) => credentials.baseURL ?? XAI_BASE;
1363
+ var authHeaders3 = (credentials) => ({
1297
1364
  Authorization: `Bearer ${apiKey4(credentials)}`,
1298
1365
  ...credentials.headers
1299
1366
  });
@@ -1411,11 +1478,11 @@ var submit4 = async (input) => {
1411
1478
  method: "POST",
1412
1479
  url: item.endpoint || input.endpoint
1413
1480
  })), { label: "batch upload JSONL", maxBytes: limits.maxUploadBytes });
1414
- const inputFileId = await uploadInputFile(jsonl, baseUrl4(input.credentials), authHeaders2(input.credentials), { purpose: null });
1415
- const raw = await requestJson(`${baseUrl4(input.credentials)}/batches`, {
1481
+ const inputFileId = await uploadInputFile(jsonl, baseUrl5(input.credentials), authHeaders3(input.credentials), { purpose: null });
1482
+ const raw = await requestJson(`${baseUrl5(input.credentials)}/batches`, {
1416
1483
  body: JSON.stringify({ input_file_id: inputFileId, name: "batchwork" }),
1417
1484
  headers: {
1418
- ...authHeaders2(input.credentials),
1485
+ ...authHeaders3(input.credentials),
1419
1486
  "content-type": "application/json"
1420
1487
  },
1421
1488
  method: "POST"
@@ -1424,21 +1491,21 @@ var submit4 = async (input) => {
1424
1491
  };
1425
1492
  var retrieve4 = async (id, credentials) => {
1426
1493
  const batchId = assertSimpleProviderId(BATCH_ID_LABEL, id);
1427
- const raw = await requestJson(`${baseUrl4(credentials)}/batches/${batchId}`, {
1428
- headers: authHeaders2(credentials)
1494
+ const raw = await requestJson(`${baseUrl5(credentials)}/batches/${batchId}`, {
1495
+ headers: authHeaders3(credentials)
1429
1496
  });
1430
1497
  return normalizeSnapshot5(raw);
1431
1498
  };
1432
1499
  async function* results4(id, credentials) {
1433
1500
  const batchId = assertSimpleProviderId(BATCH_ID_LABEL, id);
1434
- const headers3 = authHeaders2(credentials);
1501
+ const headers3 = authHeaders3(credentials);
1435
1502
  let token;
1436
1503
  do {
1437
1504
  const query = new URLSearchParams({ limit: String(RESULTS_PAGE_SIZE) });
1438
1505
  if (token) {
1439
1506
  query.set("pagination_token", token);
1440
1507
  }
1441
- const raw = await requestJson(`${baseUrl4(credentials)}/batches/${batchId}/results?${query.toString()}`, { headers: headers3 });
1508
+ const raw = await requestJson(`${baseUrl5(credentials)}/batches/${batchId}/results?${query.toString()}`, { headers: headers3 });
1442
1509
  const page = asRecord(raw);
1443
1510
  for (const item of Array.isArray(page.results) ? page.results : []) {
1444
1511
  yield normalizeResult3(item);
@@ -1448,8 +1515,8 @@ async function* results4(id, credentials) {
1448
1515
  }
1449
1516
  var cancel4 = async (id, credentials) => {
1450
1517
  const batchId = assertSimpleProviderId(BATCH_ID_LABEL, id);
1451
- await requestJson(`${baseUrl4(credentials)}/batches/${batchId}:cancel`, {
1452
- headers: authHeaders2(credentials),
1518
+ await requestJson(`${baseUrl5(credentials)}/batches/${batchId}:cancel`, {
1519
+ headers: authHeaders3(credentials),
1453
1520
  method: "POST"
1454
1521
  });
1455
1522
  };
@@ -1464,6 +1531,7 @@ var xaiAdapter = {
1464
1531
  // src/providers/index.ts
1465
1532
  var adapters = {
1466
1533
  anthropic: anthropicAdapter,
1534
+ azure: azureAdapter,
1467
1535
  google: googleAdapter,
1468
1536
  groq: groqAdapter,
1469
1537
  mistral: mistralAdapter,
@@ -1473,7 +1541,7 @@ var adapters = {
1473
1541
  };
1474
1542
  var getAdapter = (provider) => adapters[provider];
1475
1543
 
1476
- export { BatchworkError, UnsupportedProviderError, MissingDependencyError, resolveBatchLimits, assertByteLength, mapWithConcurrency, isTerminalStatus, BatchJob, getAdapter };
1544
+ export { BatchworkError, UnsupportedProviderError, MissingDependencyError, resolveBatchLimits, assertByteLength, trimTrailingSlashes, isTerminalStatus, BatchJob, getAdapter };
1477
1545
 
1478
- //# debugId=4C939B96519322C164756E2164756E21
1479
- //# sourceMappingURL=chunk-gwa0dkhj.js.map
1546
+ //# debugId=C29D2DE3DBB0695664756E2164756E21
1547
+ //# sourceMappingURL=chunk-n50t31ca.js.map