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