@xmodeai/sdk 0.1.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/dist/index.js ADDED
@@ -0,0 +1,667 @@
1
+ //#region src/core/errors.ts
2
+ /** Base class for every error thrown by the SDK. */
3
+ var XModeError = class extends Error {
4
+ constructor(message) {
5
+ super(message);
6
+ this.name = "XModeError";
7
+ }
8
+ };
9
+ /**
10
+ * An error response from the API. The concrete subclass is chosen by the stable
11
+ * `error.code` contract; an unrecognized code yields a plain `XModeAPIError`
12
+ * (forward-compat — a new server code never crashes error handling).
13
+ */
14
+ var XModeAPIError = class extends XModeError {
15
+ status;
16
+ code;
17
+ fields;
18
+ /** Value of the `X-XMode-Request-Id` response header, when present. */
19
+ requestId;
20
+ constructor(init) {
21
+ super(init.message);
22
+ this.name = "XModeAPIError";
23
+ this.status = init.status;
24
+ this.code = init.code;
25
+ this.fields = init.fields;
26
+ this.requestId = init.requestId;
27
+ }
28
+ };
29
+ /** 400 `validation_error` — request failed schema validation. `fields` lists offending paths. */
30
+ var ValidationError = class extends XModeAPIError {
31
+ constructor(init) {
32
+ super(init);
33
+ this.name = "ValidationError";
34
+ }
35
+ };
36
+ /** 401 `unauthorized` — missing or invalid API key. */
37
+ var AuthenticationError = class extends XModeAPIError {
38
+ constructor(init) {
39
+ super(init);
40
+ this.name = "AuthenticationError";
41
+ }
42
+ };
43
+ /** 403 `forbidden` / `account_blocked` — the action is not allowed. */
44
+ var PermissionDeniedError = class extends XModeAPIError {
45
+ constructor(init) {
46
+ super(init);
47
+ this.name = "PermissionDeniedError";
48
+ }
49
+ };
50
+ /** 404 `not_found` — the resource does not exist (scoped to your account). */
51
+ var NotFoundError = class extends XModeAPIError {
52
+ constructor(init) {
53
+ super(init);
54
+ this.name = "NotFoundError";
55
+ }
56
+ };
57
+ /** 409 `conflict` — e.g. deleting an end-user that still has pending generations. */
58
+ var ConflictError = class extends XModeAPIError {
59
+ constructor(init) {
60
+ super(init);
61
+ this.name = "ConflictError";
62
+ }
63
+ };
64
+ /** 429 `rate_limited` — too many requests. `retryAfter` (seconds) set when provided. */
65
+ var RateLimitError = class extends XModeAPIError {
66
+ retryAfter;
67
+ constructor(init) {
68
+ super(init);
69
+ this.name = "RateLimitError";
70
+ this.retryAfter = init.retryAfter;
71
+ }
72
+ };
73
+ /** 402 `payment_required` — insufficient xTokens balance. */
74
+ var PaymentRequiredError = class extends XModeAPIError {
75
+ constructor(init) {
76
+ super(init);
77
+ this.name = "PaymentRequiredError";
78
+ }
79
+ };
80
+ /** 422 `content_policy` — the prompt or inputs were rejected by the content filter. */
81
+ var ContentPolicyError = class extends XModeAPIError {
82
+ constructor(init) {
83
+ super(init);
84
+ this.name = "ContentPolicyError";
85
+ }
86
+ };
87
+ /** `provider_error` / `provider_timeout` — upstream generation failed; safe to retry. */
88
+ var ProviderError = class extends XModeAPIError {
89
+ constructor(init) {
90
+ super(init);
91
+ this.name = "ProviderError";
92
+ }
93
+ };
94
+ /** `internal_error` — unexpected server-side failure. */
95
+ var InternalServerError = class extends XModeAPIError {
96
+ constructor(init) {
97
+ super(init);
98
+ this.name = "InternalServerError";
99
+ }
100
+ };
101
+ /** A network-level failure (DNS, connection reset, fetch threw) before a response was received. */
102
+ var APIConnectionError = class extends XModeError {
103
+ cause;
104
+ constructor(message = "Connection error", cause) {
105
+ super(message);
106
+ this.name = "APIConnectionError";
107
+ this.cause = cause;
108
+ }
109
+ };
110
+ /** The request exceeded the configured `timeout` before a response arrived. */
111
+ var APIConnectionTimeoutError = class extends APIConnectionError {
112
+ constructor(message = "Request timed out") {
113
+ super(message);
114
+ this.name = "APIConnectionTimeoutError";
115
+ }
116
+ };
117
+ /** A `*.wait` / `*.createAndWait` poll did not reach a terminal status in time. */
118
+ var XModePollTimeoutError = class extends XModeError {
119
+ /** The most recent record observed before the timeout. */
120
+ lastRecord;
121
+ constructor(message, lastRecord) {
122
+ super(message);
123
+ this.name = "XModePollTimeoutError";
124
+ this.lastRecord = lastRecord;
125
+ }
126
+ };
127
+ const CODE_TO_CLASS = {
128
+ validation_error: ValidationError,
129
+ unauthorized: AuthenticationError,
130
+ account_blocked: AuthenticationError,
131
+ forbidden: PermissionDeniedError,
132
+ not_found: NotFoundError,
133
+ conflict: ConflictError,
134
+ rate_limited: RateLimitError,
135
+ payment_required: PaymentRequiredError,
136
+ content_policy: ContentPolicyError,
137
+ provider_error: ProviderError,
138
+ provider_timeout: ProviderError,
139
+ internal_error: InternalServerError
140
+ };
141
+ function isErrorBody(value) {
142
+ return typeof value === "object" && value !== null && "error" in value && typeof value.error === "object" && value.error !== null;
143
+ }
144
+ /**
145
+ * Build the right error instance from a non-2xx response. Tolerant: a non-JSON
146
+ * body or an unknown `code` still yields a usable `XModeAPIError`, never a
147
+ * secondary parse error.
148
+ */
149
+ function castAPIError(status, body, rawText, requestId, retryAfter) {
150
+ let code = `http_${status}`;
151
+ let message = rawText || `Request failed with status ${status}`;
152
+ let fields;
153
+ if (isErrorBody(body)) {
154
+ const err = body.error;
155
+ if (typeof err.code === "string" && err.code) code = err.code;
156
+ if (typeof err.message === "string" && err.message) message = err.message;
157
+ if (err.fields && typeof err.fields === "object") fields = err.fields;
158
+ }
159
+ const init = {
160
+ status,
161
+ code,
162
+ message,
163
+ fields,
164
+ requestId,
165
+ retryAfter
166
+ };
167
+ const Cls = CODE_TO_CLASS[code];
168
+ if (Cls) return new Cls(init);
169
+ if (status === 429) return new RateLimitError(init);
170
+ if (status === 404) return new NotFoundError(init);
171
+ if (status === 401) return new AuthenticationError(init);
172
+ if (status === 403) return new PermissionDeniedError(init);
173
+ return new XModeAPIError(init);
174
+ }
175
+ //#endregion
176
+ //#region src/core/retry.ts
177
+ const MAX_BACKOFF_MS = 8e3;
178
+ const RETRY_AFTER_CAP_MS = 6e4;
179
+ /**
180
+ * Parse a `Retry-After` header into milliseconds. Supports both the
181
+ * delta-seconds form (`"3"`) and the HTTP-date form. Returns null when absent
182
+ * or unparseable.
183
+ */
184
+ function parseRetryAfterMs(header) {
185
+ if (!header) return null;
186
+ const seconds = Number(header);
187
+ if (Number.isFinite(seconds)) return Math.max(0, seconds * 1e3);
188
+ const date = Date.parse(header);
189
+ if (!Number.isNaN(date)) return Math.max(0, date - Date.now());
190
+ return null;
191
+ }
192
+ /** Same as {@link parseRetryAfterMs} but in whole seconds, for `RateLimitError.retryAfter`. */
193
+ function parseRetryAfterSeconds(header) {
194
+ const ms = parseRetryAfterMs(header);
195
+ return ms === null ? void 0 : Math.ceil(ms / 1e3);
196
+ }
197
+ /**
198
+ * Exponential backoff with full jitter. `Retry-After`, when present, sets a
199
+ * floor (capped at 60s) so we never retry sooner than the server asked.
200
+ */
201
+ function backoffDelay(attempt, retryAfterHeader) {
202
+ const exp = Math.min(MAX_BACKOFF_MS, 500 * 2 ** attempt);
203
+ const jittered = exp / 2 + Math.random() * (exp / 2);
204
+ const retryAfter = parseRetryAfterMs(retryAfterHeader);
205
+ if (retryAfter !== null) return Math.min(RETRY_AFTER_CAP_MS, Math.max(retryAfter, jittered));
206
+ return jittered;
207
+ }
208
+ /** Promise-based sleep that rejects if the (optional) signal aborts. */
209
+ function sleep(ms, signal) {
210
+ return new Promise((resolve, reject) => {
211
+ if (signal?.aborted) {
212
+ reject(signal.reason ?? /* @__PURE__ */ new Error("Aborted"));
213
+ return;
214
+ }
215
+ const onAbort = () => {
216
+ clearTimeout(timer);
217
+ reject(signal?.reason ?? /* @__PURE__ */ new Error("Aborted"));
218
+ };
219
+ const timer = setTimeout(() => {
220
+ signal?.removeEventListener("abort", onAbort);
221
+ resolve();
222
+ }, ms);
223
+ signal?.addEventListener("abort", onAbort, { once: true });
224
+ });
225
+ }
226
+ //#endregion
227
+ //#region src/core/request.ts
228
+ function defaultPolicy(method) {
229
+ return method === "GET" ? "idempotent" : "create";
230
+ }
231
+ function isRetryableStatus(status, policy) {
232
+ if (policy === "create") return status === 429;
233
+ return status === 408 || status === 429 || status >= 500;
234
+ }
235
+ function buildUrl(baseUrl, path, query) {
236
+ let url = baseUrl + path;
237
+ if (query) {
238
+ const params = new URLSearchParams();
239
+ for (const [key, value] of Object.entries(query)) if (value !== void 0) params.append(key, String(value));
240
+ const qs = params.toString();
241
+ if (qs) url += (url.includes("?") ? "&" : "?") + qs;
242
+ }
243
+ return url;
244
+ }
245
+ function buildHeaders(config, args) {
246
+ const headers = {
247
+ Authorization: `Bearer ${config.apiKey}`,
248
+ Accept: "application/json",
249
+ "User-Agent": config.userAgent,
250
+ ...config.defaultHeaders,
251
+ ...args.headers
252
+ };
253
+ if (args.body !== void 0) headers["Content-Type"] = "application/json";
254
+ return headers;
255
+ }
256
+ function combineSignals(user, timeout) {
257
+ if (!user) return timeout;
258
+ if (typeof AbortSignal.any === "function") return AbortSignal.any([user, timeout]);
259
+ const controller = new AbortController();
260
+ const relay = (s) => controller.abort(s.reason);
261
+ for (const s of [user, timeout]) if (s.aborted) relay(s);
262
+ else s.addEventListener("abort", () => relay(s), { once: true });
263
+ return controller.signal;
264
+ }
265
+ async function parseSuccessBody(res) {
266
+ if (res.status === 204) return void 0;
267
+ const text = await res.text();
268
+ if (!text) return void 0;
269
+ return JSON.parse(text);
270
+ }
271
+ function safeJsonParse(text) {
272
+ try {
273
+ return JSON.parse(text);
274
+ } catch {
275
+ return;
276
+ }
277
+ }
278
+ /**
279
+ * Execute a single logical request, applying auth, timeout, the tolerant JSON
280
+ * reader (responses are never schema-validated), and the retry loop.
281
+ */
282
+ async function execute(config, args) {
283
+ const policy = args.policy ?? defaultPolicy(args.method);
284
+ const maxRetries = args.maxRetries ?? config.maxRetries;
285
+ const url = buildUrl(config.baseUrl, args.path, args.query);
286
+ const headers = buildHeaders(config, args);
287
+ const body = args.body === void 0 ? void 0 : JSON.stringify(args.body);
288
+ for (let attempt = 0;; attempt++) {
289
+ const timeoutSignal = AbortSignal.timeout(args.timeout ?? config.timeout);
290
+ let res;
291
+ try {
292
+ res = await config.fetch(url, {
293
+ method: args.method,
294
+ headers,
295
+ body,
296
+ signal: combineSignals(args.signal, timeoutSignal)
297
+ });
298
+ } catch (err) {
299
+ if (args.signal?.aborted) throw args.signal.reason instanceof Error ? args.signal.reason : new APIConnectionError("Request aborted", err);
300
+ if (policy === "idempotent" && attempt < maxRetries) {
301
+ await sleep(backoffDelay(attempt, null), args.signal);
302
+ continue;
303
+ }
304
+ if (timeoutSignal.aborted) throw new APIConnectionTimeoutError();
305
+ throw new APIConnectionError("Connection error", err);
306
+ }
307
+ const requestId = res.headers.get("x-xmode-request-id") ?? void 0;
308
+ if (res.ok) return await parseSuccessBody(res);
309
+ if (isRetryableStatus(res.status, policy) && attempt < maxRetries) {
310
+ await sleep(backoffDelay(attempt, res.headers.get("retry-after")), args.signal);
311
+ continue;
312
+ }
313
+ const rawText = await res.text().catch(() => "");
314
+ throw castAPIError(res.status, safeJsonParse(rawText), rawText, requestId, parseRetryAfterSeconds(res.headers.get("retry-after")));
315
+ }
316
+ }
317
+ //#endregion
318
+ //#region src/core/pagination.ts
319
+ /**
320
+ * One page of a paginated list, plus the ability to fetch the next page and to
321
+ * iterate every item across all pages with `for await`.
322
+ */
323
+ var Page = class Page {
324
+ items;
325
+ page;
326
+ pageSize;
327
+ hasMore;
328
+ fetcher;
329
+ query;
330
+ constructor(data, fetcher, query) {
331
+ this.items = data.items;
332
+ this.page = data.page;
333
+ this.pageSize = data.pageSize;
334
+ this.hasMore = data.hasMore;
335
+ this.fetcher = fetcher;
336
+ this.query = query;
337
+ }
338
+ /** Fetch the next page, or `null` when there are no more. */
339
+ async getNextPage() {
340
+ if (!this.hasMore) return null;
341
+ const nextQuery = {
342
+ ...this.query,
343
+ page: this.page + 1
344
+ };
345
+ return new Page(await this.fetcher(nextQuery), this.fetcher, nextQuery);
346
+ }
347
+ async *[Symbol.asyncIterator]() {
348
+ let current = this;
349
+ while (current) {
350
+ for (const item of current.items) yield item;
351
+ current = await current.getNextPage();
352
+ }
353
+ }
354
+ };
355
+ /**
356
+ * The return type of `list()` methods. `await` it to get the first {@link Page};
357
+ * `for await (... of ...)` it to stream every item across all pages.
358
+ */
359
+ var PagePromise = class extends Promise {
360
+ static get [Symbol.species]() {
361
+ return Promise;
362
+ }
363
+ async *[Symbol.asyncIterator]() {
364
+ yield* await this;
365
+ }
366
+ };
367
+ /** Build a {@link PagePromise} from a thunk that loads the first page. */
368
+ function makePagePromise(load) {
369
+ return new PagePromise((resolve, reject) => {
370
+ load().then(resolve, reject);
371
+ });
372
+ }
373
+ //#endregion
374
+ //#region src/resources/balance.ts
375
+ var Balance = class {
376
+ client;
377
+ constructor(client) {
378
+ this.client = client;
379
+ }
380
+ /** Current xTokens balance. */
381
+ get(options) {
382
+ return this.client.request({
383
+ method: "GET",
384
+ path: "/v1/balance",
385
+ policy: "idempotent",
386
+ ...options
387
+ });
388
+ }
389
+ /** Paginated ledger of balance changes. `await` for one page, or `for await` for all. */
390
+ history(params = {}, options) {
391
+ const query = {
392
+ page: params.page,
393
+ pageSize: params.pageSize
394
+ };
395
+ const fetcher = (q) => this.client.request({
396
+ method: "GET",
397
+ path: "/v1/balance/history",
398
+ query: q,
399
+ policy: "idempotent",
400
+ ...options
401
+ });
402
+ return makePagePromise(async () => new Page(await fetcher(query), fetcher, query));
403
+ }
404
+ };
405
+ //#endregion
406
+ //#region src/resources/end-users.ts
407
+ function toQuery$1(params) {
408
+ return {
409
+ page: params.page,
410
+ pageSize: params.pageSize,
411
+ blocked: params.blocked === void 0 ? void 0 : String(params.blocked),
412
+ email: params.email
413
+ };
414
+ }
415
+ var EndUsers = class {
416
+ client;
417
+ constructor(client) {
418
+ this.client = client;
419
+ }
420
+ /** List your downstream end-users. `await` for one page, or `for await` for all. */
421
+ list(params = {}, options) {
422
+ const query = toQuery$1(params);
423
+ const fetcher = (q) => this.client.request({
424
+ method: "GET",
425
+ path: "/v1/end-users",
426
+ query: q,
427
+ policy: "idempotent",
428
+ ...options
429
+ });
430
+ return makePagePromise(async () => new Page(await fetcher(query), fetcher, query));
431
+ }
432
+ /** Block an end-user. New generations are refused; pending ones still finish. */
433
+ block(email, options) {
434
+ return this.client.request({
435
+ method: "POST",
436
+ path: `/v1/end-users/${encodeURIComponent(email)}/block`,
437
+ policy: "idempotent",
438
+ ...options
439
+ });
440
+ }
441
+ /** Unblock a previously blocked end-user. */
442
+ unblock(email, options) {
443
+ return this.client.request({
444
+ method: "POST",
445
+ path: `/v1/end-users/${encodeURIComponent(email)}/unblock`,
446
+ policy: "idempotent",
447
+ ...options
448
+ });
449
+ }
450
+ /**
451
+ * Delete an end-user and all their generations and stored media. Fails with a
452
+ * `ConflictError` (409) if the end-user still has pending generations.
453
+ */
454
+ delete(email, options) {
455
+ return this.client.request({
456
+ method: "DELETE",
457
+ path: `/v1/end-users/${encodeURIComponent(email)}`,
458
+ policy: "idempotent",
459
+ ...options
460
+ });
461
+ }
462
+ };
463
+ //#endregion
464
+ //#region src/core/poll.ts
465
+ const DEFAULT_POLL_INTERVAL_MS = 5e3;
466
+ const DEFAULT_POLL_TIMEOUT_MS = 10 * 6e4;
467
+ const TERMINAL_STATUSES = new Set([
468
+ "succeeded",
469
+ "failed",
470
+ "expired"
471
+ ]);
472
+ /** True once a record reaches a status from which it will not change. */
473
+ function isTerminal(record) {
474
+ return TERMINAL_STATUSES.has(record.status);
475
+ }
476
+ /**
477
+ * Poll `fetchRecord` until it returns a terminal record, then resolve with it.
478
+ *
479
+ * Tolerant of statuses this SDK version does not know: anything outside the
480
+ * terminal set is treated as "still in progress", so a server-added status
481
+ * never aborts a wait. Throws {@link XModePollTimeoutError} (carrying the last
482
+ * record seen) if the budget elapses first.
483
+ */
484
+ async function pollUntilTerminal(fetchRecord, options = {}) {
485
+ const pollInterval = options.pollInterval ?? 5e3;
486
+ const timeout = options.timeout ?? 6e5;
487
+ const deadline = Date.now() + timeout;
488
+ for (;;) {
489
+ const record = await fetchRecord();
490
+ if (isTerminal(record)) return record;
491
+ if (Date.now() + pollInterval >= deadline) throw new XModePollTimeoutError(`Did not reach a terminal status within ${timeout}ms (last status: ${record.status}).`, record);
492
+ await sleep(pollInterval, options.signal);
493
+ }
494
+ }
495
+ //#endregion
496
+ //#region src/resources/generations.ts
497
+ function toQuery(params) {
498
+ return {
499
+ page: params.page,
500
+ pageSize: params.pageSize,
501
+ endUserEmail: params.endUserEmail,
502
+ status: params.status,
503
+ createdAfter: params.createdAfter
504
+ };
505
+ }
506
+ var Generations = class {
507
+ client;
508
+ constructor(client) {
509
+ this.client = client;
510
+ }
511
+ /** Enqueue an image generation. Resolves with the `queued` record (202). */
512
+ create(input, options) {
513
+ return this.client.request({
514
+ method: "POST",
515
+ path: "/v1/generations",
516
+ body: input,
517
+ policy: "create",
518
+ ...options
519
+ });
520
+ }
521
+ /** Fetch the current state of a generation. */
522
+ get(requestId, options) {
523
+ return this.client.request({
524
+ method: "GET",
525
+ path: `/v1/generations/${encodeURIComponent(requestId)}`,
526
+ policy: "idempotent",
527
+ ...options
528
+ });
529
+ }
530
+ /** List generations. `await` for the first page, or `for await` to stream all items. */
531
+ list(params = {}, options) {
532
+ const query = toQuery(params);
533
+ const fetcher = (q) => this.client.request({
534
+ method: "GET",
535
+ path: "/v1/generations",
536
+ query: q,
537
+ policy: "idempotent",
538
+ ...options
539
+ });
540
+ return makePagePromise(async () => new Page(await fetcher(query), fetcher, query));
541
+ }
542
+ /** Create a generation and poll until it reaches a terminal status. */
543
+ async createAndWait(input, options) {
544
+ const queued = await this.create(input, { signal: options?.signal });
545
+ return this.wait(queued.requestId, options);
546
+ }
547
+ /** Poll an existing generation until it reaches a terminal status. */
548
+ wait(requestId, options) {
549
+ return pollUntilTerminal(() => this.get(requestId, { signal: options?.signal }), options);
550
+ }
551
+ };
552
+ //#endregion
553
+ //#region src/resources/videos.ts
554
+ const DEFAULT_VIDEO_TIMEOUT_MS = 20 * 6e4;
555
+ var Videos = class {
556
+ client;
557
+ constructor(client) {
558
+ this.client = client;
559
+ }
560
+ /** Enqueue a video generation. Resolves with the `queued` record (202). */
561
+ create(input, options) {
562
+ return this.client.request({
563
+ method: "POST",
564
+ path: "/v1/videos",
565
+ body: input,
566
+ policy: "create",
567
+ ...options
568
+ });
569
+ }
570
+ /** Fetch the current state of a video. */
571
+ get(requestId, options) {
572
+ return this.client.request({
573
+ method: "GET",
574
+ path: `/v1/videos/${encodeURIComponent(requestId)}`,
575
+ policy: "idempotent",
576
+ ...options
577
+ });
578
+ }
579
+ /** Create a video and poll until it reaches a terminal status. */
580
+ async createAndWait(input, options) {
581
+ const queued = await this.create(input, { signal: options?.signal });
582
+ return this.wait(queued.requestId, options);
583
+ }
584
+ /** Poll an existing video until it reaches a terminal status. */
585
+ wait(requestId, options) {
586
+ return pollUntilTerminal(() => this.get(requestId, { signal: options?.signal }), {
587
+ ...options,
588
+ timeout: options?.timeout ?? DEFAULT_VIDEO_TIMEOUT_MS
589
+ });
590
+ }
591
+ };
592
+ //#endregion
593
+ //#region src/version.ts
594
+ const VERSION = "0.1.0";
595
+ //#endregion
596
+ //#region src/client.ts
597
+ const DEFAULT_BASE_URL = "https://api.xmode.ai";
598
+ const DEFAULT_TIMEOUT_MS = 3e4;
599
+ const DEFAULT_MAX_RETRIES = 2;
600
+ function readEnv(key) {
601
+ return globalThis.process?.env?.[key];
602
+ }
603
+ function normalizeBaseUrl(url) {
604
+ return url.replace(/\/+$/, "");
605
+ }
606
+ /**
607
+ * The xMode API client.
608
+ *
609
+ * ```ts
610
+ * import XMode from '@xmodeai/sdk';
611
+ * const client = new XMode({ apiKey: process.env.XMODE_API_KEY });
612
+ * const result = await client.generations.createAndWait({
613
+ * endUserEmail: 'alice@your-product.com',
614
+ * model: 'xSD4.5',
615
+ * prompt: 'a red panda astronaut, studio lighting',
616
+ * });
617
+ * ```
618
+ */
619
+ var XMode = class {
620
+ generations;
621
+ videos;
622
+ balance;
623
+ endUsers;
624
+ config;
625
+ constructor(options = {}) {
626
+ const apiKey = options.apiKey ?? readEnv("XMODE_API_KEY");
627
+ if (!apiKey) throw new XModeError("Missing API key. Pass `apiKey` to new XMode({ apiKey }) or set the XMODE_API_KEY environment variable.");
628
+ const fetchImpl = options.fetch ?? (typeof globalThis.fetch === "function" ? globalThis.fetch.bind(globalThis) : void 0);
629
+ if (!fetchImpl) throw new XModeError("No global fetch found. Use Node 18+, or pass a `fetch` implementation in the client options.");
630
+ this.config = {
631
+ apiKey,
632
+ baseUrl: normalizeBaseUrl(options.baseUrl ?? DEFAULT_BASE_URL),
633
+ timeout: options.timeout ?? DEFAULT_TIMEOUT_MS,
634
+ maxRetries: options.maxRetries ?? DEFAULT_MAX_RETRIES,
635
+ fetch: fetchImpl,
636
+ defaultHeaders: options.defaultHeaders ?? {},
637
+ userAgent: `xmode-sdk/${VERSION}`
638
+ };
639
+ this.generations = new Generations(this);
640
+ this.videos = new Videos(this);
641
+ this.balance = new Balance(this);
642
+ this.endUsers = new EndUsers(this);
643
+ }
644
+ /**
645
+ * Low-level escape hatch. Call any endpoint / pass any body before a typed
646
+ * method exists for it (e.g. a brand-new model parameter). Applies the same
647
+ * auth, retry and error handling as the typed resource methods.
648
+ *
649
+ * ```ts
650
+ * await client.request({
651
+ * method: 'POST',
652
+ * path: '/v1/videos',
653
+ * body: { endUserEmail, model: 'xSV2.0', prompt, image, cameraMotion: 'orbit' },
654
+ * });
655
+ * ```
656
+ */
657
+ request(args) {
658
+ return execute(this.config, args);
659
+ }
660
+ };
661
+ //#endregion
662
+ //#region src/index.ts
663
+ var src_default = XMode;
664
+ //#endregion
665
+ export { APIConnectionError, APIConnectionTimeoutError, AuthenticationError, ConflictError, ContentPolicyError, DEFAULT_POLL_INTERVAL_MS, DEFAULT_POLL_TIMEOUT_MS, InternalServerError, NotFoundError, Page, PagePromise, PaymentRequiredError, PermissionDeniedError, ProviderError, RateLimitError, VERSION, ValidationError, XMode, XModeAPIError, XModeError, XModePollTimeoutError, src_default as default, isTerminal, pollUntilTerminal };
666
+
667
+ //# sourceMappingURL=index.js.map