sonilo 0.1.0 → 0.2.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/README.md +64 -5
- package/dist/index.cjs +239 -22
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +137 -4
- package/dist/index.d.ts +137 -4
- package/dist/index.js +234 -22
- package/dist/index.js.map +1 -1
- package/package.json +4 -2
package/README.md
CHANGED
|
@@ -36,6 +36,27 @@ const track = await sonilo.videoToMusic.generate({
|
|
|
36
36
|
await sonilo.videoToMusic.generate({ videoUrl: "https://example.com/clip.mp4" });
|
|
37
37
|
```
|
|
38
38
|
|
|
39
|
+
## Configuration
|
|
40
|
+
|
|
41
|
+
```ts
|
|
42
|
+
const client = new SoniloClient({
|
|
43
|
+
apiKey: "sk_...", // defaults to SONILO_API_KEY
|
|
44
|
+
baseUrl: "https://api.sonilo.com",
|
|
45
|
+
timeout: 600_000, // milliseconds, default 600000 (10 minutes)
|
|
46
|
+
});
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
`timeout` bounds one-shot requests (account, tasks, SFX submits) and
|
|
50
|
+
`download()` — it protects against a stalled connection hanging forever.
|
|
51
|
+
It does **not** bound streaming music generation
|
|
52
|
+
(`textToMusic`/`videoToMusic` `.stream()`/`.generate()`): those hold the
|
|
53
|
+
response body open for as long as generation takes, so an absolute timeout
|
|
54
|
+
would kill a healthy long-running stream. Pass your own `signal` in
|
|
55
|
+
`TextToMusicParams`/`VideoToMusicParams` (e.g. from an `AbortController`,
|
|
56
|
+
or `AbortSignal.timeout(ms)` for an absolute cap) to bound or cancel a
|
|
57
|
+
music stream instead — it's forwarded to `fetch` as-is and never
|
|
58
|
+
rewrapped as `RequestTimeoutError`.
|
|
59
|
+
|
|
39
60
|
## Streaming
|
|
40
61
|
|
|
41
62
|
```ts
|
|
@@ -65,6 +86,37 @@ await sonilo.textToMusic.generate({
|
|
|
65
86
|
});
|
|
66
87
|
```
|
|
67
88
|
|
|
89
|
+
## Sound effects (async tasks)
|
|
90
|
+
|
|
91
|
+
SFX endpoints are asynchronous: submitting returns a `task_id`, and the result
|
|
92
|
+
is fetched by polling. `generate()` wraps submit + poll:
|
|
93
|
+
|
|
94
|
+
```ts
|
|
95
|
+
import { SoniloClient, download } from "sonilo";
|
|
96
|
+
import { writeFile } from "node:fs/promises";
|
|
97
|
+
|
|
98
|
+
const client = new SoniloClient();
|
|
99
|
+
const result = await client.textToSfx.generate({ prompt: "glass shattering", duration: 5 });
|
|
100
|
+
await writeFile("sfx.m4a", await download(result.audio));
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
Or control polling yourself:
|
|
104
|
+
|
|
105
|
+
```ts
|
|
106
|
+
const task = await client.videoToSfx.submit({
|
|
107
|
+
video: "clip.mp4", // Node.js path; pass File/Blob in the browser
|
|
108
|
+
segments: [{ start: 0, end: 2.5, prompt: "footsteps on gravel" }],
|
|
109
|
+
audioFormat: "wav",
|
|
110
|
+
});
|
|
111
|
+
const result = await client.tasks.wait(task.task_id, { pollInterval: 2000, timeout: 600000 });
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
`tasks.get(taskId)` fetches state once and never throws on a failed task;
|
|
115
|
+
`tasks.wait()` / `generate()` throw `TaskFailedError` (with `.code`,
|
|
116
|
+
`.refunded`) on failure and `TaskTimeoutError` if the deadline passes — the
|
|
117
|
+
task keeps running server-side and can still be polled afterwards. Result URLs
|
|
118
|
+
are presigned and expire; download promptly or re-fetch via `tasks.get`.
|
|
119
|
+
|
|
68
120
|
## Account
|
|
69
121
|
|
|
70
122
|
```ts
|
|
@@ -77,8 +129,15 @@ const usage = await sonilo.account.usage({ days: 7 });
|
|
|
77
129
|
All errors extend `SoniloError`: `AuthenticationError` (401),
|
|
78
130
|
`PaymentRequiredError` (402), `RateLimitError` (429, `.retryAfter`),
|
|
79
131
|
`BadRequestError` (400/413/422, `.detail`), `APIError` (anything else),
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
132
|
+
`GenerationError` for failures mid-stream, `TaskFailedError` (`.code`,
|
|
133
|
+
`.taskId`, `.refunded`) for a failed SFX task, `TaskTimeoutError`
|
|
134
|
+
(`.taskId`) when `tasks.wait()` / `generate()` hits its deadline, and
|
|
135
|
+
`RequestTimeoutError` when a one-shot request or `download()` is aborted
|
|
136
|
+
by its own `timeout` (a caller-supplied `AbortSignal` is never rewrapped
|
|
137
|
+
this way, and streaming music generation is never subject to this timeout
|
|
138
|
+
at all).
|
|
139
|
+
|
|
140
|
+
Every `APIError` also carries `.status`, `.body` (the parsed response),
|
|
141
|
+
`.code` (the API's error code, e.g. `"rate_limit_exceeded"`), and `.errors`
|
|
142
|
+
(the validation detail array on a 422), in addition to any subclass-specific
|
|
143
|
+
properties above.
|
package/dist/index.cjs
CHANGED
|
@@ -23,12 +23,17 @@ __export(index_exports, {
|
|
|
23
23
|
APIError: () => APIError,
|
|
24
24
|
AuthenticationError: () => AuthenticationError,
|
|
25
25
|
BadRequestError: () => BadRequestError,
|
|
26
|
+
DEFAULT_TIMEOUT_MS: () => DEFAULT_TIMEOUT_MS,
|
|
26
27
|
GenerationError: () => GenerationError,
|
|
27
28
|
PaymentRequiredError: () => PaymentRequiredError,
|
|
28
29
|
RateLimitError: () => RateLimitError,
|
|
30
|
+
RequestTimeoutError: () => RequestTimeoutError,
|
|
29
31
|
SoniloClient: () => SoniloClient,
|
|
30
32
|
SoniloError: () => SoniloError,
|
|
33
|
+
TaskFailedError: () => TaskFailedError,
|
|
34
|
+
TaskTimeoutError: () => TaskTimeoutError,
|
|
31
35
|
VERSION: () => VERSION,
|
|
36
|
+
download: () => download,
|
|
32
37
|
isAudioChunkEvent: () => isAudioChunkEvent,
|
|
33
38
|
isErrorEvent: () => isErrorEvent
|
|
34
39
|
});
|
|
@@ -46,6 +51,9 @@ var APIError = class extends SoniloError {
|
|
|
46
51
|
super(message);
|
|
47
52
|
this.status = status;
|
|
48
53
|
this.body = body;
|
|
54
|
+
const parsed = body;
|
|
55
|
+
this.code = typeof parsed?.code === "string" ? parsed.code : void 0;
|
|
56
|
+
this.errors = Array.isArray(parsed?.errors) ? parsed.errors : void 0;
|
|
49
57
|
}
|
|
50
58
|
};
|
|
51
59
|
var AuthenticationError = class extends APIError {
|
|
@@ -55,6 +63,9 @@ var PaymentRequiredError = class extends APIError {
|
|
|
55
63
|
var BadRequestError = class extends APIError {
|
|
56
64
|
get detail() {
|
|
57
65
|
const body = this.body;
|
|
66
|
+
if (typeof body?.message === "string" && body.message) {
|
|
67
|
+
return body.message;
|
|
68
|
+
}
|
|
58
69
|
return typeof body?.detail === "string" ? body.detail : void 0;
|
|
59
70
|
}
|
|
60
71
|
};
|
|
@@ -70,6 +81,25 @@ var GenerationError = class extends SoniloError {
|
|
|
70
81
|
this.code = code;
|
|
71
82
|
}
|
|
72
83
|
};
|
|
84
|
+
var TaskFailedError = class extends SoniloError {
|
|
85
|
+
constructor(message, opts) {
|
|
86
|
+
super(message);
|
|
87
|
+
this.code = opts.code;
|
|
88
|
+
this.taskId = opts.taskId;
|
|
89
|
+
this.refunded = opts.refunded;
|
|
90
|
+
}
|
|
91
|
+
};
|
|
92
|
+
var TaskTimeoutError = class extends SoniloError {
|
|
93
|
+
constructor(message, taskId) {
|
|
94
|
+
super(message);
|
|
95
|
+
this.taskId = taskId;
|
|
96
|
+
}
|
|
97
|
+
};
|
|
98
|
+
var RequestTimeoutError = class extends SoniloError {
|
|
99
|
+
};
|
|
100
|
+
function isTimeoutSignalError(err) {
|
|
101
|
+
return err instanceof Error && (err.name === "TimeoutError" || err.name === "AbortError");
|
|
102
|
+
}
|
|
73
103
|
async function errorFromResponse(res) {
|
|
74
104
|
const text = await res.text().catch(() => "");
|
|
75
105
|
let body = text;
|
|
@@ -77,8 +107,25 @@ async function errorFromResponse(res) {
|
|
|
77
107
|
body = JSON.parse(text);
|
|
78
108
|
} catch {
|
|
79
109
|
}
|
|
80
|
-
const
|
|
81
|
-
const
|
|
110
|
+
const parsed = body;
|
|
111
|
+
const rawMessage = typeof parsed?.message === "string" && parsed.message ? parsed.message : void 0;
|
|
112
|
+
const rawDetail = parsed?.detail;
|
|
113
|
+
const isDetailAbsent = rawDetail === void 0 || rawDetail === null || rawDetail === "";
|
|
114
|
+
let reason;
|
|
115
|
+
if (rawMessage !== void 0) {
|
|
116
|
+
reason = rawMessage;
|
|
117
|
+
} else if (isDetailAbsent) {
|
|
118
|
+
reason = res.statusText || "request failed";
|
|
119
|
+
} else if (typeof rawDetail === "string") {
|
|
120
|
+
reason = rawDetail;
|
|
121
|
+
} else {
|
|
122
|
+
try {
|
|
123
|
+
reason = JSON.stringify(rawDetail);
|
|
124
|
+
} catch {
|
|
125
|
+
reason = res.statusText || "request failed";
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
const message = `HTTP ${res.status}: ${reason}`;
|
|
82
129
|
switch (res.status) {
|
|
83
130
|
case 401:
|
|
84
131
|
return new AuthenticationError(message, res.status, body);
|
|
@@ -114,6 +161,56 @@ var Account = class {
|
|
|
114
161
|
}
|
|
115
162
|
};
|
|
116
163
|
|
|
164
|
+
// src/resources/tasks.ts
|
|
165
|
+
var DEFAULT_POLL_INTERVAL_MS = 2e3;
|
|
166
|
+
var DEFAULT_WAIT_TIMEOUT_MS = 6e5;
|
|
167
|
+
var sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
168
|
+
function validateWaitArgs(pollInterval, timeout) {
|
|
169
|
+
if (pollInterval < 0) {
|
|
170
|
+
throw new SoniloError(`pollInterval must be >= 0, got ${pollInterval}`);
|
|
171
|
+
}
|
|
172
|
+
if (timeout < 0) {
|
|
173
|
+
throw new SoniloError(`timeout must be >= 0, got ${timeout}`);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
var Tasks = class {
|
|
177
|
+
constructor(client) {
|
|
178
|
+
this.client = client;
|
|
179
|
+
}
|
|
180
|
+
/** Fetch current task state. Never throws on a failed status. */
|
|
181
|
+
async get(taskId) {
|
|
182
|
+
const res = await this.client.request(`/v1/tasks/${encodeURIComponent(taskId)}`);
|
|
183
|
+
return await res.json();
|
|
184
|
+
}
|
|
185
|
+
/** Poll until the task is terminal; throw on failure or deadline. */
|
|
186
|
+
async wait(taskId, opts = {}) {
|
|
187
|
+
const pollInterval = opts.pollInterval ?? DEFAULT_POLL_INTERVAL_MS;
|
|
188
|
+
const timeout = opts.timeout ?? DEFAULT_WAIT_TIMEOUT_MS;
|
|
189
|
+
validateWaitArgs(pollInterval, timeout);
|
|
190
|
+
const deadline = performance.now() + timeout;
|
|
191
|
+
for (; ; ) {
|
|
192
|
+
const result = await this.get(taskId);
|
|
193
|
+
if (result.status === "succeeded") return result;
|
|
194
|
+
if (result.status === "failed") {
|
|
195
|
+
const message = result.error?.message || "Generation failed";
|
|
196
|
+
throw new TaskFailedError(`Task ${taskId} failed: ${message}`, {
|
|
197
|
+
code: result.error?.code,
|
|
198
|
+
taskId,
|
|
199
|
+
refunded: result.refunded
|
|
200
|
+
});
|
|
201
|
+
}
|
|
202
|
+
const remaining = deadline - performance.now();
|
|
203
|
+
if (remaining <= 0) {
|
|
204
|
+
throw new TaskTimeoutError(
|
|
205
|
+
`Task ${taskId} still processing after ${timeout}ms; it may finish later \u2014 resume with tasks.wait or tasks.get`,
|
|
206
|
+
taskId
|
|
207
|
+
);
|
|
208
|
+
}
|
|
209
|
+
await sleep(Math.min(pollInterval, remaining));
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
};
|
|
213
|
+
|
|
117
214
|
// src/streaming.ts
|
|
118
215
|
function decodeBase64(b64) {
|
|
119
216
|
const bin = atob(b64);
|
|
@@ -122,9 +219,14 @@ function decodeBase64(b64) {
|
|
|
122
219
|
return out;
|
|
123
220
|
}
|
|
124
221
|
function toEvent(line) {
|
|
125
|
-
const
|
|
222
|
+
const parsed = JSON.parse(line);
|
|
223
|
+
if (typeof parsed !== "object" || parsed === null) return null;
|
|
224
|
+
const raw = parsed;
|
|
126
225
|
if (raw.type === "audio_chunk" && typeof raw.data === "string") {
|
|
127
|
-
|
|
226
|
+
try {
|
|
227
|
+
return { ...raw, type: "audio_chunk", data: decodeBase64(raw.data) };
|
|
228
|
+
} catch {
|
|
229
|
+
}
|
|
128
230
|
}
|
|
129
231
|
return raw;
|
|
130
232
|
}
|
|
@@ -141,12 +243,18 @@ async function* parseNdjson(body) {
|
|
|
141
243
|
while ((nl = buffer.indexOf("\n")) !== -1) {
|
|
142
244
|
const line = buffer.slice(0, nl).trim();
|
|
143
245
|
buffer = buffer.slice(nl + 1);
|
|
144
|
-
if (line)
|
|
246
|
+
if (line) {
|
|
247
|
+
const ev = toEvent(line);
|
|
248
|
+
if (ev !== null) yield ev;
|
|
249
|
+
}
|
|
145
250
|
}
|
|
146
251
|
}
|
|
147
252
|
buffer += decoder.decode();
|
|
148
253
|
const tail = buffer.trim();
|
|
149
|
-
if (tail)
|
|
254
|
+
if (tail) {
|
|
255
|
+
const ev = toEvent(tail);
|
|
256
|
+
if (ev !== null) yield ev;
|
|
257
|
+
}
|
|
150
258
|
} finally {
|
|
151
259
|
await reader.cancel().catch(() => {
|
|
152
260
|
});
|
|
@@ -158,7 +266,12 @@ async function collectTrack(events) {
|
|
|
158
266
|
let cost;
|
|
159
267
|
let sawComplete = false;
|
|
160
268
|
for await (const ev of events) {
|
|
161
|
-
if (ev.type === "audio_chunk"
|
|
269
|
+
if (ev.type === "audio_chunk") {
|
|
270
|
+
if (!(ev.data instanceof Uint8Array)) {
|
|
271
|
+
throw new GenerationError(
|
|
272
|
+
"received a malformed audio_chunk event (missing or non-decodable data)"
|
|
273
|
+
);
|
|
274
|
+
}
|
|
162
275
|
chunks.push(ev.data);
|
|
163
276
|
} else if (ev.type === "title" && typeof ev.title === "string") {
|
|
164
277
|
title = ev.title;
|
|
@@ -166,7 +279,7 @@ async function collectTrack(events) {
|
|
|
166
279
|
const { type: _type, ...rest } = ev;
|
|
167
280
|
cost = rest;
|
|
168
281
|
} else if (ev.type === "error") {
|
|
169
|
-
const message = typeof ev.message === "string" ? ev.message : "generation failed";
|
|
282
|
+
const message = typeof ev.message === "string" && ev.message !== "" ? ev.message : "generation failed";
|
|
170
283
|
const code = typeof ev.code === "string" ? ev.code : void 0;
|
|
171
284
|
throw new GenerationError(message, code);
|
|
172
285
|
} else if (ev.type === "complete") {
|
|
@@ -199,10 +312,11 @@ var TextToMusic = class {
|
|
|
199
312
|
if (params.segments !== void 0) {
|
|
200
313
|
form.set("segments", JSON.stringify(params.segments));
|
|
201
314
|
}
|
|
202
|
-
const res = await this.client.request(
|
|
203
|
-
|
|
204
|
-
body: form
|
|
205
|
-
|
|
315
|
+
const res = await this.client.request(
|
|
316
|
+
"/v1/text-to-music",
|
|
317
|
+
{ method: "POST", body: form, signal: params.signal },
|
|
318
|
+
{ timeout: null }
|
|
319
|
+
);
|
|
206
320
|
if (!res.body) throw new SoniloError("Response has no body");
|
|
207
321
|
yield* parseNdjson(res.body);
|
|
208
322
|
}
|
|
@@ -270,10 +384,11 @@ var VideoToMusic = class {
|
|
|
270
384
|
if (params.segments !== void 0) {
|
|
271
385
|
form.set("segments", JSON.stringify(params.segments));
|
|
272
386
|
}
|
|
273
|
-
const res = await this.client.request(
|
|
274
|
-
|
|
275
|
-
body: form
|
|
276
|
-
|
|
387
|
+
const res = await this.client.request(
|
|
388
|
+
"/v1/video-to-music",
|
|
389
|
+
{ method: "POST", body: form, signal: params.signal },
|
|
390
|
+
{ timeout: null }
|
|
391
|
+
);
|
|
277
392
|
if (!res.body) throw new SoniloError("Response has no body");
|
|
278
393
|
yield* parseNdjson(res.body);
|
|
279
394
|
}
|
|
@@ -282,11 +397,67 @@ var VideoToMusic = class {
|
|
|
282
397
|
}
|
|
283
398
|
};
|
|
284
399
|
|
|
400
|
+
// src/resources/textToSfx.ts
|
|
401
|
+
var TextToSfx = class {
|
|
402
|
+
constructor(client) {
|
|
403
|
+
this.client = client;
|
|
404
|
+
}
|
|
405
|
+
async submit(params) {
|
|
406
|
+
const form = new FormData();
|
|
407
|
+
form.set("prompt", params.prompt);
|
|
408
|
+
form.set("duration", String(params.duration));
|
|
409
|
+
if (params.audioFormat !== void 0) form.set("audio_format", params.audioFormat);
|
|
410
|
+
const res = await this.client.request("/v1/text-to-sfx", {
|
|
411
|
+
method: "POST",
|
|
412
|
+
body: form
|
|
413
|
+
});
|
|
414
|
+
return await res.json();
|
|
415
|
+
}
|
|
416
|
+
async generate(params, opts) {
|
|
417
|
+
const task = await this.submit(params);
|
|
418
|
+
return this.client.tasks.wait(task.task_id, opts);
|
|
419
|
+
}
|
|
420
|
+
};
|
|
421
|
+
|
|
422
|
+
// src/resources/videoToSfx.ts
|
|
423
|
+
var VideoToSfx = class {
|
|
424
|
+
constructor(client) {
|
|
425
|
+
this.client = client;
|
|
426
|
+
}
|
|
427
|
+
async submit(params) {
|
|
428
|
+
if (params.video === void 0 === (params.videoUrl === void 0)) {
|
|
429
|
+
throw new SoniloError("Provide exactly one of video or videoUrl");
|
|
430
|
+
}
|
|
431
|
+
const form = new FormData();
|
|
432
|
+
if (params.video !== void 0) {
|
|
433
|
+
const { blob, filename } = await toUploadBlob(params.video);
|
|
434
|
+
form.set("video", blob, filename);
|
|
435
|
+
} else {
|
|
436
|
+
form.set("video_url", params.videoUrl);
|
|
437
|
+
}
|
|
438
|
+
if (params.prompt !== void 0) form.set("prompt", params.prompt);
|
|
439
|
+
if (params.segments !== void 0) {
|
|
440
|
+
form.set("segments", JSON.stringify(params.segments));
|
|
441
|
+
}
|
|
442
|
+
if (params.audioFormat !== void 0) form.set("audio_format", params.audioFormat);
|
|
443
|
+
const res = await this.client.request("/v1/video-to-sfx", {
|
|
444
|
+
method: "POST",
|
|
445
|
+
body: form
|
|
446
|
+
});
|
|
447
|
+
return await res.json();
|
|
448
|
+
}
|
|
449
|
+
async generate(params, opts) {
|
|
450
|
+
const task = await this.submit(params);
|
|
451
|
+
return this.client.tasks.wait(task.task_id, opts);
|
|
452
|
+
}
|
|
453
|
+
};
|
|
454
|
+
|
|
285
455
|
// src/version.ts
|
|
286
|
-
var VERSION = "0.
|
|
456
|
+
var VERSION = "0.2.0";
|
|
287
457
|
|
|
288
458
|
// src/client.ts
|
|
289
459
|
var DEFAULT_BASE_URL = "https://api.sonilo.com";
|
|
460
|
+
var DEFAULT_TIMEOUT_MS = 6e5;
|
|
290
461
|
var SoniloClient = class {
|
|
291
462
|
constructor(options = {}) {
|
|
292
463
|
const envKey = typeof process !== "undefined" ? process.env?.SONILO_API_KEY : void 0;
|
|
@@ -299,22 +470,63 @@ var SoniloClient = class {
|
|
|
299
470
|
this.apiKey = apiKey;
|
|
300
471
|
this.baseUrl = (options.baseUrl ?? DEFAULT_BASE_URL).replace(/\/+$/, "");
|
|
301
472
|
this.fetchFn = (options.fetch ?? globalThis.fetch).bind(globalThis);
|
|
473
|
+
this.timeout = options.timeout ?? DEFAULT_TIMEOUT_MS;
|
|
302
474
|
this.account = new Account(this);
|
|
475
|
+
this.tasks = new Tasks(this);
|
|
303
476
|
this.textToMusic = new TextToMusic(this);
|
|
304
477
|
this.videoToMusic = new VideoToMusic(this);
|
|
478
|
+
this.textToSfx = new TextToSfx(this);
|
|
479
|
+
this.videoToSfx = new VideoToSfx(this);
|
|
305
480
|
}
|
|
306
|
-
/**
|
|
307
|
-
|
|
481
|
+
/**
|
|
482
|
+
* Perform an authenticated request; throws a typed error on non-2xx.
|
|
483
|
+
*
|
|
484
|
+
* `opts.timeout` overrides the client's default timeout for this call;
|
|
485
|
+
* pass `null` to disable the abort-on-timeout behavior entirely (used by
|
|
486
|
+
* the streaming music endpoints — see textToMusic.ts / videoToMusic.ts).
|
|
487
|
+
* A caller-supplied `init.signal` always wins over any timeout signal.
|
|
488
|
+
*/
|
|
489
|
+
async request(path, init = {}, opts = {}) {
|
|
308
490
|
const headers = new Headers(init.headers);
|
|
309
491
|
headers.set("Authorization", `Bearer ${this.apiKey}`);
|
|
310
492
|
headers.set("X-Sonilo-Client", "sdk-js");
|
|
311
493
|
headers.set("X-Sonilo-Client-Version", VERSION);
|
|
312
|
-
const
|
|
313
|
-
|
|
314
|
-
|
|
494
|
+
const timeout = opts.timeout === void 0 ? this.timeout : opts.timeout;
|
|
495
|
+
const ownsSignal = init.signal == null && timeout !== null;
|
|
496
|
+
const signal = init.signal ?? (timeout === null ? void 0 : AbortSignal.timeout(timeout));
|
|
497
|
+
try {
|
|
498
|
+
const res = await this.fetchFn(`${this.baseUrl}${path}`, { ...init, headers, signal });
|
|
499
|
+
if (!res.ok) throw await errorFromResponse(res);
|
|
500
|
+
return res;
|
|
501
|
+
} catch (err) {
|
|
502
|
+
if (ownsSignal && isTimeoutSignalError(err)) {
|
|
503
|
+
throw new RequestTimeoutError(`Request to ${path} timed out after ${timeout}ms`);
|
|
504
|
+
}
|
|
505
|
+
throw err;
|
|
506
|
+
}
|
|
315
507
|
}
|
|
316
508
|
};
|
|
317
509
|
|
|
510
|
+
// src/download.ts
|
|
511
|
+
async function download(media, fetchFn = globalThis.fetch, timeout = DEFAULT_TIMEOUT_MS) {
|
|
512
|
+
if (!media?.url) {
|
|
513
|
+
throw new SoniloError("No media to download");
|
|
514
|
+
}
|
|
515
|
+
let res;
|
|
516
|
+
try {
|
|
517
|
+
res = await fetchFn(media.url, { signal: AbortSignal.timeout(timeout) });
|
|
518
|
+
} catch (err) {
|
|
519
|
+
if (isTimeoutSignalError(err)) {
|
|
520
|
+
throw new RequestTimeoutError(`Download of ${media.url} timed out after ${timeout}ms`);
|
|
521
|
+
}
|
|
522
|
+
throw err;
|
|
523
|
+
}
|
|
524
|
+
if (!res.ok) {
|
|
525
|
+
throw new SoniloError(`Download failed: HTTP ${res.status}`);
|
|
526
|
+
}
|
|
527
|
+
return new Uint8Array(await res.arrayBuffer());
|
|
528
|
+
}
|
|
529
|
+
|
|
318
530
|
// src/types.ts
|
|
319
531
|
function isAudioChunkEvent(event) {
|
|
320
532
|
return event.type === "audio_chunk" && event.data instanceof Uint8Array;
|
|
@@ -327,12 +539,17 @@ function isErrorEvent(event) {
|
|
|
327
539
|
APIError,
|
|
328
540
|
AuthenticationError,
|
|
329
541
|
BadRequestError,
|
|
542
|
+
DEFAULT_TIMEOUT_MS,
|
|
330
543
|
GenerationError,
|
|
331
544
|
PaymentRequiredError,
|
|
332
545
|
RateLimitError,
|
|
546
|
+
RequestTimeoutError,
|
|
333
547
|
SoniloClient,
|
|
334
548
|
SoniloError,
|
|
549
|
+
TaskFailedError,
|
|
550
|
+
TaskTimeoutError,
|
|
335
551
|
VERSION,
|
|
552
|
+
download,
|
|
336
553
|
isAudioChunkEvent,
|
|
337
554
|
isErrorEvent
|
|
338
555
|
});
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/errors.ts","../src/resources/account.ts","../src/streaming.ts","../src/resources/textToMusic.ts","../src/upload.ts","../src/resources/videoToMusic.ts","../src/version.ts","../src/client.ts","../src/types.ts"],"sourcesContent":["export { SoniloClient, type SoniloClientOptions } from \"./client.js\";\nexport {\n APIError,\n AuthenticationError,\n BadRequestError,\n GenerationError,\n PaymentRequiredError,\n RateLimitError,\n SoniloError,\n} from \"./errors.js\";\nexport { VERSION } from \"./version.js\";\nexport type {\n AccountServices,\n AudioChunkEvent,\n CompleteEvent,\n CostEvent,\n CostInfo,\n DailyUsage,\n ErrorEvent,\n Segment,\n SegmentLabel,\n StreamEvent,\n TextToMusicParams,\n TitleEvent,\n Track,\n UnknownEvent,\n UsageResponse,\n UsageSummary,\n VideoInput,\n VideoToMusicParams,\n} from \"./types.js\";\nexport { isAudioChunkEvent, isErrorEvent } from \"./types.js\";\n","export class SoniloError extends Error {\n constructor(message: string) {\n super(message);\n this.name = new.target.name;\n }\n}\n\nexport class APIError extends SoniloError {\n readonly status: number;\n readonly body: unknown;\n\n constructor(message: string, status: number, body?: unknown) {\n super(message);\n this.status = status;\n this.body = body;\n }\n}\n\nexport class AuthenticationError extends APIError {}\n\nexport class PaymentRequiredError extends APIError {}\n\nexport class BadRequestError extends APIError {\n get detail(): string | undefined {\n const body = this.body as { detail?: unknown } | undefined;\n return typeof body?.detail === \"string\" ? body.detail : undefined;\n }\n}\n\nexport class RateLimitError extends APIError {\n readonly retryAfter?: number;\n\n constructor(message: string, status: number, body?: unknown, retryAfter?: number) {\n super(message, status, body);\n this.retryAfter = retryAfter;\n }\n}\n\n/** Raised by generate() when an `error` event arrives mid-stream. */\nexport class GenerationError extends SoniloError {\n readonly code?: string;\n\n constructor(message: string, code?: string) {\n super(message);\n this.code = code;\n }\n}\n\nexport async function errorFromResponse(res: Response): Promise<APIError> {\n const text = await res.text().catch(() => \"\");\n let body: unknown = text;\n try {\n body = JSON.parse(text);\n } catch {\n // keep raw text\n }\n const detail =\n typeof (body as { detail?: unknown })?.detail === \"string\"\n ? (body as { detail: string }).detail\n : res.statusText || \"request failed\";\n const message = `HTTP ${res.status}: ${detail}`;\n\n switch (res.status) {\n case 401:\n return new AuthenticationError(message, res.status, body);\n case 402:\n return new PaymentRequiredError(message, res.status, body);\n case 429: {\n const ra = res.headers.get(\"retry-after\");\n const retryAfter = ra !== null && ra !== \"\" && !Number.isNaN(Number(ra)) ? Number(ra) : undefined;\n return new RateLimitError(message, res.status, body, retryAfter);\n }\n case 400:\n case 413:\n case 422:\n return new BadRequestError(message, res.status, body);\n default:\n return new APIError(message, res.status, body);\n }\n}\n","import type { SoniloClient } from \"../client.js\";\nimport type { AccountServices, UsageResponse } from \"../types.js\";\n\nexport class Account {\n constructor(private readonly client: SoniloClient) {}\n\n async services(): Promise<AccountServices> {\n const res = await this.client.request(\"/v1/account/services\");\n return (await res.json()) as AccountServices;\n }\n\n async usage(params: { days?: number } = {}): Promise<UsageResponse> {\n const query = params.days !== undefined ? `?days=${params.days}` : \"\";\n const res = await this.client.request(`/v1/account/usage${query}`);\n return (await res.json()) as UsageResponse;\n }\n}\n","import { GenerationError } from \"./errors.js\";\nimport type { CostInfo, StreamEvent, Track } from \"./types.js\";\n\nexport function decodeBase64(b64: string): Uint8Array {\n const bin = atob(b64);\n const out = new Uint8Array(bin.length);\n for (let i = 0; i < bin.length; i++) out[i] = bin.charCodeAt(i);\n return out;\n}\n\nfunction toEvent(line: string): StreamEvent {\n const raw = JSON.parse(line) as { type: string; [key: string]: unknown };\n if (raw.type === \"audio_chunk\" && typeof raw.data === \"string\") {\n return { ...raw, type: \"audio_chunk\", data: decodeBase64(raw.data) };\n }\n return raw as StreamEvent;\n}\n\nexport async function* parseNdjson(\n body: ReadableStream<Uint8Array>,\n): AsyncGenerator<StreamEvent, void, undefined> {\n const reader = body.getReader();\n const decoder = new TextDecoder();\n let buffer = \"\";\n try {\n for (;;) {\n const { done, value } = await reader.read();\n if (done) break;\n buffer += decoder.decode(value, { stream: true });\n let nl: number;\n while ((nl = buffer.indexOf(\"\\n\")) !== -1) {\n const line = buffer.slice(0, nl).trim();\n buffer = buffer.slice(nl + 1);\n if (line) yield toEvent(line);\n }\n }\n buffer += decoder.decode();\n const tail = buffer.trim();\n if (tail) yield toEvent(tail);\n } finally {\n await reader.cancel().catch(() => {});\n }\n}\n\nexport async function collectTrack(events: AsyncIterable<StreamEvent>): Promise<Track> {\n const chunks: Uint8Array[] = [];\n let title: string | undefined;\n let cost: CostInfo | undefined;\n let sawComplete = false;\n\n for await (const ev of events) {\n if (ev.type === \"audio_chunk\" && ev.data instanceof Uint8Array) {\n chunks.push(ev.data);\n } else if (ev.type === \"title\" && typeof ev.title === \"string\") {\n title = ev.title;\n } else if (ev.type === \"cost\") {\n const { type: _type, ...rest } = ev;\n cost = rest as CostInfo;\n } else if (ev.type === \"error\") {\n const message = typeof ev.message === \"string\" ? ev.message : \"generation failed\";\n const code = typeof ev.code === \"string\" ? ev.code : undefined;\n throw new GenerationError(message, code);\n } else if (ev.type === \"complete\") {\n sawComplete = true;\n }\n // unknown event types: ignored\n }\n\n if (!sawComplete) {\n throw new GenerationError(\"stream ended before a 'complete' event (truncated response)\");\n }\n\n const total = chunks.reduce((n, c) => n + c.length, 0);\n const audio = new Uint8Array(total);\n let offset = 0;\n for (const c of chunks) {\n audio.set(c, offset);\n offset += c.length;\n }\n return { audio, title, cost };\n}\n","import type { SoniloClient } from \"../client.js\";\nimport { SoniloError } from \"../errors.js\";\nimport { collectTrack, parseNdjson } from \"../streaming.js\";\nimport type { StreamEvent, TextToMusicParams, Track } from \"../types.js\";\n\nexport class TextToMusic {\n constructor(private readonly client: SoniloClient) {}\n\n /** Stream raw generation events (audio chunks pre-decoded to bytes). */\n async *stream(params: TextToMusicParams): AsyncGenerator<StreamEvent, void, undefined> {\n const form = new FormData();\n form.set(\"prompt\", params.prompt);\n form.set(\"duration\", String(params.duration));\n if (params.segments !== undefined) {\n form.set(\"segments\", JSON.stringify(params.segments));\n }\n const res = await this.client.request(\"/v1/text-to-music\", {\n method: \"POST\",\n body: form,\n });\n if (!res.body) throw new SoniloError(\"Response has no body\");\n yield* parseNdjson(res.body);\n }\n\n /** Generate and buffer the whole track; throws GenerationError on stream errors. */\n generate(params: TextToMusicParams): Promise<Track> {\n return collectTrack(this.stream(params));\n }\n}\n","import { SoniloError } from \"./errors.js\";\nimport type { VideoInput } from \"./types.js\";\n\nconst DEFAULT_FILENAME = \"video.mp4\";\n\n/**\n * Normalize every accepted video input into a FormData-ready Blob.\n * String inputs are file paths and work only in Node.js; browsers must\n * pass File/Blob/bytes/streams.\n */\nexport async function toUploadBlob(\n video: VideoInput,\n): Promise<{ blob: Blob; filename: string }> {\n if (typeof video === \"string\") {\n const isNode =\n typeof process !== \"undefined\" && Boolean((process as { versions?: { node?: string } }).versions?.node);\n if (!isNode) {\n throw new SoniloError(\n \"File paths are only supported in Node.js; pass a File or Blob in the browser\",\n );\n }\n const fsModule = \"node:fs/promises\";\n const { readFile } = (await import(\n /* webpackIgnore: true */ /* @vite-ignore */ fsModule\n )) as typeof import(\"node:fs/promises\");\n const data = await readFile(video);\n const filename = video.split(/[\\\\/]/).pop() || DEFAULT_FILENAME;\n return { blob: new Blob([data]), filename };\n }\n if (typeof File !== \"undefined\" && video instanceof File) {\n return { blob: video, filename: video.name || DEFAULT_FILENAME };\n }\n if (video instanceof Blob) {\n return { blob: video, filename: DEFAULT_FILENAME };\n }\n if (video instanceof Uint8Array) {\n return { blob: new Blob([video as unknown as BlobPart]), filename: DEFAULT_FILENAME };\n }\n if (video instanceof ArrayBuffer) {\n return { blob: new Blob([video]), filename: DEFAULT_FILENAME };\n }\n if (video instanceof ReadableStream) {\n return { blob: await new Response(video).blob(), filename: DEFAULT_FILENAME };\n }\n throw new SoniloError(\"Unsupported video input type\");\n}\n","import type { SoniloClient } from \"../client.js\";\nimport { SoniloError } from \"../errors.js\";\nimport { collectTrack, parseNdjson } from \"../streaming.js\";\nimport { toUploadBlob } from \"../upload.js\";\nimport type { StreamEvent, Track, VideoToMusicParams } from \"../types.js\";\n\nexport class VideoToMusic {\n constructor(private readonly client: SoniloClient) {}\n\n async *stream(params: VideoToMusicParams): AsyncGenerator<StreamEvent, void, undefined> {\n if ((params.video === undefined) === (params.videoUrl === undefined)) {\n throw new SoniloError(\"Provide exactly one of video or videoUrl\");\n }\n const form = new FormData();\n if (params.video !== undefined) {\n const { blob, filename } = await toUploadBlob(params.video);\n form.set(\"video\", blob, filename);\n } else {\n form.set(\"video_url\", params.videoUrl as string);\n }\n if (params.prompt !== undefined) form.set(\"prompt\", params.prompt);\n if (params.segments !== undefined) {\n form.set(\"segments\", JSON.stringify(params.segments));\n }\n const res = await this.client.request(\"/v1/video-to-music\", {\n method: \"POST\",\n body: form,\n });\n if (!res.body) throw new SoniloError(\"Response has no body\");\n yield* parseNdjson(res.body);\n }\n\n generate(params: VideoToMusicParams): Promise<Track> {\n return collectTrack(this.stream(params));\n }\n}\n","export const VERSION = \"0.1.0\";\n","import { SoniloError, errorFromResponse } from \"./errors.js\";\nimport { Account } from \"./resources/account.js\";\nimport { TextToMusic } from \"./resources/textToMusic.js\";\nimport { VideoToMusic } from \"./resources/videoToMusic.js\";\nimport { VERSION } from \"./version.js\";\n\nexport interface SoniloClientOptions {\n /** Defaults to the SONILO_API_KEY environment variable (Node.js only). */\n apiKey?: string;\n /** Defaults to https://api.sonilo.com */\n baseUrl?: string;\n /** Injection point for tests and custom transports. */\n fetch?: typeof globalThis.fetch;\n}\n\nconst DEFAULT_BASE_URL = \"https://api.sonilo.com\";\n\nexport class SoniloClient {\n readonly baseUrl: string;\n private readonly apiKey: string;\n private readonly fetchFn: typeof globalThis.fetch;\n readonly account: Account;\n readonly textToMusic: TextToMusic;\n readonly videoToMusic: VideoToMusic;\n\n constructor(options: SoniloClientOptions = {}) {\n const envKey =\n typeof process !== \"undefined\" ? process.env?.SONILO_API_KEY : undefined;\n const apiKey = options.apiKey ?? envKey;\n if (!apiKey) {\n throw new SoniloError(\n \"Missing API key: pass { apiKey } or set the SONILO_API_KEY environment variable\",\n );\n }\n this.apiKey = apiKey;\n this.baseUrl = (options.baseUrl ?? DEFAULT_BASE_URL).replace(/\\/+$/, \"\");\n this.fetchFn = (options.fetch ?? globalThis.fetch).bind(globalThis);\n this.account = new Account(this);\n this.textToMusic = new TextToMusic(this);\n this.videoToMusic = new VideoToMusic(this);\n }\n\n /** Perform an authenticated request; throws a typed error on non-2xx. */\n async request(path: string, init: RequestInit = {}): Promise<Response> {\n const headers = new Headers(init.headers);\n headers.set(\"Authorization\", `Bearer ${this.apiKey}`);\n headers.set(\"X-Sonilo-Client\", \"sdk-js\");\n headers.set(\"X-Sonilo-Client-Version\", VERSION);\n const res = await this.fetchFn(`${this.baseUrl}${path}`, { ...init, headers });\n if (!res.ok) throw await errorFromResponse(res);\n return res;\n }\n}\n","export type SegmentLabel =\n | \"intro\"\n | \"verse\"\n | \"pre-chorus\"\n | \"chorus\"\n | \"bridge\"\n | \"break\"\n | \"silence\"\n | \"outro\"\n | \"none\";\n\nexport interface Segment {\n start: number;\n prompt: string;\n label?: SegmentLabel;\n}\n\n/** Monetary fields are strings, exactly as the backend serializes them. */\nexport interface CostInfo {\n billing_rate_per_sec: string;\n billing_before_discount: string;\n billing_after_discount: string;\n discount_factor: string;\n}\n\nexport interface AudioChunkEvent {\n type: \"audio_chunk\";\n /** Decoded from the wire's base64 by the SDK. */\n data: Uint8Array;\n}\n\nexport interface TitleEvent {\n type: \"title\";\n title: string;\n summary?: string;\n display_tags?: string[];\n [key: string]: unknown;\n}\n\nexport interface CompleteEvent {\n type: \"complete\";\n [key: string]: unknown;\n}\n\nexport interface ErrorEvent {\n type: \"error\";\n code?: string;\n message?: string;\n [key: string]: unknown;\n}\n\nexport interface CostEvent extends CostInfo {\n type: \"cost\";\n}\n\n/** Forward-compatibility: unrecognized event types are passed through. */\nexport interface UnknownEvent {\n type: string;\n [key: string]: unknown;\n}\n\nexport type StreamEvent =\n | AudioChunkEvent\n | TitleEvent\n | CompleteEvent\n | ErrorEvent\n | CostEvent\n | UnknownEvent;\n\nexport interface Track {\n audio: Uint8Array;\n title?: string;\n cost?: CostInfo;\n}\n\nexport interface TextToMusicParams {\n prompt: string;\n duration: number;\n segments?: Segment[];\n}\n\n/** string = file path (Node.js only). */\nexport type VideoInput =\n | File\n | Blob\n | Uint8Array\n | ArrayBuffer\n | ReadableStream<Uint8Array>\n | string;\n\nexport interface VideoToMusicParams {\n video?: VideoInput;\n videoUrl?: string;\n prompt?: string;\n segments?: Segment[];\n}\n\nexport interface AccountServices {\n available_services: string[];\n rpm_limit: number;\n concurrency_limit: number;\n discount_factor: number | string;\n max_upload_size_mb: number | null;\n}\n\nexport interface UsageSummary {\n total_requests: number;\n total_duration_seconds: number;\n total_cost: number | string;\n period_start: string;\n period_end: string;\n [key: string]: unknown;\n}\n\nexport interface DailyUsage {\n date: string;\n requests: number;\n duration_seconds: number;\n cost: number | string;\n}\n\nexport interface UsageResponse {\n summary: UsageSummary;\n daily: DailyUsage[];\n}\n\nexport function isAudioChunkEvent(event: StreamEvent): event is AudioChunkEvent {\n return event.type === \"audio_chunk\" && (event as AudioChunkEvent).data instanceof Uint8Array;\n}\n\nexport function isErrorEvent(event: StreamEvent): event is ErrorEvent {\n return event.type === \"error\";\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/errors.ts","../src/resources/account.ts","../src/resources/tasks.ts","../src/streaming.ts","../src/resources/textToMusic.ts","../src/upload.ts","../src/resources/videoToMusic.ts","../src/resources/textToSfx.ts","../src/resources/videoToSfx.ts","../src/version.ts","../src/client.ts","../src/download.ts","../src/types.ts"],"sourcesContent":["export { DEFAULT_TIMEOUT_MS, SoniloClient, type SoniloClientOptions } from \"./client.js\";\nexport {\n APIError,\n AuthenticationError,\n BadRequestError,\n GenerationError,\n PaymentRequiredError,\n RateLimitError,\n RequestTimeoutError,\n SoniloError,\n TaskFailedError,\n TaskTimeoutError,\n} from \"./errors.js\";\nexport { download } from \"./download.js\";\nexport { VERSION } from \"./version.js\";\nexport type {\n AccountServices,\n AudioChunkEvent,\n CompleteEvent,\n CostEvent,\n CostInfo,\n DailyUsage,\n ErrorEvent,\n Segment,\n SegmentLabel,\n SfxAudioFormat,\n SfxError,\n SfxMedia,\n SfxResult,\n SfxSegment,\n SfxTask,\n StreamEvent,\n TextToMusicParams,\n TextToSfxParams,\n TitleEvent,\n Track,\n UnknownEvent,\n UsageResponse,\n UsageSummary,\n VideoInput,\n VideoToMusicParams,\n VideoToSfxParams,\n WaitOptions,\n} from \"./types.js\";\nexport { isAudioChunkEvent, isErrorEvent } from \"./types.js\";\n","export class SoniloError extends Error {\n constructor(message: string) {\n super(message);\n this.name = new.target.name;\n }\n}\n\nexport class APIError extends SoniloError {\n readonly status: number;\n readonly body: unknown;\n /** The API's typed error code (e.g. \"rate_limit_exceeded\"), distinct from the HTTP status. */\n readonly code?: string;\n /** Per-field validation details, present on a 422. */\n readonly errors?: unknown[];\n\n constructor(message: string, status: number, body?: unknown) {\n super(message);\n this.status = status;\n this.body = body;\n const parsed = body as { code?: unknown; errors?: unknown } | undefined;\n this.code = typeof parsed?.code === \"string\" ? parsed.code : undefined;\n this.errors = Array.isArray(parsed?.errors) ? parsed.errors : undefined;\n }\n}\n\nexport class AuthenticationError extends APIError {}\n\nexport class PaymentRequiredError extends APIError {}\n\nexport class BadRequestError extends APIError {\n get detail(): string | undefined {\n const body = this.body as { message?: unknown; detail?: unknown } | undefined;\n if (typeof body?.message === \"string\" && body.message) {\n return body.message;\n }\n return typeof body?.detail === \"string\" ? body.detail : undefined;\n }\n}\n\nexport class RateLimitError extends APIError {\n readonly retryAfter?: number;\n\n constructor(message: string, status: number, body?: unknown, retryAfter?: number) {\n super(message, status, body);\n this.retryAfter = retryAfter;\n }\n}\n\n/** Raised by generate() when an `error` event arrives mid-stream. */\nexport class GenerationError extends SoniloError {\n readonly code?: string;\n\n constructor(message: string, code?: string) {\n super(message);\n this.code = code;\n }\n}\n\n/** Raised by tasks.wait()/generate() when an SFX task reaches `failed`. */\nexport class TaskFailedError extends SoniloError {\n readonly code?: string;\n readonly taskId: string;\n readonly refunded?: boolean;\n\n constructor(\n message: string,\n opts: { code?: string; taskId: string; refunded?: boolean },\n ) {\n super(message);\n this.code = opts.code;\n this.taskId = opts.taskId;\n this.refunded = opts.refunded;\n }\n}\n\n/** Poll deadline passed. The task may still finish server-side — resume with\n * tasks.wait(taskId) or tasks.get(taskId). */\nexport class TaskTimeoutError extends SoniloError {\n readonly taskId: string;\n\n constructor(message: string, taskId: string) {\n super(message);\n this.taskId = taskId;\n }\n}\n\n/** Raised when a one-shot request or download is aborted by its own timeout\n * signal (as opposed to a caller-supplied AbortSignal, which propagates\n * untouched). */\nexport class RequestTimeoutError extends SoniloError {}\n\n/**\n * True if `err` is the rejection produced when an `AbortSignal.timeout()`\n * we created fires. Used to distinguish \"our\" timeout aborts (which should be\n * rethrown as `RequestTimeoutError`) from a caller-supplied signal's abort\n * (which must propagate untouched).\n */\nexport function isTimeoutSignalError(err: unknown): boolean {\n return err instanceof Error && (err.name === \"TimeoutError\" || err.name === \"AbortError\");\n}\n\nexport async function errorFromResponse(res: Response): Promise<APIError> {\n const text = await res.text().catch(() => \"\");\n let body: unknown = text;\n try {\n body = JSON.parse(text);\n } catch {\n // keep raw text\n }\n const parsed = body as { message?: unknown; detail?: unknown } | undefined;\n const rawMessage = typeof parsed?.message === \"string\" && parsed.message ? parsed.message : undefined;\n const rawDetail = parsed?.detail;\n const isDetailAbsent = rawDetail === undefined || rawDetail === null || rawDetail === \"\";\n let reason: string;\n if (rawMessage !== undefined) {\n reason = rawMessage;\n } else if (isDetailAbsent) {\n reason = res.statusText || \"request failed\";\n } else if (typeof rawDetail === \"string\") {\n reason = rawDetail;\n } else {\n try {\n reason = JSON.stringify(rawDetail);\n } catch {\n reason = res.statusText || \"request failed\";\n }\n }\n const message = `HTTP ${res.status}: ${reason}`;\n\n switch (res.status) {\n case 401:\n return new AuthenticationError(message, res.status, body);\n case 402:\n return new PaymentRequiredError(message, res.status, body);\n case 429: {\n const ra = res.headers.get(\"retry-after\");\n const retryAfter = ra !== null && ra !== \"\" && !Number.isNaN(Number(ra)) ? Number(ra) : undefined;\n return new RateLimitError(message, res.status, body, retryAfter);\n }\n case 400:\n case 413:\n case 422:\n return new BadRequestError(message, res.status, body);\n default:\n return new APIError(message, res.status, body);\n }\n}\n","import type { SoniloClient } from \"../client.js\";\nimport type { AccountServices, UsageResponse } from \"../types.js\";\n\nexport class Account {\n constructor(private readonly client: SoniloClient) {}\n\n async services(): Promise<AccountServices> {\n const res = await this.client.request(\"/v1/account/services\");\n return (await res.json()) as AccountServices;\n }\n\n async usage(params: { days?: number } = {}): Promise<UsageResponse> {\n const query = params.days !== undefined ? `?days=${params.days}` : \"\";\n const res = await this.client.request(`/v1/account/usage${query}`);\n return (await res.json()) as UsageResponse;\n }\n}\n","import type { SoniloClient } from \"../client.js\";\nimport { SoniloError, TaskFailedError, TaskTimeoutError } from \"../errors.js\";\nimport type { SfxResult, WaitOptions } from \"../types.js\";\n\nexport const DEFAULT_POLL_INTERVAL_MS = 2000;\nexport const DEFAULT_WAIT_TIMEOUT_MS = 600_000;\n\nconst sleep = (ms: number) => new Promise<void>((resolve) => setTimeout(resolve, ms));\n\n/** A negative delay is clamped to 0 by setTimeout, which would turn the poll\n * loop into a busy loop hammering the API until the deadline. */\nfunction validateWaitArgs(pollInterval: number, timeout: number): void {\n if (pollInterval < 0) {\n throw new SoniloError(`pollInterval must be >= 0, got ${pollInterval}`);\n }\n if (timeout < 0) {\n throw new SoniloError(`timeout must be >= 0, got ${timeout}`);\n }\n}\n\nexport class Tasks {\n constructor(private readonly client: SoniloClient) {}\n\n /** Fetch current task state. Never throws on a failed status. */\n async get(taskId: string): Promise<SfxResult> {\n const res = await this.client.request(`/v1/tasks/${encodeURIComponent(taskId)}`);\n return (await res.json()) as SfxResult;\n }\n\n /** Poll until the task is terminal; throw on failure or deadline. */\n async wait(taskId: string, opts: WaitOptions = {}): Promise<SfxResult> {\n const pollInterval = opts.pollInterval ?? DEFAULT_POLL_INTERVAL_MS;\n const timeout = opts.timeout ?? DEFAULT_WAIT_TIMEOUT_MS;\n validateWaitArgs(pollInterval, timeout);\n const deadline = performance.now() + timeout;\n for (;;) {\n const result = await this.get(taskId);\n if (result.status === \"succeeded\") return result;\n if (result.status === \"failed\") {\n const message = result.error?.message || \"Generation failed\";\n throw new TaskFailedError(`Task ${taskId} failed: ${message}`, {\n code: result.error?.code,\n taskId,\n refunded: result.refunded,\n });\n }\n const remaining = deadline - performance.now();\n if (remaining <= 0) {\n throw new TaskTimeoutError(\n `Task ${taskId} still processing after ${timeout}ms; ` +\n \"it may finish later — resume with tasks.wait or tasks.get\",\n taskId,\n );\n }\n await sleep(Math.min(pollInterval, remaining));\n }\n }\n}\n","import { GenerationError } from \"./errors.js\";\nimport type { CostInfo, StreamEvent, Track } from \"./types.js\";\n\nexport function decodeBase64(b64: string): Uint8Array {\n const bin = atob(b64);\n const out = new Uint8Array(bin.length);\n for (let i = 0; i < bin.length; i++) out[i] = bin.charCodeAt(i);\n return out;\n}\n\n/** Returns `null` for a valid-JSON-but-non-object line (e.g. a bare `null`\n * or a number/string), which carries no event `type` and is skipped like any\n * other junk line rather than crashing on a `.type` read off `null`. */\nfunction toEvent(line: string): StreamEvent | null {\n const parsed: unknown = JSON.parse(line);\n if (typeof parsed !== \"object\" || parsed === null) return null;\n const raw = parsed as { type: string; [key: string]: unknown };\n if (raw.type === \"audio_chunk\" && typeof raw.data === \"string\") {\n try {\n return { ...raw, type: \"audio_chunk\", data: decodeBase64(raw.data) };\n } catch {\n // Don't raise here: this must reach collectTrack's malformed-chunk\n // check, which turns undecodable data into a typed GenerationError.\n // Raising in place would let a raw DOMException escape\n // stream()/generate(), breaking the SDK's \"all errors extend\n // SoniloError\" contract.\n }\n }\n return raw as StreamEvent;\n}\n\nexport async function* parseNdjson(\n body: ReadableStream<Uint8Array>,\n): AsyncGenerator<StreamEvent, void, undefined> {\n const reader = body.getReader();\n const decoder = new TextDecoder();\n let buffer = \"\";\n try {\n for (;;) {\n const { done, value } = await reader.read();\n if (done) break;\n buffer += decoder.decode(value, { stream: true });\n let nl: number;\n while ((nl = buffer.indexOf(\"\\n\")) !== -1) {\n const line = buffer.slice(0, nl).trim();\n buffer = buffer.slice(nl + 1);\n if (line) {\n const ev = toEvent(line);\n if (ev !== null) yield ev;\n }\n }\n }\n buffer += decoder.decode();\n const tail = buffer.trim();\n if (tail) {\n const ev = toEvent(tail);\n if (ev !== null) yield ev;\n }\n } finally {\n await reader.cancel().catch(() => {});\n }\n}\n\nexport async function collectTrack(events: AsyncIterable<StreamEvent>): Promise<Track> {\n const chunks: Uint8Array[] = [];\n let title: string | undefined;\n let cost: CostInfo | undefined;\n let sawComplete = false;\n\n for await (const ev of events) {\n if (ev.type === \"audio_chunk\") {\n // A malformed chunk (missing/non-decodable `data`) must not be\n // silently dropped: that would hand back a \"successful\" Track with\n // empty or truncated audio and no indication anything went wrong.\n if (!(ev.data instanceof Uint8Array)) {\n throw new GenerationError(\n \"received a malformed audio_chunk event (missing or non-decodable data)\",\n );\n }\n chunks.push(ev.data);\n } else if (ev.type === \"title\" && typeof ev.title === \"string\") {\n title = ev.title;\n } else if (ev.type === \"cost\") {\n const { type: _type, ...rest } = ev;\n cost = rest as CostInfo;\n } else if (ev.type === \"error\") {\n const message = typeof ev.message === \"string\" && ev.message !== \"\" ? ev.message : \"generation failed\";\n const code = typeof ev.code === \"string\" ? ev.code : undefined;\n throw new GenerationError(message, code);\n } else if (ev.type === \"complete\") {\n sawComplete = true;\n }\n // unknown event types: ignored\n }\n\n if (!sawComplete) {\n throw new GenerationError(\"stream ended before a 'complete' event (truncated response)\");\n }\n\n const total = chunks.reduce((n, c) => n + c.length, 0);\n const audio = new Uint8Array(total);\n let offset = 0;\n for (const c of chunks) {\n audio.set(c, offset);\n offset += c.length;\n }\n return { audio, title, cost };\n}\n","import type { SoniloClient } from \"../client.js\";\nimport { SoniloError } from \"../errors.js\";\nimport { collectTrack, parseNdjson } from \"../streaming.js\";\nimport type { StreamEvent, TextToMusicParams, Track } from \"../types.js\";\n\nexport class TextToMusic {\n constructor(private readonly client: SoniloClient) {}\n\n /** Stream raw generation events (audio chunks pre-decoded to bytes). */\n async *stream(params: TextToMusicParams): AsyncGenerator<StreamEvent, void, undefined> {\n const form = new FormData();\n form.set(\"prompt\", params.prompt);\n form.set(\"duration\", String(params.duration));\n if (params.segments !== undefined) {\n form.set(\"segments\", JSON.stringify(params.segments));\n }\n // Opt out of the client's absolute request timeout: this holds the\n // response body open and reads NDJSON chunks for as long as generation\n // takes, so an AbortSignal keyed to elapsed time would kill a healthy,\n // still-streaming, long-duration track. Pass `params.signal` yourself to\n // bound or cancel the stream instead — it is forwarded to `fetch` as-is.\n const res = await this.client.request(\n \"/v1/text-to-music\",\n { method: \"POST\", body: form, signal: params.signal },\n { timeout: null },\n );\n if (!res.body) throw new SoniloError(\"Response has no body\");\n yield* parseNdjson(res.body);\n }\n\n /** Generate and buffer the whole track; throws GenerationError on stream errors. */\n generate(params: TextToMusicParams): Promise<Track> {\n return collectTrack(this.stream(params));\n }\n}\n","import { SoniloError } from \"./errors.js\";\nimport type { VideoInput } from \"./types.js\";\n\nconst DEFAULT_FILENAME = \"video.mp4\";\n\n/**\n * Normalize every accepted video input into a FormData-ready Blob.\n * String inputs are file paths and work only in Node.js; browsers must\n * pass File/Blob/bytes/streams.\n */\nexport async function toUploadBlob(\n video: VideoInput,\n): Promise<{ blob: Blob; filename: string }> {\n if (typeof video === \"string\") {\n const isNode =\n typeof process !== \"undefined\" && Boolean((process as { versions?: { node?: string } }).versions?.node);\n if (!isNode) {\n throw new SoniloError(\n \"File paths are only supported in Node.js; pass a File or Blob in the browser\",\n );\n }\n const fsModule = \"node:fs/promises\";\n const { readFile } = (await import(\n /* webpackIgnore: true */ /* @vite-ignore */ fsModule\n )) as typeof import(\"node:fs/promises\");\n const data = await readFile(video);\n const filename = video.split(/[\\\\/]/).pop() || DEFAULT_FILENAME;\n return { blob: new Blob([data]), filename };\n }\n if (typeof File !== \"undefined\" && video instanceof File) {\n return { blob: video, filename: video.name || DEFAULT_FILENAME };\n }\n if (video instanceof Blob) {\n return { blob: video, filename: DEFAULT_FILENAME };\n }\n if (video instanceof Uint8Array) {\n return { blob: new Blob([video as unknown as BlobPart]), filename: DEFAULT_FILENAME };\n }\n if (video instanceof ArrayBuffer) {\n return { blob: new Blob([video]), filename: DEFAULT_FILENAME };\n }\n if (video instanceof ReadableStream) {\n return { blob: await new Response(video).blob(), filename: DEFAULT_FILENAME };\n }\n throw new SoniloError(\"Unsupported video input type\");\n}\n","import type { SoniloClient } from \"../client.js\";\nimport { SoniloError } from \"../errors.js\";\nimport { collectTrack, parseNdjson } from \"../streaming.js\";\nimport { toUploadBlob } from \"../upload.js\";\nimport type { StreamEvent, Track, VideoToMusicParams } from \"../types.js\";\n\nexport class VideoToMusic {\n constructor(private readonly client: SoniloClient) {}\n\n async *stream(params: VideoToMusicParams): AsyncGenerator<StreamEvent, void, undefined> {\n if ((params.video === undefined) === (params.videoUrl === undefined)) {\n throw new SoniloError(\"Provide exactly one of video or videoUrl\");\n }\n const form = new FormData();\n if (params.video !== undefined) {\n const { blob, filename } = await toUploadBlob(params.video);\n form.set(\"video\", blob, filename);\n } else {\n form.set(\"video_url\", params.videoUrl as string);\n }\n if (params.prompt !== undefined) form.set(\"prompt\", params.prompt);\n if (params.segments !== undefined) {\n form.set(\"segments\", JSON.stringify(params.segments));\n }\n // Opt out of the client's absolute request timeout: this holds the\n // response body open and reads NDJSON chunks for as long as generation\n // takes, so an AbortSignal keyed to elapsed time would kill a healthy,\n // still-streaming request (e.g. a slow video upload or long track). Pass\n // `params.signal` yourself to bound or cancel the stream instead — it is\n // forwarded to `fetch` as-is.\n const res = await this.client.request(\n \"/v1/video-to-music\",\n { method: \"POST\", body: form, signal: params.signal },\n { timeout: null },\n );\n if (!res.body) throw new SoniloError(\"Response has no body\");\n yield* parseNdjson(res.body);\n }\n\n generate(params: VideoToMusicParams): Promise<Track> {\n return collectTrack(this.stream(params));\n }\n}\n","import type { SoniloClient } from \"../client.js\";\nimport type { SfxResult, SfxTask, TextToSfxParams, WaitOptions } from \"../types.js\";\n\nexport class TextToSfx {\n constructor(private readonly client: SoniloClient) {}\n\n async submit(params: TextToSfxParams): Promise<SfxTask> {\n const form = new FormData();\n form.set(\"prompt\", params.prompt);\n form.set(\"duration\", String(params.duration));\n if (params.audioFormat !== undefined) form.set(\"audio_format\", params.audioFormat);\n const res = await this.client.request(\"/v1/text-to-sfx\", {\n method: \"POST\",\n body: form,\n });\n return (await res.json()) as SfxTask;\n }\n\n async generate(params: TextToSfxParams, opts?: WaitOptions): Promise<SfxResult> {\n const task = await this.submit(params);\n return this.client.tasks.wait(task.task_id, opts);\n }\n}\n","import type { SoniloClient } from \"../client.js\";\nimport { SoniloError } from \"../errors.js\";\nimport { toUploadBlob } from \"../upload.js\";\nimport type { SfxResult, SfxTask, VideoToSfxParams, WaitOptions } from \"../types.js\";\n\nexport class VideoToSfx {\n constructor(private readonly client: SoniloClient) {}\n\n async submit(params: VideoToSfxParams): Promise<SfxTask> {\n if ((params.video === undefined) === (params.videoUrl === undefined)) {\n throw new SoniloError(\"Provide exactly one of video or videoUrl\");\n }\n const form = new FormData();\n if (params.video !== undefined) {\n const { blob, filename } = await toUploadBlob(params.video);\n form.set(\"video\", blob, filename);\n } else {\n form.set(\"video_url\", params.videoUrl as string);\n }\n if (params.prompt !== undefined) form.set(\"prompt\", params.prompt);\n if (params.segments !== undefined) {\n form.set(\"segments\", JSON.stringify(params.segments));\n }\n if (params.audioFormat !== undefined) form.set(\"audio_format\", params.audioFormat);\n const res = await this.client.request(\"/v1/video-to-sfx\", {\n method: \"POST\",\n body: form,\n });\n return (await res.json()) as SfxTask;\n }\n\n async generate(params: VideoToSfxParams, opts?: WaitOptions): Promise<SfxResult> {\n const task = await this.submit(params);\n return this.client.tasks.wait(task.task_id, opts);\n }\n}\n","export const VERSION = \"0.2.0\";\n","import { RequestTimeoutError, SoniloError, errorFromResponse, isTimeoutSignalError } from \"./errors.js\";\nimport { Account } from \"./resources/account.js\";\nimport { Tasks } from \"./resources/tasks.js\";\nimport { TextToMusic } from \"./resources/textToMusic.js\";\nimport { VideoToMusic } from \"./resources/videoToMusic.js\";\nimport { TextToSfx } from \"./resources/textToSfx.js\";\nimport { VideoToSfx } from \"./resources/videoToSfx.js\";\nimport { VERSION } from \"./version.js\";\n\nexport interface SoniloClientOptions {\n /** Defaults to the SONILO_API_KEY environment variable (Node.js only). */\n apiKey?: string;\n /** Defaults to https://api.sonilo.com */\n baseUrl?: string;\n /** Injection point for tests and custom transports. */\n fetch?: typeof globalThis.fetch;\n /** Milliseconds before an in-flight request is aborted. Default 600000. */\n timeout?: number;\n}\n\nconst DEFAULT_BASE_URL = \"https://api.sonilo.com\";\n\n/** Milliseconds before an in-flight request is aborted, unless overridden. */\nexport const DEFAULT_TIMEOUT_MS = 600_000;\n\nexport class SoniloClient {\n readonly baseUrl: string;\n private readonly apiKey: string;\n private readonly fetchFn: typeof globalThis.fetch;\n private readonly timeout: number;\n readonly account: Account;\n readonly tasks: Tasks;\n readonly textToMusic: TextToMusic;\n readonly videoToMusic: VideoToMusic;\n readonly textToSfx: TextToSfx;\n readonly videoToSfx: VideoToSfx;\n\n constructor(options: SoniloClientOptions = {}) {\n const envKey =\n typeof process !== \"undefined\" ? process.env?.SONILO_API_KEY : undefined;\n const apiKey = options.apiKey ?? envKey;\n if (!apiKey) {\n throw new SoniloError(\n \"Missing API key: pass { apiKey } or set the SONILO_API_KEY environment variable\",\n );\n }\n this.apiKey = apiKey;\n this.baseUrl = (options.baseUrl ?? DEFAULT_BASE_URL).replace(/\\/+$/, \"\");\n this.fetchFn = (options.fetch ?? globalThis.fetch).bind(globalThis);\n this.timeout = options.timeout ?? DEFAULT_TIMEOUT_MS;\n this.account = new Account(this);\n this.tasks = new Tasks(this);\n this.textToMusic = new TextToMusic(this);\n this.videoToMusic = new VideoToMusic(this);\n this.textToSfx = new TextToSfx(this);\n this.videoToSfx = new VideoToSfx(this);\n }\n\n /**\n * Perform an authenticated request; throws a typed error on non-2xx.\n *\n * `opts.timeout` overrides the client's default timeout for this call;\n * pass `null` to disable the abort-on-timeout behavior entirely (used by\n * the streaming music endpoints — see textToMusic.ts / videoToMusic.ts).\n * A caller-supplied `init.signal` always wins over any timeout signal.\n */\n async request(\n path: string,\n init: RequestInit = {},\n opts: { timeout?: number | null } = {},\n ): Promise<Response> {\n const headers = new Headers(init.headers);\n headers.set(\"Authorization\", `Bearer ${this.apiKey}`);\n headers.set(\"X-Sonilo-Client\", \"sdk-js\");\n headers.set(\"X-Sonilo-Client-Version\", VERSION);\n const timeout = opts.timeout === undefined ? this.timeout : opts.timeout;\n // We only \"own\" the signal (and may later rewrap its abort as a\n // RequestTimeoutError) when the caller didn't supply one and a timeout\n // is actually enabled.\n const ownsSignal = init.signal == null && timeout !== null;\n const signal = init.signal ?? (timeout === null ? undefined : AbortSignal.timeout(timeout));\n try {\n const res = await this.fetchFn(`${this.baseUrl}${path}`, { ...init, headers, signal });\n if (!res.ok) throw await errorFromResponse(res);\n return res;\n } catch (err) {\n if (ownsSignal && isTimeoutSignalError(err)) {\n throw new RequestTimeoutError(`Request to ${path} timed out after ${timeout}ms`);\n }\n throw err;\n }\n }\n}\n","import { DEFAULT_TIMEOUT_MS } from \"./client.js\";\nimport { RequestTimeoutError, SoniloError, isTimeoutSignalError } from \"./errors.js\";\nimport type { SfxMedia } from \"./types.js\";\n\n/** Fetch a result media file. The URL is presigned — no API key is sent. */\nexport async function download(\n media: SfxMedia | undefined,\n fetchFn: typeof globalThis.fetch = globalThis.fetch,\n timeout: number = DEFAULT_TIMEOUT_MS,\n): Promise<Uint8Array> {\n if (!media?.url) {\n throw new SoniloError(\"No media to download\");\n }\n let res: Response;\n try {\n res = await fetchFn(media.url, { signal: AbortSignal.timeout(timeout) });\n } catch (err) {\n if (isTimeoutSignalError(err)) {\n throw new RequestTimeoutError(`Download of ${media.url} timed out after ${timeout}ms`);\n }\n throw err;\n }\n if (!res.ok) {\n throw new SoniloError(`Download failed: HTTP ${res.status}`);\n }\n return new Uint8Array(await res.arrayBuffer());\n}\n","export type SegmentLabel =\n | \"intro\"\n | \"verse\"\n | \"pre-chorus\"\n | \"chorus\"\n | \"bridge\"\n | \"break\"\n | \"silence\"\n | \"outro\"\n | \"none\";\n\nexport interface Segment {\n start: number;\n prompt: string;\n label?: SegmentLabel;\n}\n\n/** Monetary fields are strings, exactly as the backend serializes them. */\nexport interface CostInfo {\n billing_rate_per_sec: string;\n billing_before_discount: string;\n billing_after_discount: string;\n discount_factor: string;\n}\n\nexport interface AudioChunkEvent {\n type: \"audio_chunk\";\n /** Decoded from the wire's base64 by the SDK. */\n data: Uint8Array;\n}\n\nexport interface TitleEvent {\n type: \"title\";\n title: string;\n summary?: string;\n display_tags?: string[];\n [key: string]: unknown;\n}\n\nexport interface CompleteEvent {\n type: \"complete\";\n [key: string]: unknown;\n}\n\nexport interface ErrorEvent {\n type: \"error\";\n code?: string;\n message?: string;\n [key: string]: unknown;\n}\n\nexport interface CostEvent extends CostInfo {\n type: \"cost\";\n}\n\n/** Forward-compatibility: unrecognized event types are passed through. */\nexport interface UnknownEvent {\n type: string;\n [key: string]: unknown;\n}\n\nexport type StreamEvent =\n | AudioChunkEvent\n | TitleEvent\n | CompleteEvent\n | ErrorEvent\n | CostEvent\n | UnknownEvent;\n\nexport interface Track {\n audio: Uint8Array;\n title?: string;\n cost?: CostInfo;\n}\n\nexport interface TextToMusicParams {\n prompt: string;\n duration: number;\n segments?: Segment[];\n /** Bounds the stream: aborting this cancels the in-flight generation.\n * Passed straight through to `fetch` — it is never rewrapped as\n * RequestTimeoutError, since the client's own absolute timeout does not\n * apply to streaming music generation. */\n signal?: AbortSignal;\n}\n\n/** string = file path (Node.js only). */\nexport type VideoInput =\n | File\n | Blob\n | Uint8Array\n | ArrayBuffer\n | ReadableStream<Uint8Array>\n | string;\n\nexport interface VideoToMusicParams {\n video?: VideoInput;\n videoUrl?: string;\n prompt?: string;\n segments?: Segment[];\n /** Bounds the stream: aborting this cancels the in-flight generation.\n * Passed straight through to `fetch` — it is never rewrapped as\n * RequestTimeoutError, since the client's own absolute timeout does not\n * apply to streaming music generation. */\n signal?: AbortSignal;\n}\n\nexport interface AccountServices {\n available_services: string[];\n rpm_limit: number;\n concurrency_limit: number;\n discount_factor: number | string;\n max_upload_size_mb: number | null;\n}\n\nexport interface UsageSummary {\n total_requests: number;\n total_duration_seconds: number;\n total_cost: number | string;\n period_start: string;\n period_end: string;\n [key: string]: unknown;\n}\n\nexport interface DailyUsage {\n date: string;\n requests: number;\n duration_seconds: number;\n cost: number | string;\n}\n\nexport interface UsageResponse {\n summary: UsageSummary;\n daily: DailyUsage[];\n}\n\nexport function isAudioChunkEvent(event: StreamEvent): event is AudioChunkEvent {\n return event.type === \"audio_chunk\" && (event as AudioChunkEvent).data instanceof Uint8Array;\n}\n\nexport function isErrorEvent(event: StreamEvent): event is ErrorEvent {\n return event.type === \"error\";\n}\n\n/** SFX segments (unlike music `Segment`) require `end`, must start at 0,\n * and be contiguous; validated server-side. */\nexport interface SfxSegment {\n start: number;\n end: number;\n prompt: string;\n}\n\nexport type SfxAudioFormat = \"wav\" | \"mp3\" | \"aac\" | \"flac\";\n\n/** Submission ack for the async SFX endpoints. */\nexport interface SfxTask {\n task_id: string;\n status: string;\n}\n\n/** A generated file re-hosted on R2 behind a presigned URL. */\nexport interface SfxMedia {\n url: string;\n content_type?: string;\n file_size?: number;\n}\n\nexport interface SfxError {\n code?: string;\n message?: string;\n}\n\n/** State of an SFX task (`tasks.get`) or its final result (`wait`/`generate`). */\nexport interface SfxResult {\n task_id: string;\n type?: string;\n status: \"processing\" | \"succeeded\" | \"failed\" | (string & {});\n audio?: SfxMedia;\n video?: SfxMedia;\n /** Only present when the account's task-field whitelist enables cost. */\n cost?: number;\n error?: SfxError;\n refunded?: boolean;\n [key: string]: unknown;\n}\n\nexport interface TextToSfxParams {\n prompt: string;\n duration: number;\n audioFormat?: SfxAudioFormat;\n}\n\nexport interface VideoToSfxParams {\n video?: VideoInput;\n videoUrl?: string;\n prompt?: string;\n segments?: SfxSegment[];\n audioFormat?: SfxAudioFormat;\n}\n\nexport interface WaitOptions {\n /** Milliseconds between polls. Default 2000. */\n pollInterval?: number;\n /** Overall deadline in milliseconds. Default 600000. */\n timeout?: number;\n}\n"],"mappings":"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","names":[]}
|