beplus-mcp 0.38.0 → 0.41.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 +21 -1
- package/dist/index.js +381 -117
- package/dist/index.js.map +1 -1
- package/dist/remote-server.d.ts +24 -0
- package/dist/remote-server.js +28792 -0
- package/dist/remote-server.js.map +1 -0
- package/package.json +8 -1
package/dist/index.js
CHANGED
|
@@ -3303,8 +3303,8 @@ var require_utils = __commonJS({
|
|
|
3303
3303
|
var HOST_DELIMS = { "@": "%40", "/": "%2F", "?": "%3F", "#": "%23", ":": "%3A" };
|
|
3304
3304
|
var HOST_DELIM_RE = /[@/?#:]/g;
|
|
3305
3305
|
var HOST_DELIM_NO_COLON_RE = /[@/?#]/g;
|
|
3306
|
-
function reescapeHostDelimiters(host,
|
|
3307
|
-
const re =
|
|
3306
|
+
function reescapeHostDelimiters(host, isIP2) {
|
|
3307
|
+
const re = isIP2 ? HOST_DELIM_NO_COLON_RE : HOST_DELIM_RE;
|
|
3308
3308
|
re.lastIndex = 0;
|
|
3309
3309
|
return host.replace(re, (ch) => HOST_DELIMS[ch]);
|
|
3310
3310
|
}
|
|
@@ -3786,7 +3786,7 @@ var require_fast_uri = __commonJS({
|
|
|
3786
3786
|
fragment: void 0
|
|
3787
3787
|
};
|
|
3788
3788
|
let malformedAuthorityOrPort = false;
|
|
3789
|
-
let
|
|
3789
|
+
let isIP2 = false;
|
|
3790
3790
|
if (options.reference === "suffix") {
|
|
3791
3791
|
if (options.scheme) {
|
|
3792
3792
|
uri = options.scheme + ":" + uri;
|
|
@@ -3816,9 +3816,9 @@ var require_fast_uri = __commonJS({
|
|
|
3816
3816
|
if (ipv4result === false) {
|
|
3817
3817
|
const ipv6result = normalizeIPv6(parsed.host);
|
|
3818
3818
|
parsed.host = ipv6result.host.toLowerCase();
|
|
3819
|
-
|
|
3819
|
+
isIP2 = ipv6result.isIPV6;
|
|
3820
3820
|
} else {
|
|
3821
|
-
|
|
3821
|
+
isIP2 = true;
|
|
3822
3822
|
}
|
|
3823
3823
|
}
|
|
3824
3824
|
if (parsed.scheme === void 0 && parsed.userinfo === void 0 && parsed.host === void 0 && parsed.port === void 0 && parsed.query === void 0 && !parsed.path) {
|
|
@@ -3835,7 +3835,7 @@ var require_fast_uri = __commonJS({
|
|
|
3835
3835
|
}
|
|
3836
3836
|
const schemeHandler = getSchemeHandler(options.scheme || parsed.scheme);
|
|
3837
3837
|
if (!options.unicodeSupport && (!schemeHandler || !schemeHandler.unicodeSupport)) {
|
|
3838
|
-
if (parsed.host && (options.domainHost || schemeHandler && schemeHandler.domainHost) &&
|
|
3838
|
+
if (parsed.host && (options.domainHost || schemeHandler && schemeHandler.domainHost) && isIP2 === false && nonSimpleDomain(parsed.host)) {
|
|
3839
3839
|
try {
|
|
3840
3840
|
parsed.host = URL.domainToASCII(parsed.host.toLowerCase());
|
|
3841
3841
|
} catch (e) {
|
|
@@ -3849,7 +3849,7 @@ var require_fast_uri = __commonJS({
|
|
|
3849
3849
|
parsed.scheme = unescape(parsed.scheme);
|
|
3850
3850
|
}
|
|
3851
3851
|
if (parsed.host !== void 0) {
|
|
3852
|
-
parsed.host = reescapeHostDelimiters(unescape(parsed.host),
|
|
3852
|
+
parsed.host = reescapeHostDelimiters(unescape(parsed.host), isIP2);
|
|
3853
3853
|
}
|
|
3854
3854
|
}
|
|
3855
3855
|
if (parsed.path) {
|
|
@@ -21634,6 +21634,23 @@ var BeplusClient = class {
|
|
|
21634
21634
|
deleteCustomVoice(id) {
|
|
21635
21635
|
return this.request("DELETE", `/tts/custom-voices/${encodeURIComponent(id)}`, { timeoutMs: 3e4 });
|
|
21636
21636
|
}
|
|
21637
|
+
// request() já desembrulha `result`; o estado vem em `{ state }`.
|
|
21638
|
+
async startMusicTool(generationId, body2) {
|
|
21639
|
+
const r = await this.request(
|
|
21640
|
+
"POST",
|
|
21641
|
+
`/generations/${encodeURIComponent(generationId)}/music-tools`,
|
|
21642
|
+
{ body: body2, timeoutMs: 6e4 }
|
|
21643
|
+
);
|
|
21644
|
+
return r?.state ?? null;
|
|
21645
|
+
}
|
|
21646
|
+
async getMusicTool(generationId, clipId, tool) {
|
|
21647
|
+
const r = await this.request(
|
|
21648
|
+
"GET",
|
|
21649
|
+
`/generations/${encodeURIComponent(generationId)}/music-tools`,
|
|
21650
|
+
{ query: { clip_id: clipId, tool }, timeoutMs: 2e4 }
|
|
21651
|
+
);
|
|
21652
|
+
return r?.state ?? null;
|
|
21653
|
+
}
|
|
21637
21654
|
models() {
|
|
21638
21655
|
return this.request("GET", "/models");
|
|
21639
21656
|
}
|
|
@@ -21732,6 +21749,69 @@ async function pollUntilDone(client, generationId, budgetMs, jitterSeed = 0) {
|
|
|
21732
21749
|
|
|
21733
21750
|
// src/media.ts
|
|
21734
21751
|
import { readFile as readFile2, stat as stat2 } from "fs/promises";
|
|
21752
|
+
|
|
21753
|
+
// src/remote.ts
|
|
21754
|
+
import { lookup } from "dns/promises";
|
|
21755
|
+
import { isIP } from "net";
|
|
21756
|
+
var remote = false;
|
|
21757
|
+
function refuseLocalPath(ref) {
|
|
21758
|
+
if (!remote) return;
|
|
21759
|
+
throw new ApiError(
|
|
21760
|
+
0,
|
|
21761
|
+
`"${ref}" \xE9 um caminho de arquivo, e esta conex\xE3o roda no servidor da BePlus, que n\xE3o enxerga o seu computador. Mande uma URL p\xFAblica (http/https) ou suba o arquivo pela plataforma e use o link dele.`
|
|
21762
|
+
);
|
|
21763
|
+
}
|
|
21764
|
+
var MAX_REDIRECTS = 5;
|
|
21765
|
+
function v4Private(ip) {
|
|
21766
|
+
const [a, b] = ip.split(".").map(Number);
|
|
21767
|
+
return a === 0 || a === 10 || a === 127 || a === 169 && b === 254 || a === 172 && b >= 16 && b <= 31 || a === 192 && b === 168 || a === 100 && b >= 64 && b <= 127 || a >= 224;
|
|
21768
|
+
}
|
|
21769
|
+
function privateAddress(ip) {
|
|
21770
|
+
if (isIP(ip) === 4) return v4Private(ip);
|
|
21771
|
+
const v6 = ip.toLowerCase();
|
|
21772
|
+
const dotted = /^::ffff:(\d+\.\d+\.\d+\.\d+)$/.exec(v6);
|
|
21773
|
+
if (dotted) return v4Private(dotted[1]);
|
|
21774
|
+
const hex = /^::ffff:([0-9a-f]{1,4}):([0-9a-f]{1,4})$/.exec(v6);
|
|
21775
|
+
if (hex) {
|
|
21776
|
+
const hi = parseInt(hex[1], 16);
|
|
21777
|
+
const lo = parseInt(hex[2], 16);
|
|
21778
|
+
return v4Private(`${hi >> 8}.${hi & 255}.${lo >> 8}.${lo & 255}`);
|
|
21779
|
+
}
|
|
21780
|
+
return v6 === "::" || v6 === "::1" || /^f[cd]/.test(v6) || /^fe[89ab]/.test(v6) || /^ff/.test(v6);
|
|
21781
|
+
}
|
|
21782
|
+
async function assertPublic(url) {
|
|
21783
|
+
if (url.protocol !== "http:" && url.protocol !== "https:") {
|
|
21784
|
+
throw new ApiError(0, `S\xF3 baixo endere\xE7o http ou https: ${url.href}`);
|
|
21785
|
+
}
|
|
21786
|
+
const host = url.hostname.replace(/^\[|\]$/g, "").toLowerCase();
|
|
21787
|
+
if (host === "localhost" || host.endsWith(".localhost") || host.endsWith(".internal") || host.endsWith(".local")) {
|
|
21788
|
+
throw new ApiError(0, `Endere\xE7o interno n\xE3o \xE9 aceito: ${url.host}`);
|
|
21789
|
+
}
|
|
21790
|
+
const addrs = isIP(host) ? [{ address: host }] : await lookup(host, { all: true }).catch(() => []);
|
|
21791
|
+
if (addrs.length === 0) throw new ApiError(0, `N\xE3o encontrei o endere\xE7o ${url.host}.`);
|
|
21792
|
+
if (addrs.some((a) => privateAddress(a.address))) {
|
|
21793
|
+
throw new ApiError(0, `Endere\xE7o de rede privada n\xE3o \xE9 aceito: ${url.host}`);
|
|
21794
|
+
}
|
|
21795
|
+
}
|
|
21796
|
+
async function safeFetch(input, init = {}) {
|
|
21797
|
+
if (!remote) return fetch(input, init);
|
|
21798
|
+
let url = new URL(input);
|
|
21799
|
+
for (let hop = 0; hop <= MAX_REDIRECTS; hop++) {
|
|
21800
|
+
await assertPublic(url);
|
|
21801
|
+
const res = await fetch(url, { ...init, redirect: "manual" });
|
|
21802
|
+
const location = res.headers.get("location");
|
|
21803
|
+
if (res.status >= 300 && res.status < 400 && location) {
|
|
21804
|
+
await res.body?.cancel().catch(() => {
|
|
21805
|
+
});
|
|
21806
|
+
url = new URL(location, url);
|
|
21807
|
+
continue;
|
|
21808
|
+
}
|
|
21809
|
+
return res;
|
|
21810
|
+
}
|
|
21811
|
+
throw new ApiError(0, `Redirecionamentos demais ao baixar ${input}.`);
|
|
21812
|
+
}
|
|
21813
|
+
|
|
21814
|
+
// src/media.ts
|
|
21735
21815
|
import { basename, extname, isAbsolute, resolve } from "path";
|
|
21736
21816
|
import { homedir as homedir2 } from "os";
|
|
21737
21817
|
import { fileURLToPath } from "url";
|
|
@@ -21819,6 +21899,7 @@ function isRemoteRef(ref) {
|
|
|
21819
21899
|
return /^https?:\/\//i.test(ref);
|
|
21820
21900
|
}
|
|
21821
21901
|
function toLocalPath(ref) {
|
|
21902
|
+
refuseLocalPath(ref);
|
|
21822
21903
|
if (ref.startsWith("file://")) return fileURLToPath(ref);
|
|
21823
21904
|
if (ref === "~") return homedir2();
|
|
21824
21905
|
if (ref.startsWith("~/")) return resolve(homedir2(), ref.slice(2));
|
|
@@ -21858,8 +21939,9 @@ async function loadRemote(url) {
|
|
|
21858
21939
|
let name = decodeURIComponent(basename(u.pathname)) || "arquivo";
|
|
21859
21940
|
let res;
|
|
21860
21941
|
try {
|
|
21861
|
-
res = await
|
|
21862
|
-
} catch {
|
|
21942
|
+
res = await safeFetch(url, { signal: AbortSignal.timeout(DOWNLOAD_TIMEOUT_MS) });
|
|
21943
|
+
} catch (e) {
|
|
21944
|
+
if (e instanceof ApiError) throw e;
|
|
21863
21945
|
throw new ApiError(0, `N\xE3o consegui baixar ${url} (rede ou tempo esgotado).`);
|
|
21864
21946
|
}
|
|
21865
21947
|
if (!res.ok) {
|
|
@@ -22152,12 +22234,14 @@ var IMAGE_MODELS = [
|
|
|
22152
22234
|
"openai/gpt-image-1",
|
|
22153
22235
|
"bytedance/seedream-5.0-lite",
|
|
22154
22236
|
"bytedance/seedream-5.0-pro",
|
|
22237
|
+
"bytedance/seedream-5.0-pro-layers",
|
|
22155
22238
|
"midjourney"
|
|
22156
22239
|
];
|
|
22157
22240
|
var SEEDREAM_LITE_SIZES = ["2K", "3K", "4K"];
|
|
22158
22241
|
var SEEDREAM_PRO_SIZES = ["1K", "1.5K", "2K"];
|
|
22159
22242
|
var SEEDREAM_PRO_PIXELS = { min: 921600, max: 4624220 };
|
|
22160
22243
|
var SEEDREAM_PRO_MAX_IMAGES = 4;
|
|
22244
|
+
var SEEDREAM_LAYERS_SIZES = ["auto", "1K", "1.5K", "2K"];
|
|
22161
22245
|
var VIDEO_MODELS = [
|
|
22162
22246
|
"bytedance/seedance-2.5",
|
|
22163
22247
|
"bytedance/seedance-2.0",
|
|
@@ -22188,9 +22272,14 @@ var UPSCALE_VIDEO_VARIANTS = [
|
|
|
22188
22272
|
"Starlight Fast 2"
|
|
22189
22273
|
];
|
|
22190
22274
|
var MUSIC_MODELS = [
|
|
22275
|
+
"suno/v6",
|
|
22276
|
+
"suno/v6-wild",
|
|
22277
|
+
"suno/v6-mini",
|
|
22191
22278
|
"suno/v5-5",
|
|
22192
22279
|
"suno/v4-5"
|
|
22193
22280
|
];
|
|
22281
|
+
var MUSIC_TASK_TYPES = ["create", "extend", "cover", "upload_extend", "upload_cover", "sample"];
|
|
22282
|
+
var MUSIC_TOOLS = ["wav", "midi", "stems-basic", "stems-full", "bpm", "lyrics-timeline"];
|
|
22194
22283
|
var VOCAL_GENDERS = ["f", "m"];
|
|
22195
22284
|
var TTS_MODELS = [
|
|
22196
22285
|
"gemini-3.8-flash-lite-tts",
|
|
@@ -22247,9 +22336,254 @@ var DEFAULTS = {
|
|
|
22247
22336
|
videoDuration: 5,
|
|
22248
22337
|
ttsModel: "gemini-3.8-flash-lite-tts",
|
|
22249
22338
|
voice: "Kore",
|
|
22250
|
-
musicModel: "suno/
|
|
22339
|
+
musicModel: "suno/v6"
|
|
22251
22340
|
};
|
|
22252
22341
|
|
|
22342
|
+
// src/tools/music.ts
|
|
22343
|
+
var MUSIC_BUDGET_MS = 15e4;
|
|
22344
|
+
var TOOL_POLL_MS = 5e3;
|
|
22345
|
+
var TOOL_BUDGET_MS = 24e4;
|
|
22346
|
+
var sleep2 = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
22347
|
+
function validateMusicArgs(a) {
|
|
22348
|
+
const type = a.task_type ?? "create";
|
|
22349
|
+
const customMode = a.custom_mode ?? false;
|
|
22350
|
+
if (a.title && a.title.trim().length > 79) return "`title` aceita no m\xE1ximo 79 caracteres.";
|
|
22351
|
+
if (a.auto_lyrics && !customMode) {
|
|
22352
|
+
return "`auto_lyrics` s\xF3 vale no modo letra (custom_mode=true). No modo descri\xE7\xE3o a IA j\xE1 escreve a letra.";
|
|
22353
|
+
}
|
|
22354
|
+
if (customMode && !a.lyrics?.trim() && !(a.auto_lyrics && a.description?.trim())) {
|
|
22355
|
+
return a.auto_lyrics ? "Com `auto_lyrics`, passe o tema em `lyrics` (ou `description`)." : "Modo letra (custom_mode=true) exige `lyrics`.";
|
|
22356
|
+
}
|
|
22357
|
+
if (!customMode && !a.description?.trim()) return "Modo descri\xE7\xE3o (custom_mode=false) exige `description`.";
|
|
22358
|
+
const needsSource = type === "extend" || type === "cover";
|
|
22359
|
+
const needsReference = type === "upload_extend" || type === "upload_cover" || type === "sample";
|
|
22360
|
+
const needsContinueAt = type === "extend" || type === "upload_extend";
|
|
22361
|
+
const usesWeight = type === "cover" || type === "upload_cover" || type === "sample";
|
|
22362
|
+
if (needsSource) {
|
|
22363
|
+
if (!a.source_generation_id?.trim() || !a.source_clip_id?.trim()) {
|
|
22364
|
+
return `task_type=${type} exige \`source_generation_id\` e \`source_clip_id\` (o clip_id sai em generate_music/check_generation da gera\xE7\xE3o original).`;
|
|
22365
|
+
}
|
|
22366
|
+
} else if (a.source_generation_id || a.source_clip_id) {
|
|
22367
|
+
return "`source_generation_id` e `source_clip_id` s\xF3 valem com task_type=extend ou cover.";
|
|
22368
|
+
}
|
|
22369
|
+
if (needsReference) {
|
|
22370
|
+
if (!a.reference_audio_url?.trim()) return `task_type=${type} exige \`reference_audio_url\` (URL ou caminho local do \xE1udio).`;
|
|
22371
|
+
} else if (a.reference_audio_url) {
|
|
22372
|
+
return "`reference_audio_url` s\xF3 vale com task_type=upload_extend, upload_cover ou sample.";
|
|
22373
|
+
}
|
|
22374
|
+
if (needsContinueAt) {
|
|
22375
|
+
if (a.continue_at == null) return `task_type=${type} exige \`continue_at\` (segundo em que a m\xFAsica continua).`;
|
|
22376
|
+
} else if (a.continue_at != null) {
|
|
22377
|
+
return "`continue_at` s\xF3 vale com task_type=extend ou upload_extend.";
|
|
22378
|
+
}
|
|
22379
|
+
if (a.audio_weight != null && !usesWeight) {
|
|
22380
|
+
return "`audio_weight` s\xF3 vale com task_type=cover, upload_cover ou sample.";
|
|
22381
|
+
}
|
|
22382
|
+
if (type === "sample") {
|
|
22383
|
+
if (a.sample_start_s == null || a.sample_end_s == null) return "task_type=sample exige `sample_start_s` e `sample_end_s`.";
|
|
22384
|
+
if (a.sample_end_s <= a.sample_start_s) return "`sample_end_s` precisa ser maior que `sample_start_s`.";
|
|
22385
|
+
if (!customMode && a.description && a.description.trim().length > 200) {
|
|
22386
|
+
return "No task_type=sample a `description` aceita no m\xE1ximo 200 caracteres.";
|
|
22387
|
+
}
|
|
22388
|
+
if (a.tags && a.tags.trim().length > 200) return "No task_type=sample as `tags` aceitam no m\xE1ximo 200 caracteres.";
|
|
22389
|
+
} else if (a.sample_start_s != null || a.sample_end_s != null) {
|
|
22390
|
+
return "`sample_start_s` e `sample_end_s` s\xF3 valem com task_type=sample.";
|
|
22391
|
+
}
|
|
22392
|
+
return null;
|
|
22393
|
+
}
|
|
22394
|
+
async function getMusicClips(client, generationId, status) {
|
|
22395
|
+
try {
|
|
22396
|
+
const st = status ?? await client.getStatus(generationId);
|
|
22397
|
+
return Array.isArray(st.music_clips) ? st.music_clips : [];
|
|
22398
|
+
} catch {
|
|
22399
|
+
return [];
|
|
22400
|
+
}
|
|
22401
|
+
}
|
|
22402
|
+
function formatMusicClips(generationId, urls, clips) {
|
|
22403
|
+
const lines = (urls.length ? urls : clips.map((c) => c.audio_url ?? "")).map((u, i) => {
|
|
22404
|
+
const clip = clips[i];
|
|
22405
|
+
return `${i + 1}. ${u || "(sem URL)"}${clip?.clip_id ? `
|
|
22406
|
+
clip_id: ${clip.clip_id}` : ""}`;
|
|
22407
|
+
});
|
|
22408
|
+
const chain = clips.length ? `
|
|
22409
|
+
Para estender/cobrir ou usar music_tool: generation_id=${generationId} + o clip_id da faixa.` : "";
|
|
22410
|
+
return `${lines.join("\n") || "(sem URLs)"}${chain}`;
|
|
22411
|
+
}
|
|
22412
|
+
function registerMusicTools(server, client, cfg) {
|
|
22413
|
+
server.registerTool(
|
|
22414
|
+
"generate_music",
|
|
22415
|
+
{
|
|
22416
|
+
title: "Gerar m\xFAsica",
|
|
22417
|
+
description: 'Gera uma m\xFAsica completa (com vocais ou instrumental) usando o Suno no IA Lab da BePlus. ASS\xCDNCRONO e LENTO (geralmente 30s\u20132+ min). Aguarda at\xE9 ~2,5 min; se n\xE3o terminar, retorna o generation_id e o status \u2014 ent\xE3o use check_generation. Consome diamantes. DOIS modos: (a) DESCRI\xC7\xC3O (custom_mode=false, padr\xE3o) \u2192 passe `description` ("uma balada triste de piano"); (b) LETRA PR\xD3PRIA (custom_mode=true) \u2192 passe `lyrics` (a letra) e opcionalmente `title`. Use `tags` para g\xEAnero/estilo em ambos os modos (ex.: "sertanejo, animado, vocal feminino"). `vocal_gender` (f/m) vale para todos os modelos e \xE9 ignorado quando `make_instrumental=true`. Cada gera\xE7\xE3o produz 2 faixas, cada uma com `clip_id`. `duration` (10 a 360s) \xE9 s\xF3 uma meta aproximada; com letra, a letra manda. `task_type` muda a opera\xE7\xE3o: create (padr\xE3o), extend (continuar uma faixa sua, exige source_generation_id + source_clip_id + continue_at), cover (nova vers\xE3o de uma faixa sua, exige source_generation_id + source_clip_id), upload_extend / upload_cover (partem de um \xE1udio seu em `reference_audio_url`, URL ou caminho local) e sample (usa um trecho do \xE1udio, exige reference_audio_url + sample_start_s + sample_end_s). Extend e upload_extend custam o pre\xE7o "suno/extend"; o resto, o pre\xE7o do modelo. Depois de pronta, use music_tool para WAV, MIDI, stems, BPM e letra sincronizada de uma faixa.' + TEAM_GATING_NOTE,
|
|
22418
|
+
inputSchema: {
|
|
22419
|
+
model: external_exports.enum(MUSIC_MODELS).default(DEFAULTS.musicModel).describe(
|
|
22420
|
+
"suno/v6 = padr\xE3o, polido e previs\xEDvel; suno/v6-wild = o mesmo, de prop\xF3sito menos previs\xEDvel; suno/v6-mini = menor e mais r\xE1pido. suno/v5-5 e suno/v4-5 s\xE3o nomes legados, hoje atendidos pelo v6."
|
|
22421
|
+
),
|
|
22422
|
+
custom_mode: external_exports.boolean().optional().describe("false (padr\xE3o) = gera a partir de `description`; true = usa sua `lyrics` pr\xF3pria."),
|
|
22423
|
+
description: external_exports.string().max(2500).optional().describe("Modo descri\xE7\xE3o (custom_mode=false): descreva a m\xFAsica desejada. Obrigat\xF3rio nesse modo."),
|
|
22424
|
+
lyrics: external_exports.string().max(5e3).optional().describe("Modo letra (custom_mode=true): a letra completa da m\xFAsica. Obrigat\xF3rio nesse modo."),
|
|
22425
|
+
title: external_exports.string().max(79).optional().describe("T\xEDtulo da m\xFAsica (modo letra). M\xE1ximo 79 caracteres."),
|
|
22426
|
+
tags: external_exports.string().max(1e3).optional().describe('G\xEAnero/estilo, separados por v\xEDrgula (ex.: "pop, energ\xE9tico, vocal masculino").'),
|
|
22427
|
+
make_instrumental: external_exports.boolean().optional().describe("true = sem vocais (apenas instrumental). Padr\xE3o false."),
|
|
22428
|
+
vocal_gender: external_exports.enum(VOCAL_GENDERS).optional().describe("G\xEAnero do vocal: f (feminino) ou m (masculino). Vale para todos os modelos; ignorado se instrumental."),
|
|
22429
|
+
negative_tags: external_exports.string().max(1e3).optional().describe("Estilos/elementos a evitar."),
|
|
22430
|
+
style_weight: external_exports.number().min(0).max(1).optional().describe("Peso do estilo (0\u20131). Maior = segue mais as tags. Omita para o modelo decidir."),
|
|
22431
|
+
weirdness_constraint: external_exports.number().min(0).max(1).optional().describe("Criatividade/imprevisibilidade (0\u20131). Omita para o modelo decidir."),
|
|
22432
|
+
duration: external_exports.number().int().min(10).max(360).optional().describe("Dura\xE7\xE3o-alvo aproximada em segundos (10 a 360). Com `lyrics`, a letra domina o tamanho."),
|
|
22433
|
+
auto_lyrics: external_exports.boolean().optional().describe("S\xF3 no modo letra (custom_mode=true): a IA escreve letra, t\xEDtulo e tags usando `lyrics` como TEMA."),
|
|
22434
|
+
task_type: external_exports.enum(MUSIC_TASK_TYPES).optional().describe("create (padr\xE3o) | extend | cover | upload_extend | upload_cover | sample. Veja a descri\xE7\xE3o da ferramenta."),
|
|
22435
|
+
source_generation_id: external_exports.string().min(1).optional().describe("extend/cover: id da gera\xE7\xE3o de m\xFAsica original."),
|
|
22436
|
+
source_clip_id: external_exports.string().min(1).optional().describe("extend/cover: clip_id da faixa original (aparece no resultado de generate_music / check_generation)."),
|
|
22437
|
+
continue_at: external_exports.number().min(0).optional().describe("extend/upload_extend: segundo da faixa a partir do qual continuar."),
|
|
22438
|
+
audio_weight: external_exports.number().min(0).max(1).optional().describe("cover/upload_cover/sample: fidelidade ao \xE1udio original (0 a 1)."),
|
|
22439
|
+
reference_audio_url: external_exports.string().min(1).optional().describe("upload_extend/upload_cover/sample: \xE1udio de origem. URL p\xFAblica ou caminho local (sobe para o storage da BePlus; outros hosts s\xE3o recusados)."),
|
|
22440
|
+
sample_start_s: external_exports.number().min(0).optional().describe("sample: in\xEDcio do trecho, em segundos."),
|
|
22441
|
+
sample_end_s: external_exports.number().min(0).optional().describe("sample: fim do trecho, em segundos (maior que o in\xEDcio)."),
|
|
22442
|
+
project: external_exports.string().max(64).optional().describe('Projeto (uuid OU code curto, ex.: "VRAO-26"). S\xF3 omita se o usu\xE1rio J\xC1 confirmou o projeto ativo nesta sess\xE3o.'),
|
|
22443
|
+
billing: external_exports.enum(["personal"]).optional().describe(
|
|
22444
|
+
'"personal" = cobra do seu diamante pessoal, IGNORANDO o projeto ativo (mesmo se houver um). Use quando quiser garantir que isto N\xC3O sai do budget de nenhum projeto. Incompat\xEDvel com `project`.'
|
|
22445
|
+
)
|
|
22446
|
+
}
|
|
22447
|
+
},
|
|
22448
|
+
async (args) => {
|
|
22449
|
+
try {
|
|
22450
|
+
const customMode = args.custom_mode ?? false;
|
|
22451
|
+
const invalid = validateMusicArgs(args);
|
|
22452
|
+
if (invalid) return errorResult(invalid);
|
|
22453
|
+
const input = {
|
|
22454
|
+
custom_mode: customMode,
|
|
22455
|
+
make_instrumental: args.make_instrumental ?? false
|
|
22456
|
+
};
|
|
22457
|
+
if (args.vocal_gender) input.vocal_gender = args.vocal_gender;
|
|
22458
|
+
if (args.negative_tags?.trim()) input.negative_tags = args.negative_tags.trim();
|
|
22459
|
+
if (args.style_weight != null) input.style_weight = args.style_weight;
|
|
22460
|
+
if (args.weirdness_constraint != null) input.weirdness_constraint = args.weirdness_constraint;
|
|
22461
|
+
if (args.tags?.trim()) input.tags = args.tags.trim();
|
|
22462
|
+
if (args.duration != null) input.duration = args.duration;
|
|
22463
|
+
if (args.auto_lyrics != null) input.auto_lyrics = args.auto_lyrics;
|
|
22464
|
+
if (args.task_type) input.task_type = args.task_type;
|
|
22465
|
+
if (args.source_generation_id) input.source_generation_id = args.source_generation_id.trim();
|
|
22466
|
+
if (args.source_clip_id) input.source_clip_id = args.source_clip_id.trim();
|
|
22467
|
+
if (args.continue_at != null) input.continue_at = args.continue_at;
|
|
22468
|
+
if (args.audio_weight != null) input.audio_weight = args.audio_weight;
|
|
22469
|
+
if (args.sample_start_s != null) input.sample_start_s = args.sample_start_s;
|
|
22470
|
+
if (args.sample_end_s != null) input.sample_end_s = args.sample_end_s;
|
|
22471
|
+
if (args.reference_audio_url) input.reference_audio_url = await resolveRef(cfg, args.reference_audio_url.trim());
|
|
22472
|
+
if (customMode) {
|
|
22473
|
+
input.prompt = args.lyrics?.trim() || args.description.trim();
|
|
22474
|
+
if (args.title?.trim()) input.title = args.title.trim();
|
|
22475
|
+
} else {
|
|
22476
|
+
const desc = args.description.trim();
|
|
22477
|
+
input.gpt_description_prompt = desc;
|
|
22478
|
+
input.prompt = desc;
|
|
22479
|
+
}
|
|
22480
|
+
const start = await client.startGeneration(
|
|
22481
|
+
compact({
|
|
22482
|
+
model: args.model,
|
|
22483
|
+
input,
|
|
22484
|
+
project: resolveProjectArg(args.project, args.billing, cfg.activeProject),
|
|
22485
|
+
billing: args.billing
|
|
22486
|
+
})
|
|
22487
|
+
);
|
|
22488
|
+
if (!start.success || !start.generation_id) {
|
|
22489
|
+
if (isTeamGatingError(start.message)) return errorResult(await buildPickProjectMessage(client));
|
|
22490
|
+
return errorResult(start.message || "Falha ao iniciar a gera\xE7\xE3o.");
|
|
22491
|
+
}
|
|
22492
|
+
const footer = costFooter(start, cfg);
|
|
22493
|
+
const { settled, status } = await pollUntilDone(client, start.generation_id, MUSIC_BUDGET_MS);
|
|
22494
|
+
if (!settled) {
|
|
22495
|
+
return textResult(
|
|
22496
|
+
`\u23F3 M\xFAsica ainda em produ\xE7\xE3o (id: ${start.generation_id}, ${status.progress ?? 0}%). Suno costuma levar alguns minutos \u2014 use check_generation com esse id.
|
|
22497
|
+
${footer}`
|
|
22498
|
+
);
|
|
22499
|
+
}
|
|
22500
|
+
if (status.status === "failed") {
|
|
22501
|
+
return errorResult(`Gera\xE7\xE3o falhou: ${status.error_message || "erro desconhecido"}.
|
|
22502
|
+
${footer}`);
|
|
22503
|
+
}
|
|
22504
|
+
const urls = outputUrls(status);
|
|
22505
|
+
const clips = await getMusicClips(client, start.generation_id, status);
|
|
22506
|
+
const list = formatMusicClips(start.generation_id, urls, clips);
|
|
22507
|
+
return textResult(`\u2705 M\xFAsica gerada (${args.model}), generation_id: ${start.generation_id}.
|
|
22508
|
+
${list}
|
|
22509
|
+
${footer}`);
|
|
22510
|
+
} catch (e) {
|
|
22511
|
+
const msg = e instanceof ApiError ? e.message : e instanceof Error ? e.message : "";
|
|
22512
|
+
if (isTeamGatingError(msg)) return errorResult(await buildPickProjectMessage(client));
|
|
22513
|
+
if (isBudgetBlockedError(msg)) {
|
|
22514
|
+
return errorResult(`${apiErrorToUserMessage(e)}
|
|
22515
|
+
${BUDGET_BLOCKED_GUIDANCE}`);
|
|
22516
|
+
}
|
|
22517
|
+
return errorResult(apiErrorToUserMessage(e));
|
|
22518
|
+
}
|
|
22519
|
+
}
|
|
22520
|
+
);
|
|
22521
|
+
server.registerTool(
|
|
22522
|
+
"music_tool",
|
|
22523
|
+
{
|
|
22524
|
+
title: "Ferramenta de faixa de m\xFAsica",
|
|
22525
|
+
description: "Roda uma ferramenta numa faixa de m\xFAsica Suno j\xE1 pronta: wav (2 cr\xE9ditos AIMusic), midi (10), stems-basic (20, 4 stems: vocal e instrumental), stems-full (50, at\xE9 24 stems), bpm (1) e lyrics-timeline (1, palavras com tempo). Consome diamantes (o valor vem do backend); pedir de novo uma ferramenta j\xE1 conclu\xEDda devolve o resultado salvo sem cobrar. Aguarda at\xE9 ~4 min. Informe generation_id e a faixa por `clip_id` ou `clip_index` (1 ou 2).",
|
|
22526
|
+
inputSchema: {
|
|
22527
|
+
generation_id: external_exports.string().min(1).describe("Id da gera\xE7\xE3o de m\xFAsica conclu\xEDda."),
|
|
22528
|
+
tool: external_exports.enum(MUSIC_TOOLS).describe("wav | midi | stems-basic | stems-full | bpm | lyrics-timeline."),
|
|
22529
|
+
clip_id: external_exports.string().min(1).optional().describe("clip_id da faixa (veja generate_music / check_generation)."),
|
|
22530
|
+
clip_index: external_exports.number().int().min(1).max(2).optional().describe("Alternativa ao clip_id: 1 ou 2."),
|
|
22531
|
+
full_words: external_exports.boolean().optional().describe("lyrics-timeline: true devolve todas as palavras com tempo. Padr\xE3o: s\xF3 um resumo.")
|
|
22532
|
+
}
|
|
22533
|
+
},
|
|
22534
|
+
async (args) => {
|
|
22535
|
+
try {
|
|
22536
|
+
let clipId = args.clip_id?.trim();
|
|
22537
|
+
if (!clipId) {
|
|
22538
|
+
if (!args.clip_index) return errorResult("Informe `clip_id` ou `clip_index` (1 ou 2).");
|
|
22539
|
+
const clips = await getMusicClips(client, args.generation_id);
|
|
22540
|
+
clipId = clips[args.clip_index - 1]?.clip_id;
|
|
22541
|
+
if (!clipId) return errorResult("N\xE3o achei o clip_id dessa faixa. Passe `clip_id` explicitamente.");
|
|
22542
|
+
}
|
|
22543
|
+
const tool = args.tool;
|
|
22544
|
+
let state = await client.startMusicTool(args.generation_id, { clip_id: clipId, tool });
|
|
22545
|
+
const started = Date.now();
|
|
22546
|
+
while (state?.status === "processing" && Date.now() - started < TOOL_BUDGET_MS) {
|
|
22547
|
+
await sleep2(TOOL_POLL_MS);
|
|
22548
|
+
state = await client.getMusicTool(args.generation_id, clipId, tool) ?? state;
|
|
22549
|
+
}
|
|
22550
|
+
if (!state) return errorResult("O backend n\xE3o devolveu o estado da ferramenta.");
|
|
22551
|
+
if (state.status === "failed") {
|
|
22552
|
+
return errorResult(`${tool} falhou: ${state.error || "erro desconhecido"}.`);
|
|
22553
|
+
}
|
|
22554
|
+
if (state.status === "processing") {
|
|
22555
|
+
return textResult(
|
|
22556
|
+
`\u23F3 ${tool} ainda processando (custo ${state.cost} \u{1F48E}). Chame music_tool de novo com os mesmos argumentos em instantes: n\xE3o cobra de novo.`
|
|
22557
|
+
);
|
|
22558
|
+
}
|
|
22559
|
+
return textResult(`\u2705 ${tool} pronto (${state.cost} \u{1F48E}).
|
|
22560
|
+
${formatToolResult(state, args.full_words ?? false)}`);
|
|
22561
|
+
} catch (e) {
|
|
22562
|
+
return errorResult(apiErrorToUserMessage(e));
|
|
22563
|
+
}
|
|
22564
|
+
}
|
|
22565
|
+
);
|
|
22566
|
+
}
|
|
22567
|
+
function formatToolResult(state, fullWords) {
|
|
22568
|
+
const r = state.result ?? {};
|
|
22569
|
+
const lines = [];
|
|
22570
|
+
if (r.url) lines.push(r.url);
|
|
22571
|
+
if (r.stems?.length) lines.push(...r.stems.map((s) => `${s.title}: ${s.url}`));
|
|
22572
|
+
if (r.bpm) lines.push(`BPM m\xE9dio ${r.bpm.avg} (m\xEDn ${r.bpm.min}, m\xE1x ${r.bpm.max})`);
|
|
22573
|
+
if (r.words?.length) {
|
|
22574
|
+
if (fullWords) {
|
|
22575
|
+
lines.push(r.words.map((w) => `${w.s.toFixed(2)}-${w.e.toFixed(2)} ${w.w}`).join("\n"));
|
|
22576
|
+
} else {
|
|
22577
|
+
const first = r.words.slice(0, 12).map((w) => w.w).join(" ");
|
|
22578
|
+
const end = r.words[r.words.length - 1].e;
|
|
22579
|
+
lines.push(
|
|
22580
|
+
`${r.words.length} palavras sincronizadas, at\xE9 ${end.toFixed(1)}s. In\xEDcio: "${first}". Use full_words=true para a lista completa.`
|
|
22581
|
+
);
|
|
22582
|
+
}
|
|
22583
|
+
}
|
|
22584
|
+
return lines.join("\n") || "(sem resultado)";
|
|
22585
|
+
}
|
|
22586
|
+
|
|
22253
22587
|
// src/tools/generation.ts
|
|
22254
22588
|
var IMAGE_BUDGET_MS = 9e4;
|
|
22255
22589
|
var VIDEO_BUDGET_MS = 12e4;
|
|
@@ -22282,9 +22616,11 @@ function registerGenerationTools(server, client, cfg) {
|
|
|
22282
22616
|
// Sem teto local: quem conhece o limite de cada provedor é o backend, e
|
|
22283
22617
|
// ele responde com quanto reduzir. Um `.max()` aqui rejeitava prompts
|
|
22284
22618
|
// que o modelo aceita (Seedream/nano-banana não têm limite publicado).
|
|
22285
|
-
prompt: external_exports.string().
|
|
22619
|
+
prompt: external_exports.string().default("").describe(
|
|
22620
|
+
'Descri\xE7\xE3o da imagem desejada. No seedream-5.0-pro-layers \xE9 opcional: diga o que separar (ex.: "o produto, o texto e o fundo") ou deixe vazio para o modelo escolher.'
|
|
22621
|
+
),
|
|
22286
22622
|
model: external_exports.enum(IMAGE_MODELS).default(DEFAULTS.imageModel).describe(
|
|
22287
|
-
"nano-banana-2.1 = padr\xE3o, o mais novo e o mais barato do Google (metade do 2, texto mais n\xEDtido); nano-banana-2 = vers\xE3o anterior, mesmo comportamento; nano-banana-pro = melhor qualidade; gpt-image-2 = OpenAI; gpt-image-1 = OpenAI, \xDANICO que faz fundo transparente de verdade (PNG/WebP com alpha) \u2014 o gpt-image-2 N\xC3O faz, use este pra recorte/sticker; seedream-5.0-lite = mais barato do cat\xE1logo, \xFAnico que chega a 4K, gera s\xE9ries e mant\xE9m o ESTILO da imagem de refer\xEAncia; seedream-5.0-pro = acabamento fotorrealista, s\xF3 at\xE9 2K (1K/1.5K/2K, propor\xE7\xE3o, tamanho livre e at\xE9 4 imagens por pedido) \u2014 preserva o personagem da refer\xEAncia mas costuma reinterpretar o estilo (um vetor flat volta em 3D)."
|
|
22623
|
+
"nano-banana-2.1 = padr\xE3o, o mais novo e o mais barato do Google (metade do 2, texto mais n\xEDtido); nano-banana-2 = vers\xE3o anterior, mesmo comportamento; nano-banana-pro = melhor qualidade; gpt-image-2 = OpenAI; gpt-image-1 = OpenAI, \xDANICO que faz fundo transparente de verdade (PNG/WebP com alpha) \u2014 o gpt-image-2 N\xC3O faz, use este pra recorte/sticker; seedream-5.0-lite = mais barato do cat\xE1logo, \xFAnico que chega a 4K, gera s\xE9ries e mant\xE9m o ESTILO da imagem de refer\xEAncia; seedream-5.0-pro = acabamento fotorrealista, s\xF3 at\xE9 2K (1K/1.5K/2K, propor\xE7\xE3o, tamanho livre e at\xE9 4 imagens por pedido) \u2014 preserva o personagem da refer\xEAncia mas costuma reinterpretar o estilo (um vetor flat volta em 3D); seedream-5.0-pro-layers = N\xC3O gera do zero: separa UMA imagem (reference_images com 1 item) em base + at\xE9 16 camadas PNG transparentes do mesmo tamanho, prontas para empilhar ou virar PSD. Cobra o teto de 17 imagens e estorna as que n\xE3o vierem."
|
|
22288
22624
|
),
|
|
22289
22625
|
aspect_ratio: external_exports.enum(ASPECT_RATIOS).optional().describe(
|
|
22290
22626
|
"Propor\xE7\xE3o. nano-banana: padr\xE3o 1:1. Seedream Pro: combina com size (1K/1.5K/2K) e vira LARGURAxALTURA no backend; sem ela o modelo escolhe o formato pelo prompt."
|
|
@@ -22341,6 +22677,25 @@ function registerGenerationTools(server, client, cfg) {
|
|
|
22341
22677
|
const isSeedreamLite = args.model === "bytedance/seedream-5.0-lite";
|
|
22342
22678
|
const isSeedreamPro = args.model === "bytedance/seedream-5.0-pro";
|
|
22343
22679
|
const isSeedream = isSeedreamLite || isSeedreamPro;
|
|
22680
|
+
const isSeedreamLayers = args.model === "bytedance/seedream-5.0-pro-layers";
|
|
22681
|
+
if (!isSeedreamLayers && !args.prompt.trim()) {
|
|
22682
|
+
return errorResult("prompt \xE9 obrigat\xF3rio (s\xF3 o seedream-5.0-pro-layers aceita sem prompt).");
|
|
22683
|
+
}
|
|
22684
|
+
if (isSeedreamLayers) {
|
|
22685
|
+
if (args.reference_images?.length !== 1) {
|
|
22686
|
+
return errorResult(
|
|
22687
|
+
"seedream-5.0-pro-layers separa UMA imagem em camadas: passe exatamente 1 item em reference_images."
|
|
22688
|
+
);
|
|
22689
|
+
}
|
|
22690
|
+
if (args.size && !SEEDREAM_LAYERS_SIZES.includes(args.size)) {
|
|
22691
|
+
return errorResult(
|
|
22692
|
+
`seedream-5.0-pro-layers aceita size ${SEEDREAM_LAYERS_SIZES.join("/")} (voc\xEA pediu ${args.size}).`
|
|
22693
|
+
);
|
|
22694
|
+
}
|
|
22695
|
+
if (args.num_images || args.batch_images) {
|
|
22696
|
+
return errorResult("seedream-5.0-pro-layers devolve sempre base + camadas; num_images e batch_images n\xE3o se aplicam.");
|
|
22697
|
+
}
|
|
22698
|
+
}
|
|
22344
22699
|
if (isSeedream) {
|
|
22345
22700
|
const allowed = isSeedreamPro ? SEEDREAM_PRO_SIZES : SEEDREAM_LITE_SIZES;
|
|
22346
22701
|
const free = args.size?.match(/^(\d+)x(\d+)$/);
|
|
@@ -22405,7 +22760,7 @@ function registerGenerationTools(server, client, cfg) {
|
|
|
22405
22760
|
reference_images,
|
|
22406
22761
|
google_search: args.google_search,
|
|
22407
22762
|
image_search: args.image_search,
|
|
22408
|
-
size: args.size ?? (isSeedream ? "2K" : void 0),
|
|
22763
|
+
size: args.size ?? (isSeedream ? "2K" : isSeedreamLayers ? "auto" : void 0),
|
|
22409
22764
|
// Série do Seedream Lite: o backend lê o par sequential_image_generation
|
|
22410
22765
|
// + max_images (nomes da API da BytePlus). `batch_images` é só o nome
|
|
22411
22766
|
// amigável exposto aqui.
|
|
@@ -22772,7 +23127,8 @@ ${BUDGET_BLOCKED_GUIDANCE}`);
|
|
|
22772
23127
|
if (status.status === "completed") {
|
|
22773
23128
|
const urls = outputUrls(status);
|
|
22774
23129
|
const blocks = await buildImageBlocks(urls, cfg);
|
|
22775
|
-
const
|
|
23130
|
+
const isAudio = urls.length > 0 && urls.every((u) => /\.(mp3|m4a|wav|ogg)(\?|$)/i.test(u));
|
|
23131
|
+
const list = isAudio ? formatMusicClips(generation_id, urls, await getMusicClips(client, generation_id, status)) : urls.map((u, i) => `${i + 1}. ${u}`).join("\n") || "(sem URLs)";
|
|
22776
23132
|
const draftProvider = status.seedance_provider ?? "byteplus";
|
|
22777
23133
|
const draftHint = status.draft_task_id ? `
|
|
22778
23134
|
|
|
@@ -22927,6 +23283,7 @@ function isRemote(ref) {
|
|
|
22927
23283
|
return /^https?:\/\//i.test(ref);
|
|
22928
23284
|
}
|
|
22929
23285
|
function toLocalPath2(ref) {
|
|
23286
|
+
refuseLocalPath(ref);
|
|
22930
23287
|
if (ref.startsWith("file://")) return fileURLToPath2(ref);
|
|
22931
23288
|
if (ref === "~") return homedir3();
|
|
22932
23289
|
if (ref.startsWith("~/")) return resolve2(homedir3(), ref.slice(2));
|
|
@@ -22952,7 +23309,7 @@ async function readAt(ref, offset, len) {
|
|
|
22952
23309
|
const ctrl = new AbortController();
|
|
22953
23310
|
const timer = setTimeout(() => ctrl.abort(), FETCH_TIMEOUT_MS);
|
|
22954
23311
|
try {
|
|
22955
|
-
const res = await
|
|
23312
|
+
const res = await safeFetch(ref, {
|
|
22956
23313
|
headers: { Range: `bytes=${offset}-${offset + len - 1}` },
|
|
22957
23314
|
signal: ctrl.signal
|
|
22958
23315
|
});
|
|
@@ -22995,7 +23352,7 @@ async function readHead(ref, bytes) {
|
|
|
22995
23352
|
const ctrl = new AbortController();
|
|
22996
23353
|
const timer = setTimeout(() => ctrl.abort(), FETCH_TIMEOUT_MS);
|
|
22997
23354
|
try {
|
|
22998
|
-
const res = await
|
|
23355
|
+
const res = await safeFetch(ref, {
|
|
22999
23356
|
headers: { Range: `bytes=0-${bytes - 1}` },
|
|
23000
23357
|
signal: ctrl.signal
|
|
23001
23358
|
});
|
|
@@ -23139,7 +23496,7 @@ function probeMp4(b) {
|
|
|
23139
23496
|
}
|
|
23140
23497
|
async function fileSizeOf(ref) {
|
|
23141
23498
|
if (!isRemote(ref)) return (await stat3(toLocalPath2(ref))).size;
|
|
23142
|
-
const res = await
|
|
23499
|
+
const res = await safeFetch(ref, { method: "HEAD" });
|
|
23143
23500
|
return Number(res.headers.get("content-length") ?? 0);
|
|
23144
23501
|
}
|
|
23145
23502
|
var ProbeError = class extends Error {
|
|
@@ -23701,100 +24058,6 @@ Use o valor de "voice" no generate_audio com um modelo 3.8 (sem multi_speaker).`
|
|
|
23701
24058
|
);
|
|
23702
24059
|
}
|
|
23703
24060
|
|
|
23704
|
-
// src/tools/music.ts
|
|
23705
|
-
var MUSIC_BUDGET_MS = 15e4;
|
|
23706
|
-
function registerMusicTools(server, client, cfg) {
|
|
23707
|
-
server.registerTool(
|
|
23708
|
-
"generate_music",
|
|
23709
|
-
{
|
|
23710
|
-
title: "Gerar m\xFAsica",
|
|
23711
|
-
description: 'Gera uma m\xFAsica completa (com vocais ou instrumental) usando o Suno no IA Lab da BePlus. ASS\xCDNCRONO e LENTO (geralmente 30s\u20132+ min). Aguarda at\xE9 ~2,5 min; se n\xE3o terminar, retorna o generation_id e o status \u2014 ent\xE3o use check_generation. Consome diamantes. DOIS modos: (a) DESCRI\xC7\xC3O (custom_mode=false, padr\xE3o) \u2192 passe `description` ("uma balada triste de piano"); (b) LETRA PR\xD3PRIA (custom_mode=true) \u2192 passe `lyrics` (a letra) e opcionalmente `title`. Use `tags` para g\xEAnero/estilo em ambos os modos (ex.: "sertanejo, animado, vocal feminino"). `vocal_gender` (f/m) s\xF3 \xE9 aceito pelo Suno v4.5 e \xE9 ignorado quando `make_instrumental=true`.' + TEAM_GATING_NOTE,
|
|
23712
|
-
inputSchema: {
|
|
23713
|
-
model: external_exports.enum(MUSIC_MODELS).default(DEFAULTS.musicModel).describe("suno/v5-5 = mais novo; suno/v4-5 = r\xE1pido/consistente (\xFAnico que aceita vocal_gender)."),
|
|
23714
|
-
custom_mode: external_exports.boolean().optional().describe("false (padr\xE3o) = gera a partir de `description`; true = usa sua `lyrics` pr\xF3pria."),
|
|
23715
|
-
description: external_exports.string().max(2500).optional().describe("Modo descri\xE7\xE3o (custom_mode=false): descreva a m\xFAsica desejada. Obrigat\xF3rio nesse modo."),
|
|
23716
|
-
lyrics: external_exports.string().max(5e3).optional().describe("Modo letra (custom_mode=true): a letra completa da m\xFAsica. Obrigat\xF3rio nesse modo."),
|
|
23717
|
-
title: external_exports.string().max(120).optional().describe("T\xEDtulo da m\xFAsica (modo letra)."),
|
|
23718
|
-
tags: external_exports.string().max(1e3).optional().describe('G\xEAnero/estilo, separados por v\xEDrgula (ex.: "pop, energ\xE9tico, vocal masculino").'),
|
|
23719
|
-
make_instrumental: external_exports.boolean().optional().describe("true = sem vocais (apenas instrumental). Padr\xE3o false."),
|
|
23720
|
-
vocal_gender: external_exports.enum(VOCAL_GENDERS).optional().describe("G\xEAnero do vocal: f (feminino) ou m (masculino). S\xF3 Suno v4.5; ignorado se instrumental."),
|
|
23721
|
-
negative_tags: external_exports.string().max(1e3).optional().describe("Estilos/elementos a evitar."),
|
|
23722
|
-
style_weight: external_exports.number().min(0).max(1).optional().describe("Peso do estilo (0\u20131). Maior = segue mais as tags. Omita para o modelo decidir."),
|
|
23723
|
-
weirdness_constraint: external_exports.number().min(0).max(1).optional().describe("Criatividade/imprevisibilidade (0\u20131). Omita para o modelo decidir."),
|
|
23724
|
-
project: external_exports.string().max(64).optional().describe('Projeto (uuid OU code curto, ex.: "VRAO-26"). S\xF3 omita se o usu\xE1rio J\xC1 confirmou o projeto ativo nesta sess\xE3o.'),
|
|
23725
|
-
billing: external_exports.enum(["personal"]).optional().describe(
|
|
23726
|
-
'"personal" = cobra do seu diamante pessoal, IGNORANDO o projeto ativo (mesmo se houver um). Use quando quiser garantir que isto N\xC3O sai do budget de nenhum projeto. Incompat\xEDvel com `project`.'
|
|
23727
|
-
)
|
|
23728
|
-
}
|
|
23729
|
-
},
|
|
23730
|
-
async (args) => {
|
|
23731
|
-
try {
|
|
23732
|
-
const customMode = args.custom_mode ?? false;
|
|
23733
|
-
if (customMode && !args.lyrics?.trim()) {
|
|
23734
|
-
return errorResult("Modo letra (custom_mode=true) exige `lyrics`.");
|
|
23735
|
-
}
|
|
23736
|
-
if (!customMode && !args.description?.trim()) {
|
|
23737
|
-
return errorResult("Modo descri\xE7\xE3o (custom_mode=false) exige `description`.");
|
|
23738
|
-
}
|
|
23739
|
-
const input = {
|
|
23740
|
-
custom_mode: customMode,
|
|
23741
|
-
make_instrumental: args.make_instrumental ?? false
|
|
23742
|
-
};
|
|
23743
|
-
if (args.vocal_gender) input.vocal_gender = args.vocal_gender;
|
|
23744
|
-
if (args.negative_tags?.trim()) input.negative_tags = args.negative_tags.trim();
|
|
23745
|
-
if (args.style_weight != null) input.style_weight = args.style_weight;
|
|
23746
|
-
if (args.weirdness_constraint != null) input.weirdness_constraint = args.weirdness_constraint;
|
|
23747
|
-
if (args.tags?.trim()) input.tags = args.tags.trim();
|
|
23748
|
-
if (customMode) {
|
|
23749
|
-
input.prompt = args.lyrics.trim();
|
|
23750
|
-
if (args.title?.trim()) input.title = args.title.trim();
|
|
23751
|
-
} else {
|
|
23752
|
-
const desc = args.description.trim();
|
|
23753
|
-
input.gpt_description_prompt = desc;
|
|
23754
|
-
input.prompt = desc;
|
|
23755
|
-
}
|
|
23756
|
-
const start = await client.startGeneration(
|
|
23757
|
-
compact({
|
|
23758
|
-
model: args.model,
|
|
23759
|
-
input,
|
|
23760
|
-
project: resolveProjectArg(args.project, args.billing, cfg.activeProject),
|
|
23761
|
-
billing: args.billing
|
|
23762
|
-
})
|
|
23763
|
-
);
|
|
23764
|
-
if (!start.success || !start.generation_id) {
|
|
23765
|
-
if (isTeamGatingError(start.message)) return errorResult(await buildPickProjectMessage(client));
|
|
23766
|
-
return errorResult(start.message || "Falha ao iniciar a gera\xE7\xE3o.");
|
|
23767
|
-
}
|
|
23768
|
-
const footer = costFooter(start, cfg);
|
|
23769
|
-
const { settled, status } = await pollUntilDone(client, start.generation_id, MUSIC_BUDGET_MS);
|
|
23770
|
-
if (!settled) {
|
|
23771
|
-
return textResult(
|
|
23772
|
-
`\u23F3 M\xFAsica ainda em produ\xE7\xE3o (id: ${start.generation_id}, ${status.progress ?? 0}%). Suno costuma levar alguns minutos \u2014 use check_generation com esse id.
|
|
23773
|
-
${footer}`
|
|
23774
|
-
);
|
|
23775
|
-
}
|
|
23776
|
-
if (status.status === "failed") {
|
|
23777
|
-
return errorResult(`Gera\xE7\xE3o falhou: ${status.error_message || "erro desconhecido"}.
|
|
23778
|
-
${footer}`);
|
|
23779
|
-
}
|
|
23780
|
-
const urls = outputUrls(status);
|
|
23781
|
-
const list = urls.map((u, i) => `${i + 1}. ${u}`).join("\n") || "(sem URLs)";
|
|
23782
|
-
return textResult(`\u2705 M\xFAsica gerada (${args.model}).
|
|
23783
|
-
${list}
|
|
23784
|
-
${footer}`);
|
|
23785
|
-
} catch (e) {
|
|
23786
|
-
const msg = e instanceof ApiError ? e.message : e instanceof Error ? e.message : "";
|
|
23787
|
-
if (isTeamGatingError(msg)) return errorResult(await buildPickProjectMessage(client));
|
|
23788
|
-
if (isBudgetBlockedError(msg)) {
|
|
23789
|
-
return errorResult(`${apiErrorToUserMessage(e)}
|
|
23790
|
-
${BUDGET_BLOCKED_GUIDANCE}`);
|
|
23791
|
-
}
|
|
23792
|
-
return errorResult(apiErrorToUserMessage(e));
|
|
23793
|
-
}
|
|
23794
|
-
}
|
|
23795
|
-
);
|
|
23796
|
-
}
|
|
23797
|
-
|
|
23798
24061
|
// src/tools/account.ts
|
|
23799
24062
|
function registerAccountTools(server, client, _cfg) {
|
|
23800
24063
|
server.registerTool(
|
|
@@ -23884,6 +24147,7 @@ Limites simult\xE2neos por usu\xE1rio: imagem=${m.limits.perUserMaxImage}, v\xED
|
|
|
23884
24147
|
quality: external_exports.string().optional(),
|
|
23885
24148
|
aspect_ratio: external_exports.string().optional().describe("Seedream Pro: a propor\xE7\xE3o muda os pixels e a faixa de pre\xE7o."),
|
|
23886
24149
|
num_images: external_exports.number().int().min(1).optional().describe("Seedream Pro: imagens por pedido; cada uma \xE9 cobrada."),
|
|
24150
|
+
task_type: external_exports.string().optional().describe('[m\xFAsica] extend e upload_extend usam o pre\xE7o "suno/extend"; os demais, o do modelo.'),
|
|
23887
24151
|
provider: external_exports.enum(["byteplus", "fal"]).optional().describe("Seedance: provedor que vai gerar (padr\xE3o byteplus). `fal` tem pre\xE7o pr\xF3prio, mais caro."),
|
|
23888
24152
|
// Upscale: o preço não sai de uma chave fixa — vem das medidas do
|
|
23889
24153
|
// arquivo (megapixel de saída na imagem, bloco de 10s no vídeo). Sem
|
|
@@ -23920,7 +24184,7 @@ Limites simult\xE2neos por usu\xE1rio: imagem=${m.limits.perUserMaxImage}, v\xED
|
|
|
23920
24184
|
}
|
|
23921
24185
|
const c = await client.calculateCost(
|
|
23922
24186
|
compact({
|
|
23923
|
-
model: args.model,
|
|
24187
|
+
model: args.model.startsWith("suno/") && (args.task_type === "extend" || args.task_type === "upload_extend") ? "suno/extend" : args.model,
|
|
23924
24188
|
resolution: args.resolution,
|
|
23925
24189
|
duration: args.duration,
|
|
23926
24190
|
mode: args.mode,
|
|
@@ -24514,7 +24778,7 @@ var KINDS = {
|
|
|
24514
24778
|
music: {
|
|
24515
24779
|
title: "Trilha",
|
|
24516
24780
|
size: { w: 320, h: 212 },
|
|
24517
|
-
data: { model: "suno/
|
|
24781
|
+
data: { model: "suno/v6", instrumental: true, negativeTags: ["vocals", "choir", "whistling", "marching band", "epic orchestral"] },
|
|
24518
24782
|
inputs: ["prompt", "negative"],
|
|
24519
24783
|
outputs: ["audio", "approved"],
|
|
24520
24784
|
dynamic: "lyrics quando n\xE3o \xE9 instrumental"
|
|
@@ -26005,10 +26269,10 @@ function withExtension(name, file) {
|
|
|
26005
26269
|
return ext && extname3(clean).toLowerCase() !== ext.toLowerCase() ? `${clean}${ext}` : clean;
|
|
26006
26270
|
}
|
|
26007
26271
|
async function uploadMedia(cfg, source, name) {
|
|
26008
|
-
const
|
|
26009
|
-
const loaded =
|
|
26272
|
+
const remote2 = isRemoteRef(source);
|
|
26273
|
+
const loaded = remote2 ? await loadRemote(source) : await loadLocal(source);
|
|
26010
26274
|
const file = { ...loaded, name: withExtension(name, loaded) };
|
|
26011
|
-
const reused =
|
|
26275
|
+
const reused = remote2 && new URL(source).host === BEPLUS_STORAGE_HOST;
|
|
26012
26276
|
const url = reused ? source : await uploadBytes(cfg, file);
|
|
26013
26277
|
const kind = kindOfMime(file.mime);
|
|
26014
26278
|
let meta = null;
|
|
@@ -27559,7 +27823,7 @@ N\xF3s: ${Object.entries(ids).map(([k, v]) => `${k}=${v}`).join(" \xB7 ")}` : ""
|
|
|
27559
27823
|
import { createRequire } from "module";
|
|
27560
27824
|
var require2 = createRequire(import.meta.url);
|
|
27561
27825
|
function readVersion() {
|
|
27562
|
-
if ("0.
|
|
27826
|
+
if ("0.41.0") return "0.41.0";
|
|
27563
27827
|
try {
|
|
27564
27828
|
const pkg = require2("../package.json");
|
|
27565
27829
|
return pkg.version ?? "0.0.0-unknown";
|