@j-o-r/hello-dave 0.1.1 → 0.1.4
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/CHANGELOG.md +42 -25
- package/README.md +81 -221
- package/TODO.md +173 -35
- package/agents/agent_creator.js +105 -0
- package/agents/agent_creator.prompt.md +371 -0
- package/agents/ask_agent.js +64 -127
- package/agents/claude_agent.js +68 -0
- package/agents/code_agent.js +55 -135
- package/agents/code_agent.prompt.md +50 -0
- package/agents/echo_agent.js +76 -0
- package/agents/financial_expert.js +75 -0
- package/agents/gpt_agent.js +52 -103
- package/agents/gpt_code.js +81 -0
- package/agents/grok_agent.js +58 -114
- package/agents/minimax_agent.js +92 -0
- package/agents/mureka_agent.js +77 -0
- package/agents/planner_agent.js +172 -0
- package/agents/stability_agent.js +87 -0
- package/agents/test_agent.js +75 -157
- package/agents/weather_agent.js +73 -0
- package/agents/workflow_agent.js +189 -0
- package/bin/dave.js +436 -184
- package/docs/bin-dave.md +85 -35
- package/docs/cdn-ssh.md +100 -0
- package/docs/creating-agents.md +301 -0
- package/docs/creating-toolsets.md +336 -0
- package/docs/docs-organization.md +48 -0
- package/docs/project-overview.md +86 -51
- package/lib/API/elevenlabs.io/music.compose.md +441 -0
- package/lib/API/elevenlabs.io/music.create-composition-plan.md +370 -0
- package/lib/API/elevenlabs.io/music.stream.md +425 -0
- package/lib/API/lalal.ai/lalal.js +445 -0
- package/lib/API/lalal.ai/openapi.json +2614 -0
- package/lib/API/minimax/ImageToolset.js +82 -37
- package/lib/API/minimax/MusicToolset.js +125 -79
- package/lib/API/minimax/VideoToolset.js +170 -167
- package/lib/API/minimax/image.js +5 -1
- package/lib/API/minimax/music.js +210 -23
- package/lib/API/minimax/video.js +242 -53
- package/lib/API/mureka/MusicToolset.js +646 -0
- package/lib/API/mureka/README.md +41 -0
- package/lib/API/mureka/index.js +7 -0
- package/lib/API/mureka/music.js +658 -0
- package/lib/API/openai.com/index.js +7 -0
- package/lib/API/openai.com/{reponses/text.js → responses.js} +64 -18
- package/lib/API/openai.com/video.create.character.md +40 -0
- package/lib/API/openai.com/video.create.md +219 -0
- package/lib/API/openai.com/video.delete.md +44 -0
- package/lib/API/openai.com/video.download.md +31 -0
- package/lib/API/openai.com/video.edit.md +155 -0
- package/lib/API/openai.com/video.extend.md +166 -0
- package/lib/API/openai.com/video.fetch.character.md +43 -0
- package/lib/API/openai.com/video.js +784 -0
- package/lib/API/openai.com/video.list.md +201 -0
- package/lib/API/openai.com/video.remix.md +175 -0
- package/lib/API/openai.com/video.retrieve.md +139 -0
- package/lib/API/openai.com/videoToolset.js +616 -0
- package/lib/API/stability.ai/ImageToolset.js +131 -40
- package/lib/API/stability.ai/MusicToolset.js +79 -47
- package/lib/API/stability.ai/audio.js +63 -131
- package/lib/API/x.ai/chat.responses.md +1040 -0
- package/lib/API/x.ai/image.js +229 -59
- package/lib/API/x.ai/imageToolset.js +376 -0
- package/lib/API/x.ai/index.js +1 -1
- package/lib/API/x.ai/responses.js +9 -18
- package/lib/Agent.js +271 -0
- package/lib/Agent.js.old +284 -0
- package/lib/AgentLauncher.js +562 -0
- package/lib/Cli.js +87 -13
- package/lib/Prompt.js +23 -1
- package/lib/Session.js +5 -4
- package/lib/ToolSet.js +102 -6
- package/lib/agentLoader.js +369 -0
- package/lib/cdn.js +67 -231
- package/lib/{CdnToolset.js → cdnToolset.js} +47 -64
- package/lib/defaultToolsets.js +43 -0
- package/lib/fafs.js +1 -1
- package/lib/genericToolset.js +442 -119
- package/lib/handOffToolset.js +179 -0
- package/lib/index.js +34 -27
- package/lib/toolsetLoader.js +248 -0
- package/package.json +11 -5
- package/types/API/lalal.ai/lalal.d.ts +116 -0
- package/types/API/minimax/image.d.ts +2 -1
- package/types/API/minimax/music.d.ts +189 -26
- package/types/API/minimax/video.d.ts +100 -31
- package/types/API/mureka/index.d.ts +7 -0
- package/types/API/mureka/music.d.ts +472 -0
- package/types/API/openai.com/index.d.ts +7 -0
- package/types/API/openai.com/{reponses/text.d.ts → responses.d.ts} +11 -11
- package/types/API/openai.com/video.d.ts +409 -0
- package/types/API/openai.com/videoToolset.d.ts +24 -0
- package/types/API/stability.ai/audio.d.ts +14 -103
- package/types/API/stability.ai/image.d.ts +2 -2
- package/types/API/x.ai/image.d.ts +138 -26
- package/types/API/x.ai/imageToolset.d.ts +3 -0
- package/types/API/x.ai/index.d.ts +1 -1
- package/types/API/x.ai/responses.d.ts +4 -4
- package/types/Agent.d.ts +123 -0
- package/types/AgentLauncher.d.ts +222 -0
- package/types/Cli.d.ts +28 -8
- package/types/Prompt.d.ts +23 -5
- package/types/Session.d.ts +1 -1
- package/types/ToolSet.d.ts +10 -0
- package/types/agentLoader.d.ts +78 -0
- package/types/cdn.d.ts +15 -90
- package/types/defaultToolsets.d.ts +9 -0
- package/types/fafs.d.ts +1 -1
- package/types/genericToolset.d.ts +1 -1
- package/types/handOffToolset.d.ts +28 -0
- package/types/index.d.ts +19 -17
- package/types/toolsetLoader.d.ts +114 -0
- package/utils/format_log.js +101 -23
- package/utils/launch_agent.js +18 -0
- package/utils/list_sessions.sh +13 -5
- package/utils/search_sessions.sh +65 -29
- package/utils/toolsets.js +33 -0
- package/README.md.bak.1779452127 +0 -240
- package/agents/codeserver.sh +0 -47
- package/agents/daisy_agent.js +0 -173
- package/agents/docs_agent.js +0 -148
- package/agents/memory_agent.js +0 -263
- package/agents/minimax.js +0 -173
- package/agents/npm_agent.js +0 -202
- package/agents/prompt_agent.js +0 -133
- package/agents/readme_agent.js +0 -148
- package/agents/spawn_agent.js +0 -160
- package/agents/stability.js +0 -173
- package/agents/todo_agent.js +0 -175
- package/bin/codeDave +0 -58
- package/docs/agent-dave-websocket-protocol.md +0 -180
- package/docs/agent-manager.md +0 -244
- package/docs/codeserver-pattern.md +0 -191
- package/docs/generic-toolset.md +0 -326
- package/docs/howtos/agent-networking.md +0 -253
- package/docs/howtos/spawn-agents.md.bak +0 -200
- package/docs/howtos/spawn-agents.md.bak_new +0 -200
- package/docs/multi-agent-clusters.md +0 -265
- package/docs/music-toolsets.md +0 -137
- package/docs/path-resolution-best-practices.md +0 -104
- package/docs/plans/minimax-music-generation.md +0 -80
- package/docs/plans/unified-agent-architecture.md +0 -146
- package/docs/plans/websocket-streaming-plan.md.bak +0 -317
- package/docs/prompt/spawn_agent.md +0 -175
- package/docs/prompt/spawn_agent.md.bak +0 -201
- package/docs/prompt/task_clarification_and_documentation.md +0 -35
- package/docs/prompt-class.md +0 -141
- package/docs/todo-archive-infra-2026-04-21.md +0 -15
- package/docs/todo-archive-v0.0.8.md +0 -1
- package/docs/todo-archive-v0.1.0.md +0 -32
- package/docs/todo-archive.md +0 -44
- package/docs/tools-syntax-validation.md +0 -121
- package/docs/toolset.md +0 -164
- package/docs/xai-responses.md +0 -111
- package/docs/xai_collections.md +0 -106
- package/lib/API/x.ai/ImageToolset.js +0 -165
- package/lib/API/x.ai/text.js +0 -415
- package/lib/AgentClient.js +0 -248
- package/lib/AgentManager.js +0 -245
- package/lib/AgentServer.js +0 -404
- package/lib/wsCli.js +0 -287
- package/lib/wsIO.js +0 -90
- package/types/API/x.ai/text.d.ts +0 -286
- package/types/AgentClient.d.ts +0 -109
- package/types/AgentManager.d.ts +0 -100
- package/types/AgentServer.d.ts +0 -89
- package/types/wsCli.d.ts +0 -17
- package/types/wsIO.d.ts +0 -30
- package/utils/test.sh +0 -46
- /package/docs/{suggestions.md → _notes/token-counts.md} +0 -0
- /package/lib/API/openai.com/{reponses/MESSAGES.md → MESSAGES.md} +0 -0
- /package/types/API/{x.ai/ImageToolset.d.ts → mureka/MusicToolset.d.ts} +0 -0
- /package/types/{CdnToolset.d.ts → cdnToolset.d.ts} +0 -0
|
@@ -0,0 +1,445 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file lib/API/lalal.ai/lalal.js
|
|
3
|
+
* @module lalal.ai/lalal
|
|
4
|
+
* @description Pure HTTP wrapper for the LALAL.AI API v1 (https://www.lalal.ai/api/v1/).
|
|
5
|
+
*
|
|
6
|
+
* Provides a clean, production-ready interface for:
|
|
7
|
+
* - File upload (/upload/)
|
|
8
|
+
* - Task management: split (stem_separator, demuser, voice_clean, multistem), voice change
|
|
9
|
+
* - Status checking and polling (/check/)
|
|
10
|
+
* - Cancellation, deletion, limits
|
|
11
|
+
* - Batch operations
|
|
12
|
+
* - Voice packs listing
|
|
13
|
+
*
|
|
14
|
+
* Authentication via `X-License-Key` header (env: LALAL_AI_LICENSE_KEY).
|
|
15
|
+
* All processing is asynchronous — use check/poll helpers.
|
|
16
|
+
*
|
|
17
|
+
* High-level methods handle upload → start task → poll → result processing.
|
|
18
|
+
* Supports local files, Buffers, Blobs, remote URLs (auto-download for upload).
|
|
19
|
+
*
|
|
20
|
+
* @see OpenAPI: lib/API/lalal.ai/openapi.json
|
|
21
|
+
* @see Official examples: https://github.com/OmniSaleGmbH/lalalai/tree/master/api-v1/python
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
/* ============================================================
|
|
25
|
+
IMPORTS & CONSTANTS
|
|
26
|
+
============================================================ */
|
|
27
|
+
|
|
28
|
+
import { request as doRequest } from '@j-o-r/apiserver';
|
|
29
|
+
import fs from 'fs/promises';
|
|
30
|
+
import path from 'path';
|
|
31
|
+
|
|
32
|
+
const BASE_URL = 'https://www.lalal.ai/api/v1';
|
|
33
|
+
const TMP_DIR = path.join(process.cwd(), '.cache', 'lalalai');
|
|
34
|
+
|
|
35
|
+
/* ============================================================
|
|
36
|
+
AUTHENTICATION & HEADERS
|
|
37
|
+
============================================================ */
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Builds authenticated request headers for LALAL.AI API.
|
|
41
|
+
* Requires the `LALAL_AI_LICENSE_KEY` environment variable.
|
|
42
|
+
*
|
|
43
|
+
* @function getHeaders
|
|
44
|
+
* @param {Object} [extraHeaders={}] - Additional headers (e.g. Content-Disposition)
|
|
45
|
+
* @returns {Object} Headers with X-License-Key
|
|
46
|
+
* @throws {Error} If license key missing
|
|
47
|
+
*/
|
|
48
|
+
const getHeaders = (extraHeaders = {}) => {
|
|
49
|
+
if (!process.env.LALAL_AI_LICENSE_KEY) {
|
|
50
|
+
throw new Error('Missing LALAL_AI_LICENSE_KEY! Please export LALAL_AI_LICENSE_KEY=your_license_key');
|
|
51
|
+
}
|
|
52
|
+
return {
|
|
53
|
+
'X-License-Key': process.env.LALAL_AI_LICENSE_KEY,
|
|
54
|
+
...extraHeaders
|
|
55
|
+
};
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
/* ============================================================
|
|
59
|
+
TEMPORARY FILE MANAGEMENT
|
|
60
|
+
============================================================ */
|
|
61
|
+
|
|
62
|
+
async function ensureTmpDir() {
|
|
63
|
+
await fs.mkdir(TMP_DIR, { recursive: true });
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Downloads a remote file (audio) to temp dir.
|
|
68
|
+
*
|
|
69
|
+
* @async
|
|
70
|
+
* @function downloadRemoteToTemp
|
|
71
|
+
* @param {string} url - Remote URL
|
|
72
|
+
* @param {string} [prefix='lalal-download'] - Filename prefix
|
|
73
|
+
* @returns {Promise<string>} Local file path
|
|
74
|
+
*/
|
|
75
|
+
async function downloadRemoteToTemp(url, prefix = 'lalal-download') {
|
|
76
|
+
await ensureTmpDir();
|
|
77
|
+
const response = await fetch(url);
|
|
78
|
+
if (!response.ok) {
|
|
79
|
+
throw new Error(`Failed to download: ${response.status} ${response.statusText}`);
|
|
80
|
+
}
|
|
81
|
+
const buffer = Buffer.from(await response.arrayBuffer());
|
|
82
|
+
let filename = `${prefix}-${Date.now()}.mp3`;
|
|
83
|
+
try {
|
|
84
|
+
const urlObj = new URL(url);
|
|
85
|
+
const base = path.basename(urlObj.pathname);
|
|
86
|
+
if (base) filename = base;
|
|
87
|
+
} catch (_) {}
|
|
88
|
+
const localPath = path.join(TMP_DIR, filename);
|
|
89
|
+
await fs.writeFile(localPath, buffer);
|
|
90
|
+
return localPath;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Prepares upload payload: returns { body: Buffer, headers: { 'Content-Disposition': ... } }
|
|
95
|
+
*
|
|
96
|
+
* @async
|
|
97
|
+
* @function prepareUploadPayload
|
|
98
|
+
* @param {string|Buffer|Blob} input - File path, URL, Buffer or Blob
|
|
99
|
+
* @param {string} [filename] - Optional filename (required for Buffers/Blobs)
|
|
100
|
+
* @returns {Promise<{body: Buffer, contentDisposition: string}>}
|
|
101
|
+
*/
|
|
102
|
+
async function prepareUploadPayload(input, filename) {
|
|
103
|
+
let buffer;
|
|
104
|
+
let finalFilename = filename;
|
|
105
|
+
|
|
106
|
+
if (typeof input === 'string') {
|
|
107
|
+
if (input.startsWith('http://') || input.startsWith('https://')) {
|
|
108
|
+
const localPath = await downloadRemoteToTemp(input, 'upload-source');
|
|
109
|
+
buffer = await fs.readFile(localPath);
|
|
110
|
+
finalFilename = finalFilename || path.basename(localPath);
|
|
111
|
+
} else {
|
|
112
|
+
buffer = await fs.readFile(input);
|
|
113
|
+
finalFilename = finalFilename || path.basename(input);
|
|
114
|
+
}
|
|
115
|
+
} else if (input instanceof Buffer) {
|
|
116
|
+
buffer = input;
|
|
117
|
+
if (!finalFilename) throw new Error('filename required for Buffer input');
|
|
118
|
+
} else if (input instanceof Blob) {
|
|
119
|
+
buffer = Buffer.from(await input.arrayBuffer());
|
|
120
|
+
finalFilename = finalFilename || input.name || `upload-${Date.now()}.mp3`;
|
|
121
|
+
} else {
|
|
122
|
+
throw new Error('Unsupported input type for upload');
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// Build Content-Disposition (simple version; production should handle RFC 6266/5987)
|
|
126
|
+
const disposition = `attachment; filename="${finalFilename.replace(/"/g, '')}"`;
|
|
127
|
+
return { body: buffer, contentDisposition: disposition };
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/* ============================================================
|
|
131
|
+
CORE REQUEST HELPERS
|
|
132
|
+
============================================================ */
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Generic POST helper with JSON body.
|
|
136
|
+
*
|
|
137
|
+
* @async
|
|
138
|
+
* @function postJson
|
|
139
|
+
* @param {string} endpoint - Relative path e.g. 'split/stem_separator/'
|
|
140
|
+
* @param {Object} body - JSON payload
|
|
141
|
+
* @param {Object} [extraHeaders={}] - Extra headers
|
|
142
|
+
* @returns {Promise<Object>} Response object from doRequest
|
|
143
|
+
*/
|
|
144
|
+
async function postJson(endpoint, body, extraHeaders = {}) {
|
|
145
|
+
const url = `${BASE_URL}/${endpoint}`;
|
|
146
|
+
const headers = getHeaders({ 'Content-Type': 'application/json', ...extraHeaders });
|
|
147
|
+
return doRequest(url, 'POST', headers, JSON.stringify(body));
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Upload helper (binary octet-stream with Content-Disposition).
|
|
152
|
+
*
|
|
153
|
+
* @async
|
|
154
|
+
* @function uploadFile
|
|
155
|
+
* @param {string|Buffer|Blob} input
|
|
156
|
+
* @param {string} [filename]
|
|
157
|
+
* @returns {Promise<Object>} { id, name, size, duration, expires }
|
|
158
|
+
*/
|
|
159
|
+
async function uploadFile(input, filename) {
|
|
160
|
+
const { body, contentDisposition } = await prepareUploadPayload(input, filename);
|
|
161
|
+
const url = `${BASE_URL}/upload/`;
|
|
162
|
+
const headers = getHeaders({ 'Content-Disposition': contentDisposition, 'Content-Type': 'application/octet-stream' });
|
|
163
|
+
|
|
164
|
+
const res = await doRequest(url, 'POST', headers, body);
|
|
165
|
+
|
|
166
|
+
if (res.status === 200) {
|
|
167
|
+
return res.response;
|
|
168
|
+
}
|
|
169
|
+
throw new Error(`Upload failed: ${res.status} ${JSON.stringify(res.response)}`);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/* ============================================================
|
|
173
|
+
POLLING & CHECK HELPERS
|
|
174
|
+
============================================================ */
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* Checks task status.
|
|
178
|
+
*
|
|
179
|
+
* @async
|
|
180
|
+
* @function checkTasks
|
|
181
|
+
* @param {string|string[]} taskIds - Single ID or array
|
|
182
|
+
* @returns {Promise<Object>} CheckV1Response
|
|
183
|
+
*/
|
|
184
|
+
async function checkTasks(taskIds) {
|
|
185
|
+
const ids = Array.isArray(taskIds) ? taskIds : [taskIds];
|
|
186
|
+
const res = await postJson('check/', { task_ids: ids });
|
|
187
|
+
if (res.status === 200) {
|
|
188
|
+
return res.response;
|
|
189
|
+
}
|
|
190
|
+
throw new Error(`Check failed: ${res.status} ${JSON.stringify(res.response)}`);
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* Polls /check/ until task completes, errors, or times out.
|
|
195
|
+
*
|
|
196
|
+
* @async
|
|
197
|
+
* @function pollForTask
|
|
198
|
+
* @param {string} taskId
|
|
199
|
+
* @param {number} [maxAttempts=120] - ~10 minutes at 5s interval
|
|
200
|
+
* @param {number} [intervalMs=5000]
|
|
201
|
+
* @returns {Promise<Object>} The success result or throws on error/cancel/timeout
|
|
202
|
+
*/
|
|
203
|
+
async function pollForTask(taskId, maxAttempts = 120, intervalMs = 5000) {
|
|
204
|
+
for (let attempt = 0; attempt < maxAttempts; attempt++) {
|
|
205
|
+
const checkRes = await checkTasks(taskId);
|
|
206
|
+
const taskResult = checkRes.result?.[taskId];
|
|
207
|
+
|
|
208
|
+
if (!taskResult) {
|
|
209
|
+
throw new Error(`Task ${taskId} not found in check response`);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
const status = taskResult.status;
|
|
213
|
+
|
|
214
|
+
if (status === 'success') {
|
|
215
|
+
return taskResult;
|
|
216
|
+
}
|
|
217
|
+
if (status === 'error' || status === 'server_error') {
|
|
218
|
+
throw new Error(`Task ${taskId} failed: ${JSON.stringify(taskResult.error || taskResult)}`);
|
|
219
|
+
}
|
|
220
|
+
if (status === 'cancelled') {
|
|
221
|
+
throw new Error(`Task ${taskId} was cancelled`);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
// progress or queued
|
|
225
|
+
await new Promise(resolve => setTimeout(resolve, intervalMs));
|
|
226
|
+
}
|
|
227
|
+
throw new Error(`Timeout polling task ${taskId} after ${maxAttempts} attempts`);
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
/* ============================================================
|
|
231
|
+
SPLIT HELPERS (high-level)
|
|
232
|
+
============================================================ */
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* Starts a stem separator split task.
|
|
236
|
+
*
|
|
237
|
+
* @async
|
|
238
|
+
* @function splitStemSeparator
|
|
239
|
+
* @param {string} sourceId
|
|
240
|
+
* @param {Object} presets - StemSeparatorSplitterPresetsV1
|
|
241
|
+
* @param {string} [idempotencyKey]
|
|
242
|
+
* @returns {Promise<{task_id: string}>}
|
|
243
|
+
*/
|
|
244
|
+
async function splitStemSeparator(sourceId, presets, idempotencyKey = null) {
|
|
245
|
+
const body = { source_id: sourceId, presets };
|
|
246
|
+
if (idempotencyKey) body.idempotency_key = idempotencyKey;
|
|
247
|
+
const res = await postJson('split/stem_separator/', body);
|
|
248
|
+
if (res.status === 200) return res.response;
|
|
249
|
+
throw new Error(`Split failed: ${res.status} ${JSON.stringify(res.response)}`);
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
/**
|
|
253
|
+
* Starts a demuser (music removal) task.
|
|
254
|
+
*/
|
|
255
|
+
async function splitDemuser(sourceId, presets = {}, idempotencyKey = null) {
|
|
256
|
+
const body = { source_id: sourceId, presets: { stem: 'music', ...presets } };
|
|
257
|
+
if (idempotencyKey) body.idempotency_key = idempotencyKey;
|
|
258
|
+
const res = await postJson('split/demuser/', body);
|
|
259
|
+
if (res.status === 200) return res.response;
|
|
260
|
+
throw new Error(`Demuser split failed: ${res.status} ${JSON.stringify(res.response)}`);
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
/**
|
|
264
|
+
* Starts a voice clean task.
|
|
265
|
+
*/
|
|
266
|
+
async function splitVoiceClean(sourceId, presets = {}, idempotencyKey = null) {
|
|
267
|
+
const body = { source_id: sourceId, presets: { stem: 'voice', ...presets } };
|
|
268
|
+
if (idempotencyKey) body.idempotency_key = idempotencyKey;
|
|
269
|
+
const res = await postJson('split/voice_clean/', body);
|
|
270
|
+
if (res.status === 200) return res.response;
|
|
271
|
+
throw new Error(`Voice clean split failed: ${res.status} ${JSON.stringify(res.response)}`);
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
/**
|
|
275
|
+
* Starts a multistem split task.
|
|
276
|
+
*/
|
|
277
|
+
async function splitMultistem(sourceId, presets, idempotencyKey = null) {
|
|
278
|
+
const body = { source_id: sourceId, presets };
|
|
279
|
+
if (idempotencyKey) body.idempotency_key = idempotencyKey;
|
|
280
|
+
const res = await postJson('split/multistem/', body);
|
|
281
|
+
if (res.status === 200) return res.response;
|
|
282
|
+
throw new Error(`Multistem split failed: ${res.status} ${JSON.stringify(res.response)}`);
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
/* ============================================================
|
|
286
|
+
BATCH SPLIT
|
|
287
|
+
============================================================ */
|
|
288
|
+
|
|
289
|
+
async function batchSplitStemSeparator(items) {
|
|
290
|
+
const res = await postJson('split/batch/stem_separator/', { items });
|
|
291
|
+
if (res.status === 200) return res.response;
|
|
292
|
+
throw new Error(`Batch split failed: ${res.status}`);
|
|
293
|
+
}
|
|
294
|
+
// Similar batch functions can be added for demuser, voice_clean
|
|
295
|
+
|
|
296
|
+
/* ============================================================
|
|
297
|
+
VOICE CHANGE & PACKS
|
|
298
|
+
============================================================ */
|
|
299
|
+
|
|
300
|
+
async function changeVoice(sourceId, presets, idempotencyKey = null) {
|
|
301
|
+
const body = { source_id: sourceId, presets };
|
|
302
|
+
if (idempotencyKey) body.idempotency_key = idempotencyKey;
|
|
303
|
+
const res = await postJson('change_voice/', body);
|
|
304
|
+
if (res.status === 200) return res.response;
|
|
305
|
+
throw new Error(`Voice change failed: ${res.status} ${JSON.stringify(res.response)}`);
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
async function listVoicePacks() {
|
|
309
|
+
const res = await postJson('voice_packs/list/');
|
|
310
|
+
if (res.status === 200) return res.response;
|
|
311
|
+
throw new Error(`List voice packs failed: ${res.status}`);
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
/* ============================================================
|
|
315
|
+
OTHER ENDPOINTS
|
|
316
|
+
============================================================ */
|
|
317
|
+
|
|
318
|
+
async function cancelTasks(taskIds) {
|
|
319
|
+
const res = await postJson('cancel/', { task_ids: Array.isArray(taskIds) ? taskIds : [taskIds] });
|
|
320
|
+
if (res.status === 200) return res.response;
|
|
321
|
+
throw new Error(`Cancel failed: ${res.status}`);
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
async function cancelAllTasks() {
|
|
325
|
+
const res = await postJson('cancel/all/');
|
|
326
|
+
if (res.status === 200) return res.response;
|
|
327
|
+
throw new Error(`Cancel all failed: ${res.status}`);
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
async function deleteSource(sourceId) {
|
|
331
|
+
const res = await postJson('delete/', { source_id: sourceId });
|
|
332
|
+
if (res.status === 200) return res.response;
|
|
333
|
+
throw new Error(`Delete failed: ${res.status}`);
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
async function getMinutesLeft() {
|
|
337
|
+
const res = await postJson('limits/minutes_left/');
|
|
338
|
+
if (res.status === 200) return res.response;
|
|
339
|
+
throw new Error(`Limits failed: ${res.status}`);
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
/* ============================================================
|
|
343
|
+
HIGH-LEVEL WORKFLOW HELPERS
|
|
344
|
+
============================================================ */
|
|
345
|
+
|
|
346
|
+
/**
|
|
347
|
+
* Full workflow: upload file → start split → poll → return result with tracks.
|
|
348
|
+
*
|
|
349
|
+
* @async
|
|
350
|
+
* @function processSplit
|
|
351
|
+
* @param {string|Buffer|Blob} input - Audio source
|
|
352
|
+
* @param {string} stem - 'vocals' | 'voice' | 'music' | etc. (determines endpoint)
|
|
353
|
+
* @param {Object} [options] - presets + other options
|
|
354
|
+
* @returns {Promise<Object>} Success result from check (with tracks)
|
|
355
|
+
*/
|
|
356
|
+
async function processSplit(input, stem, options = {}) {
|
|
357
|
+
const source = await uploadFile(input, options.filename);
|
|
358
|
+
const sourceId = source.id;
|
|
359
|
+
|
|
360
|
+
let task;
|
|
361
|
+
const presets = options.presets || {};
|
|
362
|
+
|
|
363
|
+
if (stem === 'voice') {
|
|
364
|
+
task = await splitVoiceClean(sourceId, { stem: 'voice', noise_cancelling_level: options.noise_cancelling_level ?? 1, ...presets });
|
|
365
|
+
} else if (stem === 'music') {
|
|
366
|
+
task = await splitDemuser(sourceId, { stem: 'music', ...presets });
|
|
367
|
+
} else {
|
|
368
|
+
task = await splitStemSeparator(sourceId, { stem, extraction_level: options.extraction_level ?? 'deep_extraction', ...presets });
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
const taskId = task.task_id;
|
|
372
|
+
const result = await pollForTask(taskId);
|
|
373
|
+
|
|
374
|
+
// Optionally auto-download tracks
|
|
375
|
+
if (options.autoDownload) {
|
|
376
|
+
// implement download logic here or return urls
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
return { sourceId, taskId, result };
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
/**
|
|
383
|
+
* Downloads a track URL to local file.
|
|
384
|
+
*
|
|
385
|
+
* @async
|
|
386
|
+
* @function downloadTrack
|
|
387
|
+
* @param {string} url - Track download URL from check result
|
|
388
|
+
* @param {string} [destDir] - Destination directory (defaults to TMP_DIR)
|
|
389
|
+
* @returns {Promise<string>} Local path
|
|
390
|
+
*/
|
|
391
|
+
async function downloadTrack(url, destDir = TMP_DIR) {
|
|
392
|
+
await ensureTmpDir();
|
|
393
|
+
const response = await fetch(url);
|
|
394
|
+
if (!response.ok) throw new Error(`Download failed: ${response.status}`);
|
|
395
|
+
|
|
396
|
+
const cdHeader = response.headers.get('content-disposition') || '';
|
|
397
|
+
let filename = `track-${Date.now()}.mp3`;
|
|
398
|
+
// Simple filename extraction (improve with email-message parser if needed)
|
|
399
|
+
const match = cdHeader.match(/filename="?([^"]+)"?/);
|
|
400
|
+
if (match) filename = match[1];
|
|
401
|
+
|
|
402
|
+
const localPath = path.join(destDir, filename);
|
|
403
|
+
const buffer = Buffer.from(await response.arrayBuffer());
|
|
404
|
+
await fs.writeFile(localPath, buffer);
|
|
405
|
+
return localPath;
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
/* ============================================================
|
|
409
|
+
EXPORTS
|
|
410
|
+
============================================================ */
|
|
411
|
+
|
|
412
|
+
export {
|
|
413
|
+
// Auth & utils
|
|
414
|
+
getHeaders,
|
|
415
|
+
uploadFile,
|
|
416
|
+
prepareUploadPayload,
|
|
417
|
+
downloadRemoteToTemp,
|
|
418
|
+
downloadTrack,
|
|
419
|
+
|
|
420
|
+
// Core
|
|
421
|
+
checkTasks,
|
|
422
|
+
pollForTask,
|
|
423
|
+
|
|
424
|
+
// Splits
|
|
425
|
+
splitStemSeparator,
|
|
426
|
+
splitDemuser,
|
|
427
|
+
splitVoiceClean,
|
|
428
|
+
splitMultistem,
|
|
429
|
+
|
|
430
|
+
// Batch
|
|
431
|
+
batchSplitStemSeparator,
|
|
432
|
+
|
|
433
|
+
// Voice
|
|
434
|
+
changeVoice,
|
|
435
|
+
listVoicePacks,
|
|
436
|
+
|
|
437
|
+
// Management
|
|
438
|
+
cancelTasks,
|
|
439
|
+
cancelAllTasks,
|
|
440
|
+
deleteSource,
|
|
441
|
+
getMinutesLeft,
|
|
442
|
+
|
|
443
|
+
// High-level
|
|
444
|
+
processSplit
|
|
445
|
+
};
|