segmind 0.2.2 → 0.2.4

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.d.ts CHANGED
@@ -147,6 +147,12 @@ interface UploadOptions {
147
147
  filename?: string;
148
148
  /** The file's type, when neither the name nor the bytes tell. */
149
149
  mimeType?: string;
150
+ /**
151
+ * Keep the file in your Segmind asset library and return a URL that does not
152
+ * expire, as the Python SDK's upload does. By default the file is a temporary
153
+ * model input, kept for at least two days.
154
+ */
155
+ persist?: boolean;
150
156
  signal?: AbortSignal;
151
157
  }
152
158
  interface UploadedFile {
@@ -154,11 +160,13 @@ interface UploadedFile {
154
160
  url: string;
155
161
  filename: string;
156
162
  size: number;
163
+ /** True when the file is in your asset library and the URL does not expire. */
164
+ persistent: boolean;
157
165
  }
158
166
  /**
159
167
  * Uploads files to pass as model inputs. Each file goes straight to storage on
160
168
  * a presigned URL, so large videos never travel as base64 JSON. The URLs are
161
- * public and temporary: the files are kept for at least two days.
169
+ * public. They are temporary unless you pass `persist: true`.
162
170
  */
163
171
  declare class Files {
164
172
  private readonly client;
@@ -168,6 +176,12 @@ declare class Files {
168
176
  /** Uploads several files, in order. */
169
177
  upload(files: FileInput[], options?: Omit<UploadOptions, 'filename'>): Promise<UploadedFile[]>;
170
178
  private uploadOne;
179
+ /** A temporary model input on the inference API's input bucket. */
180
+ private stage;
181
+ /** A file in the caller's asset library: presign, PUT, then confirm to register it. */
182
+ private persist;
183
+ /** A PUT to the same key is idempotent, so it retries. `bare` keeps the API key off the storage host. */
184
+ private put;
171
185
  }
172
186
 
173
187
  /** One page of a paginated list. */
@@ -391,11 +405,20 @@ declare class Webhooks {
391
405
  add(webhookUrl: string, eventTypes: WebhookEventType[], options?: {
392
406
  signal?: AbortSignal;
393
407
  }): Promise<Webhook>;
394
- /** Changes a registration's URL and event types. */
395
- update(webhookId: string, webhookUrl: string, eventTypes: WebhookEventType[], options?: {
408
+ /**
409
+ * Points an active registration at a new URL. The event types stay as
410
+ * registered: to change them, `delete` the registration and `add` a new one.
411
+ *
412
+ * @param _eventTypes Ignored. Kept so 0.2 calls still compile; the API never
413
+ * changed event types in place.
414
+ */
415
+ update(webhookId: string, webhookUrl: string, _eventTypes?: WebhookEventType[], options?: {
396
416
  signal?: AbortSignal;
397
417
  }): Promise<Record<string, unknown>>;
398
- /** Deactivates a registration. */
418
+ /**
419
+ * Deactivates a registration. Not retried: a second attempt after a lost
420
+ * response would report the registration as already deactivated.
421
+ */
399
422
  delete(webhookId: string, options?: {
400
423
  signal?: AbortSignal;
401
424
  }): Promise<Record<string, unknown>>;
@@ -418,6 +441,8 @@ interface ClientOptions {
418
441
  spotURL?: string;
419
442
  /** The account API. Default https://cloud.segmind.com/api. */
420
443
  consoleURL?: string;
444
+ /** The asset library API, for persistent uploads. Default https://platform-api.segmind.com. */
445
+ assetsURL?: string;
421
446
  /** Milliseconds per HTTP request. Default 600 000. */
422
447
  timeout?: number;
423
448
  /** Retries for requests that cannot bill twice. Default 2. */
@@ -445,6 +470,7 @@ declare class Segmind {
445
470
  readonly baseURL: string;
446
471
  readonly spotURL: string;
447
472
  readonly consoleURL: string;
473
+ readonly assetsURL: string;
448
474
  readonly models: Models;
449
475
  readonly accounts: Accounts;
450
476
  /** Upload files to pass as model inputs. */
@@ -581,7 +607,7 @@ declare class InferenceTimeout extends SegmindError {
581
607
  constructor(requestId: string, elapsedMs: number);
582
608
  }
583
609
 
584
- declare const VERSION = "0.2.2";
610
+ declare const VERSION = "0.2.4";
585
611
 
586
612
  /** `new Segmind().run(...)` with the key from `SEGMIND_API_KEY`. */
587
613
  declare const run: (slug: string, params?: Params, options?: RunOptions) => Promise<RunResult>;
package/dist/index.js CHANGED
@@ -423,12 +423,16 @@ var Files = class {
423
423
  }
424
424
  async uploadOne(input, options) {
425
425
  const file = await readFileInput(input, { ...options, caller: "files.upload" });
426
- const { http } = this.client;
427
- const ticket = await http.json({
426
+ const url = options.persist ? await this.persist(file, options.signal) : await this.stage(file, options.signal);
427
+ return { url, filename: file.filename, size: file.bytes.byteLength, persistent: options.persist === true };
428
+ }
429
+ /** A temporary model input on the inference API's input bucket. */
430
+ async stage(file, signal) {
431
+ const ticket = await this.client.http.json({
428
432
  method: "POST",
429
433
  url: `${this.client.spotURL}/inference-request/input-presign`,
430
434
  body: { filename: file.filename, size: file.bytes.byteLength },
431
- signal: options.signal,
435
+ signal,
432
436
  retry: true
433
437
  });
434
438
  if (!ticket.upload_url || !ticket.url) {
@@ -437,16 +441,41 @@ var Files = class {
437
441
  const headers = { ...ticket.required_headers };
438
442
  delete headers["Content-Length"];
439
443
  delete headers["content-length"];
440
- await http.request({
441
- method: "PUT",
442
- url: ticket.upload_url,
443
- rawBody: file.bytes,
444
- headers,
445
- bare: true,
446
- signal: options.signal,
444
+ await this.put(ticket.upload_url, file, headers, signal);
445
+ return ticket.url;
446
+ }
447
+ /** A file in the caller's asset library: presign, PUT, then confirm to register it. */
448
+ async persist(file, signal) {
449
+ const contentType = file.mimeType;
450
+ if (!contentType) {
451
+ throw new SegmindError(`files.upload: cannot tell the type of ${file.filename}; pass { mimeType } to persist it`);
452
+ }
453
+ const { http, assetsURL } = this.client;
454
+ const ticket = await http.json({
455
+ method: "POST",
456
+ url: `${assetsURL}/upload-asset/presign`,
457
+ body: { filename: file.filename, contentType, size: file.bytes.byteLength },
458
+ signal,
447
459
  retry: true
448
460
  });
449
- return { url: ticket.url, filename: file.filename, size: file.bytes.byteLength };
461
+ if (!ticket.presignedUrl || !ticket.s3Key || !ticket.publicUrl) {
462
+ throw new SegmindError("files.upload: the asset presign response had no presignedUrl, s3Key or publicUrl", {
463
+ body: ticket
464
+ });
465
+ }
466
+ await this.put(ticket.presignedUrl, file, { "Content-Type": contentType }, signal);
467
+ const confirmed = await http.json({
468
+ method: "POST",
469
+ url: `${assetsURL}/upload-asset/confirm`,
470
+ body: { s3Key: ticket.s3Key, publicUrl: ticket.publicUrl, contentType, filename: file.filename },
471
+ signal,
472
+ retry: true
473
+ });
474
+ return confirmed.asset?.url ?? ticket.publicUrl;
475
+ }
476
+ /** A PUT to the same key is idempotent, so it retries. `bare` keeps the API key off the storage host. */
477
+ async put(url, file, headers, signal) {
478
+ await this.client.http.request({ method: "PUT", url, rawBody: file.bytes, headers, bare: true, signal, retry: true });
450
479
  }
451
480
  };
452
481
 
@@ -684,7 +713,7 @@ function failureDetail(body) {
684
713
  }
685
714
 
686
715
  // src/version.ts
687
- var VERSION = "0.2.2";
716
+ var VERSION = "0.2.4";
688
717
 
689
718
  // src/webhooks.ts
690
719
  var Webhooks = class {
@@ -703,16 +732,26 @@ var Webhooks = class {
703
732
  signal: options.signal
704
733
  });
705
734
  }
706
- /** Changes a registration's URL and event types. */
707
- async update(webhookId, webhookUrl, eventTypes, options = {}) {
708
- return this.call("POST", "/update", {
709
- body: { webhook_id: webhookId, webhook_url: webhookUrl, event: { types: requireTypes(eventTypes) } },
710
- signal: options.signal
735
+ /**
736
+ * Points an active registration at a new URL. The event types stay as
737
+ * registered: to change them, `delete` the registration and `add` a new one.
738
+ *
739
+ * @param _eventTypes Ignored. Kept so 0.2 calls still compile; the API never
740
+ * changed event types in place.
741
+ */
742
+ async update(webhookId, webhookUrl, _eventTypes, options = {}) {
743
+ return this.call("PUT", "/update", {
744
+ body: { webhook_id: webhookId, webhook_url: webhookUrl },
745
+ signal: options.signal,
746
+ retry: true
711
747
  });
712
748
  }
713
- /** Deactivates a registration. */
749
+ /**
750
+ * Deactivates a registration. Not retried: a second attempt after a lost
751
+ * response would report the registration as already deactivated.
752
+ */
714
753
  async delete(webhookId, options = {}) {
715
- return this.call("GET", "/inactive", { query: { webhook_id: webhookId }, signal: options.signal, retry: true });
754
+ return this.call("DELETE", "/inactive", { query: { webhook_id: webhookId }, signal: options.signal });
716
755
  }
717
756
  /** Delivery history for one registration. */
718
757
  async logs(webhookId, options = {}) {
@@ -733,10 +772,12 @@ function requireTypes(eventTypes) {
733
772
  var DEFAULT_BASE_URL = "https://api.segmind.com";
734
773
  var DEFAULT_SPOT_URL = "https://api.spotprod.segmind.com";
735
774
  var DEFAULT_CONSOLE_URL = "https://cloud.segmind.com/api";
775
+ var DEFAULT_ASSETS_URL = "https://platform-api.segmind.com";
736
776
  var Segmind = class {
737
777
  baseURL;
738
778
  spotURL;
739
779
  consoleURL;
780
+ assetsURL;
740
781
  models;
741
782
  accounts;
742
783
  /** Upload files to pass as model inputs. */
@@ -765,6 +806,7 @@ var Segmind = class {
765
806
  this.baseURL = trimBase(options.baseURL ?? readEnv("SEGMIND_BASE_URL") ?? DEFAULT_BASE_URL);
766
807
  this.spotURL = trimSlash(options.spotURL ?? DEFAULT_SPOT_URL);
767
808
  this.consoleURL = trimSlash(options.consoleURL ?? DEFAULT_CONSOLE_URL);
809
+ this.assetsURL = trimSlash(options.assetsURL ?? DEFAULT_ASSETS_URL);
768
810
  const headers = { "X-Initiator": "SDK-JS", ...options.defaultHeaders };
769
811
  const node = nodeVersion();
770
812
  if (runtime === "node" && node) headers["User-Agent"] = `segmind-js/${VERSION} node/${node}`;
@@ -857,7 +899,7 @@ var Segmind = class {
857
899
  body
858
900
  });
859
901
  }
860
- return new Job(this.http, validateSubmit(body, response.status), transform);
902
+ return new Job(this.http, onBase(validateSubmit(body, response.status), this.baseURL), transform);
861
903
  }
862
904
  if (canRetry && response.status === 503) {
863
905
  await response.body?.cancel().catch(() => void 0);
@@ -918,6 +960,14 @@ function path(slug) {
918
960
  if (!clean) throw new SegmindError("A model slug is required.");
919
961
  return clean;
920
962
  }
963
+ function onBase(body, baseURL) {
964
+ const origin = new URL(baseURL).origin;
965
+ const pin = (url) => {
966
+ const target = new URL(url, `${baseURL}/`);
967
+ return target.origin === origin ? target.href : `${baseURL}${target.pathname}${target.search}`;
968
+ };
969
+ return { ...body, status_url: pin(body.status_url), response_url: pin(body.response_url) };
970
+ }
921
971
  function trimSlash(url) {
922
972
  return url.replace(/\/+$/, "");
923
973
  }