@zweer/mage-space-client 0.0.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.mjs ADDED
@@ -0,0 +1,1267 @@
1
+ import { decodeJwt } from "jose";
2
+ //#region lib/errors.ts
3
+ /** Typed error hierarchy for the mage.space client. */
4
+ /** Base class for every error thrown by this library. */
5
+ var MageSpaceError = class extends Error {
6
+ constructor(message, options) {
7
+ super(message, options);
8
+ this.name = "MageSpaceError";
9
+ }
10
+ };
11
+ /** Authentication failures (token refresh, session creation). */
12
+ var AuthError = class extends MageSpaceError {
13
+ constructor(message, options) {
14
+ super(message, options);
15
+ this.name = "AuthError";
16
+ }
17
+ };
18
+ /** Action-hash discovery failures (page/chunk fetch, empty registry). */
19
+ var DiscoveryError = class extends MageSpaceError {
20
+ constructor(message, options) {
21
+ super(message, options);
22
+ this.name = "DiscoveryError";
23
+ }
24
+ };
25
+ /** A Server Action returned 404, indicating a stale (post-deploy) hash. */
26
+ var StaleActionError = class extends MageSpaceError {
27
+ action;
28
+ constructor(action, options) {
29
+ super(`Action "${action}" returned 404 (stale hash)`, options);
30
+ this.action = action;
31
+ this.name = "StaleActionError";
32
+ }
33
+ };
34
+ /** Failures while submitting or polling a generation job. */
35
+ var GenerationError = class extends MageSpaceError {
36
+ code;
37
+ constructor(message, code, options) {
38
+ super(message, options);
39
+ this.code = code;
40
+ this.name = "GenerationError";
41
+ }
42
+ };
43
+ /** Failure to parse a React Server Component (RSC) wire response. */
44
+ var RscParseError = class extends MageSpaceError {
45
+ constructor(message, options) {
46
+ super(message, options);
47
+ this.name = "RscParseError";
48
+ }
49
+ };
50
+ /** A wait/poll operation exceeded its deadline or was aborted. */
51
+ var TimeoutError = class extends MageSpaceError {
52
+ constructor(message, options) {
53
+ super(message, options);
54
+ this.name = "TimeoutError";
55
+ }
56
+ };
57
+ /**
58
+ * Guard for irreversible operations: throws unless `{ confirm: true }` is passed.
59
+ *
60
+ * @throws {MageSpaceError} when confirmation is missing.
61
+ */
62
+ function requireConfirm(action, opts) {
63
+ if (opts?.confirm !== true) throw new MageSpaceError(`${action} is irreversible and requires explicit confirmation: pass { confirm: true }`);
64
+ }
65
+ //#endregion
66
+ //#region lib/actions.ts
67
+ /**
68
+ * Action-hash discovery and caching.
69
+ *
70
+ * mage.space's Server Action hashes change on every Vercel deploy, so the client
71
+ * discovers them from the page's JS bundles and caches the result. The registry
72
+ * serves hashes from a warm seed / cache and only re-discovers when a hash goes
73
+ * stale (a request 404s) or when explicitly refreshed.
74
+ */
75
+ /** `createServerReference)("<hash>", callServer, void 0, findSourceMapURL, "<fn>")` */
76
+ const CREATE_SERVER_REF_RE = /createServerReference\)\("([0-9a-f]+)"[^)]*?"([^"]+)"\)/g;
77
+ /** `/_next/static/chunks/....js` references in the page HTML. */
78
+ const CHUNK_RE = /\/_next\/static\/chunks\/[^"'`()\s]+?\.js/g;
79
+ /**
80
+ * Default pages scraped during discovery. Server Actions are split per route, so
81
+ * a single page misses route-specific actions (e.g. `deleteReference` lives only
82
+ * in the `/references` bundle) — scrape the main routes and union their chunks.
83
+ */
84
+ const DISCOVERY_PAGES = [
85
+ "/explore",
86
+ "/creations",
87
+ "/characters",
88
+ "/references"
89
+ ];
90
+ /** Default in-process snapshot cache. */
91
+ var InMemoryActionCache = class {
92
+ snapshot = null;
93
+ async load() {
94
+ return this.snapshot;
95
+ }
96
+ async save(snapshot) {
97
+ this.snapshot = snapshot;
98
+ }
99
+ };
100
+ /**
101
+ * Discover the current Server Action hashes by scraping the page HTML for JS
102
+ * chunk URLs and extracting `createServerReference(...)` calls from each chunk.
103
+ *
104
+ * @throws {DiscoveryError} on network failure or when nothing is discovered.
105
+ */
106
+ async function discoverActions(opts) {
107
+ const { fetch, baseUrl } = opts;
108
+ const pages = opts.discoveryPath !== void 0 ? [opts.discoveryPath] : opts.discoveryPaths ?? DISCOVERY_PAGES;
109
+ let deploymentId = "";
110
+ const chunkPaths = /* @__PURE__ */ new Set();
111
+ for (const page of pages) {
112
+ let res;
113
+ try {
114
+ res = await fetch(`${baseUrl}${page}`, { headers: { accept: "text/html" } });
115
+ } catch {
116
+ continue;
117
+ }
118
+ if (!res.ok) continue;
119
+ if (deploymentId === "") deploymentId = res.headers.get("x-deployment-id") ?? "";
120
+ const html = await res.text();
121
+ for (const match of html.matchAll(CHUNK_RE)) {
122
+ const chunkPath = match[0];
123
+ if (chunkPath !== void 0) chunkPaths.add(chunkPath);
124
+ }
125
+ }
126
+ if (chunkPaths.size === 0) throw new DiscoveryError("No JS chunk references found in discovery pages");
127
+ const hashes = {};
128
+ await Promise.all([...chunkPaths].map(async (path) => {
129
+ try {
130
+ const chunkRes = await fetch(`${baseUrl}${path}`, { headers: { accept: "application/javascript" } });
131
+ if (!chunkRes.ok) return;
132
+ const js = await chunkRes.text();
133
+ for (const match of js.matchAll(CREATE_SERVER_REF_RE)) {
134
+ const hash = match[1];
135
+ const fn = match[2];
136
+ if (hash !== void 0 && fn !== void 0 && !(fn in hashes)) hashes[fn] = hash;
137
+ }
138
+ } catch {}
139
+ }));
140
+ if (Object.keys(hashes).length === 0) throw new DiscoveryError("No server action hashes discovered in JS chunks");
141
+ return {
142
+ deploymentId,
143
+ hashes,
144
+ discoveredAt: Date.now()
145
+ };
146
+ }
147
+ /**
148
+ * Serves action hashes from a warm seed / pluggable cache, discovering lazily and
149
+ * refreshing on staleness. Concurrent refreshes are de-duplicated (single-flight).
150
+ */
151
+ var ActionRegistry = class {
152
+ fetch;
153
+ baseUrl;
154
+ cache;
155
+ seed;
156
+ discoveryPath;
157
+ discoveryPaths;
158
+ maxAgeMs;
159
+ snapshot = null;
160
+ inflight = null;
161
+ constructor(opts) {
162
+ this.fetch = opts.fetch;
163
+ this.baseUrl = opts.baseUrl;
164
+ this.cache = opts.cache ?? new InMemoryActionCache();
165
+ this.seed = opts.seed;
166
+ this.discoveryPath = opts.discoveryPath;
167
+ this.discoveryPaths = opts.discoveryPaths;
168
+ this.maxAgeMs = opts.maxAgeMs ?? 0;
169
+ }
170
+ isStale(snapshot) {
171
+ return this.maxAgeMs > 0 && Date.now() - snapshot.discoveredAt > this.maxAgeMs;
172
+ }
173
+ /** Return the current snapshot, loading from cache/seed or discovering as needed. */
174
+ async getSnapshot() {
175
+ if (this.snapshot !== null && !this.isStale(this.snapshot)) return this.snapshot;
176
+ const cached = await this.cache.load();
177
+ if (cached !== null && !this.isStale(cached)) {
178
+ this.snapshot = cached;
179
+ return cached;
180
+ }
181
+ if (this.seed !== void 0) {
182
+ this.snapshot = this.seed;
183
+ await this.cache.save(this.seed);
184
+ return this.seed;
185
+ }
186
+ return this.refresh();
187
+ }
188
+ /** Force re-discovery. Concurrent callers share one in-flight discovery. */
189
+ async refresh() {
190
+ if (this.inflight !== null) return this.inflight;
191
+ this.inflight = (async () => {
192
+ try {
193
+ const fresh = await discoverActions({
194
+ fetch: this.fetch,
195
+ baseUrl: this.baseUrl,
196
+ discoveryPath: this.discoveryPath,
197
+ discoveryPaths: this.discoveryPaths
198
+ });
199
+ this.snapshot = fresh;
200
+ await this.cache.save(fresh);
201
+ return fresh;
202
+ } finally {
203
+ this.inflight = null;
204
+ }
205
+ })();
206
+ return this.inflight;
207
+ }
208
+ /** Resolve a function name to its current hash + deployment id. */
209
+ async getHash(action) {
210
+ let snapshot = await this.getSnapshot();
211
+ let hash = snapshot.hashes[action];
212
+ if (hash === void 0) {
213
+ snapshot = await this.refresh();
214
+ hash = snapshot.hashes[action];
215
+ }
216
+ if (hash === void 0) throw new DiscoveryError(`Action "${action}" not found after discovery`);
217
+ return {
218
+ hash,
219
+ deploymentId: snapshot.deploymentId
220
+ };
221
+ }
222
+ /** Drop the cached snapshot and re-discover (called on a stale 404). */
223
+ async invalidate() {
224
+ this.snapshot = null;
225
+ return this.refresh();
226
+ }
227
+ };
228
+ //#endregion
229
+ //#region lib/rsc.ts
230
+ /**
231
+ * Minimal parser for the React Server Component (RSC) "Flight" wire format used
232
+ * by Next.js Server Action responses.
233
+ *
234
+ * The wire format is a sequence of newline-delimited rows:
235
+ *
236
+ * ```text
237
+ * 0:{"a":"$@1","f":"","b":"buildId"}
238
+ * 1:{"history_id":"e3357fb8-...","architecture_config":"$T0:0:architectureConfig"}
239
+ * ```
240
+ *
241
+ * Each row is `<hexId>:<jsonPayload>`. Values may contain sentinels:
242
+ * - `$undefined` → `undefined`
243
+ * - `$@<id>` → reference to another row (a resolved promise)
244
+ * - `$$...` → an escaped literal string starting with `$`
245
+ * - `$<other>` → a typed marker (kept verbatim, best-effort)
246
+ *
247
+ * @remarks
248
+ * The exact envelope for some read actions (e.g. `getHistoryById`) should be
249
+ * confirmed against live traffic via the rev-eng Playwright flow; this parser is
250
+ * intentionally tolerant and falls back to the last JSON row when the row-0
251
+ * `{ a: ... }` envelope is absent.
252
+ */
253
+ const REF_RE = /^\$@([0-9a-f]+)$/i;
254
+ /** A plain row reference like `$5` (not a promise `$@5`, nor a `$D`/`$S`/... marker). */
255
+ const HEX_REF_RE = /^\$([0-9a-f]+)$/i;
256
+ const ROW_ID_RE = /^[0-9a-f]+$/i;
257
+ /** Return the char index after consuming exactly `byteLen` UTF-8 bytes from `start`. */
258
+ function endOfUtf8Slice(text, start, byteLen) {
259
+ let bytes = 0;
260
+ let i = start;
261
+ while (i < text.length && bytes < byteLen) {
262
+ const cp = text.codePointAt(i);
263
+ bytes += cp <= 127 ? 1 : cp <= 2047 ? 2 : cp <= 65535 ? 3 : 4;
264
+ i += cp > 65535 ? 2 : 1;
265
+ }
266
+ return i;
267
+ }
268
+ /**
269
+ * Parse the raw Flight text into a map of `rowId → value`.
270
+ *
271
+ * @remarks
272
+ * Most rows are `id:<json>\n`, but text rows are length-delimited:
273
+ * `id:T<hexlen>,<hexlen UTF-8 bytes>` and are NOT newline-terminated — the next
274
+ * row begins immediately after the counted bytes. A naive line split therefore
275
+ * glues the following data row onto a multi-line prompt (which is exactly why
276
+ * large `getHistoryPaginated` responses failed to parse). This parser is
277
+ * position/length aware so length-delimited rows are consumed exactly and every
278
+ * subsequent row stays isolated.
279
+ */
280
+ function parseFlightRows(text) {
281
+ const rows = /* @__PURE__ */ new Map();
282
+ const n = text.length;
283
+ let pos = 0;
284
+ while (pos < n) {
285
+ if (text[pos] === "\n") {
286
+ pos += 1;
287
+ continue;
288
+ }
289
+ const colon = text.indexOf(":", pos);
290
+ if (colon === -1) break;
291
+ const id = text.slice(pos, colon);
292
+ if (!ROW_ID_RE.test(id)) {
293
+ const nl = text.indexOf("\n", pos);
294
+ if (nl === -1) break;
295
+ pos = nl + 1;
296
+ continue;
297
+ }
298
+ if (text[colon + 1] === "T") {
299
+ const comma = text.indexOf(",", colon + 2);
300
+ if (comma === -1) break;
301
+ const byteLen = Number.parseInt(text.slice(colon + 2, comma), 16);
302
+ const start = comma + 1;
303
+ const end = endOfUtf8Slice(text, start, byteLen);
304
+ rows.set(id, text.slice(start, end));
305
+ pos = end;
306
+ continue;
307
+ }
308
+ const nl = text.indexOf("\n", colon + 1);
309
+ const payload = nl === -1 ? text.slice(colon + 1) : text.slice(colon + 1, nl);
310
+ pos = nl === -1 ? n : nl + 1;
311
+ const first = payload[0];
312
+ if (first === void 0) continue;
313
+ if (!(first === "{" || first === "[" || first === "\"" || first === "-" || first >= "0" && first <= "9" || payload === "true" || payload === "false" || payload === "null")) continue;
314
+ try {
315
+ rows.set(id, JSON.parse(payload));
316
+ } catch {}
317
+ }
318
+ return rows;
319
+ }
320
+ /** Recursively resolve Flight sentinels/references within a parsed value. */
321
+ function resolveFlightValue(value, rows, seen = /* @__PURE__ */ new Set()) {
322
+ if (typeof value === "string") {
323
+ if (value === "$undefined") return;
324
+ const ref = REF_RE.exec(value) ?? HEX_REF_RE.exec(value);
325
+ if (ref) {
326
+ const refId = ref[1];
327
+ if (refId === void 0 || seen.has(refId)) return;
328
+ seen.add(refId);
329
+ const target = rows.get(refId);
330
+ return target === void 0 ? void 0 : resolveFlightValue(target, rows, seen);
331
+ }
332
+ if (value.startsWith("$$")) return value.slice(1);
333
+ return value;
334
+ }
335
+ if (Array.isArray(value)) return value.map((entry) => resolveFlightValue(entry, rows, seen));
336
+ if (value !== null && typeof value === "object") {
337
+ const out = {};
338
+ for (const [key, entry] of Object.entries(value)) out[key] = resolveFlightValue(entry, rows, seen);
339
+ return out;
340
+ }
341
+ return value;
342
+ }
343
+ /**
344
+ * Parse a Server Action response and return the action's resolved return value.
345
+ *
346
+ * @throws {RscParseError} when the response contains no parseable rows.
347
+ */
348
+ function parseServerActionResponse(text) {
349
+ const rows = parseFlightRows(text);
350
+ if (rows.size === 0) throw new RscParseError("Empty or unparseable RSC response");
351
+ const root = rows.get("0");
352
+ if (root !== null && typeof root === "object" && !Array.isArray(root) && "a" in root) return resolveFlightValue(root.a, rows);
353
+ let lastId = null;
354
+ for (const id of rows.keys()) if (lastId === null || Number.parseInt(id, 16) > Number.parseInt(lastId, 16)) lastId = id;
355
+ return resolveFlightValue(lastId === null ? void 0 : rows.get(lastId), rows);
356
+ }
357
+ /**
358
+ * Unwrap a single-object result that some Server Actions (notably the
359
+ * `*Parallel` family) wrap in a one-element array.
360
+ *
361
+ * @remarks
362
+ * `getCreationsPaginatedParallel` and `getMentionSuggestionsParallel` return their
363
+ * payload as `[value]` on the RSC wire (`1:["$@2"]`), whereas most actions return
364
+ * the value directly. This normalizes object-returning actions to the value; it is
365
+ * a no-op when the response is already the object. Do NOT use it for actions whose
366
+ * genuine return type is an array (e.g. `getReferences`).
367
+ */
368
+ function unwrapResult(value) {
369
+ return Array.isArray(value) && value.length === 1 ? value[0] : value;
370
+ }
371
+ //#endregion
372
+ //#region lib/characters.ts
373
+ /** Character actions are served from the `/explore` page. */
374
+ const CHARACTERS_PATH = "/explore";
375
+ const DEFAULT_SEARCH_LIMIT = 10;
376
+ var CharactersService = class {
377
+ http;
378
+ auth;
379
+ constructor(http, auth) {
380
+ this.http = http;
381
+ this.auth = auth;
382
+ }
383
+ /** Search the caller's characters (and optionally public references) by name. */
384
+ async search(opts) {
385
+ const [uid, session] = await Promise.all([this.auth.getUserId(), this.auth.getSession()]);
386
+ const body = {
387
+ query: opts.query,
388
+ uid,
389
+ limit: opts.limit ?? DEFAULT_SEARCH_LIMIT,
390
+ includePublicReferences: opts.includePublicReferences ?? false,
391
+ referenceModalities: opts.referenceModalities ?? ["image", "audio"]
392
+ };
393
+ const { data } = await this.http.callAction({
394
+ action: "getMentionSuggestionsParallel",
395
+ path: CHARACTERS_PATH,
396
+ args: [body],
397
+ session
398
+ });
399
+ return unwrapResult(data);
400
+ }
401
+ /** Create a new character from an already-uploaded image URL. */
402
+ async create(input) {
403
+ const session = await this.auth.getSession();
404
+ const body = {
405
+ name: input.name,
406
+ username: input.username,
407
+ image_url: input.image_url,
408
+ audio_url: input.audio_url ?? null,
409
+ description: input.description ?? null,
410
+ tags: input.tags ?? [],
411
+ visibility: input.visibility ?? "private",
412
+ moderation: input.moderation ?? []
413
+ };
414
+ const { data } = await this.http.callAction({
415
+ action: "createCharacter",
416
+ path: CHARACTERS_PATH,
417
+ args: [body],
418
+ session
419
+ });
420
+ return data;
421
+ }
422
+ /** Update a character's mutable fields (name, description, tags, visibility, voice). */
423
+ async update(id, patch) {
424
+ const session = await this.auth.getSession();
425
+ const { data } = await this.http.callAction({
426
+ action: "updateCharacter",
427
+ path: CHARACTERS_PATH,
428
+ args: [id, patch],
429
+ session
430
+ });
431
+ return data;
432
+ }
433
+ /** Soft-delete a character. */
434
+ async delete(id) {
435
+ const session = await this.auth.getSession();
436
+ await this.http.callAction({
437
+ action: "deleteCharacter",
438
+ path: CHARACTERS_PATH,
439
+ args: [id],
440
+ session
441
+ });
442
+ }
443
+ /** Fetch a single character by id; resolves to `null` when it does not exist. */
444
+ async get(id) {
445
+ const session = await this.auth.getSession();
446
+ const { data } = await this.http.callAction({
447
+ action: "getCharacter",
448
+ path: CHARACTERS_PATH,
449
+ args: [id],
450
+ session
451
+ });
452
+ return data ?? null;
453
+ }
454
+ /** Fetch multiple characters by id in a single call. */
455
+ async getMany(ids) {
456
+ const session = await this.auth.getSession();
457
+ const { data } = await this.http.callAction({
458
+ action: "getCharacters",
459
+ path: CHARACTERS_PATH,
460
+ args: [ids],
461
+ session
462
+ });
463
+ return data ?? [];
464
+ }
465
+ /** Make a character public. */
466
+ async publish(id) {
467
+ const session = await this.auth.getSession();
468
+ await this.http.callAction({
469
+ action: "publishCharacter",
470
+ path: CHARACTERS_PATH,
471
+ args: [id],
472
+ session
473
+ });
474
+ }
475
+ /** Make a character private. */
476
+ async unpublish(id) {
477
+ const session = await this.auth.getSession();
478
+ await this.http.callAction({
479
+ action: "unpublishCharacter",
480
+ path: CHARACTERS_PATH,
481
+ args: [id],
482
+ session
483
+ });
484
+ }
485
+ /**
486
+ * Upload a character reference image (base64 data URL) and return its CDN URL.
487
+ *
488
+ * @param dataUrl - `data:image/jpeg;base64,...` (JPEG preferred; PNG/WebP ok).
489
+ */
490
+ async uploadImage(dataUrl) {
491
+ const [uid, session] = await Promise.all([this.auth.getUserId(), this.auth.getSession()]);
492
+ const { data } = await this.http.callAction({
493
+ action: "uploadCharacterImage",
494
+ path: CHARACTERS_PATH,
495
+ args: [dataUrl, uid],
496
+ session
497
+ });
498
+ return data;
499
+ }
500
+ /**
501
+ * Browse characters with pagination.
502
+ *
503
+ * @remarks
504
+ * Omit `uid` to browse the public/featured feed; pass it to page through a
505
+ * single user's characters.
506
+ */
507
+ async listPaginated(opts = {}) {
508
+ const session = await this.auth.getSession();
509
+ const limit = opts.limit ?? 100;
510
+ const offset = opts.offset ?? 0;
511
+ const filters = {};
512
+ if (opts.uid !== void 0) filters.uid = opts.uid;
513
+ if (opts.visibility !== void 0) filters.visibility = opts.visibility;
514
+ if (opts.modality !== void 0) filters.modality = opts.modality;
515
+ if (opts.featured !== void 0) filters.featured = opts.featured;
516
+ const { data } = await this.http.callAction({
517
+ action: "getCharactersPaginated",
518
+ path: CHARACTERS_PATH,
519
+ args: [
520
+ limit,
521
+ offset,
522
+ filters
523
+ ],
524
+ session
525
+ });
526
+ return data;
527
+ }
528
+ /**
529
+ * Generate a voice for a character from its reference image.
530
+ *
531
+ * @remarks Costs gems (not covered by the unlimited tier).
532
+ * @experimental Request body inferred from JS bundle; not yet verified live.
533
+ */
534
+ async generateVoice(opts) {
535
+ const [authToken, session] = await Promise.all([this.auth.getIdToken(), this.auth.getSession()]);
536
+ const { data } = await this.http.callAction({
537
+ action: "generateCharacterVoice",
538
+ path: CHARACTERS_PATH,
539
+ args: [{
540
+ imageUrl: opts.imageUrl,
541
+ guidancePrompt: opts.guidancePrompt ?? ""
542
+ }, authToken],
543
+ session
544
+ });
545
+ return data;
546
+ }
547
+ /**
548
+ * Follow a (public) character.
549
+ *
550
+ * @experimental Request body inferred from JS bundle; not yet verified live.
551
+ */
552
+ async follow(id) {
553
+ const session = await this.auth.getSession();
554
+ await this.http.callAction({
555
+ action: "followCharacter",
556
+ path: CHARACTERS_PATH,
557
+ args: [id],
558
+ session
559
+ });
560
+ }
561
+ /**
562
+ * Unfollow a character.
563
+ *
564
+ * @experimental Request body inferred from JS bundle; not yet verified live.
565
+ */
566
+ async unfollow(id) {
567
+ const session = await this.auth.getSession();
568
+ await this.http.callAction({
569
+ action: "unfollowCharacter",
570
+ path: CHARACTERS_PATH,
571
+ args: [id],
572
+ session
573
+ });
574
+ }
575
+ };
576
+ //#endregion
577
+ //#region lib/auth.ts
578
+ /**
579
+ * Firebase authentication: refresh token → ID token → session cookie.
580
+ *
581
+ * The refresh token is the only long-lived credential the caller supplies.
582
+ * From it we mint a Firebase ID token (~1h) via the Secure Token REST API, and
583
+ * exchange that for a `__session` cookie (~24h) via the `createUserSession`
584
+ * Server Action. Both are cached and refreshed 5 minutes before expiry.
585
+ */
586
+ const DEFAULT_FIREBASE_API_KEY = "AIzaSyAzUV2NNUOlLTL04jwmUw9oLhjteuv6Qr4";
587
+ const SECURE_TOKEN_URL = "https://securetoken.googleapis.com/v1/token";
588
+ const EXPIRY_BUFFER_MS = 3e5;
589
+ const SESSION_DEFAULT_TTL_MS = 864e5;
590
+ const ID_TOKEN_DEFAULT_TTL_S = 3600;
591
+ var AuthManager = class {
592
+ refreshToken;
593
+ http;
594
+ fetchImpl;
595
+ apiKey;
596
+ sessionPath;
597
+ tokens = null;
598
+ session = null;
599
+ constructor(opts) {
600
+ this.refreshToken = opts.refreshToken;
601
+ this.http = opts.http;
602
+ this.fetchImpl = opts.fetch;
603
+ this.apiKey = opts.firebaseApiKey ?? DEFAULT_FIREBASE_API_KEY;
604
+ this.sessionPath = opts.sessionPath ?? "/creations";
605
+ }
606
+ /** Return a valid Firebase ID token, refreshing via the refresh token if needed. */
607
+ async getIdToken() {
608
+ if (this.tokens !== null && Date.now() < this.tokens.expiresAt - EXPIRY_BUFFER_MS) return this.tokens.idToken;
609
+ return (await this.refreshTokens()).idToken;
610
+ }
611
+ /** Return the Firebase uid (fetching an ID token first if necessary). */
612
+ async getUserId() {
613
+ if (this.tokens !== null) return this.tokens.userId;
614
+ return (await this.refreshTokens()).userId;
615
+ }
616
+ /** Return a valid `__session` cookie, creating one via `createUserSession` if needed. */
617
+ async getSession() {
618
+ if (this.session !== null && Date.now() < this.session.expiresAt - EXPIRY_BUFFER_MS) return this.session.cookie;
619
+ const idToken = await this.getIdToken();
620
+ const cookie = extractSessionCookie((await this.http.callAction({
621
+ action: "createUserSession",
622
+ path: this.sessionPath,
623
+ args: [idToken]
624
+ })).headers);
625
+ if (cookie === null) throw new AuthError("createUserSession did not return a __session cookie");
626
+ const expiresAt = jwtExpiryMs(cookie) ?? Date.now() + SESSION_DEFAULT_TTL_MS;
627
+ this.session = {
628
+ cookie,
629
+ expiresAt
630
+ };
631
+ return cookie;
632
+ }
633
+ async refreshTokens() {
634
+ const body = new URLSearchParams({
635
+ grant_type: "refresh_token",
636
+ refresh_token: this.refreshToken
637
+ });
638
+ const res = await this.fetchImpl(`${SECURE_TOKEN_URL}?key=${this.apiKey}`, {
639
+ method: "POST",
640
+ headers: { "content-type": "application/x-www-form-urlencoded" },
641
+ body: body.toString()
642
+ });
643
+ if (!res.ok) throw new AuthError(`Token refresh failed: HTTP ${res.status} (refresh token may be invalid or revoked)`);
644
+ const json = await res.json();
645
+ if (json.id_token === void 0 || json.user_id === void 0) throw new AuthError("Token refresh response missing id_token/user_id");
646
+ if (json.refresh_token !== void 0) this.refreshToken = json.refresh_token;
647
+ const ttlS = json.expires_in === void 0 ? ID_TOKEN_DEFAULT_TTL_S : Number(json.expires_in);
648
+ const expiresAt = jwtExpiryMs(json.id_token) ?? Date.now() + ttlS * 1e3;
649
+ this.tokens = {
650
+ idToken: json.id_token,
651
+ refreshToken: this.refreshToken,
652
+ userId: json.user_id,
653
+ expiresAt
654
+ };
655
+ return this.tokens;
656
+ }
657
+ };
658
+ /** Read the `exp` claim (ms) from a JWT, or `null` if it cannot be decoded. */
659
+ function jwtExpiryMs(jwt) {
660
+ try {
661
+ const { exp } = decodeJwt(jwt);
662
+ return typeof exp === "number" ? exp * 1e3 : null;
663
+ } catch {
664
+ return null;
665
+ }
666
+ }
667
+ /** Extract the `__session` value from a `Set-Cookie` response header. */
668
+ function extractSessionCookie(headers) {
669
+ const withGetSetCookie = headers;
670
+ const list = typeof withGetSetCookie.getSetCookie === "function" ? withGetSetCookie.getSetCookie() : headers.get("set-cookie") !== null ? [headers.get("set-cookie")] : [];
671
+ for (const cookie of list) {
672
+ const match = /(?:^|;\s*)__session=([^;]+)/.exec(cookie);
673
+ if (match !== null && match[1] !== void 0) return match[1];
674
+ }
675
+ return null;
676
+ }
677
+ //#endregion
678
+ //#region lib/generation.ts
679
+ /** Generation actions are served from the `/explore` page. */
680
+ const GENERATION_PATH = "/explore";
681
+ /** RSC sentinel for an absent value. */
682
+ const UNDEFINED$1 = "$undefined";
683
+ /** Default concept id for mango-v3-pro (from docs — verify per model via rev-eng). */
684
+ const DEFAULT_CONCEPT_ID = "0cd8c7ed2e554d0f98f20c8cf8c0f7c";
685
+ /** Default concept id for berry-2 video (from docs — verify per model via rev-eng). */
686
+ const DEFAULT_BERRY_CONCEPT_ID = "0b1fa74f7347406392422d24f2885684";
687
+ const DEFAULT_POLL_INTERVAL_MS = 4e3;
688
+ const DEFAULT_TIMEOUT_MS = 3e5;
689
+ var GenerationService = class {
690
+ http;
691
+ auth;
692
+ constructor(http, auth) {
693
+ this.http = http;
694
+ this.auth = auth;
695
+ }
696
+ /** Submit an image generation job and return its `historyId`. */
697
+ async submit(opts) {
698
+ const config = {
699
+ seed: opts.seed ?? null,
700
+ prompt: opts.prompt,
701
+ model_id: opts.model ?? "mango-v3-pro",
702
+ fast_mode: opts.fastMode ?? false,
703
+ resolution: opts.resolution ?? "1K",
704
+ architecture: opts.architecture ?? "mango",
705
+ aspect_ratio: opts.aspectRatio ?? "portrait",
706
+ image: opts.image ?? UNDEFINED$1,
707
+ additional_images: opts.additionalImages ?? [],
708
+ characters: mapCharacters(opts.characters),
709
+ references: mapCharacters(opts.references),
710
+ audio_references: [],
711
+ moodboard: UNDEFINED$1
712
+ };
713
+ return this.runArchitecture(config, opts.conceptId ?? DEFAULT_CONCEPT_ID, opts.generationMode);
714
+ }
715
+ /** Submit a video generation job (Berry-2 family) and return its `historyId`. */
716
+ async submitVideo(opts) {
717
+ const config = {
718
+ seed: opts.seed ?? null,
719
+ prompt: opts.prompt,
720
+ model_id: opts.model ?? "berry-2",
721
+ fast_mode: opts.fastMode ?? false,
722
+ resolution: opts.resolution ?? "480p",
723
+ architecture: opts.architecture ?? "berry",
724
+ berry_aspect_ratio: opts.aspectRatio ?? "9:16",
725
+ duration: opts.duration ?? "3",
726
+ image: opts.image ?? UNDEFINED$1,
727
+ additional_images: opts.additionalImages ?? [],
728
+ characters: mapCharacters(opts.characters),
729
+ references: mapCharacters(opts.references),
730
+ audio_references: []
731
+ };
732
+ return this.runArchitecture(config, opts.conceptId ?? DEFAULT_BERRY_CONCEPT_ID, opts.generationMode);
733
+ }
734
+ /** Shared `runArchitecture` call for image and video configs. */
735
+ async runArchitecture(config, conceptId, generationMode = "unlimited") {
736
+ const [authToken, session] = await Promise.all([this.auth.getIdToken(), this.auth.getSession()]);
737
+ const args = [{
738
+ architectureConfig: config,
739
+ architectureConfigToSave: "$0:0:architectureConfig",
740
+ authToken,
741
+ conceptId,
742
+ activePowerPack: null,
743
+ generationMode
744
+ }];
745
+ const { data } = await this.http.callAction({
746
+ action: "runArchitecture",
747
+ path: GENERATION_PATH,
748
+ args,
749
+ session
750
+ });
751
+ if (data !== null && typeof data === "object" && "error_code" in data) {
752
+ const code = Number(data.error_code);
753
+ throw new GenerationError(errorMessageForCode(code), code);
754
+ }
755
+ const historyId = data !== null && typeof data === "object" ? data.history_id : void 0;
756
+ if (typeof historyId !== "string") throw new GenerationError("runArchitecture did not return a history_id");
757
+ return { historyId };
758
+ }
759
+ /** Fetch a single history record by id. */
760
+ async getHistory(historyId) {
761
+ const session = await this.auth.getSession();
762
+ const { data } = await this.http.callAction({
763
+ action: "getHistoryById",
764
+ path: GENERATION_PATH,
765
+ args: [historyId],
766
+ session
767
+ });
768
+ return data;
769
+ }
770
+ /** Cancel a running generation job. */
771
+ async cancelJob(historyId) {
772
+ const session = await this.auth.getSession();
773
+ await this.http.callAction({
774
+ action: "cancelArchitectureJob",
775
+ path: GENERATION_PATH,
776
+ args: [historyId],
777
+ session
778
+ });
779
+ }
780
+ /** Poll until the job leaves the `running` state, returning the final record. */
781
+ async waitForResult(historyId, opts = {}) {
782
+ const intervalMs = opts.intervalMs ?? DEFAULT_POLL_INTERVAL_MS;
783
+ const timeoutMs = opts.timeoutMs ?? DEFAULT_TIMEOUT_MS;
784
+ const deadline = Date.now() + timeoutMs;
785
+ for (;;) {
786
+ if (opts.signal?.aborted === true) throw new TimeoutError("waitForResult was aborted");
787
+ const item = await this.getHistory(historyId);
788
+ if (item.status === "success") return item;
789
+ if (item.status === "error") throw new GenerationError(`Generation failed: ${JSON.stringify(item.error ?? null)}`);
790
+ if (Date.now() > deadline) throw new TimeoutError(`Generation timed out after ${timeoutMs}ms (last status: ${item.status})`);
791
+ await delay(intervalMs, opts.signal);
792
+ }
793
+ }
794
+ };
795
+ function delay(ms, signal) {
796
+ return new Promise((resolve, reject) => {
797
+ const timer = setTimeout(resolve, ms);
798
+ signal?.addEventListener("abort", () => {
799
+ clearTimeout(timer);
800
+ reject(new TimeoutError("waitForResult was aborted"));
801
+ }, { once: true });
802
+ });
803
+ }
804
+ /** Normalize characters for an `architectureConfig`, defaulting absent audio to the RSC sentinel. */
805
+ function mapCharacters(characters = []) {
806
+ return characters.map((c) => ({
807
+ id: c.id,
808
+ name: c.name,
809
+ username: c.username,
810
+ image_url: c.image_url,
811
+ audio_url: c.audio_url ?? UNDEFINED$1
812
+ }));
813
+ }
814
+ function errorMessageForCode(code) {
815
+ switch (code) {
816
+ case 400: return "Bad request (wrong generationMode for your subscription tier?)";
817
+ case 401: return "Authentication failed (token expired or invalid)";
818
+ case 403: return "User is banned";
819
+ case 4031: return "Prompt blocked by content moderation";
820
+ default: return `Generation error (code ${code})`;
821
+ }
822
+ }
823
+ //#endregion
824
+ //#region lib/history.ts
825
+ /** History actions are served from the `/creations` page. */
826
+ const HISTORY_PATH = "/creations";
827
+ const DEFAULT_LIMIT = 10;
828
+ /** RSC sentinel for an absent value. */
829
+ const UNDEFINED = "$undefined";
830
+ var HistoryService = class {
831
+ http;
832
+ auth;
833
+ constructor(http, auth) {
834
+ this.http = http;
835
+ this.auth = auth;
836
+ }
837
+ /** List generation history for the authenticated user. */
838
+ async list(opts = {}) {
839
+ const [uid, session] = await Promise.all([this.auth.getUserId(), this.auth.getSession()]);
840
+ const limit = opts.limit ?? DEFAULT_LIMIT;
841
+ const offset = opts.offset ?? 0;
842
+ const filters = {
843
+ status: opts.status === "all" ? UNDEFINED : opts.status ?? "success",
844
+ type: UNDEFINED
845
+ };
846
+ const { data } = await this.http.callAction({
847
+ action: "getHistoryPaginated",
848
+ path: HISTORY_PATH,
849
+ args: [
850
+ uid,
851
+ limit,
852
+ offset,
853
+ filters
854
+ ],
855
+ session
856
+ });
857
+ return unwrapResult(data);
858
+ }
859
+ /** List the user's permanently saved creations. */
860
+ async listCreations(opts = {}) {
861
+ const [uid, session] = await Promise.all([this.auth.getUserId(), this.auth.getSession()]);
862
+ const limit = opts.limit ?? DEFAULT_LIMIT;
863
+ const offset = opts.offset ?? 0;
864
+ const { data } = await this.http.callAction({
865
+ action: "getCreationsPaginatedParallel",
866
+ path: HISTORY_PATH,
867
+ args: [
868
+ uid,
869
+ limit,
870
+ offset
871
+ ],
872
+ session
873
+ });
874
+ return unwrapResult(data);
875
+ }
876
+ /**
877
+ * Save a completed generation result as a permanent creation.
878
+ *
879
+ * @param result - the `result` object of a completed history item.
880
+ * @remarks The API returns no body; call {@link listCreations} to retrieve the
881
+ * saved creation.
882
+ */
883
+ async saveCreation(result) {
884
+ const session = await this.auth.getSession();
885
+ await this.http.callAction({
886
+ action: "saveCreation",
887
+ path: HISTORY_PATH,
888
+ args: [result],
889
+ session
890
+ });
891
+ }
892
+ /**
893
+ * Permanently delete a history item. Irreversible — requires `{ confirm: true }`.
894
+ */
895
+ async deleteHistory(historyId, opts) {
896
+ requireConfirm("deleteHistory", opts);
897
+ const session = await this.auth.getSession();
898
+ await this.http.callAction({
899
+ action: "deleteHistory",
900
+ path: HISTORY_PATH,
901
+ args: [historyId],
902
+ session
903
+ });
904
+ }
905
+ /**
906
+ * Permanently delete many history items. Irreversible — requires `{ confirm: true }`.
907
+ *
908
+ * @experimental Request body inferred from JS bundle; not yet verified live.
909
+ */
910
+ async deleteManyHistories(historyIds, opts) {
911
+ requireConfirm("deleteManyHistories", opts);
912
+ const session = await this.auth.getSession();
913
+ await this.http.callAction({
914
+ action: "deleteManyHistories",
915
+ path: HISTORY_PATH,
916
+ args: [historyIds],
917
+ session
918
+ });
919
+ }
920
+ /**
921
+ * Permanently delete a saved creation. Irreversible — requires `{ confirm: true }`.
922
+ */
923
+ async deleteCreation(creationId, opts) {
924
+ requireConfirm("deleteCreation", opts);
925
+ const session = await this.auth.getSession();
926
+ await this.http.callAction({
927
+ action: "deleteCreation",
928
+ path: HISTORY_PATH,
929
+ args: [creationId],
930
+ session
931
+ });
932
+ }
933
+ /**
934
+ * Permanently delete many creations. Irreversible — requires `{ confirm: true }`.
935
+ *
936
+ * @experimental Request body inferred from JS bundle; not yet verified live.
937
+ */
938
+ async deleteManyCreations(creationIds, opts) {
939
+ requireConfirm("deleteManyCreations", opts);
940
+ const session = await this.auth.getSession();
941
+ await this.http.callAction({
942
+ action: "deleteManyCreations",
943
+ path: HISTORY_PATH,
944
+ args: [creationIds],
945
+ session
946
+ });
947
+ }
948
+ };
949
+ //#endregion
950
+ //#region lib/http.ts
951
+ const DEFAULT_USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36";
952
+ var HttpClient = class {
953
+ registry;
954
+ fetchImpl;
955
+ baseUrl;
956
+ userAgent;
957
+ constructor(registry, fetchImpl, baseUrl, userAgent = DEFAULT_USER_AGENT) {
958
+ this.registry = registry;
959
+ this.fetchImpl = fetchImpl;
960
+ this.baseUrl = baseUrl;
961
+ this.userAgent = userAgent;
962
+ }
963
+ async callAction(params, retried = false) {
964
+ const { hash, deploymentId } = await this.registry.getHash(params.action);
965
+ const headers = {
966
+ accept: "text/x-component",
967
+ "content-type": "text/plain;charset=UTF-8",
968
+ "next-action": hash,
969
+ "x-deployment-id": deploymentId,
970
+ origin: this.baseUrl,
971
+ referer: `${this.baseUrl}${params.path}`,
972
+ "user-agent": this.userAgent
973
+ };
974
+ if (params.session !== void 0) headers.cookie = `__session=${params.session}`;
975
+ const res = await this.fetchImpl(`${this.baseUrl}${params.path}`, {
976
+ method: "POST",
977
+ headers,
978
+ body: JSON.stringify(params.args)
979
+ });
980
+ if (res.status === 404 && !retried) {
981
+ await this.registry.invalidate();
982
+ return this.callAction(params, true);
983
+ }
984
+ if (!res.ok) {
985
+ const body = await safeText(res);
986
+ const suffix = body.length > 0 ? ` — ${body.slice(0, 200)}` : "";
987
+ throw new MageSpaceError(`Action "${params.action}" failed: HTTP ${res.status}${suffix}`);
988
+ }
989
+ return {
990
+ data: parseServerActionResponse(await res.text()),
991
+ headers: res.headers,
992
+ deploymentId: res.headers.get("x-deployment-id") ?? deploymentId
993
+ };
994
+ }
995
+ };
996
+ async function safeText(res) {
997
+ try {
998
+ return await res.text();
999
+ } catch {
1000
+ return "";
1001
+ }
1002
+ }
1003
+ //#endregion
1004
+ //#region lib/references.ts
1005
+ /** Reference actions are served from the `/explore` page. */
1006
+ const REFERENCES_PATH = "/explore";
1007
+ const DEFAULT_PAGE_LIMIT = 20;
1008
+ var ReferencesService = class {
1009
+ http;
1010
+ auth;
1011
+ constructor(http, auth) {
1012
+ this.http = http;
1013
+ this.auth = auth;
1014
+ }
1015
+ /**
1016
+ * Upload a reference image (base64 data URL) and return its CDN URL.
1017
+ *
1018
+ * @param dataUrl - `data:image/jpeg;base64,...` (JPEG preferred; PNG/WebP ok).
1019
+ */
1020
+ async uploadImage(dataUrl) {
1021
+ const [uid, session] = await Promise.all([this.auth.getUserId(), this.auth.getSession()]);
1022
+ const { data } = await this.http.callAction({
1023
+ action: "uploadReferenceImage",
1024
+ path: REFERENCES_PATH,
1025
+ args: [dataUrl, uid],
1026
+ session
1027
+ });
1028
+ return data;
1029
+ }
1030
+ /** Create a reusable reference from an uploaded image/audio URL. */
1031
+ async create(input) {
1032
+ const session = await this.auth.getSession();
1033
+ const body = {
1034
+ name: input.name,
1035
+ username: input.username,
1036
+ image_url: input.image_url,
1037
+ audio_url: input.audio_url ?? null,
1038
+ modality: input.modality ?? "image",
1039
+ variant: input.variant ?? "reference",
1040
+ description: input.description ?? null,
1041
+ tags: input.tags ?? [],
1042
+ visibility: input.visibility ?? "private",
1043
+ moderation: input.moderation ?? []
1044
+ };
1045
+ const { data } = await this.http.callAction({
1046
+ action: "createReference",
1047
+ path: REFERENCES_PATH,
1048
+ args: [body],
1049
+ session
1050
+ });
1051
+ return data;
1052
+ }
1053
+ /** Update a reference's mutable fields (name, description, tags, visibility). */
1054
+ async update(id, patch) {
1055
+ const session = await this.auth.getSession();
1056
+ const { data } = await this.http.callAction({
1057
+ action: "updateReference",
1058
+ path: REFERENCES_PATH,
1059
+ args: [id, patch],
1060
+ session
1061
+ });
1062
+ return data;
1063
+ }
1064
+ /**
1065
+ * Permanently delete a reference (soft-delete). Irreversible — requires
1066
+ * `{ confirm: true }`.
1067
+ */
1068
+ async delete(id, opts) {
1069
+ requireConfirm("deleteReference", opts);
1070
+ const session = await this.auth.getSession();
1071
+ await this.http.callAction({
1072
+ action: "deleteReference",
1073
+ path: REFERENCES_PATH,
1074
+ args: [id],
1075
+ session
1076
+ });
1077
+ }
1078
+ /** List the caller's own references for a given modality (default `image`). */
1079
+ async list(modality = "image") {
1080
+ const [uid, session] = await Promise.all([this.auth.getUserId(), this.auth.getSession()]);
1081
+ const { data } = await this.http.callAction({
1082
+ action: "getReferences",
1083
+ path: REFERENCES_PATH,
1084
+ args: [uid, modality],
1085
+ session
1086
+ });
1087
+ return data ?? [];
1088
+ }
1089
+ /**
1090
+ * Browse references with pagination.
1091
+ *
1092
+ * @remarks
1093
+ * Omit `uid` to browse the public/featured feed; pass it to page through a
1094
+ * single user's references.
1095
+ */
1096
+ async listPaginated(opts = {}) {
1097
+ const session = await this.auth.getSession();
1098
+ const limit = opts.limit ?? DEFAULT_PAGE_LIMIT;
1099
+ const offset = opts.offset ?? 0;
1100
+ const filters = {
1101
+ visibility: opts.visibility ?? "public",
1102
+ orderBy: opts.orderBy ?? "top",
1103
+ featuredOnly: opts.featuredOnly ?? false,
1104
+ modality: opts.modality ?? "image"
1105
+ };
1106
+ if (opts.uid !== void 0) filters.uid = opts.uid;
1107
+ const { data } = await this.http.callAction({
1108
+ action: "getReferencesPaginated",
1109
+ path: REFERENCES_PATH,
1110
+ args: [
1111
+ limit,
1112
+ offset,
1113
+ filters
1114
+ ],
1115
+ session
1116
+ });
1117
+ return data;
1118
+ }
1119
+ };
1120
+ //#endregion
1121
+ //#region lib/seed.ts
1122
+ /**
1123
+ * Bundled action-hash snapshot from the mage.space Vercel deploy discovered on
1124
+ * 2026-08-06 (`docs/api/actions.md`).
1125
+ *
1126
+ * @remarks
1127
+ * Used as a warm seed so the very first call does not pay the discovery cost.
1128
+ * Hashes change on every deploy; when one goes stale the client re-discovers
1129
+ * automatically (404 → invalidate → refresh).
1130
+ */
1131
+ const SEED_SNAPSHOT = {
1132
+ deploymentId: "dpl_GLUqzVNVADQR5wogKksDvu1m3BdH",
1133
+ discoveredAt: Date.parse("2026-08-06T00:00:00.000Z"),
1134
+ hashes: {
1135
+ createUserSession: "60fa7da54d8662813645f7077455339d23096f391c",
1136
+ deleteUserSession: "0077c71a228d26498d446a89e60ec6d17199443f9e",
1137
+ getUserHydrationData: "40314a83b96c226af04a264d1e076a06dbf3cc73b2",
1138
+ runArchitecture: "402c09c1b4694e5bb273a949a41a2f36ca82eff394",
1139
+ getHistoryById: "40b894a9a39b33b79d4eb046e4beeed7b30b21a887",
1140
+ cancelArchitectureJob: "40e4b91be4f6698821f96021adaf305a74b32af2a1",
1141
+ getHistoryPaginated: "787d245f6320977164c554a189a4d5b80c18ca3347",
1142
+ getMentionSuggestionsParallel: "7f1553e5f697d614a2982a5400edae73c4ef672ece",
1143
+ createCharacter: "4012fee65f53db4fbe9d83808da2869751fe503a87",
1144
+ updateCharacter: "60dc4b95918a5fea7b2752bedebb38e0950e19a17e",
1145
+ deleteCharacter: "40656ebd70a3804b5c18740913c8b769d4c72e1298",
1146
+ getCharacter: "40b1e8e090a726985d8868f145865a3b5bde0803b1",
1147
+ getCharacters: "402ea21b03340cd20f55ea8c15f56dbbc1ce957f51",
1148
+ publishCharacter: "409a73d2187553e2e069493147969c46d7329a7c44",
1149
+ unpublishCharacter: "40f009e2c220d4efc54b9f8968ab37e74cb66de982",
1150
+ uploadCharacterImage: "60869f48fe83aa95969a6549f84023c9e115ee274f",
1151
+ uploadReferenceImage: "606ba2b0eb79bd8f28c609706a89b52e0d9f517fa6",
1152
+ createReference: "406d91cfd5589042bd72defa1071bc7c3fb84810c4",
1153
+ updateReference: "602d6bb3ba2609661f07330224f6fd075e7af6bc9e",
1154
+ deleteReference: "409dd73e6daafe43b500b0c9f012a33fc5350af196",
1155
+ getReferences: "60a0357a926bc3e4031c00e62ca815e3ea21b3eb35",
1156
+ getReferencesPaginated: "7069d599ab890e353b790d46df56160d5c0a85348a",
1157
+ getCharactersPaginated: "7085cf87746975cbec2868405a026e22b97a20cfb4",
1158
+ getCreationsPaginatedParallel: "7f7f533eb9fc5a3fca55cb5e19d45532a27eb75f38",
1159
+ deleteHistory: "40e1f88b6813c101c03e136e4d5f5fadeb9909ee52",
1160
+ deleteManyHistories: "402a5bddb2a41165f2f64782b85b4915e63ec2a849",
1161
+ deleteCreation: "407e9ce3d04c7274e3341bf8450633dc8e3935ec9c",
1162
+ deleteManyCreations: "403d071e9dc36382b3efff80470e6d76b3edd299b8",
1163
+ saveCreation: "6087f655eaef4de56a01fb6c6a84df58cea0632e5f",
1164
+ generateCharacterVoice: "6052462e345ee9878d5c36c2a160ea1873919c2b41",
1165
+ followCharacter: "402d9d8cf924262612f29b5ef3d1a6a97d3a0a9d7a",
1166
+ unfollowCharacter: "40842140a2e9467f854870c6ad20e3dcf17bece652"
1167
+ }
1168
+ };
1169
+ //#endregion
1170
+ //#region lib/client.ts
1171
+ /**
1172
+ * {@link MageSpaceClient} — the public facade that wires together action
1173
+ * discovery, authentication, generation, and history.
1174
+ */
1175
+ const DEFAULT_BASE_URL = "https://www.mage.space";
1176
+ var MageSpaceClient = class {
1177
+ registry;
1178
+ auth;
1179
+ generationService;
1180
+ historyService;
1181
+ /** Character management (search, CRUD, pagination, uploads). */
1182
+ characters;
1183
+ /** Reference asset management (upload, create, update, list, feed). */
1184
+ references;
1185
+ constructor(options) {
1186
+ if (!options.refreshToken) throw new Error("MageSpaceClient requires a refreshToken");
1187
+ const baseUrl = options.baseUrl ?? DEFAULT_BASE_URL;
1188
+ const fetchImpl = options.fetch ?? ((input, init) => fetch(input, init));
1189
+ this.registry = new ActionRegistry({
1190
+ fetch: fetchImpl,
1191
+ baseUrl,
1192
+ cache: options.cache,
1193
+ seed: options.seed ?? SEED_SNAPSHOT
1194
+ });
1195
+ const http = new HttpClient(this.registry, fetchImpl, baseUrl, options.userAgent);
1196
+ this.auth = new AuthManager({
1197
+ refreshToken: options.refreshToken,
1198
+ http,
1199
+ fetch: fetchImpl,
1200
+ firebaseApiKey: options.firebaseApiKey
1201
+ });
1202
+ this.generationService = new GenerationService(http, this.auth);
1203
+ this.historyService = new HistoryService(http, this.auth);
1204
+ this.characters = new CharactersService(http, this.auth);
1205
+ this.references = new ReferencesService(http, this.auth);
1206
+ }
1207
+ /** Submit a generation job; returns its `historyId`. */
1208
+ generate(options) {
1209
+ return this.generationService.submit(options);
1210
+ }
1211
+ /** Submit a video generation job (Berry-2 family); returns its `historyId`. */
1212
+ generateVideo(options) {
1213
+ return this.generationService.submitVideo(options);
1214
+ }
1215
+ /** Fetch the current state of a generation by id. */
1216
+ getResult(historyId) {
1217
+ return this.generationService.getHistory(historyId);
1218
+ }
1219
+ /** Poll until a generation finishes (or fails / times out). */
1220
+ waitForResult(historyId, options) {
1221
+ return this.generationService.waitForResult(historyId, options);
1222
+ }
1223
+ /** Cancel a running generation job. */
1224
+ cancelJob(historyId) {
1225
+ return this.generationService.cancelJob(historyId);
1226
+ }
1227
+ /** List past generations for the authenticated user. */
1228
+ listHistory(options) {
1229
+ return this.historyService.list(options);
1230
+ }
1231
+ /** List the user's permanently saved creations. */
1232
+ listCreations(options) {
1233
+ return this.historyService.listCreations(options);
1234
+ }
1235
+ /** Save a completed generation result as a permanent creation. */
1236
+ saveCreation(result) {
1237
+ return this.historyService.saveCreation(result);
1238
+ }
1239
+ /** Permanently delete a history item. Irreversible — requires `{ confirm: true }`. */
1240
+ deleteHistory(historyId, options) {
1241
+ return this.historyService.deleteHistory(historyId, options);
1242
+ }
1243
+ /** Permanently delete many history items. Irreversible — requires `{ confirm: true }`. */
1244
+ deleteManyHistories(historyIds, options) {
1245
+ return this.historyService.deleteManyHistories(historyIds, options);
1246
+ }
1247
+ /** Permanently delete a saved creation. Irreversible — requires `{ confirm: true }`. */
1248
+ deleteCreation(creationId, options) {
1249
+ return this.historyService.deleteCreation(creationId, options);
1250
+ }
1251
+ /** Permanently delete many creations. Irreversible — requires `{ confirm: true }`. */
1252
+ deleteManyCreations(creationIds, options) {
1253
+ return this.historyService.deleteManyCreations(creationIds, options);
1254
+ }
1255
+ /** Force re-discovery of the Server Action hashes. */
1256
+ refreshActions() {
1257
+ return this.registry.refresh();
1258
+ }
1259
+ };
1260
+ //#endregion
1261
+ //#region lib/index.ts
1262
+ /** Public entry point for `@zweer/mage-space-client`. */
1263
+ const VERSION = "0.0.0";
1264
+ //#endregion
1265
+ export { ActionRegistry, AuthError, CharactersService, DiscoveryError, GenerationError, InMemoryActionCache, MageSpaceClient, MageSpaceError, ReferencesService, RscParseError, SEED_SNAPSHOT, StaleActionError, TimeoutError, VERSION, discoverActions, parseFlightRows, parseServerActionResponse, resolveFlightValue, unwrapResult };
1266
+
1267
+ //# sourceMappingURL=index.mjs.map