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