@origingame/origin-asset 0.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js ADDED
@@ -0,0 +1,1449 @@
1
+ // src/generate.ts
2
+ function copyParams(params) {
3
+ if (!params) {
4
+ return void 0;
5
+ }
6
+ return { ...params };
7
+ }
8
+ function mergeParams(base, extra) {
9
+ const merged = {
10
+ ...base ?? {},
11
+ ...extra ?? {}
12
+ };
13
+ return Object.keys(merged).length > 0 ? merged : void 0;
14
+ }
15
+ function resolveAssetUrl(baseUrl, value) {
16
+ if (!value) {
17
+ return null;
18
+ }
19
+ try {
20
+ return new URL(value, ensureTrailingSlash(baseUrl)).toString();
21
+ } catch {
22
+ return value;
23
+ }
24
+ }
25
+ function normalizeGenerateResponse(baseUrl, response) {
26
+ const outputUrl = resolveAssetUrl(baseUrl, response.output_url);
27
+ return {
28
+ ...response,
29
+ output_url: outputUrl,
30
+ output_files: response.output_files?.map((file) => ({
31
+ ...file,
32
+ url: resolveAssetUrl(baseUrl, file.url) ?? file.url
33
+ })),
34
+ url: outputUrl
35
+ };
36
+ }
37
+ function normalizeBatchResponse(baseUrl, response) {
38
+ return {
39
+ ...response,
40
+ frames: response.frames.map((frame) => normalizeBatchFrame(baseUrl, frame))
41
+ };
42
+ }
43
+ function normalizeBatchFrame(baseUrl, frame) {
44
+ const outputUrl = resolveAssetUrl(baseUrl, frame.output_url);
45
+ return {
46
+ ...frame,
47
+ output_url: outputUrl,
48
+ url: outputUrl
49
+ };
50
+ }
51
+ function normalizeUploadResult(baseUrl, response) {
52
+ return {
53
+ ...response,
54
+ url: resolveAssetUrl(baseUrl, response.url) ?? response.url
55
+ };
56
+ }
57
+ function normalizeAssetFile(baseUrl, file) {
58
+ return {
59
+ ...file,
60
+ url: resolveAssetUrl(baseUrl, file.url) ?? file.url
61
+ };
62
+ }
63
+ function normalizeMaybeGenerateResponse(baseUrl, value) {
64
+ if (!isRecord(value)) {
65
+ return value;
66
+ }
67
+ const outputUrl = typeof value.output_url === "string" ? resolveAssetUrl(baseUrl, value.output_url) : null;
68
+ if (!outputUrl) {
69
+ return value;
70
+ }
71
+ return {
72
+ ...value,
73
+ output_url: outputUrl,
74
+ output_files: Array.isArray(value.output_files) ? value.output_files.map((file) => {
75
+ if (!isRecord(file) || typeof file.url !== "string") {
76
+ return file;
77
+ }
78
+ return { ...file, url: resolveAssetUrl(baseUrl, file.url) ?? file.url };
79
+ }) : value.output_files,
80
+ url: outputUrl
81
+ };
82
+ }
83
+ function buildImageRequest(prompt, options = {}) {
84
+ const request = {
85
+ asset_type: "image",
86
+ prompt
87
+ };
88
+ if (options.provider) request.provider = options.provider;
89
+ if (options.model) request.model = options.model;
90
+ if (options.size) request.size = options.size;
91
+ if (options.transparent !== void 0) request.transparent = options.transparent;
92
+ if (options.input) request.input_file = options.input;
93
+ if (options.reference_images?.length) request.reference_images = options.reference_images;
94
+ if (options.edit_mode) request.edit_mode = options.edit_mode;
95
+ if (options.session_id) request.session_id = options.session_id;
96
+ const params = mergeParams(options.params, {
97
+ ...options.quality ? { quality: options.quality } : {},
98
+ ...options.background ? { background: options.background } : {},
99
+ ...options.output_format ? { output_format: options.output_format } : {},
100
+ ...options.n !== void 0 ? { n: options.n } : {},
101
+ ...options.preset ? { preset: options.preset } : {},
102
+ ...options.mask ? { mask: options.mask } : {},
103
+ ...options.mask_url ? { mask_url: options.mask_url } : {}
104
+ });
105
+ if (params) request.params = params;
106
+ return request;
107
+ }
108
+ function buildGeneratedAssetRequest(assetType, prompt, options = {}) {
109
+ const params = mergeParams(options.params, {
110
+ ...assetType === "skybox" ? { preset_hint: "skybox_equirect" } : {},
111
+ ...assetType === "decal" ? { preset_hint: "decal_transparent" } : {},
112
+ ...assetType === "heightmap" ? { preset_hint: "heightmap_grayscale" } : {},
113
+ ...options.quality ? { quality: options.quality } : {},
114
+ ...options.background ? { background: options.background } : {},
115
+ ...options.output_format ? { output_format: options.output_format } : {},
116
+ ...options.n !== void 0 ? { n: options.n } : {},
117
+ ...options.preset ? { preset: options.preset } : {}
118
+ });
119
+ return {
120
+ asset_type: assetType,
121
+ prompt,
122
+ provider: options.provider,
123
+ model: options.model,
124
+ input_file: options.input,
125
+ size: options.size,
126
+ transparent: options.transparent,
127
+ params
128
+ };
129
+ }
130
+ function buildVideoRequest(prompt, options = {}) {
131
+ const request = {
132
+ asset_type: "video",
133
+ prompt
134
+ };
135
+ if (options.provider) request.provider = options.provider;
136
+ if (options.model) request.model = options.model;
137
+ if (options.size) request.size = options.size;
138
+ if (options.reference_images?.length) request.reference_images = options.reference_images;
139
+ const params = copyParams(options.params);
140
+ if (options.ratio) {
141
+ request.params = { ...params, ratio: options.ratio };
142
+ } else if (params) {
143
+ request.params = params;
144
+ }
145
+ return request;
146
+ }
147
+ function buildAudioRequest(prompt, options = {}) {
148
+ return {
149
+ asset_type: "audio",
150
+ prompt,
151
+ provider: options.provider,
152
+ model: options.model,
153
+ params: mergeParams(options.params, {
154
+ ...options.type ? { audio_type: options.type } : {},
155
+ ...options.duration !== void 0 ? { duration_seconds: options.duration } : {}
156
+ })
157
+ };
158
+ }
159
+ function buildMusicRequest(prompt, options = {}) {
160
+ return {
161
+ asset_type: "music",
162
+ prompt,
163
+ provider: options.provider,
164
+ model: options.model,
165
+ params: mergeParams(options.params, {
166
+ ...options.duration !== void 0 ? { duration_seconds: options.duration } : {},
167
+ ...options.force_instrumental ? { force_instrumental: true } : {},
168
+ ...options.output_format ? { output_format: options.output_format } : {}
169
+ })
170
+ };
171
+ }
172
+ function buildTtsRequest(prompt, options = {}) {
173
+ const context = options.context ?? options.instructions;
174
+ return {
175
+ asset_type: "tts",
176
+ prompt,
177
+ provider: options.provider,
178
+ model: options.model,
179
+ params: mergeParams(options.params, {
180
+ ...options.voice ? { voice: options.voice } : {},
181
+ ...options.voice_id ? { voice_id: options.voice_id } : {},
182
+ ...context ? { context } : {},
183
+ ...options.style ? { style: options.style } : {},
184
+ ...options.language ? { language_type: options.language } : {},
185
+ ...options.instructions ? { instructions: options.instructions } : {}
186
+ })
187
+ };
188
+ }
189
+ function buildModel3dRequest(prompt, options = {}) {
190
+ return {
191
+ asset_type: "model3d",
192
+ prompt,
193
+ provider: options.provider,
194
+ model: options.model,
195
+ input_file: options.input,
196
+ params: mergeParams(options.params, {
197
+ ...options.ai_model ? { ai_model: options.ai_model } : {},
198
+ ...options.polycount !== void 0 ? { target_polycount: options.polycount } : {},
199
+ ...options.pbr ? { pbr: true } : {},
200
+ ...options.hd_texture ? { hd_texture: true } : {},
201
+ ...options.auto_size ? { auto_size: true } : {},
202
+ ...options.pose_mode ? { pose_mode: options.pose_mode } : {},
203
+ ...options.texture_prompt ? { texture_prompt: options.texture_prompt } : {},
204
+ ...options.format ? { format: options.format } : {},
205
+ ...options.images?.length ? { images: options.images } : {}
206
+ })
207
+ };
208
+ }
209
+ function buildCharacterRequest(prompt, options = {}) {
210
+ return {
211
+ asset_type: "character",
212
+ prompt,
213
+ provider: options.provider,
214
+ model: options.model,
215
+ input_file: options.input,
216
+ params: mergeParams(options.params, {
217
+ ...options.format ? { format: options.format } : {},
218
+ ...options.polycount !== void 0 ? { target_polycount: options.polycount } : {},
219
+ ...options.pbr ? { pbr: true } : {},
220
+ ...options.hd_texture ? { hd_texture: true } : {},
221
+ ...options.pose_mode ? { pose_mode: options.pose_mode } : {},
222
+ ...options.ai_model ? { ai_model: options.ai_model } : {},
223
+ ...options.auto_size ? { auto_size: true } : {},
224
+ ...options.texture_prompt ? { texture_prompt: options.texture_prompt } : {},
225
+ ...options.images?.length ? { images: options.images } : {}
226
+ })
227
+ };
228
+ }
229
+ function buildPropRequest(prompt, options = {}) {
230
+ return {
231
+ asset_type: "prop",
232
+ prompt,
233
+ provider: options.provider,
234
+ model: options.model,
235
+ input_file: options.input,
236
+ params: mergeParams(options.params, {
237
+ ...options.format ? { format: options.format } : {},
238
+ ...options.polycount !== void 0 ? { target_polycount: options.polycount } : {},
239
+ ...options.pbr ? { pbr: true } : {},
240
+ ...options.hd_texture ? { hd_texture: true } : {},
241
+ ...options.ai_model ? { ai_model: options.ai_model } : {},
242
+ ...options.auto_size ? { auto_size: true } : {},
243
+ ...options.texture_prompt ? { texture_prompt: options.texture_prompt } : {},
244
+ ...options.symmetry_mode ? { symmetry_mode: options.symmetry_mode } : {},
245
+ ...options.images?.length ? { images: options.images } : {}
246
+ })
247
+ };
248
+ }
249
+ function buildSpriteRequest(prompt, options = {}) {
250
+ return {
251
+ asset_type: "sprite",
252
+ prompt,
253
+ provider: options.provider,
254
+ model: options.model,
255
+ input_file: options.input,
256
+ params: mergeParams(options.params, {
257
+ animation_type: options.animation_type ?? "walk",
258
+ direction: options.direction ?? "right",
259
+ duration: options.duration ?? 2,
260
+ output_format: options.output_format ?? "spritesheet",
261
+ ...options.fps !== void 0 ? { fps: options.fps } : {},
262
+ ...options.style ? { style: options.style } : {}
263
+ })
264
+ };
265
+ }
266
+ function buildWorldRequest(prompt, options = {}) {
267
+ return {
268
+ asset_type: "world",
269
+ prompt,
270
+ provider: options.provider,
271
+ model: options.model,
272
+ input_file: options.input ?? options.image_url,
273
+ params: mergeParams(options.params, {
274
+ ...options.display_name ? { display_name: options.display_name } : {},
275
+ ...options.image_url ? { image_url: options.image_url } : {},
276
+ ...options.panorama_url ? { panorama_url: options.panorama_url } : {},
277
+ ...options.video_url ? { video_url: options.video_url } : {},
278
+ ...options.multi_image_url?.length ? { multi_image_url: options.multi_image_url } : {},
279
+ ...options.quality ? { quality: options.quality } : {}
280
+ })
281
+ };
282
+ }
283
+ function buildTextRequest(prompt, options = {}) {
284
+ return {
285
+ asset_type: "text",
286
+ prompt,
287
+ provider: options.provider,
288
+ model: options.model,
289
+ params: mergeParams(options.params, {
290
+ ...options.max_tokens !== void 0 ? { max_tokens: options.max_tokens } : {}
291
+ })
292
+ };
293
+ }
294
+ function ensureTrailingSlash(value) {
295
+ return value.endsWith("/") ? value : `${value}/`;
296
+ }
297
+ function isRecord(value) {
298
+ return typeof value === "object" && value !== null && !Array.isArray(value);
299
+ }
300
+
301
+ // src/types.ts
302
+ var AssetForgeError = class extends Error {
303
+ code;
304
+ status;
305
+ command;
306
+ details;
307
+ constructor(message, options = {}) {
308
+ super(message, options.cause !== void 0 ? { cause: options.cause } : void 0);
309
+ this.name = "AssetForgeError";
310
+ this.code = options.code ?? "ASSETFORGE_ERROR";
311
+ this.status = options.status;
312
+ this.command = options.command;
313
+ this.details = options.details;
314
+ Object.setPrototypeOf(this, new.target.prototype);
315
+ }
316
+ };
317
+ function toAssetForgeError(error) {
318
+ if (error instanceof AssetForgeError) {
319
+ return error;
320
+ }
321
+ if (error instanceof Error) {
322
+ return new AssetForgeError(error.message, {
323
+ code: "INTERNAL_ERROR",
324
+ cause: error
325
+ });
326
+ }
327
+ return new AssetForgeError(String(error), { code: "INTERNAL_ERROR" });
328
+ }
329
+
330
+ // src/stream.ts
331
+ function subscribeJob(jobId, handler, options = {}) {
332
+ return subscribeSse(`/api/jobs/${encodeURIComponent(jobId)}/events`, handler, options);
333
+ }
334
+ function subscribeAllJobs(handler, options = {}) {
335
+ return subscribeSse("/api/jobs/events", handler, options);
336
+ }
337
+ var AssetForgeStreamNamespace = class {
338
+ constructor(forge) {
339
+ this.forge = forge;
340
+ }
341
+ forge;
342
+ job(jobId, handler, options = {}) {
343
+ return subscribeJob(jobId, handler, this.options(options));
344
+ }
345
+ all(handler, options = {}) {
346
+ return subscribeAllJobs(handler, this.options(options));
347
+ }
348
+ options(options) {
349
+ return {
350
+ ...options,
351
+ baseUrl: this.forge.baseUrl,
352
+ apiKey: this.forge.apiKey
353
+ };
354
+ }
355
+ };
356
+ function subscribeSse(path, handler, options) {
357
+ const controller = new AbortController();
358
+ linkAbortSignals(options.signal, controller);
359
+ const result = runSse(path, handler, options, controller.signal);
360
+ void result.catch(() => void 0);
361
+ return {
362
+ signal: controller.signal,
363
+ result,
364
+ close: (reason = "closed") => controller.abort(reason)
365
+ };
366
+ }
367
+ async function runSse(path, handler, options, signal) {
368
+ const fetchImpl = options.fetch ?? globalThis.fetch;
369
+ if (typeof fetchImpl !== "function") {
370
+ throw new AssetForgeError("Global fetch is unavailable in this runtime", {
371
+ code: "FETCH_UNAVAILABLE"
372
+ });
373
+ }
374
+ const headers = new Headers(options.headers);
375
+ headers.set("accept", "text/event-stream");
376
+ if (options.apiKey) {
377
+ headers.set("authorization", `Bearer ${options.apiKey}`);
378
+ }
379
+ const response = await fetchImpl(new URL(path, ensureTrailingSlash2(options.baseUrl ?? DEFAULT_BASE_URL)), {
380
+ method: "GET",
381
+ headers,
382
+ signal
383
+ });
384
+ if (!response.ok) {
385
+ throw new AssetForgeError(`SSE subscription failed (${response.status})`, {
386
+ code: defaultErrorCode(response.status),
387
+ status: response.status
388
+ });
389
+ }
390
+ if (!response.body) {
391
+ throw new AssetForgeError("SSE response body is unavailable", { code: "STREAM_UNAVAILABLE" });
392
+ }
393
+ const reader = response.body.getReader();
394
+ const decoder = new TextDecoder();
395
+ let buffer = "";
396
+ while (!signal.aborted) {
397
+ const { done, value } = await reader.read();
398
+ if (done) {
399
+ break;
400
+ }
401
+ buffer += decoder.decode(value, { stream: true });
402
+ buffer = drainFrames(buffer, handler);
403
+ }
404
+ const finalText = decoder.decode();
405
+ if (finalText) {
406
+ buffer += finalText;
407
+ }
408
+ drainFrames(`${buffer}
409
+
410
+ `, handler);
411
+ }
412
+ function drainFrames(buffer, handler) {
413
+ let normalized = buffer.replace(/\r\n/g, "\n");
414
+ let boundary = normalized.indexOf("\n\n");
415
+ while (boundary !== -1) {
416
+ const frame = normalized.slice(0, boundary);
417
+ normalized = normalized.slice(boundary + 2);
418
+ const parsed = parseFrame(frame);
419
+ if (parsed) {
420
+ handler(normalizeJobEvent(parsed.event, parsed.data));
421
+ }
422
+ boundary = normalized.indexOf("\n\n");
423
+ }
424
+ return normalized;
425
+ }
426
+ function parseFrame(frame) {
427
+ let event = "message";
428
+ const data = [];
429
+ for (const line of frame.split("\n")) {
430
+ if (!line || line.startsWith(":")) {
431
+ continue;
432
+ }
433
+ const separator = line.indexOf(":");
434
+ const field = separator === -1 ? line : line.slice(0, separator);
435
+ const value = separator === -1 ? "" : line.slice(separator + 1).replace(/^ /, "");
436
+ if (field === "event") {
437
+ event = value;
438
+ } else if (field === "data") {
439
+ data.push(value);
440
+ }
441
+ }
442
+ return data.length ? { event, data: data.join("\n") } : null;
443
+ }
444
+ function normalizeJobEvent(eventName, data) {
445
+ const raw = parseJson(data);
446
+ const record = isRecord2(raw) ? raw : {};
447
+ const kind = eventName === "snapshot" || eventName === "progress" || eventName === "update" ? eventName : "update";
448
+ return {
449
+ kind,
450
+ job_id: stringField(record.job_id) ?? stringField(record.id) ?? "",
451
+ status: stringField(record.status) ?? "unknown",
452
+ progress: extractProgress(record),
453
+ output_url: stringField(record.output_url),
454
+ error_message: stringField(record.error_message),
455
+ timestamp: stringField(record.timestamp) ?? (/* @__PURE__ */ new Date()).toISOString(),
456
+ raw
457
+ };
458
+ }
459
+ function extractProgress(record) {
460
+ const nested = isRecord2(record.progress) ? record.progress : void 0;
461
+ const progress = {
462
+ ...nested ?? {}
463
+ };
464
+ if (typeof record.progress === "number") progress.progress = record.progress;
465
+ if (typeof record.current_step === "string") progress.current_step = record.current_step;
466
+ if (typeof record.step_index === "number") progress.step_index = record.step_index;
467
+ if (typeof record.step_count === "number") progress.step_count = record.step_count;
468
+ if (typeof record.message === "string") progress.message = record.message;
469
+ if (record.details !== void 0) progress.details = record.details;
470
+ return Object.keys(progress).length > 0 ? progress : void 0;
471
+ }
472
+ function parseJson(value) {
473
+ try {
474
+ return JSON.parse(value);
475
+ } catch {
476
+ return value;
477
+ }
478
+ }
479
+ function stringField(value) {
480
+ return typeof value === "string" ? value : void 0;
481
+ }
482
+ function isRecord2(value) {
483
+ return typeof value === "object" && value !== null && !Array.isArray(value);
484
+ }
485
+ function linkAbortSignals(source, controller) {
486
+ if (!source) {
487
+ return;
488
+ }
489
+ if (source.aborted) {
490
+ controller.abort(source.reason);
491
+ return;
492
+ }
493
+ const onAbort = () => {
494
+ controller.abort(source.reason);
495
+ source.removeEventListener("abort", onAbort);
496
+ };
497
+ source.addEventListener("abort", onAbort, { once: true });
498
+ }
499
+ function ensureTrailingSlash2(value) {
500
+ return value.endsWith("/") ? value : `${value}/`;
501
+ }
502
+ function defaultErrorCode(status) {
503
+ if (status === 401) return "UNAUTHORIZED";
504
+ if (status === 403) return "FORBIDDEN";
505
+ if (status === 404) return "NOT_FOUND";
506
+ if (status === 409) return "CONFLICT";
507
+ return "API_ERROR";
508
+ }
509
+
510
+ // src/jobs.ts
511
+ var AssetForgeJobs = class {
512
+ constructor(transport) {
513
+ this.transport = transport;
514
+ }
515
+ transport;
516
+ async list(query = {}, options = {}) {
517
+ return this.transport.request("GET", "/api/jobs", {
518
+ query: {
519
+ job_kind: query.job_kind,
520
+ status: query.status,
521
+ asset_type: query.asset_type,
522
+ provider_id: query.provider_id,
523
+ since: query.since,
524
+ provider_task_id: query.provider_task_id,
525
+ limit: query.limit,
526
+ offset: query.offset
527
+ },
528
+ signal: options.signal,
529
+ headers: options.headers
530
+ });
531
+ }
532
+ async status(id, options = {}) {
533
+ const job = await this.transport.request(
534
+ "GET",
535
+ `/api/jobs/${encodeURIComponent(id)}`,
536
+ {
537
+ signal: options.signal,
538
+ headers: options.headers
539
+ }
540
+ );
541
+ return {
542
+ ...job,
543
+ response: job.response !== void 0 && job.response !== null ? normalizeMaybeGenerateResponse(this.transport.baseUrl, job.response) : job.response
544
+ };
545
+ }
546
+ async cancel(id, options = {}) {
547
+ return this.transport.request(
548
+ "POST",
549
+ `/api/jobs/${encodeURIComponent(id)}/cancel`,
550
+ {
551
+ signal: options.signal,
552
+ headers: options.headers
553
+ }
554
+ );
555
+ }
556
+ async wait(id, options = {}) {
557
+ const intervalMs = Math.max(250, options.intervalMs ?? options.interval_ms ?? 1e3);
558
+ const deadline = options.timeoutMs !== void 0 ? Date.now() + options.timeoutMs : void 0;
559
+ try {
560
+ return await this.waitWithSse(id, options);
561
+ } catch (error) {
562
+ if (error instanceof AssetForgeError && error.code === "JOB_WAIT_TIMEOUT") {
563
+ throw error;
564
+ }
565
+ if (options.signal?.aborted) {
566
+ throw new AssetForgeError("Job wait aborted", { code: "ABORTED", cause: error });
567
+ }
568
+ }
569
+ while (true) {
570
+ if (options.signal?.aborted) {
571
+ throw new AssetForgeError("Job wait aborted", { code: "ABORTED" });
572
+ }
573
+ if (deadline !== void 0 && Date.now() >= deadline) {
574
+ throw new AssetForgeError(`Timed out waiting for job ${id}`, { code: "JOB_WAIT_TIMEOUT" });
575
+ }
576
+ const job = await this.status(id, options);
577
+ if (isTerminalStatus(job.status)) {
578
+ return assertSuccessfulJob(job);
579
+ }
580
+ const sleepMs = deadline === void 0 ? intervalMs : Math.min(intervalMs, Math.max(0, deadline - Date.now()));
581
+ await sleep(sleepMs, options.signal);
582
+ }
583
+ }
584
+ waitWithSse(id, options) {
585
+ return new Promise((resolve, reject) => {
586
+ let settled = false;
587
+ let timeout;
588
+ const subscription = subscribeJob(
589
+ id,
590
+ (event) => {
591
+ if (!isTerminalStatus(event.status) || settled) {
592
+ return;
593
+ }
594
+ settled = true;
595
+ if (timeout) clearTimeout(timeout);
596
+ subscription.close("terminal");
597
+ void this.status(id, options).then(
598
+ (job) => {
599
+ try {
600
+ resolve(assertSuccessfulJob(job));
601
+ } catch (error) {
602
+ reject(error);
603
+ }
604
+ },
605
+ () => reject(errorFromEvent(event))
606
+ );
607
+ },
608
+ {
609
+ baseUrl: this.transport.baseUrl,
610
+ apiKey: this.transport.apiKey,
611
+ signal: options.signal,
612
+ headers: options.headers
613
+ }
614
+ );
615
+ if (options.timeoutMs !== void 0) {
616
+ timeout = setTimeout(() => {
617
+ if (!settled) {
618
+ settled = true;
619
+ subscription.close("timeout");
620
+ reject(new AssetForgeError(`Timed out waiting for job ${id}`, { code: "JOB_WAIT_TIMEOUT" }));
621
+ }
622
+ }, options.timeoutMs);
623
+ }
624
+ void subscription.result.then(
625
+ () => {
626
+ if (!settled) {
627
+ if (timeout) clearTimeout(timeout);
628
+ reject(new AssetForgeError("SSE stream ended before job completed", { code: "STREAM_ENDED" }));
629
+ }
630
+ },
631
+ (error) => {
632
+ if (!settled) {
633
+ if (timeout) clearTimeout(timeout);
634
+ reject(error);
635
+ }
636
+ }
637
+ );
638
+ });
639
+ }
640
+ };
641
+ function isTerminalStatus(status) {
642
+ return status === "completed" || status === "failed" || status === "cancelled";
643
+ }
644
+ function assertSuccessfulJob(job) {
645
+ if (job.status === "cancelled") {
646
+ throw new AssetForgeError(job.error_message ?? "Job cancelled", {
647
+ code: "JOB_CANCELLED",
648
+ details: job
649
+ });
650
+ }
651
+ if (job.status === "failed") {
652
+ throw new AssetForgeError(job.error_message ?? "Job failed", {
653
+ code: "JOB_FAILED",
654
+ details: job
655
+ });
656
+ }
657
+ return job;
658
+ }
659
+ function errorFromEvent(event) {
660
+ if (event.status === "cancelled") {
661
+ return new AssetForgeError(event.error_message ?? "Job cancelled", {
662
+ code: "JOB_CANCELLED",
663
+ details: event.raw
664
+ });
665
+ }
666
+ return new AssetForgeError(event.error_message ?? `Job ${event.status}`, {
667
+ code: event.status === "failed" ? "JOB_FAILED" : "API_ERROR",
668
+ details: event.raw
669
+ });
670
+ }
671
+ function sleep(ms, signal) {
672
+ return new Promise((resolve, reject) => {
673
+ if (signal?.aborted) {
674
+ reject(new AssetForgeError("Operation aborted", { code: "ABORTED" }));
675
+ return;
676
+ }
677
+ const timeout = setTimeout(() => {
678
+ cleanup();
679
+ resolve();
680
+ }, ms);
681
+ const onAbort = () => {
682
+ clearTimeout(timeout);
683
+ cleanup();
684
+ reject(new AssetForgeError("Operation aborted", { code: "ABORTED" }));
685
+ };
686
+ const cleanup = () => signal?.removeEventListener("abort", onAbort);
687
+ signal?.addEventListener("abort", onAbort, { once: true });
688
+ });
689
+ }
690
+
691
+ // src/library.ts
692
+ var AssetForgeLibrary = class {
693
+ constructor(transport) {
694
+ this.transport = transport;
695
+ }
696
+ transport;
697
+ async search(query = {}, options = {}) {
698
+ return this.transport.request("GET", "/api/library/search", {
699
+ query: {
700
+ q: query.q ?? query.query,
701
+ type: query.type,
702
+ tags: query.tags,
703
+ source: query.source,
704
+ pack_id: query.pack_id,
705
+ mode: query.mode,
706
+ style: query.style,
707
+ composition: query.composition,
708
+ lighting: query.lighting,
709
+ color_tone: query.color_tone,
710
+ mood: query.mood,
711
+ theme: query.theme,
712
+ era: query.era,
713
+ gameplay_role: query.gameplay_role,
714
+ ui_element: query.ui_element,
715
+ ui_state: query.ui_state,
716
+ ui_theme_pack: query.ui_theme_pack,
717
+ nine_slice: query.nine_slice,
718
+ size_class: query.size_class,
719
+ rig_name: query.rig_name,
720
+ compatible_rig: query.compatible_rig,
721
+ enrichment_status: query.enrichment_status,
722
+ min_long_edge_px: query.min_long_edge_px,
723
+ min_width_px: query.min_width_px,
724
+ min_height_px: query.min_height_px,
725
+ max_file_size_bytes: query.max_file_size_bytes,
726
+ license: query.license,
727
+ rerank: query.rerank,
728
+ limit: query.limit,
729
+ offset: query.offset
730
+ },
731
+ signal: options.signal,
732
+ headers: options.headers
733
+ });
734
+ }
735
+ async related(id, query = {}, options = {}) {
736
+ return this.transport.request(
737
+ "GET",
738
+ `/api/library/${encodeURIComponent(id)}/related`,
739
+ {
740
+ query: {
741
+ type: query.type,
742
+ limit: query.limit
743
+ },
744
+ signal: options.signal,
745
+ headers: options.headers
746
+ }
747
+ );
748
+ }
749
+ async bundle(request, options = {}) {
750
+ const url = new URL("/api/library/bundle", ensureTrailingSlash3(this.transport.baseUrl));
751
+ const headers = new Headers(options.headers);
752
+ if (this.transport.apiKey) {
753
+ headers.set("authorization", `Bearer ${this.transport.apiKey}`);
754
+ }
755
+ headers.set("content-type", "application/json");
756
+ const response = await fetch(url.toString(), {
757
+ method: "POST",
758
+ headers,
759
+ body: JSON.stringify(request),
760
+ signal: options.signal
761
+ });
762
+ if (!response.ok) {
763
+ throw await bundleError(response);
764
+ }
765
+ return response.blob();
766
+ }
767
+ async ingest(request, options = {}) {
768
+ return this.transport.request("POST", "/api/library/ingest", {
769
+ body: request,
770
+ signal: options.signal,
771
+ headers: options.headers
772
+ });
773
+ }
774
+ async ingestSync(request, options = {}) {
775
+ return this.transport.request("POST", "/api/library/ingest/sync", {
776
+ body: request,
777
+ signal: options.signal,
778
+ headers: options.headers
779
+ });
780
+ }
781
+ async ingestStatus(jobId, options = {}) {
782
+ return this.transport.request(
783
+ "GET",
784
+ `/api/library/ingest/${encodeURIComponent(jobId)}`,
785
+ {
786
+ signal: options.signal,
787
+ headers: options.headers
788
+ }
789
+ );
790
+ }
791
+ async ingestWait(jobId, opts = {}) {
792
+ const intervalMs = Math.max(opts.intervalMs ?? 1e3, 250);
793
+ for (; ; ) {
794
+ const job = await this.ingestStatus(jobId, opts);
795
+ if (job.status === "completed") {
796
+ return ingestResultFromJob(job);
797
+ }
798
+ if (job.status === "failed" || job.status === "cancelled") {
799
+ throw new AssetForgeError(job.error_message ?? `Library ingest job ${jobId} ${job.status}`, {
800
+ code: job.status === "cancelled" ? "JOB_CANCELLED" : "API_ERROR",
801
+ details: job
802
+ });
803
+ }
804
+ await sleep2(intervalMs, opts.signal);
805
+ }
806
+ }
807
+ async packs(options = {}) {
808
+ return this.transport.request("GET", "/api/library/packs", {
809
+ signal: options.signal,
810
+ headers: options.headers
811
+ });
812
+ }
813
+ async packsInstall(request, options = {}) {
814
+ return this.transport.request("POST", "/api/library/packs/install", {
815
+ body: request,
816
+ signal: options.signal,
817
+ headers: options.headers
818
+ });
819
+ }
820
+ async packsDelete(id, options = {}) {
821
+ return this.transport.request(
822
+ "DELETE",
823
+ `/api/library/packs/${encodeURIComponent(id)}`,
824
+ {
825
+ signal: options.signal,
826
+ headers: options.headers
827
+ }
828
+ );
829
+ }
830
+ async packsRefresh(id, options = {}) {
831
+ return this.transport.request(
832
+ "POST",
833
+ `/api/library/packs/${encodeURIComponent(id)}/refresh`,
834
+ {
835
+ body: {},
836
+ signal: options.signal,
837
+ headers: options.headers
838
+ }
839
+ );
840
+ }
841
+ async add(request, options = {}) {
842
+ return this.transport.request(
843
+ "POST",
844
+ "/api/library",
845
+ {
846
+ body: request,
847
+ signal: options.signal,
848
+ headers: options.headers
849
+ }
850
+ );
851
+ }
852
+ async get(id, options = {}) {
853
+ return this.transport.request(
854
+ "GET",
855
+ `/api/library/${encodeURIComponent(id)}`,
856
+ {
857
+ signal: options.signal,
858
+ headers: options.headers
859
+ }
860
+ );
861
+ }
862
+ async delete(id, options = {}) {
863
+ return this.transport.request(
864
+ "DELETE",
865
+ `/api/library/${encodeURIComponent(id)}`,
866
+ {
867
+ signal: options.signal,
868
+ headers: options.headers
869
+ }
870
+ );
871
+ }
872
+ async catalogJobs(request = {}, options = {}) {
873
+ return this.transport.request("POST", "/api/library/catalog-jobs", {
874
+ body: request,
875
+ signal: options.signal,
876
+ headers: options.headers
877
+ });
878
+ }
879
+ async backfill(request = {}, options = {}) {
880
+ return this.transport.request("POST", "/api/library/admin/backfill", {
881
+ body: request,
882
+ signal: options.signal,
883
+ headers: options.headers
884
+ });
885
+ }
886
+ async reenrich(request = {}, options = {}) {
887
+ return this.transport.request("POST", "/api/library/admin/reenrich", {
888
+ body: request,
889
+ signal: options.signal,
890
+ headers: options.headers
891
+ });
892
+ }
893
+ };
894
+ function ingestResultFromJob(job) {
895
+ const response = job.response;
896
+ if (isIngestResult(response)) {
897
+ return response;
898
+ }
899
+ throw new AssetForgeError(`Library ingest job ${job.id} completed without an ingest result`, {
900
+ code: "API_ERROR",
901
+ details: job
902
+ });
903
+ }
904
+ function isIngestResult(value) {
905
+ return typeof value === "object" && value !== null && typeof value.library_id === "string" && typeof value.skipped === "boolean" && typeof value.embedding_dim === "number";
906
+ }
907
+ function ensureTrailingSlash3(value) {
908
+ return value.endsWith("/") ? value : `${value}/`;
909
+ }
910
+ async function bundleError(response) {
911
+ const text = await response.text();
912
+ let payload = text;
913
+ try {
914
+ payload = JSON.parse(text);
915
+ } catch {
916
+ }
917
+ const message = extractErrorMessage(payload) ?? `AssetForge API error (${response.status})`;
918
+ return new AssetForgeError(message, {
919
+ code: response.status === 404 ? "NOT_FOUND" : "API_ERROR",
920
+ status: response.status,
921
+ details: payload
922
+ });
923
+ }
924
+ function extractErrorMessage(payload) {
925
+ if (typeof payload === "string") {
926
+ return payload || void 0;
927
+ }
928
+ if (typeof payload !== "object" || payload === null) {
929
+ return void 0;
930
+ }
931
+ const error = payload.error;
932
+ if (typeof error === "string") {
933
+ return error;
934
+ }
935
+ if (typeof error === "object" && error !== null) {
936
+ const message = error.message;
937
+ return typeof message === "string" ? message : void 0;
938
+ }
939
+ return void 0;
940
+ }
941
+ function sleep2(ms, signal) {
942
+ if (signal?.aborted) {
943
+ return Promise.reject(new AssetForgeError("The operation was aborted", { code: "ABORTED" }));
944
+ }
945
+ return new Promise((resolve, reject) => {
946
+ const timer = setTimeout(resolve, ms);
947
+ signal?.addEventListener(
948
+ "abort",
949
+ () => {
950
+ clearTimeout(timer);
951
+ reject(new AssetForgeError("The operation was aborted", { code: "ABORTED" }));
952
+ },
953
+ { once: true }
954
+ );
955
+ });
956
+ }
957
+
958
+ // src/providers.ts
959
+ var AssetForgeProviders = class {
960
+ constructor(transport) {
961
+ this.transport = transport;
962
+ }
963
+ transport;
964
+ async list(options = {}) {
965
+ return this.transport.request("GET", "/api/providers", {
966
+ signal: options.signal,
967
+ headers: options.headers
968
+ });
969
+ }
970
+ async health(idOrOptions, maybeOptions = {}) {
971
+ if (typeof idOrOptions === "string") {
972
+ return this.transport.request(
973
+ "GET",
974
+ `/api/providers/${encodeURIComponent(idOrOptions)}/health`,
975
+ {
976
+ signal: maybeOptions.signal,
977
+ headers: maybeOptions.headers
978
+ }
979
+ );
980
+ }
981
+ const options = idOrOptions ?? {};
982
+ return this.transport.request("GET", "/api/providers/health", {
983
+ signal: options.signal,
984
+ headers: options.headers
985
+ });
986
+ }
987
+ };
988
+
989
+ // src/upload.ts
990
+ var AssetForgeAssets = class {
991
+ constructor(transport) {
992
+ this.transport = transport;
993
+ }
994
+ transport;
995
+ async upload(input, options = {}) {
996
+ const form = new FormData();
997
+ const { blob, filename } = normalizeUploadInput(input, options);
998
+ form.append("file", blob, filename);
999
+ const result = await this.transport.request("POST", "/api/assets/upload", {
1000
+ form,
1001
+ signal: options.signal,
1002
+ headers: options.headers
1003
+ });
1004
+ return normalizeUploadResult(this.transport.baseUrl, result);
1005
+ }
1006
+ async list(options = {}) {
1007
+ const result = await this.transport.request("GET", "/api/assets", {
1008
+ signal: options.signal,
1009
+ headers: options.headers
1010
+ });
1011
+ return {
1012
+ ...result,
1013
+ files: result.files.map((file) => normalizeAssetFile(this.transport.baseUrl, file))
1014
+ };
1015
+ }
1016
+ async delete(filename, options = {}) {
1017
+ return this.transport.request(
1018
+ "DELETE",
1019
+ `/api/assets/${encodeURIComponent(filename)}`,
1020
+ {
1021
+ signal: options.signal,
1022
+ headers: options.headers
1023
+ }
1024
+ );
1025
+ }
1026
+ };
1027
+ function normalizeUploadInput(input, options) {
1028
+ if (typeof File !== "undefined" && input instanceof File) {
1029
+ return {
1030
+ blob: input,
1031
+ filename: options.filename ?? input.name
1032
+ };
1033
+ }
1034
+ if (input instanceof Blob) {
1035
+ return {
1036
+ blob: input,
1037
+ filename: options.filename ?? inferFilename(input.type, "upload")
1038
+ };
1039
+ }
1040
+ if (input instanceof ArrayBuffer) {
1041
+ return {
1042
+ blob: new Blob([input], { type: options.type ?? "application/octet-stream" }),
1043
+ filename: options.filename ?? inferFilename(options.type, "upload")
1044
+ };
1045
+ }
1046
+ if (input instanceof Uint8Array) {
1047
+ const arrayBuffer = new Uint8Array(input).buffer;
1048
+ return {
1049
+ blob: new Blob([arrayBuffer], { type: options.type ?? "application/octet-stream" }),
1050
+ filename: options.filename ?? inferFilename(options.type, "upload")
1051
+ };
1052
+ }
1053
+ if (typeof input === "object" && input !== null && "data" in input) {
1054
+ if (!input.filename.trim()) {
1055
+ throw new AssetForgeError("Upload filename cannot be empty", {
1056
+ code: "INVALID_UPLOAD_INPUT"
1057
+ });
1058
+ }
1059
+ return {
1060
+ blob: new Blob(Array.isArray(input.data) ? input.data : [input.data], {
1061
+ type: input.type ?? options.type ?? "application/octet-stream"
1062
+ }),
1063
+ filename: options.filename ?? input.filename
1064
+ };
1065
+ }
1066
+ throw new AssetForgeError("Unsupported upload input", {
1067
+ code: "INVALID_UPLOAD_INPUT"
1068
+ });
1069
+ }
1070
+ function inferFilename(contentType, basename) {
1071
+ const extension = mimeToExtension(contentType);
1072
+ return extension ? `${basename}.${extension}` : `${basename}.bin`;
1073
+ }
1074
+ function mimeToExtension(contentType) {
1075
+ switch (contentType) {
1076
+ case "image/png":
1077
+ return "png";
1078
+ case "image/jpeg":
1079
+ return "jpg";
1080
+ case "image/webp":
1081
+ return "webp";
1082
+ case "video/mp4":
1083
+ return "mp4";
1084
+ case "audio/mpeg":
1085
+ return "mp3";
1086
+ case "audio/wav":
1087
+ return "wav";
1088
+ case "model/gltf-binary":
1089
+ return "glb";
1090
+ default:
1091
+ return null;
1092
+ }
1093
+ }
1094
+
1095
+ // src/client.ts
1096
+ var DEFAULT_BASE_URL = "https://asset.origingame.dev";
1097
+ var AssetForge = class {
1098
+ baseUrl;
1099
+ job;
1100
+ library;
1101
+ providers;
1102
+ assets;
1103
+ stream;
1104
+ voice;
1105
+ apiKey;
1106
+ fetchImpl;
1107
+ defaultHeaders;
1108
+ constructor(options) {
1109
+ if (!options.apiKey.trim()) {
1110
+ throw new AssetForgeError("AssetForge apiKey cannot be empty", {
1111
+ code: "CONFIG_ERROR"
1112
+ });
1113
+ }
1114
+ if (typeof fetch !== "function" && !options.fetch) {
1115
+ throw new AssetForgeError("Global fetch is unavailable in this runtime", {
1116
+ code: "FETCH_UNAVAILABLE"
1117
+ });
1118
+ }
1119
+ this.apiKey = options.apiKey.trim();
1120
+ this.baseUrl = options.baseUrl?.trim() || DEFAULT_BASE_URL;
1121
+ this.fetchImpl = options.fetch ?? fetch;
1122
+ this.defaultHeaders = options.headers ?? {};
1123
+ this.job = new AssetForgeJobs(this);
1124
+ this.library = new AssetForgeLibrary(this);
1125
+ this.providers = new AssetForgeProviders(this);
1126
+ this.assets = new AssetForgeAssets(this);
1127
+ this.stream = new AssetForgeStreamNamespace(this);
1128
+ const cloneVoice = ((request, reqOptions = {}) => this.submitAndMaybeWait(
1129
+ "/api/voice/clone",
1130
+ request,
1131
+ reqOptions
1132
+ ));
1133
+ this.voice = {
1134
+ clone: cloneVoice,
1135
+ design: (request, reqOptions = {}) => this.request("POST", "/api/voice/design", {
1136
+ body: request,
1137
+ signal: reqOptions.signal,
1138
+ headers: reqOptions.headers
1139
+ }),
1140
+ list: (query = {}) => this.request("GET", "/api/voice/list", {
1141
+ query: {
1142
+ type: query.type,
1143
+ page: query.page,
1144
+ page_size: query.page_size
1145
+ },
1146
+ signal: query.signal,
1147
+ headers: query.headers
1148
+ }),
1149
+ delete: (voiceId, query = {}) => this.request("DELETE", `/api/voice/${encodeURIComponent(voiceId)}`, {
1150
+ query: { type: query.type },
1151
+ signal: query.signal,
1152
+ headers: query.headers
1153
+ })
1154
+ };
1155
+ }
1156
+ async login(token = this.apiKey, options = {}) {
1157
+ return this.request("POST", "/auth/login", {
1158
+ body: { token },
1159
+ signal: options.signal,
1160
+ headers: options.headers
1161
+ });
1162
+ }
1163
+ async generate(request, options = {}) {
1164
+ return this.submitAndMaybeWait(
1165
+ "/api/generate",
1166
+ request,
1167
+ options,
1168
+ (response) => normalizeGenerateResponse(this.baseUrl, response)
1169
+ );
1170
+ }
1171
+ async batch(request, options = {}) {
1172
+ return this.submitAndMaybeWait(
1173
+ "/api/generate/batch",
1174
+ request,
1175
+ options,
1176
+ (response) => normalizeBatchResponse(this.baseUrl, response)
1177
+ );
1178
+ }
1179
+ async process(request, options = {}) {
1180
+ return this.submitAndMaybeWait("/api/process", request, options);
1181
+ }
1182
+ async process3d(request, options = {}) {
1183
+ return this.submitAndMaybeWait("/api/process3d", request, options);
1184
+ }
1185
+ async upload(input, options = {}) {
1186
+ return this.assets.upload(input, options);
1187
+ }
1188
+ image(prompt, options = {}) {
1189
+ const { signal, headers, async: asyncMode, idempotencyKey, ...rest } = options;
1190
+ return this.generate(buildImageRequest(prompt, rest), { signal, headers, async: asyncMode, idempotencyKey });
1191
+ }
1192
+ animation(prompt, options = {}) {
1193
+ const { signal, headers, async: asyncMode, idempotencyKey, ...rest } = options;
1194
+ return this.generate(buildGeneratedAssetRequest("animation", prompt, rest), { signal, headers, async: asyncMode, idempotencyKey });
1195
+ }
1196
+ material(_prompt, _options = {}) {
1197
+ return Promise.resolve(packOnlyResponse("material"));
1198
+ }
1199
+ hdri(_prompt, _options = {}) {
1200
+ return Promise.resolve(packOnlyResponse("hdri"));
1201
+ }
1202
+ vfx(_prompt, _options = {}) {
1203
+ return Promise.resolve(packOnlyResponse("vfx"));
1204
+ }
1205
+ lut(_prompt, _options = {}) {
1206
+ return Promise.resolve(packOnlyResponse("lut"));
1207
+ }
1208
+ shader(_prompt, _options = {}) {
1209
+ return Promise.resolve(packOnlyResponse("shader"));
1210
+ }
1211
+ fontTypeface(_prompt, _options = {}) {
1212
+ return Promise.resolve(packOnlyResponse("font_typeface"));
1213
+ }
1214
+ skybox(prompt, options = {}) {
1215
+ const { signal, headers, async: asyncMode, idempotencyKey, ...rest } = options;
1216
+ return this.generate(buildGeneratedAssetRequest("skybox", prompt, rest), { signal, headers, async: asyncMode, idempotencyKey });
1217
+ }
1218
+ decal(prompt, options = {}) {
1219
+ const { signal, headers, async: asyncMode, idempotencyKey, ...rest } = options;
1220
+ return this.generate(buildGeneratedAssetRequest("decal", prompt, rest), { signal, headers, async: asyncMode, idempotencyKey });
1221
+ }
1222
+ heightmap(prompt, options = {}) {
1223
+ const { signal, headers, async: asyncMode, idempotencyKey, ...rest } = options;
1224
+ return this.generate(buildGeneratedAssetRequest("heightmap", prompt, rest), { signal, headers, async: asyncMode, idempotencyKey });
1225
+ }
1226
+ video(promptOrRequest, options = {}) {
1227
+ if (typeof promptOrRequest === "string") {
1228
+ const { signal: signal2, headers: headers2, async: asyncMode2, idempotencyKey: idempotencyKey2, ...rest2 } = options;
1229
+ return this.generate(buildVideoRequest(promptOrRequest, rest2), { signal: signal2, headers: headers2, async: asyncMode2, idempotencyKey: idempotencyKey2 });
1230
+ }
1231
+ const { prompt, signal, headers, async: asyncMode, idempotencyKey, ...rest } = promptOrRequest;
1232
+ return this.generate(buildVideoRequest(prompt, rest), { signal, headers, async: asyncMode, idempotencyKey });
1233
+ }
1234
+ audio(prompt, options = {}) {
1235
+ const { signal, headers, async: asyncMode, idempotencyKey, ...rest } = options;
1236
+ return this.generate(buildAudioRequest(prompt, rest), { signal, headers, async: asyncMode, idempotencyKey });
1237
+ }
1238
+ music(prompt, options = {}) {
1239
+ const { signal, headers, async: asyncMode, idempotencyKey, ...rest } = options;
1240
+ return this.generate(buildMusicRequest(prompt, rest), { signal, headers, async: asyncMode, idempotencyKey });
1241
+ }
1242
+ tts(prompt, options = {}) {
1243
+ const { signal, headers, async: asyncMode, idempotencyKey, ...rest } = options;
1244
+ return this.generate(buildTtsRequest(prompt, rest), { signal, headers, async: asyncMode, idempotencyKey });
1245
+ }
1246
+ model3d(prompt, options = {}) {
1247
+ const { signal, headers, async: asyncMode, idempotencyKey, ...rest } = options;
1248
+ return this.generate(buildModel3dRequest(prompt, rest), { signal, headers, async: asyncMode, idempotencyKey });
1249
+ }
1250
+ character(prompt, options = {}) {
1251
+ const { signal, headers, async: asyncMode, idempotencyKey, ...rest } = options;
1252
+ return this.generate(buildCharacterRequest(prompt, rest), { signal, headers, async: asyncMode, idempotencyKey });
1253
+ }
1254
+ prop(prompt, options = {}) {
1255
+ const { signal, headers, async: asyncMode, idempotencyKey, ...rest } = options;
1256
+ return this.generate(buildPropRequest(prompt, rest), { signal, headers, async: asyncMode, idempotencyKey });
1257
+ }
1258
+ sprite(prompt, options = {}) {
1259
+ const { signal, headers, async: asyncMode, idempotencyKey, ...rest } = options;
1260
+ return this.generate(buildSpriteRequest(prompt, rest), { signal, headers, async: asyncMode, idempotencyKey });
1261
+ }
1262
+ world(prompt, options = {}) {
1263
+ const { signal, headers, async: asyncMode, idempotencyKey, ...rest } = options;
1264
+ return this.generate(buildWorldRequest(prompt, rest), { signal, headers, async: asyncMode, idempotencyKey });
1265
+ }
1266
+ text(prompt, options = {}) {
1267
+ const { signal, headers, async: asyncMode, idempotencyKey, ...rest } = options;
1268
+ return this.generate(buildTextRequest(prompt, rest), { signal, headers, async: asyncMode, idempotencyKey });
1269
+ }
1270
+ async request(method, path, options = {}) {
1271
+ const url = new URL(path, ensureTrailingSlash4(this.baseUrl));
1272
+ appendQuery(url, options.query);
1273
+ const headers = new Headers(this.defaultHeaders);
1274
+ headers.set("authorization", `Bearer ${this.apiKey}`);
1275
+ applyHeaders(headers, options.headers);
1276
+ let body;
1277
+ if (options.form) {
1278
+ body = options.form;
1279
+ } else if (options.body !== void 0) {
1280
+ headers.set("content-type", "application/json");
1281
+ body = JSON.stringify(options.body);
1282
+ }
1283
+ let response;
1284
+ try {
1285
+ response = await this.fetchImpl(url.toString(), {
1286
+ method,
1287
+ headers,
1288
+ body,
1289
+ signal: options.signal
1290
+ });
1291
+ } catch (error) {
1292
+ throw new AssetForgeError(
1293
+ error instanceof Error ? error.message : String(error),
1294
+ {
1295
+ code: options.signal?.aborted ? "ABORTED" : "NETWORK_ERROR",
1296
+ cause: error
1297
+ }
1298
+ );
1299
+ }
1300
+ const text = await response.text();
1301
+ const payload = parseJson2(text);
1302
+ const envelope = isEnvelope(payload) ? payload : null;
1303
+ if (envelope?.ok) {
1304
+ return envelope.data;
1305
+ }
1306
+ if (!response.ok || envelope?.ok === false) {
1307
+ throw createApiError(response.status, envelope?.command, envelope?.ok === false ? envelope.error : payload);
1308
+ }
1309
+ return payload;
1310
+ }
1311
+ async submitAndMaybeWait(path, body, options, normalize) {
1312
+ const ack = await this.request("POST", path, {
1313
+ body,
1314
+ signal: options.signal,
1315
+ headers: submitHeaders({ ...options, async: true })
1316
+ });
1317
+ if (options.async) {
1318
+ return ack;
1319
+ }
1320
+ const job = await this.job.wait(ack.job_id, {
1321
+ signal: options.signal,
1322
+ headers: options.headers
1323
+ });
1324
+ if (job.response === void 0 || job.response === null) {
1325
+ throw new AssetForgeError(`Completed job ${ack.job_id} is missing response payload`, {
1326
+ code: "JOB_RESPONSE_MISSING",
1327
+ details: job
1328
+ });
1329
+ }
1330
+ const response = job.response;
1331
+ return normalize ? normalize(response) : response;
1332
+ }
1333
+ };
1334
+ function createApiError(status, command, error) {
1335
+ if (typeof error === "string") {
1336
+ return new AssetForgeError(error, {
1337
+ code: defaultErrorCode2(status),
1338
+ status,
1339
+ command,
1340
+ details: error
1341
+ });
1342
+ }
1343
+ if (isApiErrorPayload(error)) {
1344
+ return new AssetForgeError(error.message ?? `AssetForge API error (${status})`, {
1345
+ code: error.code ?? defaultErrorCode2(status),
1346
+ status,
1347
+ command,
1348
+ details: error
1349
+ });
1350
+ }
1351
+ return new AssetForgeError(
1352
+ typeof error === "object" && error !== null ? JSON.stringify(error) : `AssetForge API error (${status})`,
1353
+ {
1354
+ code: defaultErrorCode2(status),
1355
+ status,
1356
+ command,
1357
+ details: error
1358
+ }
1359
+ );
1360
+ }
1361
+ function defaultErrorCode2(status) {
1362
+ if (status === 401) return "UNAUTHORIZED";
1363
+ if (status === 403) return "FORBIDDEN";
1364
+ if (status === 404) return "NOT_FOUND";
1365
+ if (status === 409) return "CONFLICT";
1366
+ return "API_ERROR";
1367
+ }
1368
+ function appendQuery(url, query) {
1369
+ if (!query) {
1370
+ return;
1371
+ }
1372
+ for (const [key, value] of Object.entries(query)) {
1373
+ if (value === void 0 || value === null) {
1374
+ continue;
1375
+ }
1376
+ url.searchParams.set(key, String(value));
1377
+ }
1378
+ }
1379
+ function applyHeaders(target, source) {
1380
+ if (!source) {
1381
+ return;
1382
+ }
1383
+ const extra = new Headers(source);
1384
+ extra.forEach((value, key) => {
1385
+ target.set(key, value);
1386
+ });
1387
+ }
1388
+ function submitHeaders(options) {
1389
+ const headers = new Headers(options.headers);
1390
+ if (options.async) {
1391
+ headers.set("prefer", "respond-async");
1392
+ }
1393
+ if (options.idempotencyKey) {
1394
+ headers.set("idempotency-key", options.idempotencyKey);
1395
+ }
1396
+ return headers;
1397
+ }
1398
+ function ensureTrailingSlash4(value) {
1399
+ return value.endsWith("/") ? value : `${value}/`;
1400
+ }
1401
+ function parseJson2(text) {
1402
+ if (!text) {
1403
+ return null;
1404
+ }
1405
+ try {
1406
+ return JSON.parse(text);
1407
+ } catch {
1408
+ return text;
1409
+ }
1410
+ }
1411
+ function isEnvelope(value) {
1412
+ return typeof value === "object" && value !== null && !Array.isArray(value) && "ok" in value;
1413
+ }
1414
+ function isApiErrorPayload(value) {
1415
+ return typeof value === "object" && value !== null && ("message" in value || "code" in value);
1416
+ }
1417
+ function packOnlyResponse(assetType) {
1418
+ return {
1419
+ ok: false,
1420
+ pack_only: true,
1421
+ asset_type: assetType,
1422
+ message: "use library packs"
1423
+ };
1424
+ }
1425
+ export {
1426
+ AssetForge,
1427
+ AssetForgeAssets,
1428
+ AssetForgeError,
1429
+ AssetForgeJobs,
1430
+ AssetForgeLibrary,
1431
+ AssetForgeProviders,
1432
+ AssetForgeStreamNamespace,
1433
+ DEFAULT_BASE_URL,
1434
+ buildAudioRequest,
1435
+ buildCharacterRequest,
1436
+ buildImageRequest,
1437
+ buildModel3dRequest,
1438
+ buildMusicRequest,
1439
+ buildPropRequest,
1440
+ buildSpriteRequest,
1441
+ buildTextRequest,
1442
+ buildTtsRequest,
1443
+ buildVideoRequest,
1444
+ buildWorldRequest,
1445
+ normalizeGenerateResponse,
1446
+ subscribeAllJobs,
1447
+ subscribeJob,
1448
+ toAssetForgeError
1449
+ };