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/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, isIP) {
3307
- const re = isIP ? HOST_DELIM_NO_COLON_RE : HOST_DELIM_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 isIP = false;
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
- isIP = ipv6result.isIPV6;
3819
+ isIP2 = ipv6result.isIPV6;
3820
3820
  } else {
3821
- isIP = true;
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) && isIP === false && nonSimpleDomain(parsed.host)) {
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), isIP);
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 fetch(url, { signal: AbortSignal.timeout(DOWNLOAD_TIMEOUT_MS) });
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/v5-5"
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().min(1).describe("Descri\xE7\xE3o da imagem desejada."),
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 list = urls.map((u, i) => `${i + 1}. ${u}`).join("\n") || "(sem URLs)";
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 fetch(ref, {
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 fetch(ref, {
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 fetch(ref, { method: "HEAD" });
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/v5-5", instrumental: true, negativeTags: ["vocals", "choir", "whistling", "marching band", "epic orchestral"] },
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 remote = isRemoteRef(source);
26009
- const loaded = remote ? await loadRemote(source) : await loadLocal(source);
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 = remote && new URL(source).host === BEPLUS_STORAGE_HOST;
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.38.0") return "0.38.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";