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/README.md CHANGED
@@ -97,7 +97,17 @@ const { url } = await segmind.files.upload('portrait.jpg'); // a path (Node), a
97
97
  const result = await segmind.run('some-image-to-image-model', { image: url });
98
98
  ```
99
99
 
100
- Each file goes straight to storage on a presigned URL, so large videos never travel as base64 JSON. The URLs are public and temporary (kept for at least two days): they are for passing files to models, not for keeping them. Pass an array to upload several, in order. Bytes without a name need `{ filename: 'clip.mp4' }` unless their type can be read from the first bytes.
100
+ Each file goes straight to storage on a presigned URL, so large videos never travel as base64 JSON. By default the URLs are public and temporary (kept for at least two days): they are for passing files to models, not for keeping them. Pass an array to upload several, in order. Bytes without a name need `{ filename: 'clip.mp4' }` unless their type can be read from the first bytes.
101
+
102
+ To keep a file, pass `persist: true`. It is saved to your asset library and its URL does not expire, the same as the Python SDK's upload:
103
+
104
+ ```ts
105
+ const logo = await segmind.files.upload('logo.png', { persist: true });
106
+ logo.url; // https://images.segmind.com/assets/…
107
+ logo.persistent; // true
108
+ ```
109
+
110
+ Persistent uploads take images, video, audio, PDF and common document types, up to 2 GB each.
101
111
 
102
112
  ## Generations
103
113
 
@@ -115,12 +125,12 @@ const latest = await segmind.generations.recent('flux-schnell');
115
125
  ```ts
116
126
  const hook = await segmind.webhooks.add('https://example.com/hooks/segmind', ['PIXELFLOW']);
117
127
  const hooks = await segmind.webhooks.get(); // each with its signing_secret
118
- await segmind.webhooks.update(hook.id, 'https://example.com/hooks/v2', ['PIXELFLOW']);
128
+ await segmind.webhooks.update(hook.id, 'https://example.com/hooks/v2'); // URL only
119
129
  await segmind.webhooks.logs(hook.id);
120
130
  await segmind.webhooks.delete(hook.id);
121
131
  ```
122
132
 
123
- Event types are subscription types: `PIXELFLOW` for PixelFlow runs, plus `WORKFLOW`, `INFERENCE` and `FINETUNE`.
133
+ Event types are subscription types: `PIXELFLOW` for PixelFlow runs, plus `WORKFLOW`, `INFERENCE` and `FINETUNE`. `update` changes the URL only; to change the event types, `delete` the registration and `add` a new one. Before 0.2.4, `update` registered a second hook and `delete` deactivated nothing, so check `webhooks.get()` for registrations left behind.
124
134
 
125
135
  ## PixelFlow workflows
126
136
 
package/dist/index.cjs CHANGED
@@ -491,12 +491,16 @@ var Files = class {
491
491
  }
492
492
  async uploadOne(input, options) {
493
493
  const file = await readFileInput(input, { ...options, caller: "files.upload" });
494
- const { http } = this.client;
495
- const ticket = await http.json({
494
+ const url = options.persist ? await this.persist(file, options.signal) : await this.stage(file, options.signal);
495
+ return { url, filename: file.filename, size: file.bytes.byteLength, persistent: options.persist === true };
496
+ }
497
+ /** A temporary model input on the inference API's input bucket. */
498
+ async stage(file, signal) {
499
+ const ticket = await this.client.http.json({
496
500
  method: "POST",
497
501
  url: `${this.client.spotURL}/inference-request/input-presign`,
498
502
  body: { filename: file.filename, size: file.bytes.byteLength },
499
- signal: options.signal,
503
+ signal,
500
504
  retry: true
501
505
  });
502
506
  if (!ticket.upload_url || !ticket.url) {
@@ -505,16 +509,41 @@ var Files = class {
505
509
  const headers = { ...ticket.required_headers };
506
510
  delete headers["Content-Length"];
507
511
  delete headers["content-length"];
508
- await http.request({
509
- method: "PUT",
510
- url: ticket.upload_url,
511
- rawBody: file.bytes,
512
- headers,
513
- bare: true,
514
- signal: options.signal,
512
+ await this.put(ticket.upload_url, file, headers, signal);
513
+ return ticket.url;
514
+ }
515
+ /** A file in the caller's asset library: presign, PUT, then confirm to register it. */
516
+ async persist(file, signal) {
517
+ const contentType = file.mimeType;
518
+ if (!contentType) {
519
+ throw new SegmindError(`files.upload: cannot tell the type of ${file.filename}; pass { mimeType } to persist it`);
520
+ }
521
+ const { http, assetsURL } = this.client;
522
+ const ticket = await http.json({
523
+ method: "POST",
524
+ url: `${assetsURL}/upload-asset/presign`,
525
+ body: { filename: file.filename, contentType, size: file.bytes.byteLength },
526
+ signal,
515
527
  retry: true
516
528
  });
517
- return { url: ticket.url, filename: file.filename, size: file.bytes.byteLength };
529
+ if (!ticket.presignedUrl || !ticket.s3Key || !ticket.publicUrl) {
530
+ throw new SegmindError("files.upload: the asset presign response had no presignedUrl, s3Key or publicUrl", {
531
+ body: ticket
532
+ });
533
+ }
534
+ await this.put(ticket.presignedUrl, file, { "Content-Type": contentType }, signal);
535
+ const confirmed = await http.json({
536
+ method: "POST",
537
+ url: `${assetsURL}/upload-asset/confirm`,
538
+ body: { s3Key: ticket.s3Key, publicUrl: ticket.publicUrl, contentType, filename: file.filename },
539
+ signal,
540
+ retry: true
541
+ });
542
+ return confirmed.asset?.url ?? ticket.publicUrl;
543
+ }
544
+ /** A PUT to the same key is idempotent, so it retries. `bare` keeps the API key off the storage host. */
545
+ async put(url, file, headers, signal) {
546
+ await this.client.http.request({ method: "PUT", url, rawBody: file.bytes, headers, bare: true, signal, retry: true });
518
547
  }
519
548
  };
520
549
 
@@ -752,7 +781,7 @@ function failureDetail(body) {
752
781
  }
753
782
 
754
783
  // src/version.ts
755
- var VERSION = "0.2.2";
784
+ var VERSION = "0.2.4";
756
785
 
757
786
  // src/webhooks.ts
758
787
  var Webhooks = class {
@@ -771,16 +800,26 @@ var Webhooks = class {
771
800
  signal: options.signal
772
801
  });
773
802
  }
774
- /** Changes a registration's URL and event types. */
775
- async update(webhookId, webhookUrl, eventTypes, options = {}) {
776
- return this.call("POST", "/update", {
777
- body: { webhook_id: webhookId, webhook_url: webhookUrl, event: { types: requireTypes(eventTypes) } },
778
- signal: options.signal
803
+ /**
804
+ * Points an active registration at a new URL. The event types stay as
805
+ * registered: to change them, `delete` the registration and `add` a new one.
806
+ *
807
+ * @param _eventTypes Ignored. Kept so 0.2 calls still compile; the API never
808
+ * changed event types in place.
809
+ */
810
+ async update(webhookId, webhookUrl, _eventTypes, options = {}) {
811
+ return this.call("PUT", "/update", {
812
+ body: { webhook_id: webhookId, webhook_url: webhookUrl },
813
+ signal: options.signal,
814
+ retry: true
779
815
  });
780
816
  }
781
- /** Deactivates a registration. */
817
+ /**
818
+ * Deactivates a registration. Not retried: a second attempt after a lost
819
+ * response would report the registration as already deactivated.
820
+ */
782
821
  async delete(webhookId, options = {}) {
783
- return this.call("GET", "/inactive", { query: { webhook_id: webhookId }, signal: options.signal, retry: true });
822
+ return this.call("DELETE", "/inactive", { query: { webhook_id: webhookId }, signal: options.signal });
784
823
  }
785
824
  /** Delivery history for one registration. */
786
825
  async logs(webhookId, options = {}) {
@@ -801,10 +840,12 @@ function requireTypes(eventTypes) {
801
840
  var DEFAULT_BASE_URL = "https://api.segmind.com";
802
841
  var DEFAULT_SPOT_URL = "https://api.spotprod.segmind.com";
803
842
  var DEFAULT_CONSOLE_URL = "https://cloud.segmind.com/api";
843
+ var DEFAULT_ASSETS_URL = "https://platform-api.segmind.com";
804
844
  var Segmind = class {
805
845
  baseURL;
806
846
  spotURL;
807
847
  consoleURL;
848
+ assetsURL;
808
849
  models;
809
850
  accounts;
810
851
  /** Upload files to pass as model inputs. */
@@ -833,6 +874,7 @@ var Segmind = class {
833
874
  this.baseURL = trimBase(options.baseURL ?? readEnv("SEGMIND_BASE_URL") ?? DEFAULT_BASE_URL);
834
875
  this.spotURL = trimSlash(options.spotURL ?? DEFAULT_SPOT_URL);
835
876
  this.consoleURL = trimSlash(options.consoleURL ?? DEFAULT_CONSOLE_URL);
877
+ this.assetsURL = trimSlash(options.assetsURL ?? DEFAULT_ASSETS_URL);
836
878
  const headers = { "X-Initiator": "SDK-JS", ...options.defaultHeaders };
837
879
  const node = nodeVersion();
838
880
  if (runtime === "node" && node) headers["User-Agent"] = `segmind-js/${VERSION} node/${node}`;
@@ -925,7 +967,7 @@ var Segmind = class {
925
967
  body
926
968
  });
927
969
  }
928
- return new Job(this.http, validateSubmit(body, response.status), transform);
970
+ return new Job(this.http, onBase(validateSubmit(body, response.status), this.baseURL), transform);
929
971
  }
930
972
  if (canRetry && response.status === 503) {
931
973
  await response.body?.cancel().catch(() => void 0);
@@ -986,6 +1028,14 @@ function path(slug) {
986
1028
  if (!clean) throw new SegmindError("A model slug is required.");
987
1029
  return clean;
988
1030
  }
1031
+ function onBase(body, baseURL) {
1032
+ const origin = new URL(baseURL).origin;
1033
+ const pin = (url) => {
1034
+ const target = new URL(url, `${baseURL}/`);
1035
+ return target.origin === origin ? target.href : `${baseURL}${target.pathname}${target.search}`;
1036
+ };
1037
+ return { ...body, status_url: pin(body.status_url), response_url: pin(body.response_url) };
1038
+ }
989
1039
  function trimSlash(url) {
990
1040
  return url.replace(/\/+$/, "");
991
1041
  }