@superlinked/sie-sdk 0.6.15 → 0.6.17
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.
- package/dist/index.cjs +735 -5
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +450 -12
- package/dist/index.d.ts +450 -12
- package/dist/index.js +732 -6
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -87,6 +87,18 @@ var ModelLoadingError = class extends SIEError {
|
|
|
87
87
|
this.model = model;
|
|
88
88
|
}
|
|
89
89
|
};
|
|
90
|
+
var ResourceExhaustedError = class extends ServerError {
|
|
91
|
+
/** The model that was requested */
|
|
92
|
+
model;
|
|
93
|
+
/** Number of retry attempts made before giving up */
|
|
94
|
+
retries;
|
|
95
|
+
constructor(message, options) {
|
|
96
|
+
super(message, "RESOURCE_EXHAUSTED", 503);
|
|
97
|
+
this.name = "ResourceExhaustedError";
|
|
98
|
+
this.model = options?.model;
|
|
99
|
+
this.retries = options?.retries ?? 0;
|
|
100
|
+
}
|
|
101
|
+
};
|
|
90
102
|
var SIEStreamError = class extends SIEError {
|
|
91
103
|
/** SIE-native error code (e.g. `context_exceeded`, `cancelled`). */
|
|
92
104
|
code;
|
|
@@ -193,16 +205,24 @@ var HTTP_CLIENT_ERROR_MIN = 400;
|
|
|
193
205
|
var HTTP_CLIENT_ERROR_MAX = 499;
|
|
194
206
|
var HTTP_SERVER_ERROR_MIN = 500;
|
|
195
207
|
var HTTP_SERVER_ERROR_MAX = 599;
|
|
208
|
+
var HTTP_GATEWAY_TIMEOUT = 504;
|
|
196
209
|
var DEFAULT_TIMEOUT = 3e4;
|
|
210
|
+
var DEFAULT_LONG_RUNNING_TIMEOUT = 12e4;
|
|
197
211
|
var DEFAULT_PROVISION_TIMEOUT = 3e5;
|
|
198
212
|
var DEFAULT_RETRY_DELAY = 5e3;
|
|
199
213
|
var DEFAULT_LEASE_RENEWAL_INTERVAL = 6e4;
|
|
214
|
+
var DEFAULT_JOB_WAIT_TIMEOUT = 6e5;
|
|
215
|
+
var DEFAULT_JOB_WAIT_POLL = 2e3;
|
|
200
216
|
var LORA_LOADING_MAX_RETRIES = 10;
|
|
201
217
|
var LORA_LOADING_DEFAULT_DELAY = 1e3;
|
|
202
218
|
var LORA_LOADING_ERROR_CODE = "LORA_LOADING";
|
|
203
219
|
var MODEL_LOADING_DEFAULT_DELAY = 5e3;
|
|
204
220
|
var MODEL_LOADING_ERROR_CODE = "MODEL_LOADING";
|
|
205
221
|
var PROVISIONING_ERROR_CODE = "PROVISIONING";
|
|
222
|
+
var RESOURCE_EXHAUSTED_MAX_RETRIES = 3;
|
|
223
|
+
var RESOURCE_EXHAUSTED_DEFAULT_DELAY = 5e3;
|
|
224
|
+
var RESOURCE_EXHAUSTED_MAX_DELAY = 3e4;
|
|
225
|
+
var RESOURCE_EXHAUSTED_ERROR_CODE = "RESOURCE_EXHAUSTED";
|
|
206
226
|
var SDK_VERSION_HEADER = "X-SIE-SDK-Version";
|
|
207
227
|
var SERVER_VERSION_HEADER = "X-SIE-Server-Version";
|
|
208
228
|
var EXT_TYPE_NUMPY = 78;
|
|
@@ -392,9 +412,19 @@ function unpackMessage(data) {
|
|
|
392
412
|
// src/internal/retry.ts
|
|
393
413
|
var RETRY_JITTER_FRACTION = 0.25;
|
|
394
414
|
function applyRetryJitter(delay) {
|
|
415
|
+
if (delay <= 0) return Math.max(delay, 0);
|
|
395
416
|
const low = delay * (1 - RETRY_JITTER_FRACTION);
|
|
396
417
|
return Math.max(0, low + Math.random() * (delay - low));
|
|
397
418
|
}
|
|
419
|
+
function computeOomBackoff(retryAfter, attempt, baseDelay = RESOURCE_EXHAUSTED_DEFAULT_DELAY, maxDelay = RESOURCE_EXHAUSTED_MAX_DELAY) {
|
|
420
|
+
const safeRetryAfter = retryAfter !== void 0 ? Math.max(retryAfter, 0) : void 0;
|
|
421
|
+
if (safeRetryAfter !== void 0 && attempt === 0) {
|
|
422
|
+
return Math.min(safeRetryAfter, maxDelay);
|
|
423
|
+
}
|
|
424
|
+
const base = safeRetryAfter !== void 0 ? Math.max(baseDelay, safeRetryAfter) : baseDelay;
|
|
425
|
+
const capped = Math.max(0, Math.min(base * 2 ** attempt, maxDelay));
|
|
426
|
+
return applyRetryJitter(capped);
|
|
427
|
+
}
|
|
398
428
|
function getRetryAfter(header) {
|
|
399
429
|
if (!header) return void 0;
|
|
400
430
|
const seconds = Number.parseInt(header, 10);
|
|
@@ -650,8 +680,12 @@ function parseCapacityInfo(data, gpuFilter) {
|
|
|
650
680
|
const parsedWorkers = workers.map((w) => ({
|
|
651
681
|
url: w.url,
|
|
652
682
|
gpu: w.gpu,
|
|
683
|
+
gpuCount: w.gpu_count ?? 0,
|
|
684
|
+
readyGpuSlots: w.ready_gpu_slots ?? w.gpu_count ?? (w.healthy ? 1 : 0),
|
|
653
685
|
healthy: w.healthy,
|
|
654
686
|
queueDepth: w.queue_depth,
|
|
687
|
+
pendingCost: w.pending_cost ?? 0,
|
|
688
|
+
inflightBatches: w.inflight_batches ?? 0,
|
|
655
689
|
loadedModels: w.loaded_models
|
|
656
690
|
}));
|
|
657
691
|
return {
|
|
@@ -669,8 +703,21 @@ function parseCapacityInfo(data, gpuFilter) {
|
|
|
669
703
|
function sleep(ms) {
|
|
670
704
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
671
705
|
}
|
|
706
|
+
function nextOomRetryDelay(opts) {
|
|
707
|
+
const { retryAfter, oomRetries, maxOomRetries, elapsedMs, provisionTimeoutMs, model } = opts;
|
|
708
|
+
const message = `Server resource exhausted after ${oomRetries} retry attempt(s) for model '${model}'`;
|
|
709
|
+
if (oomRetries >= maxOomRetries || elapsedMs >= provisionTimeoutMs) {
|
|
710
|
+
throw new ResourceExhaustedError(message, { model, retries: oomRetries });
|
|
711
|
+
}
|
|
712
|
+
const delay = computeOomBackoff(retryAfter, oomRetries);
|
|
713
|
+
if (delay >= provisionTimeoutMs - elapsedMs) {
|
|
714
|
+
throw new ResourceExhaustedError(message, { model, retries: oomRetries });
|
|
715
|
+
}
|
|
716
|
+
return delay;
|
|
717
|
+
}
|
|
672
718
|
async function withProvisioningRetry(performFetch, opts) {
|
|
673
719
|
const startTime = Date.now();
|
|
720
|
+
let oomRetries = 0;
|
|
674
721
|
while (true) {
|
|
675
722
|
const response = await performFetch();
|
|
676
723
|
await throwIfModelLoadFailed(response, opts.model);
|
|
@@ -706,6 +753,26 @@ async function withProvisioningRetry(performFetch, opts) {
|
|
|
706
753
|
await sleep(Math.min(delay, opts.provisionTimeoutMs - elapsed));
|
|
707
754
|
continue;
|
|
708
755
|
}
|
|
756
|
+
if (errorCode === RESOURCE_EXHAUSTED_ERROR_CODE) {
|
|
757
|
+
const delay = nextOomRetryDelay({
|
|
758
|
+
retryAfter: getRetryAfter2(response),
|
|
759
|
+
oomRetries,
|
|
760
|
+
maxOomRetries: RESOURCE_EXHAUSTED_MAX_RETRIES,
|
|
761
|
+
elapsedMs: Date.now() - startTime,
|
|
762
|
+
provisionTimeoutMs: opts.provisionTimeoutMs,
|
|
763
|
+
model: opts.model
|
|
764
|
+
});
|
|
765
|
+
oomRetries += 1;
|
|
766
|
+
await sleep(delay);
|
|
767
|
+
continue;
|
|
768
|
+
}
|
|
769
|
+
}
|
|
770
|
+
if (response.status === HTTP_GATEWAY_TIMEOUT) {
|
|
771
|
+
throw new ServerError(
|
|
772
|
+
"Gateway timed out (504) after the request was published to the queue; a worker may already be generating. Not retried because generation is non-idempotent (retrying could double-bill). Re-issue manually if needed.",
|
|
773
|
+
await getErrorCode(response.clone()),
|
|
774
|
+
HTTP_GATEWAY_TIMEOUT
|
|
775
|
+
);
|
|
709
776
|
}
|
|
710
777
|
if (!response.ok) {
|
|
711
778
|
await handleError(response);
|
|
@@ -717,6 +784,255 @@ async function withProvisioningRetry(performFetch, opts) {
|
|
|
717
784
|
}
|
|
718
785
|
}
|
|
719
786
|
|
|
787
|
+
// src/jobs.ts
|
|
788
|
+
var TERMINAL_JOB_STATES = /* @__PURE__ */ new Set([
|
|
789
|
+
"succeeded",
|
|
790
|
+
"failed",
|
|
791
|
+
"suspended",
|
|
792
|
+
"cancelled"
|
|
793
|
+
]);
|
|
794
|
+
var SINK_RETURN = /* @__PURE__ */ new Set(["return", "default"]);
|
|
795
|
+
var SINK_INPLACE = /* @__PURE__ */ new Set(["inplace", "in_place", "in place"]);
|
|
796
|
+
var INTERNAL_SCHEMES = /* @__PURE__ */ new Set(["upload"]);
|
|
797
|
+
var FIELD_MAP_KEYS = /* @__PURE__ */ new Set(["id_field", "input_field", "carry", "input_type"]);
|
|
798
|
+
var INPUT_TYPES = /* @__PURE__ */ new Set(["text", "document"]);
|
|
799
|
+
function isConnectorUri(value) {
|
|
800
|
+
return value.includes("://");
|
|
801
|
+
}
|
|
802
|
+
function isInternalUri(uri) {
|
|
803
|
+
return INTERNAL_SCHEMES.has(uri.split("://", 1)[0] ?? "");
|
|
804
|
+
}
|
|
805
|
+
function normItem(item, index) {
|
|
806
|
+
if (typeof item === "string") return { text: item };
|
|
807
|
+
if (item !== null && typeof item === "object") return item;
|
|
808
|
+
throw new RequestError(`item ${index} must be a string or an object`, "invalid_request", 400);
|
|
809
|
+
}
|
|
810
|
+
function connectionName(uri) {
|
|
811
|
+
const afterScheme = uri.split("://", 2)[1] ?? "";
|
|
812
|
+
const authority = afterScheme.split(/[/?#]/, 1)[0] ?? "";
|
|
813
|
+
const name = authority.trim();
|
|
814
|
+
if (!name) {
|
|
815
|
+
throw new RequestError(
|
|
816
|
+
`connector URI ${JSON.stringify(uri)} names no connection (expected 'scheme://<connection>/\u2026')`,
|
|
817
|
+
"invalid_request",
|
|
818
|
+
400
|
|
819
|
+
);
|
|
820
|
+
}
|
|
821
|
+
return name;
|
|
822
|
+
}
|
|
823
|
+
function resolveSource(source, connection) {
|
|
824
|
+
if (Array.isArray(source)) {
|
|
825
|
+
if (source.length === 0) {
|
|
826
|
+
throw new RequestError("inline source has no items", "invalid_request", 400);
|
|
827
|
+
}
|
|
828
|
+
return { items: source.map((item, i) => normItem(item, i)) };
|
|
829
|
+
}
|
|
830
|
+
if (isConnectorUri(source)) {
|
|
831
|
+
if (isInternalUri(source)) {
|
|
832
|
+
return connection ? { src: source, connection } : { src: source };
|
|
833
|
+
}
|
|
834
|
+
return { src: source, connection: connection ?? connectionName(source) };
|
|
835
|
+
}
|
|
836
|
+
if (typeof source === "string" && source.trim()) {
|
|
837
|
+
return { items: [{ text: source }] };
|
|
838
|
+
}
|
|
839
|
+
throw new RequestError(
|
|
840
|
+
"source must be inline items (a list/string) or a connector URI (scheme://<connection>/\u2026)",
|
|
841
|
+
"invalid_request",
|
|
842
|
+
400
|
|
843
|
+
);
|
|
844
|
+
}
|
|
845
|
+
function resolveSink(sink, sourceConnection, sinkConnection) {
|
|
846
|
+
if (sink === null || sink === void 0 || SINK_RETURN.has(sink.trim().toLowerCase())) {
|
|
847
|
+
return {};
|
|
848
|
+
}
|
|
849
|
+
if (SINK_INPLACE.has(sink.trim().toLowerCase())) {
|
|
850
|
+
return { sink: "inplace" };
|
|
851
|
+
}
|
|
852
|
+
if (isConnectorUri(sink)) {
|
|
853
|
+
const body = { sink };
|
|
854
|
+
if (isInternalUri(sink)) {
|
|
855
|
+
if (sinkConnection != null) body.sink_connection = sinkConnection;
|
|
856
|
+
return body;
|
|
857
|
+
}
|
|
858
|
+
const resolved = sinkConnection ?? connectionName(sink);
|
|
859
|
+
if (sinkConnection != null || resolved !== sourceConnection) {
|
|
860
|
+
body.sink_connection = resolved;
|
|
861
|
+
}
|
|
862
|
+
return body;
|
|
863
|
+
}
|
|
864
|
+
throw new RequestError(
|
|
865
|
+
`sink must be 'return', 'inplace', or a connector URI (got ${JSON.stringify(sink)})`,
|
|
866
|
+
"invalid_request",
|
|
867
|
+
400
|
|
868
|
+
);
|
|
869
|
+
}
|
|
870
|
+
function resolveFieldMap(fieldMap, outputField) {
|
|
871
|
+
const body = {};
|
|
872
|
+
if (fieldMap != null) {
|
|
873
|
+
const unknown = Object.keys(fieldMap).filter((key) => !FIELD_MAP_KEYS.has(key));
|
|
874
|
+
if (unknown.length > 0) {
|
|
875
|
+
throw new RequestError(
|
|
876
|
+
`unknown field_map key(s) ${JSON.stringify(unknown)} (known: ${[...FIELD_MAP_KEYS].join(", ")})`,
|
|
877
|
+
"invalid_request",
|
|
878
|
+
400
|
|
879
|
+
);
|
|
880
|
+
}
|
|
881
|
+
if (fieldMap.carry != null && (!Array.isArray(fieldMap.carry) || fieldMap.carry.some((c) => typeof c !== "string" || !c))) {
|
|
882
|
+
throw new RequestError(
|
|
883
|
+
`field_map.carry must be a list of field names (got ${JSON.stringify(fieldMap.carry)})`,
|
|
884
|
+
"invalid_request",
|
|
885
|
+
400
|
|
886
|
+
);
|
|
887
|
+
}
|
|
888
|
+
if (fieldMap.input_type != null && !INPUT_TYPES.has(fieldMap.input_type)) {
|
|
889
|
+
throw new RequestError(
|
|
890
|
+
`field_map.input_type must be one of ${[...INPUT_TYPES].join(", ")} (got ${JSON.stringify(fieldMap.input_type)})`,
|
|
891
|
+
"invalid_request",
|
|
892
|
+
400
|
|
893
|
+
);
|
|
894
|
+
}
|
|
895
|
+
const mapped = {};
|
|
896
|
+
if (fieldMap.id_field != null) mapped.id_field = fieldMap.id_field;
|
|
897
|
+
if (fieldMap.input_field != null) mapped.input_field = fieldMap.input_field;
|
|
898
|
+
if (fieldMap.input_type != null) mapped.input_type = fieldMap.input_type;
|
|
899
|
+
if (fieldMap.carry != null && fieldMap.carry.length > 0) mapped.carry = fieldMap.carry;
|
|
900
|
+
if (Object.keys(mapped).length > 0) body.field_map = mapped;
|
|
901
|
+
}
|
|
902
|
+
if (outputField != null) {
|
|
903
|
+
if (typeof outputField !== "string" || !outputField) {
|
|
904
|
+
throw new RequestError(
|
|
905
|
+
`output_field must be a non-empty string (got ${JSON.stringify(outputField)})`,
|
|
906
|
+
"invalid_request",
|
|
907
|
+
400
|
|
908
|
+
);
|
|
909
|
+
}
|
|
910
|
+
body.output_field = outputField;
|
|
911
|
+
}
|
|
912
|
+
return body;
|
|
913
|
+
}
|
|
914
|
+
function resolveWhen(when) {
|
|
915
|
+
if (when == null || when.trim() === "" || when.trim().toLowerCase() === "now") {
|
|
916
|
+
return {};
|
|
917
|
+
}
|
|
918
|
+
const text = when.trim();
|
|
919
|
+
if (text.toLowerCase().startsWith("schedule:")) {
|
|
920
|
+
return { when: "schedule", schedule: text.slice("schedule:".length).trim() };
|
|
921
|
+
}
|
|
922
|
+
if (text.toLowerCase().startsWith("watch:")) {
|
|
923
|
+
return { when: "watch", watch: text.slice("watch:".length).trim() };
|
|
924
|
+
}
|
|
925
|
+
if (text.toLowerCase() === "schedule") {
|
|
926
|
+
throw new RequestError(
|
|
927
|
+
"schedule trigger needs a cron expr: when='schedule:<cron>'",
|
|
928
|
+
"invalid_request",
|
|
929
|
+
400
|
|
930
|
+
);
|
|
931
|
+
}
|
|
932
|
+
if (text.split(/\s+/).length === 5) {
|
|
933
|
+
return { when: "schedule", schedule: text };
|
|
934
|
+
}
|
|
935
|
+
throw new RequestError(
|
|
936
|
+
`unrecognized when ${JSON.stringify(when)}: use 'now', 'schedule:<cron>', or 'watch:<source>'`,
|
|
937
|
+
"invalid_request",
|
|
938
|
+
400
|
|
939
|
+
);
|
|
940
|
+
}
|
|
941
|
+
function buildJobBody(options) {
|
|
942
|
+
const operation = options.operation ?? "encode";
|
|
943
|
+
const body = { operation, model: options.model };
|
|
944
|
+
const sourceFields = resolveSource(options.source, options.connection);
|
|
945
|
+
Object.assign(body, sourceFields);
|
|
946
|
+
Object.assign(
|
|
947
|
+
body,
|
|
948
|
+
resolveSink(
|
|
949
|
+
options.sink,
|
|
950
|
+
sourceFields.connection,
|
|
951
|
+
options.sinkConnection
|
|
952
|
+
)
|
|
953
|
+
);
|
|
954
|
+
const mappingFields = resolveFieldMap(options.fieldMap, options.outputField);
|
|
955
|
+
if (Object.keys(mappingFields).length > 0 && !("src" in body)) {
|
|
956
|
+
throw new RequestError(
|
|
957
|
+
"fieldMap/outputField apply to connector-src jobs; an inline items job maps nothing",
|
|
958
|
+
"invalid_request",
|
|
959
|
+
400
|
|
960
|
+
);
|
|
961
|
+
}
|
|
962
|
+
Object.assign(body, mappingFields);
|
|
963
|
+
Object.assign(body, resolveWhen(options.when));
|
|
964
|
+
if (options.outputTypes && options.outputTypes.length > 0) {
|
|
965
|
+
body.output_types = options.outputTypes;
|
|
966
|
+
}
|
|
967
|
+
if (options.options && Object.keys(options.options).length > 0) {
|
|
968
|
+
body.options = options.options;
|
|
969
|
+
}
|
|
970
|
+
return body;
|
|
971
|
+
}
|
|
972
|
+
function jobChunks(jobDoc) {
|
|
973
|
+
const raw = jobDoc.output?.chunks ?? [];
|
|
974
|
+
return raw.map((chunk) => ({
|
|
975
|
+
seq: chunk.seq,
|
|
976
|
+
items: chunk.items,
|
|
977
|
+
state: chunk.state,
|
|
978
|
+
ref: chunk.ref,
|
|
979
|
+
units: chunk.units,
|
|
980
|
+
credits: chunk.credits,
|
|
981
|
+
error: chunk.error ?? null
|
|
982
|
+
}));
|
|
983
|
+
}
|
|
984
|
+
function toNumberArrayLike(value) {
|
|
985
|
+
if (value == null) return null;
|
|
986
|
+
if (value instanceof Float32Array) return value;
|
|
987
|
+
if (Array.isArray(value)) return value;
|
|
988
|
+
if (ArrayBuffer.isView(value)) return value;
|
|
989
|
+
return null;
|
|
990
|
+
}
|
|
991
|
+
function denseInfo(dense) {
|
|
992
|
+
if (dense == null) return { dims: null, vector: null };
|
|
993
|
+
if (typeof dense === "object" && !Array.isArray(dense) && !ArrayBuffer.isView(dense)) {
|
|
994
|
+
const rec = dense;
|
|
995
|
+
let raw = null;
|
|
996
|
+
for (const key of ["values", "vector", "dense"]) {
|
|
997
|
+
if (rec[key] != null) {
|
|
998
|
+
raw = rec[key];
|
|
999
|
+
break;
|
|
1000
|
+
}
|
|
1001
|
+
}
|
|
1002
|
+
const vector2 = toNumberArrayLike(raw);
|
|
1003
|
+
let dims = typeof rec.dims === "number" ? rec.dims : null;
|
|
1004
|
+
if (dims == null && vector2 != null) dims = vector2.length;
|
|
1005
|
+
return { dims, vector: vector2 };
|
|
1006
|
+
}
|
|
1007
|
+
const vector = toNumberArrayLike(dense);
|
|
1008
|
+
return { dims: vector != null ? vector.length : null, vector };
|
|
1009
|
+
}
|
|
1010
|
+
function decodeResultItem(result) {
|
|
1011
|
+
const payload = result.result_msgpack;
|
|
1012
|
+
let decoded = null;
|
|
1013
|
+
if (payload instanceof Uint8Array) {
|
|
1014
|
+
try {
|
|
1015
|
+
decoded = unpackMessage(payload);
|
|
1016
|
+
} catch {
|
|
1017
|
+
decoded = null;
|
|
1018
|
+
}
|
|
1019
|
+
}
|
|
1020
|
+
const dense = decoded && typeof decoded === "object" ? decoded.dense : null;
|
|
1021
|
+
const { dims, vector } = denseInfo(dense);
|
|
1022
|
+
return {
|
|
1023
|
+
id: result.id ?? null,
|
|
1024
|
+
success: result.success ?? null,
|
|
1025
|
+
units: result.units ?? null,
|
|
1026
|
+
dims,
|
|
1027
|
+
dense: vector
|
|
1028
|
+
};
|
|
1029
|
+
}
|
|
1030
|
+
function decodeChunkBytes(raw) {
|
|
1031
|
+
const results = unpackMessage(raw);
|
|
1032
|
+
if (!Array.isArray(results)) return [];
|
|
1033
|
+
return results.map((r) => decodeResultItem(r));
|
|
1034
|
+
}
|
|
1035
|
+
|
|
720
1036
|
// src/sse.ts
|
|
721
1037
|
var SSE_DONE = "[DONE]";
|
|
722
1038
|
var MAX_SSE_BUFFER_CHARS = 8 * 1024 * 1024;
|
|
@@ -856,12 +1172,20 @@ function extractDataPayload(block) {
|
|
|
856
1172
|
}
|
|
857
1173
|
|
|
858
1174
|
// src/version.ts
|
|
859
|
-
var SDK_VERSION = "0.6.
|
|
1175
|
+
var SDK_VERSION = "0.6.17";
|
|
860
1176
|
|
|
861
1177
|
// src/client.ts
|
|
862
1178
|
function sleep2(ms) {
|
|
863
1179
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
864
1180
|
}
|
|
1181
|
+
function resolveUploadFilename(file, filename) {
|
|
1182
|
+
if (filename) return filename;
|
|
1183
|
+
const name = file.name;
|
|
1184
|
+
if (typeof name === "string" && name.length > 0) {
|
|
1185
|
+
return name.split(/[/\\]/).pop() || "upload.jsonl";
|
|
1186
|
+
}
|
|
1187
|
+
return "upload.jsonl";
|
|
1188
|
+
}
|
|
865
1189
|
function abortableSleep(ms, signal) {
|
|
866
1190
|
if (signal.aborted) return Promise.resolve(true);
|
|
867
1191
|
return new Promise((resolve) => {
|
|
@@ -915,6 +1239,16 @@ var SIEClient = class {
|
|
|
915
1239
|
apiKey;
|
|
916
1240
|
defaultWaitForCapacity;
|
|
917
1241
|
provisionTimeout;
|
|
1242
|
+
controlPlaneUrl;
|
|
1243
|
+
org;
|
|
1244
|
+
/** Batch class — `POST/GET /v1/jobs` on the keyed gateway. */
|
|
1245
|
+
jobs;
|
|
1246
|
+
/** Org-scoped connections (connector auth by name) on the control plane. */
|
|
1247
|
+
connections;
|
|
1248
|
+
/** OpenAI-compatible Files API — `POST/GET /v1/files`. */
|
|
1249
|
+
files;
|
|
1250
|
+
/** OpenAI-compatible Batch API — `POST/GET /v1/batches`. */
|
|
1251
|
+
batches;
|
|
918
1252
|
// Pool state: track created pools and their lease renewal scheduling
|
|
919
1253
|
pools = /* @__PURE__ */ new Map();
|
|
920
1254
|
// Version negotiation state
|
|
@@ -932,8 +1266,36 @@ var SIEClient = class {
|
|
|
932
1266
|
this.timeout = options.timeout ?? DEFAULT_TIMEOUT;
|
|
933
1267
|
this.gpu = options.gpu;
|
|
934
1268
|
this.apiKey = options.apiKey;
|
|
935
|
-
this.defaultWaitForCapacity = options.waitForCapacity ??
|
|
1269
|
+
this.defaultWaitForCapacity = options.waitForCapacity ?? true;
|
|
936
1270
|
this.provisionTimeout = options.provisionTimeout ?? DEFAULT_PROVISION_TIMEOUT;
|
|
1271
|
+
this.controlPlaneUrl = options.controlPlaneUrl?.replace(/\/$/, "");
|
|
1272
|
+
this.org = options.org;
|
|
1273
|
+
this.jobs = {
|
|
1274
|
+
submit: (submitOptions) => this.jobSubmit(submitOptions),
|
|
1275
|
+
get: (jobId) => this.jobGet(jobId),
|
|
1276
|
+
list: () => this.jobList(),
|
|
1277
|
+
cancel: (jobId) => this.jobCancel(jobId),
|
|
1278
|
+
results: (jobId) => this.jobResults(jobId),
|
|
1279
|
+
wait: (jobId, options2) => this.jobWait(jobId, options2)
|
|
1280
|
+
};
|
|
1281
|
+
this.connections = {
|
|
1282
|
+
add: (name, type, secret) => this.connectionAdd(name, type, secret),
|
|
1283
|
+
list: () => this.connectionList(),
|
|
1284
|
+
revoke: (name) => this.connectionRevoke(name)
|
|
1285
|
+
};
|
|
1286
|
+
this.files = {
|
|
1287
|
+
upload: (file, options2) => this.fileUpload(file, options2),
|
|
1288
|
+
create: (options2) => this.fileUpload(options2.file, options2),
|
|
1289
|
+
retrieve: (fileId) => this.fileRetrieve(fileId),
|
|
1290
|
+
content: (fileId) => this.fileContent(fileId),
|
|
1291
|
+
delete: (fileId) => this.fileDelete(fileId)
|
|
1292
|
+
};
|
|
1293
|
+
this.batches = {
|
|
1294
|
+
create: (options2) => this.batchCreate(options2),
|
|
1295
|
+
retrieve: (batchId) => this.batchRetrieve(batchId),
|
|
1296
|
+
list: () => this.batchList(),
|
|
1297
|
+
cancel: (batchId) => this.batchCancel(batchId)
|
|
1298
|
+
};
|
|
937
1299
|
}
|
|
938
1300
|
/**
|
|
939
1301
|
* Get the base URL of the SIE server.
|
|
@@ -1396,8 +1758,10 @@ var SIEClient = class {
|
|
|
1396
1758
|
* generator throws it instead of yielding the chunk.
|
|
1397
1759
|
*
|
|
1398
1760
|
* Retry policy mirrors {@link generate}: only explicit SAFE
|
|
1399
|
-
* pre-execution capacity signals — `503 PROVISIONING
|
|
1400
|
-
* `503 MODEL_LOADING`
|
|
1761
|
+
* pre-execution capacity signals — `503 PROVISIONING`,
|
|
1762
|
+
* `503 MODEL_LOADING` and `503 RESOURCE_EXHAUSTED` (the latter only
|
|
1763
|
+
* under `waitForCapacity`) — are retried while the provision budget
|
|
1764
|
+
* remains; a `504` is post-publish and therefore terminal.
|
|
1401
1765
|
* Once the body opens we never retry (the call is non-idempotent; a
|
|
1402
1766
|
* mid-stream failure must not re-issue generation).
|
|
1403
1767
|
*
|
|
@@ -1419,6 +1783,7 @@ var SIEClient = class {
|
|
|
1419
1783
|
}
|
|
1420
1784
|
try {
|
|
1421
1785
|
const startTime = Date.now();
|
|
1786
|
+
let oomRetries = 0;
|
|
1422
1787
|
let response;
|
|
1423
1788
|
while (true) {
|
|
1424
1789
|
if (signal?.aborted) {
|
|
@@ -1490,6 +1855,34 @@ var SIEClient = class {
|
|
|
1490
1855
|
}
|
|
1491
1856
|
continue;
|
|
1492
1857
|
}
|
|
1858
|
+
if (errorCode === RESOURCE_EXHAUSTED_ERROR_CODE) {
|
|
1859
|
+
if (!waitForCapacity) {
|
|
1860
|
+
throw new ResourceExhaustedError(
|
|
1861
|
+
`Server resource exhausted after ${oomRetries} retry attempt(s) for model '${model}'`,
|
|
1862
|
+
{ model, retries: oomRetries }
|
|
1863
|
+
);
|
|
1864
|
+
}
|
|
1865
|
+
const delay = nextOomRetryDelay({
|
|
1866
|
+
retryAfter: getRetryAfter2(attemptResponse),
|
|
1867
|
+
oomRetries,
|
|
1868
|
+
maxOomRetries: RESOURCE_EXHAUSTED_MAX_RETRIES,
|
|
1869
|
+
elapsedMs: Date.now() - startTime,
|
|
1870
|
+
provisionTimeoutMs: this.provisionTimeout,
|
|
1871
|
+
model
|
|
1872
|
+
});
|
|
1873
|
+
oomRetries += 1;
|
|
1874
|
+
if (await abortableSleep(delay, controller.signal)) {
|
|
1875
|
+
throw new SIEConnectionError("Stream aborted while provisioning", "other");
|
|
1876
|
+
}
|
|
1877
|
+
continue;
|
|
1878
|
+
}
|
|
1879
|
+
}
|
|
1880
|
+
if (attemptResponse.status === HTTP_GATEWAY_TIMEOUT) {
|
|
1881
|
+
throw new ServerError(
|
|
1882
|
+
"Gateway timed out (504) after the request was published to the queue; a worker may already be generating. Not retried because generation is non-idempotent (retrying could double-bill).",
|
|
1883
|
+
await getErrorCode(attemptResponse.clone()),
|
|
1884
|
+
HTTP_GATEWAY_TIMEOUT
|
|
1885
|
+
);
|
|
1493
1886
|
}
|
|
1494
1887
|
if (attemptResponse.status !== 200) {
|
|
1495
1888
|
await handleError(attemptResponse);
|
|
@@ -1642,12 +2035,21 @@ var SIEClient = class {
|
|
|
1642
2035
|
* @param gpus - Optional machine profile requirements for pool readiness, e.g., { "l4": 2, "l4-spot": 1 }
|
|
1643
2036
|
* @param gpuCaps - Optional maximum assigned workers per machine profile
|
|
1644
2037
|
* @param queuePool - Optional Helm/NATS queue namespace backing this logical pool. Defaults to "default".
|
|
2038
|
+
* @param options - Optional bundle filter, warm floor, and pinned models
|
|
2039
|
+
* (Python SDK `create_pool` parity)
|
|
1645
2040
|
*
|
|
1646
2041
|
* @example
|
|
1647
2042
|
* ```typescript
|
|
1648
2043
|
* // Create or update a pool with 2 L4 GPUs
|
|
1649
2044
|
* await client.createPool("eval-bench", { l4: 2 });
|
|
1650
2045
|
*
|
|
2046
|
+
* // With a bundle filter, warm floor, and pinned models
|
|
2047
|
+
* await client.createPool("eval-bench", { l4: 2 }, undefined, undefined, {
|
|
2048
|
+
* bundle: "default",
|
|
2049
|
+
* minimumWorkerCount: 1,
|
|
2050
|
+
* pinnedModels: ["bge-m3"],
|
|
2051
|
+
* });
|
|
2052
|
+
*
|
|
1651
2053
|
* // Use the pool for requests
|
|
1652
2054
|
* await client.encode("bge-m3", { text: "Hello" }, { gpu: "eval-bench/l4" });
|
|
1653
2055
|
*
|
|
@@ -1655,8 +2057,11 @@ var SIEClient = class {
|
|
|
1655
2057
|
* await client.deletePool("eval-bench");
|
|
1656
2058
|
* ```
|
|
1657
2059
|
*/
|
|
1658
|
-
async createPool(name, gpus, gpuCaps, queuePool) {
|
|
2060
|
+
async createPool(name, gpus, gpuCaps, queuePool, options = {}) {
|
|
1659
2061
|
const alreadyTracking = this.pools.has(name);
|
|
2062
|
+
if (options.minimumWorkerCount !== void 0 && options.minimumWorkerCount < 0) {
|
|
2063
|
+
throw new RangeError("minimumWorkerCount must be >= 0");
|
|
2064
|
+
}
|
|
1660
2065
|
const requestBody = {
|
|
1661
2066
|
name
|
|
1662
2067
|
};
|
|
@@ -1669,6 +2074,15 @@ var SIEClient = class {
|
|
|
1669
2074
|
if (queuePool) {
|
|
1670
2075
|
requestBody.queue_pool = queuePool;
|
|
1671
2076
|
}
|
|
2077
|
+
if (options.bundle) {
|
|
2078
|
+
requestBody.bundle = options.bundle;
|
|
2079
|
+
}
|
|
2080
|
+
if (options.minimumWorkerCount !== void 0) {
|
|
2081
|
+
requestBody.minimum_worker_count = options.minimumWorkerCount;
|
|
2082
|
+
}
|
|
2083
|
+
if (options.pinnedModels !== void 0) {
|
|
2084
|
+
requestBody.pinned_models = options.pinnedModels;
|
|
2085
|
+
}
|
|
1672
2086
|
const url = `${this.baseUrl}/v1/pools`;
|
|
1673
2087
|
const headers = {
|
|
1674
2088
|
"Content-Type": JSON_CONTENT_TYPE,
|
|
@@ -1976,6 +2390,11 @@ var SIEClient = class {
|
|
|
1976
2390
|
* Retried (capped by `provisionTimeout`):
|
|
1977
2391
|
* - 503 `PROVISIONING` when `waitForCapacity: true`
|
|
1978
2392
|
* - 503 `MODEL_LOADING` / `LORA_LOADING`
|
|
2393
|
+
* - 503 `RESOURCE_EXHAUSTED` regardless of `waitForCapacity` (bounded
|
|
2394
|
+
* exponential backoff, at most `RESOURCE_EXHAUSTED_MAX_RETRIES`)
|
|
2395
|
+
* - 504 gateway timeout when `waitForCapacity: true` — encode/score/
|
|
2396
|
+
* extract are idempotent queue paths, so a post-publish retry is safe
|
|
2397
|
+
* (unlike generate/chat, where a 504 is terminal)
|
|
1979
2398
|
* - `SIEConnectionError` with `kind === "connect"` (issue #95)
|
|
1980
2399
|
*
|
|
1981
2400
|
* `kind === "timeout"` is NOT retried — would extend the user-visible
|
|
@@ -1984,6 +2403,7 @@ var SIEClient = class {
|
|
|
1984
2403
|
async requestWithRetry(path, body, pool, gpu, waitForCapacity, model) {
|
|
1985
2404
|
const startTime = Date.now();
|
|
1986
2405
|
let loraRetries = 0;
|
|
2406
|
+
let oomRetries = 0;
|
|
1987
2407
|
while (true) {
|
|
1988
2408
|
let response;
|
|
1989
2409
|
try {
|
|
@@ -2058,6 +2478,27 @@ var SIEClient = class {
|
|
|
2058
2478
|
await sleep2(actualDelay);
|
|
2059
2479
|
continue;
|
|
2060
2480
|
}
|
|
2481
|
+
if (errorCode === RESOURCE_EXHAUSTED_ERROR_CODE) {
|
|
2482
|
+
const delay = nextOomRetryDelay({
|
|
2483
|
+
retryAfter: getRetryAfter2(response),
|
|
2484
|
+
oomRetries,
|
|
2485
|
+
maxOomRetries: RESOURCE_EXHAUSTED_MAX_RETRIES,
|
|
2486
|
+
elapsedMs: Date.now() - startTime,
|
|
2487
|
+
provisionTimeoutMs: this.provisionTimeout,
|
|
2488
|
+
model
|
|
2489
|
+
});
|
|
2490
|
+
oomRetries += 1;
|
|
2491
|
+
await sleep2(delay);
|
|
2492
|
+
continue;
|
|
2493
|
+
}
|
|
2494
|
+
}
|
|
2495
|
+
if (response.status === HTTP_GATEWAY_TIMEOUT && waitForCapacity) {
|
|
2496
|
+
const elapsed = Date.now() - startTime;
|
|
2497
|
+
if (elapsed < this.provisionTimeout) {
|
|
2498
|
+
const delay = getRetryAfter2(response) ?? MODEL_LOADING_DEFAULT_DELAY;
|
|
2499
|
+
await sleep2(Math.min(delay, this.provisionTimeout - elapsed));
|
|
2500
|
+
continue;
|
|
2501
|
+
}
|
|
2061
2502
|
}
|
|
2062
2503
|
if (!response.ok) {
|
|
2063
2504
|
await handleError(response, gpu);
|
|
@@ -2146,6 +2587,291 @@ var SIEClient = class {
|
|
|
2146
2587
|
clearTimeout(timeoutId);
|
|
2147
2588
|
}
|
|
2148
2589
|
}
|
|
2590
|
+
// ---------------------------------------------------------------------------
|
|
2591
|
+
// Jobs + connections namespaces. Jobs ride the keyed gateway
|
|
2592
|
+
// (`/v1/jobs`); connections ride the control plane (`/internal/orgs/{org}/…`).
|
|
2593
|
+
// ---------------------------------------------------------------------------
|
|
2594
|
+
/** One JSON request over `fetch` (bearer auth reused; absolute or base-relative URL). */
|
|
2595
|
+
async jsonRequest(target, method, body, timeoutMs = this.timeout) {
|
|
2596
|
+
const url = target.startsWith("http") ? target : `${this.baseUrl}${target}`;
|
|
2597
|
+
const headers = {
|
|
2598
|
+
Accept: JSON_CONTENT_TYPE,
|
|
2599
|
+
[SDK_VERSION_HEADER]: SDK_VERSION
|
|
2600
|
+
};
|
|
2601
|
+
if (this.apiKey) headers.Authorization = `Bearer ${this.apiKey}`;
|
|
2602
|
+
const init = { method, headers };
|
|
2603
|
+
if (body !== void 0) {
|
|
2604
|
+
headers["Content-Type"] = JSON_CONTENT_TYPE;
|
|
2605
|
+
init.body = JSON.stringify(body);
|
|
2606
|
+
}
|
|
2607
|
+
const controller = new AbortController();
|
|
2608
|
+
const timeoutId = setTimeout(() => controller.abort(), timeoutMs);
|
|
2609
|
+
init.signal = controller.signal;
|
|
2610
|
+
try {
|
|
2611
|
+
const response = await fetch(url, init);
|
|
2612
|
+
if (!response.ok) {
|
|
2613
|
+
await handleError(response);
|
|
2614
|
+
}
|
|
2615
|
+
this.checkServerVersion(response);
|
|
2616
|
+
const text = await response.text();
|
|
2617
|
+
return text ? JSON.parse(text) : {};
|
|
2618
|
+
} catch (error) {
|
|
2619
|
+
if (error instanceof Error && error.name === "AbortError") {
|
|
2620
|
+
throw new SIEConnectionError(`Request timeout after ${timeoutMs}ms`, "timeout");
|
|
2621
|
+
}
|
|
2622
|
+
if (error instanceof TypeError) {
|
|
2623
|
+
throw new SIEConnectionError(`Connection failed: ${error.message}`, "connect");
|
|
2624
|
+
}
|
|
2625
|
+
throw error;
|
|
2626
|
+
} finally {
|
|
2627
|
+
clearTimeout(timeoutId);
|
|
2628
|
+
}
|
|
2629
|
+
}
|
|
2630
|
+
async jobSubmit(options) {
|
|
2631
|
+
return this.jsonRequest(
|
|
2632
|
+
"/v1/jobs",
|
|
2633
|
+
"POST",
|
|
2634
|
+
buildJobBody(options),
|
|
2635
|
+
Math.max(this.timeout, DEFAULT_LONG_RUNNING_TIMEOUT)
|
|
2636
|
+
);
|
|
2637
|
+
}
|
|
2638
|
+
async jobGet(jobId) {
|
|
2639
|
+
return this.jsonRequest(`/v1/jobs/${encodeURIComponent(jobId)}`, "GET");
|
|
2640
|
+
}
|
|
2641
|
+
async jobList() {
|
|
2642
|
+
const data = await this.jsonRequest("/v1/jobs", "GET");
|
|
2643
|
+
return Array.isArray(data) ? data : data.data ?? [];
|
|
2644
|
+
}
|
|
2645
|
+
async jobCancel(jobId) {
|
|
2646
|
+
return this.jsonRequest(`/v1/jobs/${encodeURIComponent(jobId)}/cancel`, "POST");
|
|
2647
|
+
}
|
|
2648
|
+
async jobResults(jobId) {
|
|
2649
|
+
const job = await this.jobGet(jobId);
|
|
2650
|
+
const chunks = jobChunks(job);
|
|
2651
|
+
const items = [];
|
|
2652
|
+
for (const chunk of chunks) {
|
|
2653
|
+
if (chunk.state !== "succeeded" || !chunk.ref) continue;
|
|
2654
|
+
const raw = await this.readRef(chunk.ref);
|
|
2655
|
+
items.push(...decodeChunkBytes(raw));
|
|
2656
|
+
}
|
|
2657
|
+
const withDims = items.find((it) => it.dims != null);
|
|
2658
|
+
return {
|
|
2659
|
+
job_id: job.id ?? jobId,
|
|
2660
|
+
state: job.state,
|
|
2661
|
+
total_items: job.total_items,
|
|
2662
|
+
settled_credits: job.settled_credits,
|
|
2663
|
+
chunks,
|
|
2664
|
+
retrieved: items.length,
|
|
2665
|
+
dims: withDims ? withDims.dims : null,
|
|
2666
|
+
items
|
|
2667
|
+
};
|
|
2668
|
+
}
|
|
2669
|
+
async jobWait(jobId, options) {
|
|
2670
|
+
const timeoutMs = options?.timeoutMs ?? DEFAULT_JOB_WAIT_TIMEOUT;
|
|
2671
|
+
const pollMs = options?.pollMs ?? DEFAULT_JOB_WAIT_POLL;
|
|
2672
|
+
const deadline = Date.now() + timeoutMs;
|
|
2673
|
+
for (; ; ) {
|
|
2674
|
+
const job = await this.jobGet(jobId);
|
|
2675
|
+
if (job.state && TERMINAL_JOB_STATES.has(job.state)) {
|
|
2676
|
+
return job;
|
|
2677
|
+
}
|
|
2678
|
+
if (Date.now() >= deadline) {
|
|
2679
|
+
throw new RequestError(
|
|
2680
|
+
`job ${jobId} still ${JSON.stringify(job.state)} after ${timeoutMs}ms`,
|
|
2681
|
+
"job_wait_timeout",
|
|
2682
|
+
504
|
|
2683
|
+
);
|
|
2684
|
+
}
|
|
2685
|
+
await sleep2(pollMs);
|
|
2686
|
+
}
|
|
2687
|
+
}
|
|
2688
|
+
/** Retrieve a chunk's payload-store ref (http(s) URL). */
|
|
2689
|
+
async readRef(ref) {
|
|
2690
|
+
if (!ref.startsWith("http://") && !ref.startsWith("https://")) {
|
|
2691
|
+
throw new RequestError(
|
|
2692
|
+
`cannot retrieve payload-store ref ${JSON.stringify(ref)} (the TS SDK reads http(s) refs)`,
|
|
2693
|
+
"bad_ref",
|
|
2694
|
+
400
|
|
2695
|
+
);
|
|
2696
|
+
}
|
|
2697
|
+
const controller = new AbortController();
|
|
2698
|
+
const timeoutId = setTimeout(() => controller.abort(), this.timeout);
|
|
2699
|
+
try {
|
|
2700
|
+
const response = await fetch(ref, {
|
|
2701
|
+
headers: { Accept: "application/octet-stream" },
|
|
2702
|
+
signal: controller.signal
|
|
2703
|
+
});
|
|
2704
|
+
if (!response.ok) {
|
|
2705
|
+
await handleError(response);
|
|
2706
|
+
}
|
|
2707
|
+
return new Uint8Array(await response.arrayBuffer());
|
|
2708
|
+
} catch (error) {
|
|
2709
|
+
if (error instanceof Error && error.name === "AbortError") {
|
|
2710
|
+
throw new SIEConnectionError(`Request timeout after ${this.timeout}ms`, "timeout");
|
|
2711
|
+
}
|
|
2712
|
+
if (error instanceof TypeError) {
|
|
2713
|
+
throw new SIEConnectionError(`Connection failed: ${error.message}`, "connect");
|
|
2714
|
+
}
|
|
2715
|
+
throw error;
|
|
2716
|
+
} finally {
|
|
2717
|
+
clearTimeout(timeoutId);
|
|
2718
|
+
}
|
|
2719
|
+
}
|
|
2720
|
+
connectionsBase() {
|
|
2721
|
+
if (!this.controlPlaneUrl) {
|
|
2722
|
+
throw new RequestError(
|
|
2723
|
+
"connections require controlPlaneUrl on the client: new SIEClient(url, { controlPlaneUrl, org })",
|
|
2724
|
+
"missing_control_plane_url",
|
|
2725
|
+
400
|
|
2726
|
+
);
|
|
2727
|
+
}
|
|
2728
|
+
if (!this.org) {
|
|
2729
|
+
throw new RequestError(
|
|
2730
|
+
"connections require org on the client: new SIEClient(url, { controlPlaneUrl, org })",
|
|
2731
|
+
"missing_org",
|
|
2732
|
+
400
|
|
2733
|
+
);
|
|
2734
|
+
}
|
|
2735
|
+
return `${this.controlPlaneUrl}/internal/orgs/${encodeURIComponent(this.org)}/connections`;
|
|
2736
|
+
}
|
|
2737
|
+
async connectionAdd(name, type, secret) {
|
|
2738
|
+
return this.jsonRequest(this.connectionsBase(), "POST", {
|
|
2739
|
+
type,
|
|
2740
|
+
name,
|
|
2741
|
+
secret
|
|
2742
|
+
});
|
|
2743
|
+
}
|
|
2744
|
+
async connectionList() {
|
|
2745
|
+
const data = await this.jsonRequest(
|
|
2746
|
+
this.connectionsBase(),
|
|
2747
|
+
"GET"
|
|
2748
|
+
);
|
|
2749
|
+
return Array.isArray(data) ? data : data.connections ?? [];
|
|
2750
|
+
}
|
|
2751
|
+
async connectionRevoke(name) {
|
|
2752
|
+
return this.jsonRequest(
|
|
2753
|
+
`${this.connectionsBase()}/${encodeURIComponent(name)}`,
|
|
2754
|
+
"DELETE"
|
|
2755
|
+
);
|
|
2756
|
+
}
|
|
2757
|
+
// ---------------------------------------------------------------------------
|
|
2758
|
+
// Files + batches namespaces — the OpenAI-compatible file /
|
|
2759
|
+
// batch surface on the keyed gateway. Method names/args mirror `openai.files`
|
|
2760
|
+
// / `openai.batches` so switching an OpenAI-batch caller to the SDK is
|
|
2761
|
+
// mechanical.
|
|
2762
|
+
// ---------------------------------------------------------------------------
|
|
2763
|
+
/** POST a raw body and parse the JSON response (bearer auth reused). */
|
|
2764
|
+
async rawPostJson(path, body, contentType, timeoutMs = this.timeout) {
|
|
2765
|
+
const url = `${this.baseUrl}${path}`;
|
|
2766
|
+
const headers = {
|
|
2767
|
+
Accept: JSON_CONTENT_TYPE,
|
|
2768
|
+
"Content-Type": contentType,
|
|
2769
|
+
[SDK_VERSION_HEADER]: SDK_VERSION
|
|
2770
|
+
};
|
|
2771
|
+
if (this.apiKey) headers.Authorization = `Bearer ${this.apiKey}`;
|
|
2772
|
+
const controller = new AbortController();
|
|
2773
|
+
const timeoutId = setTimeout(() => controller.abort(), timeoutMs);
|
|
2774
|
+
try {
|
|
2775
|
+
const response = await fetch(url, {
|
|
2776
|
+
method: "POST",
|
|
2777
|
+
headers,
|
|
2778
|
+
body,
|
|
2779
|
+
signal: controller.signal
|
|
2780
|
+
});
|
|
2781
|
+
if (!response.ok) {
|
|
2782
|
+
await handleError(response);
|
|
2783
|
+
}
|
|
2784
|
+
this.checkServerVersion(response);
|
|
2785
|
+
const text = await response.text();
|
|
2786
|
+
return text ? JSON.parse(text) : {};
|
|
2787
|
+
} catch (error) {
|
|
2788
|
+
if (error instanceof Error && error.name === "AbortError") {
|
|
2789
|
+
throw new SIEConnectionError(`Request timeout after ${timeoutMs}ms`, "timeout");
|
|
2790
|
+
}
|
|
2791
|
+
if (error instanceof TypeError) {
|
|
2792
|
+
throw new SIEConnectionError(`Connection failed: ${error.message}`, "connect");
|
|
2793
|
+
}
|
|
2794
|
+
throw error;
|
|
2795
|
+
} finally {
|
|
2796
|
+
clearTimeout(timeoutId);
|
|
2797
|
+
}
|
|
2798
|
+
}
|
|
2799
|
+
/** GET raw bytes (bearer auth reused); used to download a file's content. */
|
|
2800
|
+
async rawGetBytes(path) {
|
|
2801
|
+
const url = `${this.baseUrl}${path}`;
|
|
2802
|
+
const headers = {
|
|
2803
|
+
Accept: "application/jsonl",
|
|
2804
|
+
[SDK_VERSION_HEADER]: SDK_VERSION
|
|
2805
|
+
};
|
|
2806
|
+
if (this.apiKey) headers.Authorization = `Bearer ${this.apiKey}`;
|
|
2807
|
+
const controller = new AbortController();
|
|
2808
|
+
const timeoutId = setTimeout(() => controller.abort(), this.timeout);
|
|
2809
|
+
try {
|
|
2810
|
+
const response = await fetch(url, { method: "GET", headers, signal: controller.signal });
|
|
2811
|
+
if (!response.ok) {
|
|
2812
|
+
await handleError(response);
|
|
2813
|
+
}
|
|
2814
|
+
this.checkServerVersion(response);
|
|
2815
|
+
return new Uint8Array(await response.arrayBuffer());
|
|
2816
|
+
} catch (error) {
|
|
2817
|
+
if (error instanceof Error && error.name === "AbortError") {
|
|
2818
|
+
throw new SIEConnectionError(`Request timeout after ${this.timeout}ms`, "timeout");
|
|
2819
|
+
}
|
|
2820
|
+
if (error instanceof TypeError) {
|
|
2821
|
+
throw new SIEConnectionError(`Connection failed: ${error.message}`, "connect");
|
|
2822
|
+
}
|
|
2823
|
+
throw error;
|
|
2824
|
+
} finally {
|
|
2825
|
+
clearTimeout(timeoutId);
|
|
2826
|
+
}
|
|
2827
|
+
}
|
|
2828
|
+
async fileUpload(file, options) {
|
|
2829
|
+
const purpose = options?.purpose ?? "batch";
|
|
2830
|
+
const filename = resolveUploadFilename(file, options?.filename);
|
|
2831
|
+
const query = new URLSearchParams({ purpose, filename }).toString();
|
|
2832
|
+
const body = file instanceof ArrayBuffer ? new Uint8Array(file) : file;
|
|
2833
|
+
return this.rawPostJson(
|
|
2834
|
+
`/v1/files?${query}`,
|
|
2835
|
+
body,
|
|
2836
|
+
"application/jsonl",
|
|
2837
|
+
Math.max(this.timeout, DEFAULT_LONG_RUNNING_TIMEOUT)
|
|
2838
|
+
);
|
|
2839
|
+
}
|
|
2840
|
+
async fileRetrieve(fileId) {
|
|
2841
|
+
return this.jsonRequest(`/v1/files/${encodeURIComponent(fileId)}`, "GET");
|
|
2842
|
+
}
|
|
2843
|
+
async fileContent(fileId) {
|
|
2844
|
+
return this.rawGetBytes(`/v1/files/${encodeURIComponent(fileId)}/content`);
|
|
2845
|
+
}
|
|
2846
|
+
async fileDelete(fileId) {
|
|
2847
|
+
return this.jsonRequest(`/v1/files/${encodeURIComponent(fileId)}`, "DELETE");
|
|
2848
|
+
}
|
|
2849
|
+
async batchCreate(options) {
|
|
2850
|
+
const body = {
|
|
2851
|
+
input_file_id: options.input_file_id,
|
|
2852
|
+
endpoint: options.endpoint ?? "/v1/embeddings",
|
|
2853
|
+
completion_window: options.completion_window ?? "24h"
|
|
2854
|
+
};
|
|
2855
|
+
if (options.metadata !== void 0) {
|
|
2856
|
+
body.metadata = options.metadata;
|
|
2857
|
+
}
|
|
2858
|
+
return this.jsonRequest(
|
|
2859
|
+
"/v1/batches",
|
|
2860
|
+
"POST",
|
|
2861
|
+
body,
|
|
2862
|
+
Math.max(this.timeout, DEFAULT_LONG_RUNNING_TIMEOUT)
|
|
2863
|
+
);
|
|
2864
|
+
}
|
|
2865
|
+
async batchRetrieve(batchId) {
|
|
2866
|
+
return this.jsonRequest(`/v1/batches/${encodeURIComponent(batchId)}`, "GET");
|
|
2867
|
+
}
|
|
2868
|
+
async batchList() {
|
|
2869
|
+
const data = await this.jsonRequest("/v1/batches", "GET");
|
|
2870
|
+
return Array.isArray(data) ? data : data.data ?? [];
|
|
2871
|
+
}
|
|
2872
|
+
async batchCancel(batchId) {
|
|
2873
|
+
return this.jsonRequest(`/v1/batches/${encodeURIComponent(batchId)}/cancel`, "POST");
|
|
2874
|
+
}
|
|
2149
2875
|
buildWsUrl(path) {
|
|
2150
2876
|
const url = new URL(this.baseUrl);
|
|
2151
2877
|
url.protocol = url.protocol === "https:" ? "wss:" : "ws:";
|
|
@@ -2291,12 +3017,16 @@ exports.ModelLoadingError = ModelLoadingError;
|
|
|
2291
3017
|
exports.PoolError = PoolError;
|
|
2292
3018
|
exports.ProvisioningError = ProvisioningError;
|
|
2293
3019
|
exports.RequestError = RequestError;
|
|
3020
|
+
exports.ResourceExhaustedError = ResourceExhaustedError;
|
|
2294
3021
|
exports.SDK_VERSION = SDK_VERSION;
|
|
2295
3022
|
exports.SIEClient = SIEClient;
|
|
2296
3023
|
exports.SIEConnectionError = SIEConnectionError;
|
|
2297
3024
|
exports.SIEError = SIEError;
|
|
2298
3025
|
exports.SIEStreamError = SIEStreamError;
|
|
2299
3026
|
exports.ServerError = ServerError;
|
|
3027
|
+
exports.TERMINAL_JOB_STATES = TERMINAL_JOB_STATES;
|
|
3028
|
+
exports.buildJobBody = buildJobBody;
|
|
3029
|
+
exports.connectionName = connectionName;
|
|
2300
3030
|
exports.denseEmbedding = denseEmbedding;
|
|
2301
3031
|
exports.detectImageFormat = detectImageFormat;
|
|
2302
3032
|
exports.maxsim = maxsim;
|