skytells 1.0.2 → 1.0.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.
Files changed (47) hide show
  1. package/README.md +332 -121
  2. package/dist/chat.cjs +33 -0
  3. package/dist/chat.d.ts +82 -0
  4. package/dist/chat.js +33 -0
  5. package/dist/client.cjs +1151 -35
  6. package/dist/client.d.ts +776 -27
  7. package/dist/client.js +1151 -35
  8. package/dist/embeddings.cjs +40 -0
  9. package/dist/embeddings.d.ts +38 -0
  10. package/dist/embeddings.js +40 -0
  11. package/dist/endpoints.cjs +25 -7
  12. package/dist/endpoints.d.ts +18 -0
  13. package/dist/endpoints.js +25 -7
  14. package/dist/http.cjs +638 -53
  15. package/dist/http.d.ts +136 -2
  16. package/dist/http.js +638 -53
  17. package/dist/index.cjs +52 -6
  18. package/dist/index.d.ts +46 -7
  19. package/dist/index.js +52 -6
  20. package/dist/orchestrator.cjs +202 -0
  21. package/dist/orchestrator.d.ts +142 -0
  22. package/dist/orchestrator.js +202 -0
  23. package/dist/prediction-urls.cjs +38 -0
  24. package/dist/prediction-urls.d.ts +18 -0
  25. package/dist/prediction-urls.js +38 -0
  26. package/dist/responses.cjs +23 -0
  27. package/dist/responses.d.ts +60 -0
  28. package/dist/responses.js +23 -0
  29. package/dist/safety.cjs +323 -0
  30. package/dist/safety.d.ts +91 -0
  31. package/dist/safety.js +323 -0
  32. package/dist/types/index.d.ts +2 -0
  33. package/dist/types/index.js +2 -0
  34. package/dist/types/inference.types.d.ts +583 -0
  35. package/dist/types/inference.types.js +65 -0
  36. package/dist/types/model.types.d.ts +58 -4
  37. package/dist/types/model.types.js +13 -0
  38. package/dist/types/orchestrator.types.d.ts +61 -0
  39. package/dist/types/orchestrator.types.js +7 -0
  40. package/dist/types/predict.types.d.ts +258 -16
  41. package/dist/types/predict.types.js +22 -0
  42. package/dist/types/shared.types.d.ts +156 -3
  43. package/dist/types/shared.types.js +73 -2
  44. package/dist/webhooks.cjs +310 -0
  45. package/dist/webhooks.d.ts +171 -0
  46. package/dist/webhooks.js +310 -0
  47. package/package.json +26 -2
package/dist/client.js CHANGED
@@ -1,59 +1,1175 @@
1
- import { HTTP } from './http.js';
2
- import { ENDPOINTS } from './endpoints.js';
1
+ /**
2
+ * Skytells prediction client: models catalog, create/poll/wait predictions, queue batching, and
3
+ * optional per-request inference compatibility guard (second argument `{ compatibilityCheck: true }` on `predict` / `create`, fourth on `run`).
4
+ *
5
+ * **Memory / lifecycle (for long‑lived servers):**
6
+ * - Sub-APIs (`chat`, `embeddings`, …) are created lazily on first access.
7
+ * - When a call passes `{ compatibilityCheck: true }`, model metadata is cached per slug with TTL
8
+ * {@link PREFETCHED_MODEL_CACHE_TTL_MS} and a cap from {@link SkytellsClient.config | config.prefetchMaxSlugs}
9
+ * (smaller when {@link ClientOptions.runtime} is `"edge"`).
10
+ * - {@link SkytellsClient.wait} / `run(..., onProgress)` use `setTimeout` between polls; pass {@link WaitOptions.signal}
11
+ * (or `RunOptions.signal` with `onProgress`) to abort and stop scheduling further delays.
12
+ * - {@link Prediction} holds the shared {@link HTTP} client and a snapshot of the API response (expected).
13
+ * - {@link SkytellsClient.queue} is an unbounded in-memory array — call {@link SkytellsClient.dispatch} or avoid piling
14
+ * requests if you need a hard limit (enforce in your app).
15
+ * - Timeouts, stream cleanup, and polling guards: see **Reliability.md** in the repo docs.
16
+ *
17
+ * @module client
18
+ */
19
+ import { HTTP, HTTP_DEFAULT_REQUEST_TIMEOUT_MS } from './http.js';
20
+ import { ENDPOINTS, ORCHESTRATOR_BASE_URL } from './endpoints.js';
21
+ import { Orchestrator } from './orchestrator.js';
22
+ import { resolvePredictionResourceUrl } from './prediction-urls.js';
23
+ import { Chat } from './chat.js';
24
+ import { Responses } from './responses.js';
25
+ import { Embeddings } from './embeddings.js';
26
+ import { Safety } from './safety.js';
27
+ import { PredictionStatus } from './types/index.js';
28
+ import { Webhook, createWebhookListener } from './webhooks.js';
29
+ import { SkytellsError } from './types/shared.types.js';
30
+ const DEFAULT_POLL_INTERVAL = 5000;
31
+ /**
32
+ * Time-to-live for in-memory cache of `GET /model/{slug}` responses used when
33
+ * a guarded predict/create/run uses `compatibilityCheck: true`. After this interval the next guarded request refetches the model.
34
+ */
35
+ export const PREFETCHED_MODEL_CACHE_TTL_MS = 10 * 60 * 1000;
36
+ /**
37
+ * Max distinct model slugs kept in the compatibility-check cache. Oldest entries are removed (FIFO) when exceeded.
38
+ * Prevents unbounded growth if callers use many unique `model` strings over time.
39
+ */
40
+ export const PREFETCHED_MODEL_CACHE_MAX_SLUGS = 64;
41
+ /**
42
+ * Default request timeout when {@link ClientOptions.runtime} is `"edge"` and `timeout` is omitted
43
+ * (aligns with common ~25–30s serverless / edge ceilings).
44
+ */
45
+ export const EDGE_DEFAULT_REQUEST_TIMEOUT_MS = 25000;
46
+ /** Max model slugs in the inference-compat cache when `runtime` is `"edge"` (lower memory). */
47
+ export const EDGE_PREFETCH_MAX_SLUGS = 16;
48
+ let edgeRuntimeHintLogged = false;
49
+ let orchestratorKeyFormatWarned = false;
50
+ function logEdgeRuntimeHints(params) {
51
+ if (edgeRuntimeHintLogged) {
52
+ return;
53
+ }
54
+ edgeRuntimeHintLogged = true;
55
+ const lines = [
56
+ '[skytells] ClientOptions.runtime "edge" — edge-oriented defaults and tips:',
57
+ ` • Request timeout: ${params.requestTimeoutMs}ms${params.userSetTimeout
58
+ ? ''
59
+ : ' (edge default when timeout omitted; pass timeout in options to override)'}.`,
60
+ ` • Inference compat cache: max ${params.prefetchMaxSlugs} slug(s).`,
61
+ ' • For wait()/run(…, onProgress): pass AbortSignal and maxWait to stay within wall-clock limits.',
62
+ ' • Webhook HMAC: Web Crypto (crypto.subtle) only.',
63
+ ];
64
+ if (params.retryRetries > 2) {
65
+ lines.push(` • retry.retries=${params.retryRetries} stacks delay on failures — keep low on edge.`);
66
+ }
67
+ if (params.userSetTimeout && params.requestTimeoutMs > 30000) {
68
+ lines.push(` • timeout ${params.requestTimeoutMs}ms may exceed typical edge limits (~25–30s); consider lowering.`);
69
+ }
70
+ console.warn(lines.join('\n'));
71
+ }
72
+ const DEPRECATED_PREDICTION_METHOD_HINTS = {
73
+ streamPrediction: 'Use prediction.stream() on the Prediction instance returned from client.run(), or pass prediction response urls when needed.',
74
+ cancelPrediction: 'Use prediction.cancel() on the Prediction from client.run().',
75
+ deletePrediction: 'Use prediction.delete() on the Prediction from client.run().',
76
+ };
77
+ function deprecateSkytellsClientPredictionMethod(name) {
78
+ console.warn(`[skytells] SkytellsClient.${name}() is deprecated and will be removed in a future major version. ${DEPRECATED_PREDICTION_METHOD_HINTS[name]}`);
79
+ }
80
+ /**
81
+ * Terminal prediction statuses — polling stops when the prediction reaches one of these.
82
+ */
83
+ const TERMINAL_STATUSES = new Set([
84
+ PredictionStatus.SUCCEEDED,
85
+ PredictionStatus.FAILED,
86
+ PredictionStatus.CANCELLED,
87
+ ]);
88
+ /** JSON body for `POST /predict` — normalizes {@link Webhook} to plain `webhook`. */
89
+ function predictionBodyForHttp(payload, overrides) {
90
+ const { webhook, ...rest } = payload;
91
+ const webhookJson = webhook === undefined ? undefined : webhook instanceof Webhook ? webhook.toJSON() : webhook;
92
+ return {
93
+ ...rest,
94
+ ...(webhookJson ? { webhook: webhookJson } : {}),
95
+ ...overrides,
96
+ };
97
+ }
98
+ /**
99
+ * Represents a completed prediction with convenience methods for accessing output
100
+ * and managing the prediction lifecycle (cancel, delete).
101
+ *
102
+ * Returned by {@link SkytellsClient.run}. Wraps a {@link PredictionResponse} and
103
+ * exposes `.output` to get results as a normalized `string[]`, plus `.cancel()`
104
+ * and `.delete()` for lifecycle management.
105
+ *
106
+ * @example
107
+ * ```ts
108
+ * const prediction = await client.run("flux-pro", { input: { prompt: "a cat" } });
109
+ * const [imageUrl] = prediction.output;
110
+ * console.log(prediction.id, prediction.status);
111
+ * const streamMeta = await prediction.stream(); // stream endpoint info (uses urls.stream)
112
+ * // Clean up when done
113
+ * await prediction.delete();
114
+ * ```
115
+ */
116
+ export class Prediction {
117
+ /** @internal Use {@link SkytellsClient.run} to create a Prediction. */
118
+ constructor(http, data) {
119
+ this.http = http;
120
+ this.data = data;
121
+ }
122
+ /**
123
+ * The full prediction response object from the API.
124
+ * Contains all fields: status, id, input, output, metrics, metadata, urls, etc.
125
+ */
126
+ get response() {
127
+ return this.data;
128
+ }
129
+ /**
130
+ * The unique prediction identifier.
131
+ * Use this to fetch, cancel, or delete the prediction later via the client.
132
+ */
133
+ get id() {
134
+ return this.data.id;
135
+ }
136
+ /**
137
+ * The current status of the prediction.
138
+ * Possible values: `pending`, `starting`, `started`, `processing`, `succeeded`, `failed`, `cancelled`.
139
+ */
140
+ get status() {
141
+ return this.data.status;
142
+ }
143
+ /**
144
+ * The raw prediction output as returned by the API.
145
+ *
146
+ * Can be a single `string`, a `string[]`, or `undefined` if incomplete.
147
+ *
148
+ * @example
149
+ * ```ts
150
+ * // Single output (string)
151
+ * console.log(prediction.output); // "https://..."
152
+ *
153
+ * // Multiple outputs (string[])
154
+ * console.log(prediction.output[0]); // "https://..."
155
+ *
156
+ * // Check type
157
+ * if (Array.isArray(prediction.output)) {
158
+ * for (const item of prediction.output) { console.log(item); }
159
+ * }
160
+ * ```
161
+ */
162
+ get output() {
163
+ return this.data.output;
164
+ }
165
+ /**
166
+ * Returns the output, normalized to a single value:
167
+ *
168
+ * - `undefined` / `null` → `undefined`
169
+ * - `"https://..."` → `"https://..."`
170
+ * - `["https://..."]` → `"https://..."` (single-element array unwrapped)
171
+ * - `["a", "b"]` → `["a", "b"]` (multi-element array kept as-is)
172
+ *
173
+ * @example
174
+ * ```ts
175
+ * const result = prediction.outputs();
176
+ * // string if single output, string[] if multiple, undefined if none
177
+ * ```
178
+ */
179
+ outputs() {
180
+ if (!this.data.output) {
181
+ return undefined;
182
+ }
183
+ if (typeof this.data.output === 'string') {
184
+ return this.data.output;
185
+ }
186
+ if (this.data.output.length === 1) {
187
+ return this.data.output[0];
188
+ }
189
+ return this.data.output;
190
+ }
191
+ /**
192
+ * Returns the full raw prediction response as a plain JSON object.
193
+ *
194
+ * @returns The raw `PredictionResponse` data from the API.
195
+ *
196
+ * @example
197
+ * ```ts
198
+ * const prediction = await client.run("flux-pro", { input: { prompt: "a cat" } });
199
+ * const json = prediction.raw();
200
+ * console.log(json.id, json.status, json.output, json.metrics);
201
+ * ```
202
+ */
203
+ raw() {
204
+ return this.data;
205
+ }
206
+ /**
207
+ * Fetches streaming metadata for this prediction (`GET` using `urls.stream` when present).
208
+ * Same behavior as the deprecated {@link SkytellsClient.streamPrediction} with this prediction’s id and urls.
209
+ *
210
+ * @returns The prediction response from the stream-info endpoint (includes `urls`, etc.).
211
+ */
212
+ async stream() {
213
+ const url = resolvePredictionResourceUrl('stream', this.data.id, this.data.urls, this.http.getBaseUrl());
214
+ return this.http.request('GET', url);
215
+ }
216
+ /**
217
+ * Cancels this prediction. Only works if the prediction is still in progress
218
+ * (status is `pending`, `starting`, `started`, or `processing`).
219
+ *
220
+ * @returns The updated prediction response with `status: 'cancelled'`.
221
+ * @throws {SkytellsError} If the prediction cannot be cancelled or the request fails.
222
+ *
223
+ * @example
224
+ * ```ts
225
+ * const prediction = await client.run("flux-pro", { input: { prompt: "a cat" } });
226
+ * await prediction.cancel();
227
+ * ```
228
+ */
229
+ async cancel() {
230
+ const url = resolvePredictionResourceUrl('cancel', this.data.id, this.data.urls, this.http.getBaseUrl());
231
+ return this.http.request('POST', url);
232
+ }
233
+ /**
234
+ * Deletes this prediction and its associated output/assets from storage.
235
+ *
236
+ * @returns The prediction response confirming deletion.
237
+ * @throws {SkytellsError} If the prediction cannot be deleted or the request fails.
238
+ *
239
+ * @example
240
+ * ```ts
241
+ * const prediction = await client.run("flux-pro", { input: { prompt: "a cat" } });
242
+ * const [url] = prediction.output;
243
+ * // ... use the output ...
244
+ * await prediction.delete(); // clean up
245
+ * ```
246
+ */
247
+ async delete() {
248
+ const url = resolvePredictionResourceUrl('delete', this.data.id, this.data.urls, this.http.getBaseUrl());
249
+ return this.http.request('DELETE', url);
250
+ }
251
+ }
252
+ /**
253
+ * Sub-API for managing predictions. Accessible via `client.predictions`.
254
+ *
255
+ * Provides methods to create predictions in the background, fetch them by ID,
256
+ * and list them with filters.
257
+ *
258
+ * @example
259
+ * ```ts
260
+ * // Create a background prediction
261
+ * const prediction = await client.predictions.create({
262
+ * model: "flux-pro",
263
+ * input: { prompt: "A sunset" },
264
+ * });
265
+ *
266
+ * // Fetch it later
267
+ * const result = await client.predictions.get(prediction.id);
268
+ *
269
+ * // List recent predictions by model
270
+ * const { data } = await client.predictions.list({ model: "flux-pro", since: "2026-01-01" });
271
+ * ```
272
+ */
273
+ export class PredictionsAPI {
274
+ /** @internal */
275
+ constructor(http, onBeforePredict) {
276
+ this.http = http;
277
+ this.onBeforePredict = onBeforePredict;
278
+ }
279
+ /**
280
+ * Creates a prediction in the background (does not wait for completion).
281
+ *
282
+ * The prediction starts processing asynchronously. Use {@link get} to poll,
283
+ * or {@link SkytellsClient.wait} to block until it finishes.
284
+ *
285
+ * @param payload - The prediction request parameters.
286
+ * @param payload.model - The model slug (e.g. `"flux-pro"`).
287
+ * @param payload.input - Key-value input parameters for the model.
288
+ * @param payload.webhook - Optional webhook to receive events.
289
+ * @param payload.stream - Enable streaming (default: `false`).
290
+ * @param sdk - SDK-only options (not sent in JSON). When `sdk.compatibilityCheck === true`, runs inference compatibility guard before POST.
291
+ * @returns The initial prediction response with `status: 'pending'` or `'starting'`.
292
+ * @throws {SkytellsError} On API errors (invalid input, model not found, insufficient credits).
293
+ *
294
+ * @example
295
+ * ```ts
296
+ * const prediction = await client.predictions.create({
297
+ * model: "flux-pro",
298
+ * input: { prompt: "An astronaut riding a unicorn" },
299
+ * });
300
+ * console.log(prediction.id, prediction.status); // "pending"
301
+ *
302
+ * // Wait for it to finish
303
+ * const result = await client.wait(prediction);
304
+ * console.log(result.output);
305
+ * ```
306
+ */
307
+ async create(payload, sdk) {
308
+ if (this.onBeforePredict) {
309
+ await this.onBeforePredict(payload, sdk);
310
+ }
311
+ return this.http.request('POST', ENDPOINTS.PREDICT, predictionBodyForHttp(payload, { await: false }));
312
+ }
313
+ /**
314
+ * Fetches a prediction by its ID.
315
+ *
316
+ * @param id - The prediction ID.
317
+ * @param urls - Optional `urls` from a prior `PredictionResponse`. When `urls.get` is set, that absolute URL is used (API protocol); otherwise `GET {baseUrl}/predictions/{id}`.
318
+ * @returns The prediction response with current status, output, metrics, etc.
319
+ * @throws {SkytellsError} If the prediction is not found or the request fails.
320
+ *
321
+ * @example
322
+ * ```ts
323
+ * const prediction = await client.predictions.get("pred_abc123");
324
+ * if (prediction.status === "succeeded") {
325
+ * console.log(prediction.output);
326
+ * }
327
+ * ```
328
+ */
329
+ async get(id, urls) {
330
+ const url = resolvePredictionResourceUrl('get', id, urls, this.http.getBaseUrl());
331
+ return this.http.request('GET', url);
332
+ }
333
+ /**
334
+ * Lists predictions with optional filters and pagination.
335
+ *
336
+ * @param options - Filter and pagination options.
337
+ * @param options.page - Page number (default: 1).
338
+ * @param options.since - Only include predictions created on or after this date (`YYYY-MM-DD`).
339
+ * @param options.until - Only include predictions created on or before this date (`YYYY-MM-DD`).
340
+ * @param options.model - Filter by model slug.
341
+ * @returns A paginated response with `data` (predictions array) and `pagination` metadata.
342
+ * @throws {SkytellsError} On authentication failure or invalid parameters.
343
+ *
344
+ * @example
345
+ * ```ts
346
+ * // List all predictions
347
+ * const { data, pagination } = await client.predictions.list();
348
+ *
349
+ * // Filter by model and date range
350
+ * const filtered = await client.predictions.list({
351
+ * model: "flux-pro",
352
+ * since: "2026-01-01",
353
+ * until: "2026-03-15",
354
+ * page: 2,
355
+ * });
356
+ * ```
357
+ */
358
+ async list(options) {
359
+ const params = new URLSearchParams();
360
+ if (options === null || options === void 0 ? void 0 : options.page) {
361
+ params.set('page', String(options.page));
362
+ }
363
+ if (options === null || options === void 0 ? void 0 : options.since) {
364
+ params.set('from', options.since);
365
+ }
366
+ if (options === null || options === void 0 ? void 0 : options.until) {
367
+ params.set('to', options.until);
368
+ }
369
+ if (options === null || options === void 0 ? void 0 : options.model) {
370
+ params.set('model', options.model);
371
+ }
372
+ const query = params.toString();
373
+ const path = query ? `${ENDPOINTS.PREDICTIONS}?${query}` : ENDPOINTS.PREDICTIONS;
374
+ return this.http.request('GET', path);
375
+ }
376
+ }
377
+ /**
378
+ * Sub-API for browsing and fetching models. Accessible via `client.models`.
379
+ *
380
+ * Provides `.list()` to get all models and `.get()` to fetch a single model by slug.
381
+ *
382
+ * @example
383
+ * ```ts
384
+ * const allModels = await client.models.list();
385
+ * const model = await client.models.get("flux-pro", { fields: ["input_schema"] });
386
+ * ```
387
+ */
388
+ export class ModelsAPI {
389
+ /** @internal */
390
+ constructor(http) {
391
+ this.http = http;
392
+ }
393
+ /**
394
+ * Lists all available models on the Skytells platform.
395
+ *
396
+ * @param options - Optional configuration.
397
+ * @param options.fields - Additional fields to include in the response
398
+ * (e.g. `["input_schema", "output_schema"]`). By default, schemas are not included.
399
+ * @returns An array of model objects with name, type, pricing, metadata, etc.
400
+ * @throws {SkytellsError} On authentication failure or server error.
401
+ *
402
+ * @example
403
+ * ```ts
404
+ * const allModels = await client.models.list();
405
+ * for (const m of allModels) {
406
+ * console.log(m.name, m.type);
407
+ * }
408
+ *
409
+ * // Include input schemas
410
+ * const withSchemas = await client.models.list({ fields: ["input_schema"] });
411
+ * ```
412
+ */
413
+ async list(options) {
414
+ var _a;
415
+ let path = ENDPOINTS.MODELS;
416
+ if ((_a = options === null || options === void 0 ? void 0 : options.fields) === null || _a === void 0 ? void 0 : _a.length) {
417
+ path += `?fields=${options.fields.join(',')}`;
418
+ }
419
+ return this.http.request('GET', path);
420
+ }
421
+ /**
422
+ * Fetches a single model by its slug.
423
+ *
424
+ * @param slug - The model slug identifier (e.g. `"flux-pro"`, `"truefusion"`, `"beatfusion"`).
425
+ * @param options - Optional configuration.
426
+ * @param options.fields - Additional fields to include (e.g. `["input_schema", "output_schema"]`).
427
+ * @returns The model object with name, type, pricing, metadata, and optionally schemas.
428
+ * @throws {SkytellsError} If the model is not found (`MODEL_NOT_FOUND`) or the request fails.
429
+ *
430
+ * @example
431
+ * ```ts
432
+ * const model = await client.models.get("flux-pro");
433
+ * console.log(model.name, model.pricing);
434
+ *
435
+ * // With schemas
436
+ * const detailed = await client.models.get("flux-pro", {
437
+ * fields: ["input_schema", "output_schema"],
438
+ * });
439
+ * console.log(detailed.input_schema);
440
+ * ```
441
+ */
442
+ async get(slug, options) {
443
+ var _a;
444
+ let path = ENDPOINTS.MODEL_BY_SLUG(slug);
445
+ if ((_a = options === null || options === void 0 ? void 0 : options.fields) === null || _a === void 0 ? void 0 : _a.length) {
446
+ path += `?fields=${options.fields.join(',')}`;
447
+ }
448
+ return this.http.request('GET', path);
449
+ }
450
+ }
451
+ /**
452
+ * The main Skytells API client. Provides methods to run predictions, list models,
453
+ * and manage prediction lifecycle.
454
+ *
455
+ * Create an instance using {@link Skytells} or the constructor directly.
456
+ *
457
+ * @example
458
+ * ```ts
459
+ * import Skytells from "skytells";
460
+ *
461
+ * const client = Skytells("sk-your-api-key", {
462
+ * timeout: 30000,
463
+ * retry: { retries: 2 },
464
+ * });
465
+ *
466
+ * // Run a model and get output
467
+ * const prediction = await client.run("flux-pro", {
468
+ * input: { prompt: "An astronaut riding a unicorn" },
469
+ * });
470
+ * const [imageUrl] = prediction.output;
471
+ *
472
+ * // List and fetch models
473
+ * const allModels = await client.models.list();
474
+ * const model = await client.models.get("flux-pro");
475
+ *
476
+ * // Background prediction
477
+ * const bg = await client.predictions.create({
478
+ * model: "flux-pro",
479
+ * input: { prompt: "A sunset" },
480
+ * });
481
+ * const result = await client.wait(bg);
482
+ *
483
+ * // Queue multiple predictions
484
+ * client.queue({ model: "flux-pro", input: { prompt: "Cat" } });
485
+ * client.queue({ model: "flux-pro", input: { prompt: "Dog" } });
486
+ * const results = await client.dispatch();
487
+ * ```
488
+ */
3
489
  export class SkytellsClient {
4
490
  /**
5
- * Creates a new Skytells client
6
- * @param apiKey Your Skytells API key
7
- * @param options Configuration options
491
+ * Sub-API for managing predictions.
492
+ * Provides `.create()`, `.get()`, and `.list()` methods.
493
+ */
494
+ get predictions() {
495
+ if (!this._predictions) {
496
+ this._predictions = new PredictionsAPI(this.http, (p, sdk) => this._maybeCompatibilityGuard(p.model, sdk === null || sdk === void 0 ? void 0 : sdk.compatibilityCheck));
497
+ }
498
+ return this._predictions;
499
+ }
500
+ /**
501
+ * Alias of {@link predictions} (singular). Same instance — use whichever reads best.
502
+ *
503
+ * @example
504
+ * ```ts
505
+ * await client.prediction.create({ model: 'flux-pro', input: { prompt: '…' } });
506
+ * // same as client.predictions.create(...)
507
+ * ```
508
+ */
509
+ get prediction() {
510
+ return this.predictions;
511
+ }
512
+ /**
513
+ * Sub-API for browsing and fetching models.
514
+ * Provides `.list()` and `.get()` methods.
515
+ */
516
+ get models() {
517
+ if (!this._models) {
518
+ this._models = new ModelsAPI(this.http);
519
+ }
520
+ return this._models;
521
+ }
522
+ /**
523
+ * Chat completions. Same as OpenAI's client.chat.completions.
524
+ *
525
+ * @example
526
+ * ```ts
527
+ * const completion = await client.chat.completions.create({
528
+ * model: 'deepbrain-router',
529
+ * messages: [{ role: 'user', content: 'Hello' }],
530
+ * });
531
+ * ```
532
+ */
533
+ get chat() {
534
+ if (!this._chat) {
535
+ this._chat = new Chat(this.http);
536
+ }
537
+ return this._chat;
538
+ }
539
+ /**
540
+ * Responses API (`POST /v1/responses`). Same as `client.chat.responses`.
541
+ *
542
+ * Follows the OpenAI Responses API schema. Supports both non-streaming and streaming modes.
543
+ *
544
+ * @example Non-streaming:
545
+ * ```ts
546
+ * const response = await client.responses.create({
547
+ * model: 'gpt-5.3-codex',
548
+ * input: [{ role: 'user', content: 'Explain recursion simply.' }],
549
+ * instructions: 'You are a helpful tutor.',
550
+ * });
551
+ * console.log(response.output[0].content[0].text);
552
+ * ```
553
+ *
554
+ * @example Streaming:
555
+ * ```ts
556
+ * const stream = await client.responses.create({
557
+ * model: 'gpt-5.3-codex',
558
+ * input: [{ role: 'user', content: 'Explain recursion simply.' }],
559
+ * stream: true,
560
+ * });
561
+ * for await (const event of stream) {
562
+ * console.log(event.type, event);
563
+ * }
564
+ * ```
565
+ */
566
+ get responses() {
567
+ if (!this._responses) {
568
+ this._responses = new Responses(this.http);
569
+ }
570
+ return this._responses;
571
+ }
572
+ /**
573
+ * Embeddings. Same as OpenAI's client.embeddings.
574
+ *
575
+ * @example
576
+ * ```ts
577
+ * const embedding = await client.embeddings.create({
578
+ * model: 'text-embedding-3-small',
579
+ * input: 'Hello world',
580
+ * });
581
+ * ```
582
+ */
583
+ get embeddings() {
584
+ if (!this._embeddings) {
585
+ this._embeddings = new Embeddings(this.http);
586
+ }
587
+ return this._embeddings;
588
+ }
589
+ /**
590
+ * Safety checks. Proactive (checkText, checkImage) and response parsing (wasFiltered, evaluate).
591
+ * evaluate() accepts text, image URLs, choices, completions, prediction results, or arrays of any.
592
+ *
593
+ * @example
594
+ * ```ts
595
+ * const result = await client.safety.checkText('user input');
596
+ * if (client.safety.wasFiltered(completion)) { ... }
597
+ * const evalResult = await client.safety.evaluate('user text', SafetyTemplates.STRICT);
598
+ * ```
599
+ */
600
+ get safety() {
601
+ if (!this._safety) {
602
+ this._safety = new Safety(this.http);
603
+ }
604
+ return this._safety;
605
+ }
606
+ /**
607
+ * [Skytells Orchestrator](https://learn.skytells.ai/docs/products/orchestrator/api-reference) — workflows,
608
+ * executions, webhook triggers, integrations, etc.
609
+ *
610
+ * Requires **`ClientOptions.orchestratorApiKey`** (`wfb_…`). Uses an internal HTTP stack aimed at the Orchestrator
611
+ * host ({@link ORCHESTRATOR_BASE_URL}): Bearer only, no `x-api-key`. Same `timeout`, `headers`, `retry`, and
612
+ * `fetch` as the main Skytells client — pass both keys on one {@link SkytellsClient} when you need both products.
613
+ *
614
+ * @throws {SkytellsError} `SDK_ERROR` if `orchestratorApiKey` was not set.
615
+ */
616
+ get orchestrator() {
617
+ if (!this._orchestratorApiKey) {
618
+ throw new SkytellsError('client.orchestrator requires ClientOptions.orchestratorApiKey (Orchestrator wfb_… key). It is not your Skytells sk-… platform key.', 'SDK_ERROR', 'https://learn.skytells.ai/docs/products/orchestrator/api-keys', 0);
619
+ }
620
+ if (!this._orchestrator) {
621
+ const b = this._orchestratorHttpBundle;
622
+ this._orchestrator = new Orchestrator(new HTTP(this._orchestratorApiKey, this._orchestratorBaseUrl, b.timeout, b.headers, b.retry, b.fetch, 'orchestrator'));
623
+ }
624
+ return this._orchestrator;
625
+ }
626
+ /**
627
+ * Target runtime from {@link ClientOptions.runtime} (`"default"` when omitted).
628
+ */
629
+ get runtime() {
630
+ return this._runtime;
631
+ }
632
+ /**
633
+ * Read-only resolved settings (timeouts, cache cap) for debugging and tests.
634
+ */
635
+ get config() {
636
+ return {
637
+ runtime: this._runtime,
638
+ requestTimeoutMs: this._requestTimeoutMs,
639
+ prefetchMaxSlugs: this._prefetchCap,
640
+ };
641
+ }
642
+ /**
643
+ * Creates a new Skytells API client.
644
+ *
645
+ * @param apiKey - Your Skytells API key (starts with `sk-`). Required for authenticated endpoints.
646
+ * @param options - Client configuration options.
647
+ * @param options.baseUrl - Override the default API base URL.
648
+ * @param options.timeout - Request timeout in milliseconds (default: 60000, or 25000 when `runtime: "edge"` and omitted).
649
+ * @param options.headers - Custom headers to include in every request.
650
+ * @param options.retry - Retry configuration: `retries`, `retryDelay`, `retryOn` status codes.
651
+ * @param options.fetch - Custom `fetch` implementation (useful for testing, proxying, or
652
+ * disabling Next.js fetch caching).
653
+ * @param options.runtime - `"edge"` for Workers / Vercel Edge / etc.: shorter default timeout, smaller compat cache, one-time hints.
654
+ *
655
+ * @example
656
+ * ```ts
657
+ * const client = new SkytellsClient("sk-your-api-key");
658
+ *
659
+ * // With options:
660
+ * const client = new SkytellsClient("sk-your-api-key", {
661
+ * timeout: 30000,
662
+ * retry: { retries: 3, retryDelay: 1000 },
663
+ * headers: { "X-Custom-Header": "value" },
664
+ * });
665
+ *
666
+ * // Next.js App Router — disable fetch caching:
667
+ * const client = new SkytellsClient("sk-your-api-key", {
668
+ * fetch: (url, opts) => fetch(url, { ...opts, cache: "no-store" }),
669
+ * });
670
+ *
671
+ * // Edge / serverless:
672
+ * const edge = new SkytellsClient("sk-…", { runtime: "edge" });
673
+ *
674
+ * // Skytells + Orchestrator (optional wfb_… key — same client):
675
+ * const both = new SkytellsClient("sk-…", { orchestratorApiKey: "wfb_…" });
676
+ * ```
8
677
  */
9
678
  constructor(apiKey, options = {}) {
10
- this.http = new HTTP(apiKey, options.baseUrl, options.timeout);
679
+ var _a, _b, _c, _d, _e, _f, _g;
680
+ this._queue = [];
681
+ /**
682
+ * In-memory `GET /model/{slug}` results for the per-prediction guard. Lazy-allocated; bounded by
683
+ * {@link SkytellsClient.config | config.prefetchMaxSlugs} and TTL {@link PREFETCHED_MODEL_CACHE_TTL_MS}.
684
+ */
685
+ this._prefetchedModelCache = null;
686
+ const runtime = (_a = options.runtime) !== null && _a !== void 0 ? _a : 'default';
687
+ const isEdge = runtime === 'edge';
688
+ const userSetTimeout = options.timeout !== undefined;
689
+ const requestTimeoutMs = userSetTimeout
690
+ ? options.timeout
691
+ : isEdge
692
+ ? EDGE_DEFAULT_REQUEST_TIMEOUT_MS
693
+ : HTTP_DEFAULT_REQUEST_TIMEOUT_MS;
694
+ const prefetchCap = isEdge ? EDGE_PREFETCH_MAX_SLUGS : PREFETCHED_MODEL_CACHE_MAX_SLUGS;
695
+ this._runtime = runtime;
696
+ this._requestTimeoutMs = requestTimeoutMs;
697
+ this._prefetchCap = prefetchCap;
698
+ if (isEdge) {
699
+ logEdgeRuntimeHints({
700
+ requestTimeoutMs,
701
+ retryRetries: (_c = (_b = options.retry) === null || _b === void 0 ? void 0 : _b.retries) !== null && _c !== void 0 ? _c : 0,
702
+ userSetTimeout,
703
+ prefetchMaxSlugs: prefetchCap,
704
+ });
705
+ }
706
+ this.http = new HTTP(apiKey, options.baseUrl, requestTimeoutMs, options.headers, options.retry, options.fetch);
707
+ this._orchestratorApiKey = ((_d = options.orchestratorApiKey) === null || _d === void 0 ? void 0 : _d.trim()) || undefined;
708
+ if (this._orchestratorApiKey && !this._orchestratorApiKey.startsWith('wfb_')) {
709
+ if (!orchestratorKeyFormatWarned) {
710
+ orchestratorKeyFormatWarned = true;
711
+ console.warn('[skytells] orchestratorApiKey normally starts with "wfb_" (Orchestrator webhook key, separate from sk-…). See https://learn.skytells.ai/docs/products/orchestrator/api-keys');
712
+ }
713
+ }
714
+ this._orchestratorBaseUrl = ((_e = options.orchestratorBaseUrl) === null || _e === void 0 ? void 0 : _e.trim()) || ORCHESTRATOR_BASE_URL;
715
+ this._orchestratorHttpBundle = {
716
+ timeout: requestTimeoutMs,
717
+ headers: (_f = options.headers) !== null && _f !== void 0 ? _f : {},
718
+ retry: (_g = options.retry) !== null && _g !== void 0 ? _g : {},
719
+ fetch: options.fetch,
720
+ };
721
+ }
722
+ /**
723
+ * Build a {@link WebhookListener} for your HTTP server. Verifies `X-Skytells-Signature` per
724
+ * [Skytells webhooks](https://docs.skytells.ai/webhooks/).
725
+ *
726
+ * - **`mode: 'general'`** (default): HMAC key is your API key — omitted `apiKey` uses this client’s key.
727
+ * - **`mode: 'enterprise'`**: pass `secret` from the dashboard.
728
+ *
729
+ * @example
730
+ * ```ts
731
+ * const hooks = client.webhookListener({ mode: 'general' });
732
+ * hooks.on(WebhookEvent.COMPLETED, async (p) => console.log(p.id));
733
+ * // Next.js: export async function POST(req: Request) { return hooks.handleRequest(req); }
734
+ * ```
735
+ */
736
+ webhookListener(options = {}) {
737
+ var _a;
738
+ const apiKey = (_a = options.apiKey) !== null && _a !== void 0 ? _a : this.http.getApiKey();
739
+ return createWebhookListener({ ...options, apiKey });
740
+ }
741
+ /**
742
+ * Alias of {@link SkytellsClient.webhookListener}. Chain handlers: `client.listen().on(WebhookEvent.COMPLETED, fn)`.
743
+ */
744
+ listen(options) {
745
+ return this.webhookListener(options);
746
+ }
747
+ /**
748
+ * Clears cached model metadata used when guarded predict/create/run passes `compatibilityCheck: true`.
749
+ *
750
+ * @param modelSlug - If set, only evicts that slug; otherwise clears the entire cache.
751
+ *
752
+ * @example
753
+ * ```ts
754
+ * // After a model was updated server-side, force the next predict to refetch metadata:
755
+ * client.purgePrefetchedModelCache("flux-pro");
756
+ *
757
+ * // Clear all cached slugs
758
+ * client.purgePrefetchedModelCache();
759
+ * ```
760
+ */
761
+ purgePrefetchedModelCache(modelSlug) {
762
+ const cache = this._prefetchedModelCache;
763
+ if (!cache) {
764
+ return;
765
+ }
766
+ if (modelSlug !== undefined) {
767
+ cache.delete(modelSlug);
768
+ if (cache.size === 0) {
769
+ this._prefetchedModelCache = null;
770
+ }
771
+ }
772
+ else {
773
+ this._prefetchedModelCache = null;
774
+ }
775
+ }
776
+ /**
777
+ * Returns model metadata for the inference compatibility check, using {@link _prefetchedModelCache} when fresh.
778
+ */
779
+ async _getModelForCompatibilityCheck(slug) {
780
+ const now = Date.now();
781
+ if (!this._prefetchedModelCache) {
782
+ this._prefetchedModelCache = new Map();
783
+ }
784
+ const cache = this._prefetchedModelCache;
785
+ const cached = cache.get(slug);
786
+ if (cached && cached.expiresAt > now) {
787
+ // Move to end of Map iteration order so FIFO eviction keeps recently used slugs.
788
+ cache.delete(slug);
789
+ cache.set(slug, cached);
790
+ return cached.model;
791
+ }
792
+ if (cached) {
793
+ cache.delete(slug);
794
+ }
795
+ const model = await this.models.get(slug);
796
+ cache.set(slug, {
797
+ model,
798
+ expiresAt: now + PREFETCHED_MODEL_CACHE_TTL_MS,
799
+ });
800
+ while (cache.size > this._prefetchCap) {
801
+ const oldest = cache.keys().next().value;
802
+ if (oldest === undefined) {
803
+ break;
804
+ }
805
+ cache.delete(oldest);
806
+ }
807
+ return model;
808
+ }
809
+ /**
810
+ * @internal When `compatibilityCheck === true`, throws SDK_ERROR if the model is chat-only here.
811
+ */
812
+ async _maybeCompatibilityGuard(modelSlug, compatibilityCheck) {
813
+ var _a;
814
+ if (compatibilityCheck !== true) {
815
+ return;
816
+ }
817
+ try {
818
+ const modelData = await this._getModelForCompatibilityCheck(modelSlug);
819
+ if ((_a = modelData.metadata) === null || _a === void 0 ? void 0 : _a.openai_compatible) {
820
+ throw new SkytellsError(`This model supports the OpenAI Chat Completions API. Did you mean client.chat.completions.create()? Use it for chat-style inference.`, 'SDK_ERROR', `Model "${modelData.namespace}" is OpenAI-compatible. Use client.chat.completions.create() instead of predict() or run().`, 0);
821
+ }
822
+ }
823
+ catch (e) {
824
+ if (e instanceof SkytellsError && e.errorId === 'SDK_ERROR') {
825
+ throw e;
826
+ }
827
+ // If model fetch fails (e.g. MODEL_NOT_FOUND), let the predict request proceed
828
+ }
829
+ }
830
+ /**
831
+ * Sends a prediction request to the Skytells API.
832
+ *
833
+ * This is the low-level prediction method. For most use cases, prefer {@link run}
834
+ * which waits for completion and returns a {@link Prediction} object with convenience methods.
835
+ *
836
+ * @param payload - The prediction request parameters.
837
+ * @param payload.model - The model slug to run (e.g. `"flux-pro"`, `"truefusion"`).
838
+ * @param payload.input - Key-value input parameters for the model (e.g. `{ prompt: "..." }`).
839
+ * @param payload.await - If `true`, the request blocks until the prediction completes (default: `false`).
840
+ * @param payload.stream - If `true`, enables streaming for the prediction (default: `false`).
841
+ * @param payload.webhook - Optional webhook configuration to receive prediction events.
842
+ * @param sdk - SDK-only options (not sent in JSON). When `sdk.compatibilityCheck === true`, runs inference compatibility guard before POST.
843
+ * @returns The prediction response object.
844
+ * @throws {SkytellsError} On API errors (invalid input, model not found, insufficient credits, etc.).
845
+ *
846
+ * @example
847
+ * ```ts
848
+ * // Fire-and-forget (returns immediately with status: "pending")
849
+ * const response = await client.predict({
850
+ * model: "flux-pro",
851
+ * input: { prompt: "A sunset over mountains" },
852
+ * });
853
+ * console.log(response.id); // use predictions.get(id) to poll
854
+ *
855
+ * // Wait for completion
856
+ * const result = await client.predict({
857
+ * model: "flux-pro",
858
+ * input: { prompt: "A sunset over mountains" },
859
+ * await: true,
860
+ * });
861
+ * console.log(result.output); // ["https://..."]
862
+ * ```
863
+ */
864
+ async predict(payload, sdk) {
865
+ await this._maybeCompatibilityGuard(payload.model, sdk === null || sdk === void 0 ? void 0 : sdk.compatibilityCheck);
866
+ return this.http.request('POST', ENDPOINTS.PREDICT, predictionBodyForHttp(payload));
867
+ }
868
+ /**
869
+ * Runs a model, waits for completion, and returns a {@link Prediction} object.
870
+ *
871
+ * This is the recommended way to generate content. It automatically sets `await: true`,
872
+ * checks for failures, and returns a `Prediction` with `.output`, `.cancel()`, and `.delete()`.
873
+ *
874
+ * If an `onProgress` callback is provided, the prediction is created in the background
875
+ * and polled every 5 seconds. The callback is invoked on each poll with the latest
876
+ * prediction state, allowing you to track progress.
877
+ *
878
+ * @param model - The model slug to run (e.g. `"flux-pro"`, `"truefusion"`, `"beatfusion"`).
879
+ * @param options - Run options.
880
+ * @param options.input - Key-value input parameters for the model.
881
+ * @param options.stream - If `true`, enables streaming (default: `false`).
882
+ * @param options.webhook - Optional webhook configuration.
883
+ * @param onProgress - Optional callback invoked on each poll with the current prediction state.
884
+ * @param sdk - SDK-only options. When `sdk.compatibilityCheck === true`, runs inference guard before predict/create. Use `undefined` for `onProgress` when you only need `sdk` (e.g. `run(m, o, undefined, { compatibilityCheck: true })`).
885
+ * @remarks With `onProgress`, uses `predictions.create` + {@link wait}. Then {@link RunOptions.interval},
886
+ * {@link RunOptions.maxWait}, and {@link RunOptions.signal} apply (same as {@link WaitOptions}).
887
+ * @returns A {@link Prediction} object with output and lifecycle methods.
888
+ * @throws {SkytellsError} On API errors or if the prediction fails (`PREDICTION_FAILED`).
889
+ *
890
+ * @example
891
+ * ```ts
892
+ * // Simple run (blocks until complete)
893
+ * const prediction = await client.run("flux-pro", {
894
+ * input: { prompt: "An astronaut riding a rainbow unicorn" },
895
+ * });
896
+ * const [imageUrl] = prediction.output;
897
+ *
898
+ * // With progress tracking
899
+ * const prediction = await client.run("flux-pro", {
900
+ * input: { prompt: "An astronaut riding a unicorn" },
901
+ * }, (p) => {
902
+ * // Note: metrics.progress may not be available during early processing stages
903
+ * console.log(`Status: ${p.status}, Progress: ${p.metrics?.progress ?? 'n/a'}`);
904
+ * });
905
+ * ```
906
+ */
907
+ async run(model, options, onProgress, sdk) {
908
+ const { input, webhook, stream, interval, maxWait, signal } = options;
909
+ const predictionBody = { input, webhook, stream };
910
+ let data;
911
+ if (onProgress) {
912
+ data = await this.predictions.create({ model, ...predictionBody }, sdk);
913
+ data = await this.wait(data, { interval, maxWait, signal }, onProgress);
914
+ }
915
+ else {
916
+ data = await this.predict({ model, ...predictionBody, await: true }, sdk);
917
+ }
918
+ if (data.status === PredictionStatus.FAILED) {
919
+ throw new SkytellsError(data.response || 'Prediction failed', 'PREDICTION_FAILED', `Prediction ${data.id} failed`);
920
+ }
921
+ return new Prediction(this.http, data);
922
+ }
923
+ /**
924
+ * Polls a prediction until it reaches a terminal status (`succeeded`, `failed`, or `cancelled`).
925
+ *
926
+ * @param prediction - The prediction response to wait on (must have an `id`).
927
+ * @param options - Polling options.
928
+ * @param options.interval - Polling interval in milliseconds (default: 5000).
929
+ * @param options.maxWait - Maximum wait time in milliseconds. Throws if exceeded.
930
+ * @param options.signal - If aborted, stops polling and throws `ABORTED` (no further poll timers scheduled).
931
+ * @param onProgress - Optional callback invoked on each poll with the latest prediction state.
932
+ * @returns The final prediction response.
933
+ * @throws {SkytellsError} If `maxWait` is exceeded (`WAIT_TIMEOUT`) or `signal` is aborted (`ABORTED`).
934
+ *
935
+ * @example
936
+ * ```ts
937
+ * const bg = await client.predictions.create({
938
+ * model: "flux-pro",
939
+ * input: { prompt: "A cat" },
940
+ * });
941
+ *
942
+ * const result = await client.wait(bg);
943
+ * console.log(result.output);
944
+ *
945
+ * // With progress and timeout
946
+ * const result = await client.wait(bg, { interval: 2000, maxWait: 120000 }, (p) => {
947
+ * console.log(p.status, p.metrics?.progress);
948
+ * });
949
+ * ```
950
+ */
951
+ async wait(prediction, options, onProgress) {
952
+ var _a;
953
+ const id = prediction === null || prediction === void 0 ? void 0 : prediction.id;
954
+ if (id == null || id === '') {
955
+ throw new SkytellsError('Cannot wait on a prediction without an id', 'SDK_ERROR', 'PredictionResponse.id is required for polling', 0);
956
+ }
957
+ const rawInterval = (_a = options === null || options === void 0 ? void 0 : options.interval) !== null && _a !== void 0 ? _a : DEFAULT_POLL_INTERVAL;
958
+ const interval = typeof rawInterval === 'number' && Number.isFinite(rawInterval) && rawInterval >= 0
959
+ ? rawInterval
960
+ : DEFAULT_POLL_INTERVAL;
961
+ const rawMax = options === null || options === void 0 ? void 0 : options.maxWait;
962
+ const maxWaitMs = typeof rawMax === 'number' && Number.isFinite(rawMax) && rawMax >= 0 ? rawMax : undefined;
963
+ const signal = options === null || options === void 0 ? void 0 : options.signal;
964
+ const startTime = Date.now();
965
+ const deadline = typeof maxWaitMs === 'number' ? startTime + maxWaitMs : null;
966
+ let current = prediction;
967
+ if (TERMINAL_STATUSES.has(current.status)) {
968
+ return current;
969
+ }
970
+ let firstPoll = true;
971
+ while (!TERMINAL_STATUSES.has(current.status)) {
972
+ if (signal === null || signal === void 0 ? void 0 : signal.aborted) {
973
+ throw SkytellsClient.abortWaitError();
974
+ }
975
+ const now = Date.now();
976
+ if (deadline !== null && now >= deadline) {
977
+ throw new SkytellsError(`Prediction ${current.id} did not complete within ${maxWaitMs}ms`, 'WAIT_TIMEOUT', `Timed out after ${maxWaitMs}ms. Last status: ${current.status}`, 408);
978
+ }
979
+ if (!firstPoll) {
980
+ const remaining = deadline !== null ? deadline - Date.now() : interval;
981
+ if (deadline !== null && remaining <= 0) {
982
+ throw new SkytellsError(`Prediction ${current.id} did not complete within ${maxWaitMs}ms`, 'WAIT_TIMEOUT', `Timed out after ${maxWaitMs}ms. Last status: ${current.status}`, 408);
983
+ }
984
+ const sleepMs = Math.min(interval, Math.max(0, remaining));
985
+ await this.delay(sleepMs, signal);
986
+ if (signal === null || signal === void 0 ? void 0 : signal.aborted) {
987
+ throw SkytellsClient.abortWaitError();
988
+ }
989
+ if (deadline !== null && Date.now() >= deadline) {
990
+ throw new SkytellsError(`Prediction ${current.id} did not complete within ${maxWaitMs}ms`, 'WAIT_TIMEOUT', `Timed out after ${maxWaitMs}ms. Last status: ${current.status}`, 408);
991
+ }
992
+ }
993
+ firstPoll = false;
994
+ current = await this.predictions.get(current.id, current.urls);
995
+ if (onProgress) {
996
+ onProgress(current);
997
+ }
998
+ }
999
+ return current;
1000
+ }
1001
+ /**
1002
+ * Adds a prediction request to the local queue.
1003
+ *
1004
+ * Queued items are not sent to the API until {@link dispatch} is called.
1005
+ * Useful for batching multiple predictions together.
1006
+ *
1007
+ * @param payload - The prediction request to queue.
1008
+ * @param sdk - Optional SDK options for this item (e.g. `{ compatibilityCheck: true }`), forwarded to {@link PredictionsAPI.create} on {@link dispatch}.
1009
+ *
1010
+ * @example
1011
+ * ```ts
1012
+ * client.queue({ model: "flux-pro", input: { prompt: "Cat" } });
1013
+ * client.queue({ model: "flux-pro", input: { prompt: "Dog" } });
1014
+ * client.queue({ model: "flux-pro", input: { prompt: "Bird" } });
1015
+ *
1016
+ * const results = await client.dispatch();
1017
+ * // results is an array of PredictionResponse for each queued item
1018
+ * ```
1019
+ */
1020
+ queue(payload, sdk) {
1021
+ this._queue.push({ request: payload, sdk });
1022
+ }
1023
+ /**
1024
+ * Dispatches all queued predictions, sending them to the API concurrently.
1025
+ *
1026
+ * Clears the queue after dispatching. Each prediction is created in the background
1027
+ * (non-blocking). Use {@link wait} on individual results if you need to wait for completion.
1028
+ *
1029
+ * @returns An array of prediction responses, one for each queued item.
1030
+ * @throws {SkytellsError} Uses **`Promise.all`**: if **any** `predictions.create` rejects, the whole
1031
+ * `dispatch()` rejects with that error (fail-fast). Partial successes are not returned.
1032
+ *
1033
+ * @example
1034
+ * ```ts
1035
+ * client.queue({ model: "flux-pro", input: { prompt: "Cat" } });
1036
+ * client.queue({ model: "flux-pro", input: { prompt: "Dog" } });
1037
+ *
1038
+ * const results = await client.dispatch();
1039
+ * for (const pred of results) {
1040
+ * console.log(pred.id, pred.status);
1041
+ * }
1042
+ *
1043
+ * // Wait for all to complete
1044
+ * const completed = await Promise.all(results.map(p => client.wait(p)));
1045
+ * ```
1046
+ */
1047
+ async dispatch() {
1048
+ const items = this._queue.splice(0);
1049
+ const results = await Promise.all(items.map((item) => this.predictions.create(item.request, item.sdk)));
1050
+ return results;
11
1051
  }
12
1052
  /**
13
- * Send a prediction request to the Skytells API
14
- * @param payload The prediction request parameters
15
- * @returns A promise that resolves to the prediction response
1053
+ * Retrieves the streaming endpoint for a prediction.
1054
+ *
1055
+ * @deprecated Use {@link Prediction.stream} on the {@link Prediction} from {@link SkytellsClient.run} instead,
1056
+ * or pass `urls` from a stored `PredictionResponse` when calling without a `Prediction` instance.
1057
+ *
1058
+ * @param id - The prediction ID to stream.
1059
+ * @param urls - Optional `urls` from a prior `PredictionResponse`. When `urls.stream` is set, that URL is used.
1060
+ * @returns The prediction response with streaming URL info.
1061
+ * @throws {SkytellsError} If the prediction is not found or streaming is not available.
1062
+ *
1063
+ * @example
1064
+ * ```ts
1065
+ * const stream = await client.streamPrediction("pred_abc123");
1066
+ * console.log(stream.urls?.stream);
1067
+ * ```
16
1068
  */
17
- async predict(payload) {
18
- return this.http.request('POST', ENDPOINTS.PREDICT, payload);
1069
+ async streamPrediction(id, urls) {
1070
+ deprecateSkytellsClientPredictionMethod('streamPrediction');
1071
+ const url = resolvePredictionResourceUrl('stream', id, urls, this.http.getBaseUrl());
1072
+ return this.http.request('GET', url);
1073
+ }
1074
+ /**
1075
+ * Cancels a running prediction by its ID.
1076
+ *
1077
+ * @deprecated Use {@link Prediction.cancel} on the {@link Prediction} from {@link SkytellsClient.run} instead.
1078
+ * Only predictions with status `pending`, `starting`, `started`, or `processing` can be cancelled.
1079
+ *
1080
+ * @param id - The prediction ID to cancel.
1081
+ * @param urls - Optional `urls` from a prior `PredictionResponse`. When `urls.cancel` is set, that URL is used.
1082
+ * @returns The updated prediction response with `status: 'cancelled'`.
1083
+ * @throws {SkytellsError} If the prediction cannot be cancelled or is not found.
1084
+ *
1085
+ * @example
1086
+ * ```ts
1087
+ * await client.cancelPrediction("pred_abc123");
1088
+ * ```
1089
+ */
1090
+ async cancelPrediction(id, urls) {
1091
+ deprecateSkytellsClientPredictionMethod('cancelPrediction');
1092
+ const url = resolvePredictionResourceUrl('cancel', id, urls, this.http.getBaseUrl());
1093
+ return this.http.request('POST', url);
1094
+ }
1095
+ /**
1096
+ * Deletes a prediction and its associated output/assets.
1097
+ *
1098
+ * @deprecated Use {@link Prediction.delete} on the {@link Prediction} from {@link SkytellsClient.run} instead.
1099
+ *
1100
+ * @param id - The prediction ID to delete.
1101
+ * @param urls - Optional `urls` from a prior `PredictionResponse`. When `urls.delete` is set, that URL is used.
1102
+ * @returns The prediction response confirming deletion.
1103
+ * @throws {SkytellsError} If the prediction is not found or the request fails.
1104
+ *
1105
+ * @example
1106
+ * ```ts
1107
+ * await client.deletePrediction("pred_abc123");
1108
+ * ```
1109
+ */
1110
+ async deletePrediction(id, urls) {
1111
+ deprecateSkytellsClientPredictionMethod('deletePrediction');
1112
+ const url = resolvePredictionResourceUrl('delete', id, urls, this.http.getBaseUrl());
1113
+ return this.http.request('DELETE', url);
1114
+ }
1115
+ /** Stops {@link wait} polling when `signal` aborts; clears the pending timer so nothing keeps firing. */
1116
+ static abortWaitError() {
1117
+ return new SkytellsError('Wait aborted', 'ABORTED', 'Polling stopped because the AbortSignal was aborted.', 0);
19
1118
  }
20
1119
  /**
21
- * Get a prediction by ID
22
- * @param id The prediction ID
23
- * @returns A promise that resolves to the prediction response
1120
+ * @param signal - When present and aborted, the promise rejects with `ABORTED` and the timer is cleared.
24
1121
  */
25
- async getPrediction(id) {
26
- return this.http.request('GET', ENDPOINTS.PREDICTION_BY_ID(id));
1122
+ delay(ms, signal) {
1123
+ const d = Math.max(0, ms);
1124
+ if (!signal) {
1125
+ return new Promise((resolve) => setTimeout(resolve, d));
1126
+ }
1127
+ if (signal.aborted) {
1128
+ return Promise.reject(SkytellsClient.abortWaitError());
1129
+ }
1130
+ return new Promise((resolve, reject) => {
1131
+ let settled = false;
1132
+ const finish = (fn) => {
1133
+ if (settled) {
1134
+ return;
1135
+ }
1136
+ settled = true;
1137
+ clearTimeout(tid);
1138
+ signal.removeEventListener('abort', onAbort);
1139
+ fn();
1140
+ };
1141
+ const onAbort = () => {
1142
+ finish(() => reject(SkytellsClient.abortWaitError()));
1143
+ };
1144
+ signal.addEventListener('abort', onAbort);
1145
+ const tid = setTimeout(() => {
1146
+ finish(() => resolve());
1147
+ }, d);
1148
+ });
27
1149
  }
1150
+ // ── Deprecated aliases ──────────────────────────────────────────────
28
1151
  /**
29
- * List all available models
30
- * @returns A promise that resolves to an array of models
1152
+ * @deprecated Use {@link predictions}.list() instead. This method will be removed in a future version.
31
1153
  */
32
- async listModels() {
33
- return this.http.request('GET', ENDPOINTS.MODELS);
1154
+ async listPredictions(page) {
1155
+ return this.predictions.list({ page });
34
1156
  }
35
1157
  /**
36
- * Stream a prediction by ID
37
- * @param id The prediction ID
38
- * @returns A promise that resolves to the prediction response
1158
+ * @deprecated Use {@link models}.list() instead. This method will be removed in a future version.
39
1159
  */
40
- async streamPrediction(id) {
41
- return this.http.request('GET', ENDPOINTS.STREAM_PREDICTION_BY_ID(id));
1160
+ async listModels(options) {
1161
+ return this.models.list(options);
42
1162
  }
43
1163
  /**
44
- * Cancel a prediction by ID
45
- * @param id The prediction ID
46
- * @returns A promise that resolves to the prediction response
1164
+ * @deprecated Use {@link predictions}.get() instead. This method will be removed in a future version.
47
1165
  */
48
- async cancelPrediction(id) {
49
- return this.http.request('POST', ENDPOINTS.CANCEL_PREDICTION_BY_ID(id));
1166
+ async getPrediction(id, urls) {
1167
+ return this.predictions.get(id, urls);
50
1168
  }
51
1169
  /**
52
- * Delete a prediction by ID
53
- * @param id The prediction ID
54
- * @returns A promise that resolves to the prediction response
1170
+ * @deprecated Use {@link models}.get() instead. This method will be removed in a future version.
55
1171
  */
56
- async deletePrediction(id) {
57
- return this.http.request('DELETE', ENDPOINTS.DELETE_PREDICTION_BY_ID(id));
1172
+ async getModel(slug, options) {
1173
+ return this.models.get(slug, options);
58
1174
  }
59
1175
  }