segmind 0.2.4 → 0.3.0

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
@@ -51,11 +51,24 @@ const image = new Uint8Array(await response.arrayBuffer()); // or response.json(
51
51
  const job = await segmind.submit('seedance-2.0', { prompt: 'A sunset timelapse' });
52
52
  console.log(job.requestId);
53
53
 
54
- const result = await job.wait({ timeout: 15 * 60_000, interval: 2_000 });
54
+ const result = await job.wait({
55
+ timeout: 15 * 60_000,
56
+ interval: (elapsed) => (elapsed < 30_000 ? 2_000 : 5_000), // or a fixed number of ms
57
+ onStatus: (status) => console.log(status.status),
58
+ });
55
59
  ```
56
60
 
57
61
  A submit carries an `X-Request-ID`. If the connection drops, the SDK sends it again with the same id, and the gateway returns the existing request instead of starting (and billing) a second one. Pass your own `requestId` to make a submit idempotent across restarts.
58
62
 
63
+ To pick a job up later, such as after a page reload, keep its id and rebuild it:
64
+
65
+ ```ts
66
+ const job = segmind.job(savedRequestId);
67
+ const result = await job.wait();
68
+ ```
69
+
70
+ The result is kept for about an hour, and only for the user who submitted the job. Anything else reads as a `SegmindError` with status 404.
71
+
59
72
  ## Chat with an LLM
60
73
 
61
74
  ```ts
@@ -109,6 +122,16 @@ logo.persistent; // true
109
122
 
110
123
  Persistent uploads take images, video, audio, PDF and common document types, up to 2 GB each.
111
124
 
125
+ ## Models
126
+
127
+ ```ts
128
+ const page = await segmind.models.list({ q: 'upscale', output: 'image', limit: 20 });
129
+ page.models; // slug, name, category, average_cost, latency_seconds, ...
130
+ const videoModels = await segmind.models.listAll({ output: 'video', sort: 'cheapest' });
131
+ ```
132
+
133
+ `list` returns one page (`has_more`, `offset`, `total`); `listAll` reads every page. `average_cost` is observed spend, not the list price.
134
+
112
135
  ## Generations
113
136
 
114
137
  ```ts
@@ -144,6 +167,14 @@ const done = await job.wait({ timeout: 20 * 60_000, interval: 5_000 });
144
167
 
145
168
  The slug is the workflow's API slug from its API tab, and inputs are keyed by each input's keyname. Runs use the current workflow engine; pass `{ engine: 'v1' }` for the legacy one. A failed run throws `InferenceFailed` whose `detail` names the node that failed. A submit is never retried, because a run bills.
146
169
 
170
+ ## Credits
171
+
172
+ ```ts
173
+ const { credits, freeCredits } = await segmind.accounts.credits();
174
+ ```
175
+
176
+ `credits` is the stored balance of the account or team making the request. Credits held by requests still running are not taken off.
177
+
147
178
  ## Errors
148
179
 
149
180
  | Error | When |
@@ -152,8 +183,10 @@ The slug is the workflow's API slug from its API tab, and inputs are keyed by ea
152
183
  | `APIConnectionError` | No response: network failure or the client timeout |
153
184
  | `InferenceFailed` | A queued request reached `FAILED`; `detail` holds the reason |
154
185
  | `InferenceTimeout` | `wait()` passed its timeout. The request may still finish: call `job.wait()` again |
186
+ | `InsufficientCredits` | The account can't pay for the request, at submit or as a failed job. `detail` holds the reason |
187
+ | `RateLimited` | Too many requests, after any retries. `retryAfterMs` is set when the server sent `Retry-After` and the runtime could read it |
155
188
 
156
- Reads and polls retry connection errors and `408`, `429`, `502`, `503` and `504`, honouring `Retry-After`.
189
+ Reads, polls and queued submits retry connection errors and `429`, `502`, `503` and `504`, honouring `Retry-After`; reads also retry `408`.
157
190
 
158
191
  ## Options
159
192
 
@@ -164,6 +197,8 @@ new Segmind({
164
197
  timeout: 600_000, // ms per HTTP request
165
198
  maxRetries: 2,
166
199
  fetch: customFetch, // any fetch implementation
200
+ initiator: 'API', // how these requests are labelled in usage history; default SDK-JS
201
+ initiatorId: 'batch-42', // what they belong to, also settable per call
167
202
  });
168
203
  ```
169
204
 
@@ -171,6 +206,19 @@ new Segmind({
171
206
 
172
207
  The constructor refuses an API key in a browser or React Native app, because every user of the app could read it. Call Segmind from your backend. Pass `dangerouslyAllowBrowser: true` only for internal tools where that is acceptable.
173
208
 
209
+ ### Signed-in Segmind users
210
+
211
+ An app where people sign in with their Segmind account can send the user's own session token instead of an API key. This works in a browser, because the token only reaches what that user could do anyway.
212
+
213
+ ```ts
214
+ const segmind = new Segmind({
215
+ tokenProvider: () => auth.getToken(), // called before every request
216
+ onUnauthorized: () => auth.refresh(), // on a 401; a new token retries the request once
217
+ });
218
+ ```
219
+
220
+ Pass `apiKey` or `tokenProvider`, not both.
221
+
174
222
  ## License
175
223
 
176
224
  Apache-2.0
package/dist/index.cjs CHANGED
@@ -42,10 +42,12 @@ __export(index_exports, {
42
42
  Generations: () => Generations,
43
43
  InferenceFailed: () => InferenceFailed,
44
44
  InferenceTimeout: () => InferenceTimeout,
45
+ InsufficientCredits: () => InsufficientCredits,
45
46
  Job: () => Job,
46
47
  Models: () => Models,
47
48
  PixelFlows: () => PixelFlows,
48
49
  PixelflowJob: () => PixelflowJob,
50
+ RateLimited: () => RateLimited,
49
51
  Segmind: () => Segmind,
50
52
  SegmindError: () => SegmindError,
51
53
  VERSION: () => VERSION,
@@ -56,6 +58,7 @@ __export(index_exports, {
56
58
  files: () => files,
57
59
  generations: () => generations,
58
60
  imageUrl: () => imageUrl,
61
+ job: () => job,
59
62
  models: () => models,
60
63
  pixelflows: () => pixelflows,
61
64
  run: () => run,
@@ -67,7 +70,7 @@ __export(index_exports, {
67
70
  module.exports = __toCommonJS(index_exports);
68
71
 
69
72
  // src/core/errors.ts
70
- var SegmindError = class _SegmindError extends Error {
73
+ var SegmindError = class extends Error {
71
74
  /** HTTP status, when the error came from a response. */
72
75
  status;
73
76
  /** The server's error text (`error` in the response body), when it sent one. */
@@ -85,20 +88,21 @@ var SegmindError = class _SegmindError extends Error {
85
88
  this.body = options.body;
86
89
  }
87
90
  /** Reads the response body and builds the error. Consumes the body. */
88
- static async fromResponse(response, requestId) {
89
- const text = await response.text().catch(() => "");
90
- let body = text;
91
- try {
92
- body = text ? JSON.parse(text) : void 0;
93
- } catch {
94
- }
95
- const detail = errorDetail(body) ?? (typeof body === "string" && body ? body.slice(0, 500) : void 0);
96
- const message = detail ? `${response.status} ${detail}` : `${response.status} ${response.statusText || "request failed"}`;
97
- return new _SegmindError(message, { status: response.status, detail, requestId, body });
91
+ static fromResponse(response, requestId) {
92
+ return errorFromResponse(response, requestId);
98
93
  }
99
94
  };
100
95
  var APIConnectionError = class extends SegmindError {
101
96
  };
97
+ var InsufficientCredits = class extends SegmindError {
98
+ };
99
+ var RateLimited = class extends SegmindError {
100
+ retryAfterMs;
101
+ constructor(message, options = {}) {
102
+ super(message, options);
103
+ this.retryAfterMs = options.retryAfterMs;
104
+ }
105
+ };
102
106
  var InferenceFailed = class extends SegmindError {
103
107
  /** The status body that reported the failure. */
104
108
  statusBody;
@@ -119,6 +123,43 @@ var InferenceTimeout = class extends SegmindError {
119
123
  this.elapsedMs = elapsedMs;
120
124
  }
121
125
  };
126
+ async function errorFromResponse(response, requestId) {
127
+ const text = await response.text().catch(() => "");
128
+ let body = text;
129
+ try {
130
+ body = text ? JSON.parse(text) : void 0;
131
+ } catch {
132
+ }
133
+ const detail = errorDetail(body) ?? plainText(body);
134
+ const message = detail ? `${response.status} ${detail}` : `${response.status} ${response.statusText || "request failed"}`;
135
+ const options = { status: response.status, detail, requestId, body };
136
+ if (response.status === 429) {
137
+ return new RateLimited(message, { ...options, retryAfterMs: parseRetryAfter(response.headers.get("retry-after")) });
138
+ }
139
+ if (response.status === 402 || isOutOfCredits(detail)) return new InsufficientCredits(message, options);
140
+ return new SegmindError(message, options);
141
+ }
142
+ function requestFailure(requestId, statusBody, detail) {
143
+ detail ??= typeof statusBody.error === "string" ? statusBody.error : void 0;
144
+ if (isOutOfCredits(detail)) {
145
+ return new InsufficientCredits(`request ${requestId} failed: ${detail}`, { detail, requestId, body: statusBody });
146
+ }
147
+ return new InferenceFailed(requestId, statusBody, detail);
148
+ }
149
+ function parseRetryAfter(value, now = Date.now()) {
150
+ if (!value) return void 0;
151
+ const seconds = Number(value);
152
+ if (Number.isFinite(seconds) && seconds >= 0) return seconds * 1e3;
153
+ const date = Date.parse(value);
154
+ return Number.isNaN(date) ? void 0 : Math.max(date - now, 0);
155
+ }
156
+ function isOutOfCredits(detail) {
157
+ return detail?.startsWith("Insufficient credits.") ?? false;
158
+ }
159
+ function plainText(body) {
160
+ if (typeof body !== "string" || !body || body.trimStart().startsWith("<")) return void 0;
161
+ return body.slice(0, 500);
162
+ }
122
163
  function errorDetail(body) {
123
164
  if (!body || typeof body !== "object") return void 0;
124
165
  const record2 = body;
@@ -236,6 +277,12 @@ function detectRuntime() {
236
277
  if (g.process?.versions?.node) return "node";
237
278
  return "other";
238
279
  }
280
+ function initiatorHeaders(options) {
281
+ return {
282
+ ...options.initiator ? { "X-Initiator": options.initiator } : {},
283
+ ...options.initiatorId ? { "X-Initiator-ID": options.initiatorId } : {}
284
+ };
285
+ }
239
286
  function readEnv(name) {
240
287
  try {
241
288
  const value = g.process?.env?.[name];
@@ -268,15 +315,15 @@ function toBase64(bytes) {
268
315
  var RETRYABLE_STATUS = /* @__PURE__ */ new Set([408, 429, 502, 503, 504]);
269
316
  var MAX_RETRY_DELAY_MS = 6e4;
270
317
  function retryDelay(attempt, retryAfter, now = Date.now()) {
271
- if (retryAfter) {
272
- const seconds = Number(retryAfter);
273
- if (Number.isFinite(seconds) && seconds >= 0) return Math.min(seconds * 1e3, MAX_RETRY_DELAY_MS);
274
- const date = Date.parse(retryAfter);
275
- if (!Number.isNaN(date)) return Math.min(Math.max(date - now, 0), MAX_RETRY_DELAY_MS);
276
- }
318
+ const asked = parseRetryAfter(retryAfter, now);
319
+ if (asked !== void 0) return Math.min(asked, MAX_RETRY_DELAY_MS);
277
320
  const base = Math.min(500 * 2 ** attempt, 8e3);
278
321
  return base * (0.75 + Math.random() * 0.25);
279
322
  }
323
+ function pollDelay(interval, elapsedMs, fallback) {
324
+ const delay = typeof interval === "function" ? interval(elapsedMs) : interval ?? fallback;
325
+ return Number.isFinite(delay) && delay >= 0 ? delay : fallback;
326
+ }
280
327
  function sleep(ms, signal) {
281
328
  return new Promise((resolve, reject) => {
282
329
  if (signal?.aborted) return reject(signal.reason);
@@ -296,23 +343,35 @@ var HttpClient = class {
296
343
  this.config = config;
297
344
  }
298
345
  config;
346
+ /** The hook call in flight, so that requests failing together share one refresh. */
347
+ unauthorized;
299
348
  /** The response whatever its status, after retries when `retry` is set. */
300
349
  async fetchRaw(req) {
301
- for (let attempt = 0; ; attempt++) {
350
+ let attempt = 0;
351
+ let reauthenticated = false;
352
+ for (; ; ) {
302
353
  const canRetry = req.retry === true && attempt < this.config.maxRetries;
354
+ const token = await this.sessionToken(req);
303
355
  let response;
304
356
  try {
305
- response = await this.attempt(req);
357
+ response = await this.attempt(req, token);
306
358
  } catch (err) {
307
359
  if (canRetry && err instanceof APIConnectionError) {
308
360
  await sleep(retryDelay(attempt, null), req.signal);
361
+ attempt++;
309
362
  continue;
310
363
  }
311
364
  throw err;
312
365
  }
366
+ if (response.status === 401 && !reauthenticated && await this.reauthenticate(req, token)) {
367
+ reauthenticated = true;
368
+ await response.body?.cancel().catch(() => void 0);
369
+ continue;
370
+ }
313
371
  if (canRetry && RETRYABLE_STATUS.has(response.status)) {
314
372
  await response.body?.cancel().catch(() => void 0);
315
373
  await sleep(retryDelay(attempt, response.headers.get("retry-after")), req.signal);
374
+ attempt++;
316
375
  continue;
317
376
  }
318
377
  return response;
@@ -328,7 +387,31 @@ var HttpClient = class {
328
387
  async json(req) {
329
388
  return readJson(await this.request(req));
330
389
  }
331
- async attempt(req) {
390
+ async sessionToken(req) {
391
+ if (!this.config.tokenProvider || req.bare) return void 0;
392
+ try {
393
+ return await this.config.tokenProvider() ?? void 0;
394
+ } catch (err) {
395
+ throw new SegmindError(`tokenProvider failed: ${describe(err)}`, { cause: err });
396
+ }
397
+ }
398
+ /** Tells the app its token was refused. True when it now has a different one to try. */
399
+ async reauthenticate(req, refused) {
400
+ if (!this.config.tokenProvider || req.bare) return false;
401
+ try {
402
+ if (this.config.onUnauthorized) {
403
+ this.unauthorized ??= Promise.resolve(this.config.onUnauthorized()).finally(() => {
404
+ this.unauthorized = void 0;
405
+ });
406
+ await this.unauthorized;
407
+ }
408
+ const next = await this.config.tokenProvider();
409
+ return Boolean(next) && next !== refused;
410
+ } catch {
411
+ return false;
412
+ }
413
+ }
414
+ async attempt(req, token) {
332
415
  const { signal } = req;
333
416
  if (signal?.aborted) throw signal.reason;
334
417
  const timeout = req.timeout ?? this.config.timeout;
@@ -342,6 +425,7 @@ var HttpClient = class {
342
425
  signal?.addEventListener("abort", onAbort, { once: true });
343
426
  const headers = req.bare ? { ...req.headers } : { ...this.config.headers, ...req.headers };
344
427
  if (this.config.apiKey && !req.bare) headers["x-api-key"] = this.config.apiKey;
428
+ if (token) headers.Authorization = `Bearer ${token}`;
345
429
  if (req.body !== void 0) headers["content-type"] = "application/json";
346
430
  const body = req.rawBody ?? (req.body === void 0 ? void 0 : JSON.stringify(req.body));
347
431
  try {
@@ -436,7 +520,12 @@ async function readFileInput(input, options) {
436
520
  if (detectRuntime() !== "node") {
437
521
  throw new SegmindError(`${options.caller}: file paths work in Node only; pass a Blob or bytes`);
438
522
  }
439
- const { readFile } = await import("node:fs/promises");
523
+ const { readFile } = await import(
524
+ /* webpackIgnore: true */
525
+ /* turbopackIgnore: true */
526
+ /* @vite-ignore */
527
+ "node:fs/promises"
528
+ );
440
529
  try {
441
530
  bytes = new Uint8Array(await readFile(input));
442
531
  } catch (err) {
@@ -619,12 +708,12 @@ var Job = class {
619
708
  return await this.read(this.statusUrl, options.signal);
620
709
  }
621
710
  /**
622
- * The finished result. Throws `InferenceFailed` when the request failed, and
623
- * a `SegmindError` when it has not finished yet (use `wait()` for that).
711
+ * The finished result. Throws `InferenceFailed` when the request failed
712
+ * (`InsufficientCredits` when that was for lack of credits), and a `SegmindError` when it has not finished yet (use `wait()` for that).
624
713
  */
625
714
  async result(options = {}) {
626
715
  const body = await this.read(this.responseUrl, options.signal);
627
- if (body.status === "FAILED") throw new InferenceFailed(this.requestId, body);
716
+ if (body.status === "FAILED") throw requestFailure(this.requestId, body);
628
717
  if (body.status !== void 0 && body.status !== "COMPLETED") {
629
718
  throw new SegmindError(`request ${this.requestId} is still ${String(body.status)}; use wait()`, {
630
719
  requestId: this.requestId,
@@ -636,15 +725,16 @@ var Job = class {
636
725
  /** Polls until the request finishes, then returns its result. */
637
726
  async wait(options = {}) {
638
727
  const timeout = options.timeout ?? DEFAULT_WAIT_TIMEOUT_MS;
639
- const interval = options.interval ?? DEFAULT_WAIT_INTERVAL_MS;
640
728
  const started = Date.now();
641
729
  for (; ; ) {
642
730
  const status = await this.status({ signal: options.signal });
731
+ options.onStatus?.(status);
643
732
  if (status.status === "COMPLETED") return this.result({ signal: options.signal });
644
- if (status.status === "FAILED") throw new InferenceFailed(this.requestId, status);
733
+ if (status.status === "FAILED") throw requestFailure(this.requestId, status);
645
734
  const elapsed = Date.now() - started;
646
735
  if (elapsed >= timeout) throw new InferenceTimeout(this.requestId, elapsed);
647
- await sleep(Math.min(interval, timeout - elapsed), options.signal);
736
+ const delay = pollDelay(options.interval, elapsed, DEFAULT_WAIT_INTERVAL_MS);
737
+ await sleep(Math.min(delay, timeout - elapsed), options.signal);
648
738
  }
649
739
  }
650
740
  /**
@@ -676,8 +766,8 @@ var PixelFlows = class {
676
766
  client;
677
767
  /** Runs a workflow and waits for its outputs. */
678
768
  async run(slug, inputs = {}, options = {}) {
679
- const job = await this.submit(slug, inputs, options);
680
- return job.wait(options);
769
+ const job2 = await this.submit(slug, inputs, options);
770
+ return job2.wait(options);
681
771
  }
682
772
  /** Starts a workflow and returns a job to wait on. Never retried: a run bills. */
683
773
  async submit(slug, inputs = {}, options = {}) {
@@ -688,6 +778,7 @@ var PixelFlows = class {
688
778
  method: "POST",
689
779
  url: `${this.client.baseURL}${route}${clean}`,
690
780
  body: inputs,
781
+ headers: initiatorHeaders(options),
691
782
  signal: options.signal
692
783
  });
693
784
  const requestId = body.request_id;
@@ -723,20 +814,21 @@ var PixelflowJob = class {
723
814
  }
724
815
  /**
725
816
  * Polls until the run finishes. Throws `InferenceFailed` (its `detail` names
726
- * the failing node) or `InferenceTimeout`; the run may still finish after a
817
+ * the failing node), `InsufficientCredits` or `InferenceTimeout`; the run may still finish after a
727
818
  * timeout, so `wait()` can be called again.
728
819
  */
729
820
  async wait(options = {}) {
730
821
  const timeout = options.timeout ?? DEFAULT_TIMEOUT_MS;
731
- const interval = options.interval ?? DEFAULT_INTERVAL_MS;
732
822
  const started = Date.now();
733
823
  for (; ; ) {
734
824
  const state = await this.status({ signal: options.signal });
825
+ options.onStatus?.(state);
735
826
  if (DONE.has(state.status)) return state;
736
- if (FAILED.has(state.status)) throw new InferenceFailed(this.requestId, state.raw, failureDetail(state.raw));
827
+ if (FAILED.has(state.status)) throw requestFailure(this.requestId, state.raw, failureDetail(state.raw));
737
828
  const elapsed = Date.now() - started;
738
- if (elapsed + interval > timeout) throw new InferenceTimeout(this.requestId, elapsed);
739
- await sleep(interval, options.signal);
829
+ const delay = pollDelay(options.interval, elapsed, DEFAULT_INTERVAL_MS);
830
+ if (elapsed + delay > timeout) throw new InferenceTimeout(this.requestId, elapsed);
831
+ await sleep(delay, options.signal);
740
832
  }
741
833
  }
742
834
  };
@@ -781,7 +873,7 @@ function failureDetail(body) {
781
873
  }
782
874
 
783
875
  // src/version.ts
784
- var VERSION = "0.2.4";
876
+ var VERSION = "0.3.0";
785
877
 
786
878
  // src/webhooks.ts
787
879
  var Webhooks = class {
@@ -859,14 +951,17 @@ var Segmind = class {
859
951
  /** @internal */
860
952
  http;
861
953
  constructor(options = {}) {
862
- const apiKey = options.apiKey ?? readEnv("SEGMIND_API_KEY");
954
+ if (options.apiKey && options.tokenProvider) {
955
+ throw new SegmindError("Pass either apiKey or tokenProvider, not both.");
956
+ }
957
+ const apiKey = options.apiKey ?? (options.tokenProvider ? void 0 : readEnv("SEGMIND_API_KEY"));
863
958
  const runtime = detectRuntime();
864
959
  if (apiKey && (runtime === "browser" || runtime === "react-native") && !options.dangerouslyAllowBrowser) {
865
960
  throw new SegmindError(
866
961
  "An API key in a browser or mobile app exposes it to every user. Call Segmind from your backend instead, or pass dangerouslyAllowBrowser: true if you accept that risk."
867
962
  );
868
963
  }
869
- if (!apiKey) {
964
+ if (!apiKey && !options.tokenProvider) {
870
965
  throw new SegmindError("Missing API key: pass { apiKey } or set the SEGMIND_API_KEY environment variable.");
871
966
  }
872
967
  const fetchImpl = options.fetch ?? globalThis.fetch?.bind(globalThis);
@@ -875,11 +970,17 @@ var Segmind = class {
875
970
  this.spotURL = trimSlash(options.spotURL ?? DEFAULT_SPOT_URL);
876
971
  this.consoleURL = trimSlash(options.consoleURL ?? DEFAULT_CONSOLE_URL);
877
972
  this.assetsURL = trimSlash(options.assetsURL ?? DEFAULT_ASSETS_URL);
878
- const headers = { "X-Initiator": "SDK-JS", ...options.defaultHeaders };
973
+ const headers = {
974
+ "X-Initiator": "SDK-JS",
975
+ ...initiatorHeaders(options),
976
+ ...options.defaultHeaders
977
+ };
879
978
  const node = nodeVersion();
880
979
  if (runtime === "node" && node) headers["User-Agent"] = `segmind-js/${VERSION} node/${node}`;
881
980
  this.http = new HttpClient({
882
981
  apiKey,
982
+ tokenProvider: options.tokenProvider,
983
+ onUnauthorized: options.onUnauthorized,
883
984
  fetch: fetchImpl,
884
985
  timeout: options.timeout ?? 6e5,
885
986
  maxRetries: options.maxRetries ?? 2,
@@ -897,8 +998,8 @@ var Segmind = class {
897
998
  * until it finishes. `params` go to the model unchanged.
898
999
  */
899
1000
  async run(slug, params = {}, options = {}) {
900
- const job = await this.submit(slug, params, options);
901
- return job.wait(options);
1001
+ const job2 = await this.submit(slug, params, options);
1002
+ return job2.wait(options);
902
1003
  }
903
1004
  /**
904
1005
  * One blocking `POST /v1/{slug}`. Returns the fetch `Response`: call
@@ -910,6 +1011,7 @@ var Segmind = class {
910
1011
  method: "POST",
911
1012
  url: `${this.baseURL}/v1/${path(slug)}`,
912
1013
  body: params,
1014
+ headers: initiatorHeaders(options),
913
1015
  signal: options.signal,
914
1016
  timeout: options.timeout
915
1017
  });
@@ -922,10 +1024,27 @@ var Segmind = class {
922
1024
  async submit(slug, params = {}, options = {}) {
923
1025
  return this.submitJob(slug, params, options, (body) => body);
924
1026
  }
1027
+ /**
1028
+ * A job submitted earlier, from its id alone, for picking one up after a
1029
+ * reload or in another process. Makes no request. The result is kept for about
1030
+ * an hour, and only for the user who submitted it: anything else reads as a
1031
+ * `SegmindError` with status 404.
1032
+ */
1033
+ job(requestId) {
1034
+ const clean = requestId.trim();
1035
+ if (!clean) throw new SegmindError("A request id is required.");
1036
+ const url = `${this.baseURL}/v2/requests/${encodeURIComponent(clean)}`;
1037
+ return new Job(this.http, {
1038
+ request_id: clean,
1039
+ status: "UNKNOWN",
1040
+ status_url: `${url}/status`,
1041
+ response_url: url
1042
+ });
1043
+ }
925
1044
  /** Chat with an LLM through the v2 queue. Returns a provider-neutral reply. */
926
1045
  async chat(slug, params, options = {}) {
927
- const job = await this.submitChat(slug, params, options);
928
- return job.wait(options);
1046
+ const job2 = await this.submitChat(slug, params, options);
1047
+ return job2.wait(options);
929
1048
  }
930
1049
  /** Chat with one blocking `POST /v1/{slug}`. */
931
1050
  async chatSync(slug, params, options = {}) {
@@ -948,7 +1067,7 @@ var Segmind = class {
948
1067
  method: "POST",
949
1068
  url,
950
1069
  body: params,
951
- headers: { "X-Request-ID": requestId },
1070
+ headers: { "X-Request-ID": requestId, ...initiatorHeaders(options) },
952
1071
  signal: options.signal
953
1072
  });
954
1073
  } catch (err) {
@@ -984,21 +1103,62 @@ var Segmind = class {
984
1103
  }
985
1104
  }
986
1105
  };
1106
+ var MODELS_PAGE_LIMIT = 100;
987
1107
  var Models = class {
988
1108
  constructor(client2) {
989
1109
  this.client = client2;
990
1110
  }
991
1111
  client;
992
- /** Every model in the catalog, under the key `Inference Model Information List`. */
993
- async list(options = {}) {
1112
+ /** One page of the catalog. Pass `offset` from the previous page while `has_more` is true. */
1113
+ async list(params = {}, options = {}) {
994
1114
  return this.client.http.json({
995
1115
  method: "GET",
996
- url: `${this.client.spotURL}/inference-model-information/list`,
1116
+ url: `${this.client.baseURL}/models`,
1117
+ query: modelsQuery(params),
997
1118
  signal: options.signal,
998
1119
  retry: true
999
1120
  });
1000
1121
  }
1122
+ /** Every model matching `params`, reading page after page. */
1123
+ async listAll(params = {}, options = {}) {
1124
+ const models2 = [];
1125
+ for (let offset = 0; ; ) {
1126
+ const page = await this.list({ ...params, limit: MODELS_PAGE_LIMIT, offset }, options);
1127
+ models2.push(...page.models);
1128
+ offset += page.models.length;
1129
+ if (!page.has_more || !page.models.length || offset >= page.total) return models2;
1130
+ }
1131
+ }
1001
1132
  };
1133
+ function modelsQuery(p) {
1134
+ return {
1135
+ q: p.q,
1136
+ sort: p.sort,
1137
+ category: p.category,
1138
+ modality: p.modality,
1139
+ input: joinList(p.input),
1140
+ output: joinList(p.output),
1141
+ provider: p.provider,
1142
+ slugs: joinList(p.slugs),
1143
+ operations: joinList(p.operations),
1144
+ features: joinList(p.features),
1145
+ min_popularity: p.minPopularity,
1146
+ min_category_percentile: p.minCategoryPercentile,
1147
+ min_cost: p.minCost,
1148
+ max_cost: p.maxCost,
1149
+ max_latency: p.maxLatency,
1150
+ is_new: p.isNew,
1151
+ created_after: p.createdAfter,
1152
+ include_coming_soon: p.includeComingSoon,
1153
+ facets: p.facets,
1154
+ limit: p.limit,
1155
+ offset: p.offset
1156
+ };
1157
+ }
1158
+ function joinList(value) {
1159
+ if (value === void 0) return void 0;
1160
+ return Array.isArray(value) ? value.join(",") : value;
1161
+ }
1002
1162
  var Accounts = class {
1003
1163
  constructor(client2) {
1004
1164
  this.client = client2;
@@ -1013,6 +1173,16 @@ var Accounts = class {
1013
1173
  retry: true
1014
1174
  });
1015
1175
  }
1176
+ /** The current credit balance. */
1177
+ async credits(options = {}) {
1178
+ const { "free-credits": freeCredits, ...rest } = await this.client.http.json({
1179
+ method: "GET",
1180
+ url: `${this.client.baseURL}/v1/get-user-credits`,
1181
+ signal: options.signal,
1182
+ retry: true
1183
+ });
1184
+ return { ...rest, credits: Number(rest.credits), freeCredits: Number(freeCredits) };
1185
+ }
1016
1186
  };
1017
1187
  function validateSubmit(body, status) {
1018
1188
  if (!body.request_id || !body.status_url || !body.response_url) {
@@ -1061,14 +1231,17 @@ function client() {
1061
1231
  var run = (slug, params, options) => client().run(slug, params, options);
1062
1232
  var runSync = (slug, params, options) => client().runSync(slug, params, options);
1063
1233
  var submit = (slug, params, options) => client().submit(slug, params, options);
1234
+ var job = (requestId) => client().job(requestId);
1064
1235
  var chat = (slug, params, options) => client().chat(slug, params, options);
1065
1236
  var chatSync = (slug, params, options) => client().chatSync(slug, params, options);
1066
1237
  var submitChat = (slug, params, options) => client().submitChat(slug, params, options);
1067
1238
  var models = {
1068
- list: (options) => client().models.list(options)
1239
+ list: (params, options) => client().models.list(params, options),
1240
+ listAll: (params, options) => client().models.listAll(params, options)
1069
1241
  };
1070
1242
  var accounts = {
1071
- current: (options) => client().accounts.current(options)
1243
+ current: (options) => client().accounts.current(options),
1244
+ credits: (options) => client().accounts.credits(options)
1072
1245
  };
1073
1246
  var files = {
1074
1247
  upload: ((input, options) => client().files.upload(input, options))