beplus-mcp 0.18.0 → 0.19.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 +1 -0
- package/dist/index.js +520 -61
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -82,6 +82,7 @@ Reinicie o cliente. Rode a tool **`whoami`** para confirmar o vínculo.
|
|
|
82
82
|
|------|-----------|
|
|
83
83
|
| `generate_image` | Gera imagem (nano-banana / gpt-image-2). Bloqueante ~90s; retorna URL + imagem inline. Suporta refs, tamanho/qualidade, e extras do gpt-image-2 (background, moderation, output_format/compression). |
|
|
84
84
|
| `generate_video` | Gera vídeo (Seedance / Kling). Aguarda ~120s; senão devolve o id pra `check_generation`. Suporta first/last frame, refs de imagem/vídeo/áudio, prompt negativo, áudio gerado, mode e motion-control. |
|
|
85
|
+
| `upscale` | Melhora a resolução de uma imagem ou vídeo que já existe (Topaz). Detecta o tipo pelo arquivo. Imagem até 4x (~20s, a partir de 6💎); vídeo até 30s e saída até 1080p, custa por bloco de 10s e **descarta o áudio**. Mede o arquivo sozinho para o preço sair certo. ⚠️ Vídeo é restrito à equipe. |
|
|
85
86
|
| `generate_audio` | Sintetiza fala (Gemini TTS, ~30 vozes, multi-locutor). Síncrono. |
|
|
86
87
|
| `generate_music` | Gera música completa (Suno v5.5 / v4.5) — modo descrição ou letra própria, tags, instrumental, vocal_gender, controles criativos. Async. |
|
|
87
88
|
| `check_generation` | Status de uma geração async por id. |
|
package/dist/index.js
CHANGED
|
@@ -515,6 +515,24 @@ var VIDEO_MODELS = [
|
|
|
515
515
|
"kwaivgi/kling-v3-video",
|
|
516
516
|
"kwaivgi/kling-v3-omni-video"
|
|
517
517
|
];
|
|
518
|
+
var UPSCALE_IMAGE_VARIANTS = [
|
|
519
|
+
"Wonder 3.5",
|
|
520
|
+
"Wonder 3",
|
|
521
|
+
"Wonder 2",
|
|
522
|
+
"Wonder",
|
|
523
|
+
"Recover 3",
|
|
524
|
+
"Standard MAX",
|
|
525
|
+
"Redefine",
|
|
526
|
+
"Recovery V2",
|
|
527
|
+
"Recovery"
|
|
528
|
+
];
|
|
529
|
+
var UPSCALE_VIDEO_VARIANTS = [
|
|
530
|
+
"Starlight Precise 2.6",
|
|
531
|
+
"Starlight HQ",
|
|
532
|
+
"Starlight Mini",
|
|
533
|
+
"Starlight Sharp",
|
|
534
|
+
"Starlight Fast 2"
|
|
535
|
+
];
|
|
518
536
|
var MUSIC_MODELS = [
|
|
519
537
|
"suno/v5-5",
|
|
520
538
|
"suno/v4-5"
|
|
@@ -865,11 +883,11 @@ ${BUDGET_BLOCKED_GUIDANCE}`);
|
|
|
865
883
|
`${args.model} s\xF3 suporta 480p ou 720p (voc\xEA pediu ${args.resolution}). Use seedance-2.0 (standard) para 1080p/4K.`
|
|
866
884
|
);
|
|
867
885
|
}
|
|
868
|
-
if (args.model
|
|
869
|
-
const
|
|
870
|
-
if (!
|
|
886
|
+
if (args.model?.startsWith("bytedance/seedance-") && args.aspect_ratio) {
|
|
887
|
+
const SEEDANCE_RATIOS = ["1:1", "16:9", "9:16", "4:3", "3:4", "21:9", "adaptive"];
|
|
888
|
+
if (!SEEDANCE_RATIOS.includes(args.aspect_ratio)) {
|
|
871
889
|
return errorResult(
|
|
872
|
-
|
|
890
|
+
`${args.model} n\xE3o aceita a propor\xE7\xE3o ${args.aspect_ratio}. Use uma destas: ${SEEDANCE_RATIOS.join(", ")}.`
|
|
873
891
|
);
|
|
874
892
|
}
|
|
875
893
|
}
|
|
@@ -996,7 +1014,7 @@ ${list}${flagsLine(status)}` }, ...blocks] };
|
|
|
996
1014
|
return textResult(`Nenhuma gera\xE7\xE3o${scope} encontrada.`);
|
|
997
1015
|
}
|
|
998
1016
|
const lines = generations.map((g, i) => {
|
|
999
|
-
const type = g.generation_type || "image";
|
|
1017
|
+
const type = g.model?.startsWith("topaz/upscale") ? "upscale" : g.generation_type || "image";
|
|
1000
1018
|
const when = g.created_at ? new Date(g.created_at).toISOString().slice(0, 10) : "\u2014";
|
|
1001
1019
|
const prompt = (g.prompt || "").replace(/\s+/g, " ").slice(0, 80);
|
|
1002
1020
|
const url = firstOutputUrl(g.output);
|
|
@@ -1098,8 +1116,413 @@ ${list}${flagsLine(status)}` }, ...blocks] };
|
|
|
1098
1116
|
);
|
|
1099
1117
|
}
|
|
1100
1118
|
|
|
1101
|
-
// src/tools/
|
|
1119
|
+
// src/tools/upscale.ts
|
|
1102
1120
|
import { z as z2 } from "zod";
|
|
1121
|
+
|
|
1122
|
+
// src/probe.ts
|
|
1123
|
+
import { readFile as readFile2, open } from "fs/promises";
|
|
1124
|
+
import { extname as extname2, isAbsolute as isAbsolute2, resolve as resolve2 } from "path";
|
|
1125
|
+
import { homedir as homedir2 } from "os";
|
|
1126
|
+
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
1127
|
+
var IMAGE_EXT = /* @__PURE__ */ new Set([".png", ".jpg", ".jpeg", ".webp"]);
|
|
1128
|
+
var VIDEO_EXT = /* @__PURE__ */ new Set([".mp4", ".mov", ".m4v"]);
|
|
1129
|
+
var HEADER_BYTES = 128 * 1024;
|
|
1130
|
+
var MAX_SCAN_BYTES = 8 * 1024 * 1024;
|
|
1131
|
+
var FETCH_TIMEOUT_MS = 3e4;
|
|
1132
|
+
function isRemote(ref) {
|
|
1133
|
+
return /^https?:\/\//i.test(ref);
|
|
1134
|
+
}
|
|
1135
|
+
function toLocalPath2(ref) {
|
|
1136
|
+
if (ref.startsWith("file://")) return fileURLToPath2(ref);
|
|
1137
|
+
if (ref === "~") return homedir2();
|
|
1138
|
+
if (ref.startsWith("~/")) return resolve2(homedir2(), ref.slice(2));
|
|
1139
|
+
return isAbsolute2(ref) ? ref : resolve2(process.cwd(), ref);
|
|
1140
|
+
}
|
|
1141
|
+
function kindFromExt(ref) {
|
|
1142
|
+
const ext = extname2(new URL(ref, "file:///").pathname || ref).toLowerCase();
|
|
1143
|
+
if (IMAGE_EXT.has(ext)) return "image";
|
|
1144
|
+
if (VIDEO_EXT.has(ext)) return "video";
|
|
1145
|
+
return null;
|
|
1146
|
+
}
|
|
1147
|
+
async function readHead(ref, bytes) {
|
|
1148
|
+
if (!isRemote(ref)) {
|
|
1149
|
+
const fh = await open(toLocalPath2(ref), "r");
|
|
1150
|
+
try {
|
|
1151
|
+
const buf = Buffer.alloc(bytes);
|
|
1152
|
+
const { bytesRead } = await fh.read(buf, 0, bytes, 0);
|
|
1153
|
+
return buf.subarray(0, bytesRead);
|
|
1154
|
+
} finally {
|
|
1155
|
+
await fh.close();
|
|
1156
|
+
}
|
|
1157
|
+
}
|
|
1158
|
+
const ctrl = new AbortController();
|
|
1159
|
+
const timer = setTimeout(() => ctrl.abort(), FETCH_TIMEOUT_MS);
|
|
1160
|
+
try {
|
|
1161
|
+
const res = await fetch(ref, {
|
|
1162
|
+
headers: { Range: `bytes=0-${bytes - 1}` },
|
|
1163
|
+
signal: ctrl.signal
|
|
1164
|
+
});
|
|
1165
|
+
if (!res.ok && res.status !== 206) throw new Error(`HTTP ${res.status}`);
|
|
1166
|
+
if (!res.body) return Buffer.from(await res.arrayBuffer());
|
|
1167
|
+
const chunks = [];
|
|
1168
|
+
let total = 0;
|
|
1169
|
+
const reader = res.body.getReader();
|
|
1170
|
+
while (total < bytes) {
|
|
1171
|
+
const { done, value } = await reader.read();
|
|
1172
|
+
if (done || !value) break;
|
|
1173
|
+
chunks.push(Buffer.from(value));
|
|
1174
|
+
total += value.length;
|
|
1175
|
+
}
|
|
1176
|
+
void reader.cancel().catch(() => {
|
|
1177
|
+
});
|
|
1178
|
+
return Buffer.concat(chunks).subarray(0, bytes);
|
|
1179
|
+
} finally {
|
|
1180
|
+
clearTimeout(timer);
|
|
1181
|
+
}
|
|
1182
|
+
}
|
|
1183
|
+
function probePng(b) {
|
|
1184
|
+
if (b.length < 24) return null;
|
|
1185
|
+
if (b.readUInt32BE(0) !== 2303741511) return null;
|
|
1186
|
+
if (b.toString("ascii", 12, 16) !== "IHDR") return null;
|
|
1187
|
+
return { width: b.readUInt32BE(16), height: b.readUInt32BE(20) };
|
|
1188
|
+
}
|
|
1189
|
+
function probeJpeg(b) {
|
|
1190
|
+
if (b.length < 4 || b[0] !== 255 || b[1] !== 216) return null;
|
|
1191
|
+
let i = 2;
|
|
1192
|
+
while (i + 9 < b.length) {
|
|
1193
|
+
if (b[i] !== 255) {
|
|
1194
|
+
i += 1;
|
|
1195
|
+
continue;
|
|
1196
|
+
}
|
|
1197
|
+
const marker = b[i + 1];
|
|
1198
|
+
if (marker >= 192 && marker <= 207 && marker !== 196 && marker !== 200 && marker !== 204) {
|
|
1199
|
+
return { height: b.readUInt16BE(i + 5), width: b.readUInt16BE(i + 7) };
|
|
1200
|
+
}
|
|
1201
|
+
if (marker === 216 || marker === 1 || marker >= 208 && marker <= 215) {
|
|
1202
|
+
i += 2;
|
|
1203
|
+
continue;
|
|
1204
|
+
}
|
|
1205
|
+
i += 2 + b.readUInt16BE(i + 2);
|
|
1206
|
+
}
|
|
1207
|
+
return null;
|
|
1208
|
+
}
|
|
1209
|
+
function probeWebp(b) {
|
|
1210
|
+
if (b.length < 30) return null;
|
|
1211
|
+
if (b.toString("ascii", 0, 4) !== "RIFF" || b.toString("ascii", 8, 12) !== "WEBP") return null;
|
|
1212
|
+
const fmt2 = b.toString("ascii", 12, 16);
|
|
1213
|
+
if (fmt2 === "VP8 ") {
|
|
1214
|
+
return { width: b.readUInt16LE(26) & 16383, height: b.readUInt16LE(28) & 16383 };
|
|
1215
|
+
}
|
|
1216
|
+
if (fmt2 === "VP8L") {
|
|
1217
|
+
const bits = b.readUInt32LE(21);
|
|
1218
|
+
return { width: (bits & 16383) + 1, height: (bits >> 14 & 16383) + 1 };
|
|
1219
|
+
}
|
|
1220
|
+
if (fmt2 === "VP8X") {
|
|
1221
|
+
const w = b[24] | b[25] << 8 | b[26] << 16;
|
|
1222
|
+
const h = b[27] | b[28] << 8 | b[29] << 16;
|
|
1223
|
+
return { width: w + 1, height: h + 1 };
|
|
1224
|
+
}
|
|
1225
|
+
return null;
|
|
1226
|
+
}
|
|
1227
|
+
function probeMp4(b) {
|
|
1228
|
+
let duration;
|
|
1229
|
+
let width = 0;
|
|
1230
|
+
let height = 0;
|
|
1231
|
+
let frames = 0;
|
|
1232
|
+
let mediaDuration;
|
|
1233
|
+
const walk = (start, end, depth) => {
|
|
1234
|
+
let i = start;
|
|
1235
|
+
while (i + 8 <= end && depth < 8) {
|
|
1236
|
+
let size = b.readUInt32BE(i);
|
|
1237
|
+
const type = b.toString("ascii", i + 4, i + 8);
|
|
1238
|
+
let head = 8;
|
|
1239
|
+
if (size === 1) {
|
|
1240
|
+
if (i + 16 > end) return;
|
|
1241
|
+
size = Number(b.readBigUInt64BE(i + 8));
|
|
1242
|
+
head = 16;
|
|
1243
|
+
}
|
|
1244
|
+
if (size < head || i + size > end) {
|
|
1245
|
+
return;
|
|
1246
|
+
}
|
|
1247
|
+
if (type === "moov" || type === "trak" || type === "mdia" || type === "minf" || type === "stbl") {
|
|
1248
|
+
walk(i + head, i + size, depth + 1);
|
|
1249
|
+
} else if (type === "mvhd") {
|
|
1250
|
+
const version = b[i + head];
|
|
1251
|
+
if (version === 1) {
|
|
1252
|
+
const timescale = b.readUInt32BE(i + head + 20);
|
|
1253
|
+
const dur2 = Number(b.readBigUInt64BE(i + head + 24));
|
|
1254
|
+
if (timescale) duration = dur2 / timescale;
|
|
1255
|
+
} else {
|
|
1256
|
+
const timescale = b.readUInt32BE(i + head + 12);
|
|
1257
|
+
const dur2 = b.readUInt32BE(i + head + 16);
|
|
1258
|
+
if (timescale) duration = dur2 / timescale;
|
|
1259
|
+
}
|
|
1260
|
+
} else if (type === "tkhd") {
|
|
1261
|
+
const version = b[i + head];
|
|
1262
|
+
const matrixAt = i + head + (version === 1 ? 52 : 40);
|
|
1263
|
+
const w = b.readUInt32BE(matrixAt + 36) / 65536;
|
|
1264
|
+
const h = b.readUInt32BE(matrixAt + 40) / 65536;
|
|
1265
|
+
if (w > 0 && h > 0) {
|
|
1266
|
+
const a = b.readInt32BE(matrixAt) / 65536;
|
|
1267
|
+
const d = b.readInt32BE(matrixAt + 16) / 65536;
|
|
1268
|
+
const rotated = Math.abs(a) < 0.01 && Math.abs(d) < 0.01;
|
|
1269
|
+
const tw = Math.round(rotated ? h : w);
|
|
1270
|
+
const th = Math.round(rotated ? w : h);
|
|
1271
|
+
if (tw * th > width * height) {
|
|
1272
|
+
width = tw;
|
|
1273
|
+
height = th;
|
|
1274
|
+
}
|
|
1275
|
+
}
|
|
1276
|
+
} else if (type === "mdhd") {
|
|
1277
|
+
const version = b[i + head];
|
|
1278
|
+
if (version === 1) {
|
|
1279
|
+
const ts = b.readUInt32BE(i + head + 20);
|
|
1280
|
+
const dur2 = Number(b.readBigUInt64BE(i + head + 24));
|
|
1281
|
+
if (ts) mediaDuration = dur2 / ts;
|
|
1282
|
+
} else {
|
|
1283
|
+
const ts = b.readUInt32BE(i + head + 12);
|
|
1284
|
+
const dur2 = b.readUInt32BE(i + head + 16);
|
|
1285
|
+
if (ts) mediaDuration = dur2 / ts;
|
|
1286
|
+
}
|
|
1287
|
+
} else if (type === "stsz") {
|
|
1288
|
+
const count = b.readUInt32BE(i + head + 8);
|
|
1289
|
+
if (count > frames) frames = count;
|
|
1290
|
+
}
|
|
1291
|
+
i += size;
|
|
1292
|
+
}
|
|
1293
|
+
};
|
|
1294
|
+
walk(0, b.length, 0);
|
|
1295
|
+
if (!width || !height) return null;
|
|
1296
|
+
const dur = duration ?? mediaDuration;
|
|
1297
|
+
const fps = dur && frames ? Math.round(frames / dur * 100) / 100 : void 0;
|
|
1298
|
+
return { width, height, duration: dur, fps };
|
|
1299
|
+
}
|
|
1300
|
+
var ProbeError = class extends Error {
|
|
1301
|
+
};
|
|
1302
|
+
async function probeMedia(ref) {
|
|
1303
|
+
const kind = kindFromExt(ref);
|
|
1304
|
+
if (!kind) {
|
|
1305
|
+
throw new ProbeError(
|
|
1306
|
+
"Formato n\xE3o reconhecido pela extens\xE3o. Aceito: png, jpg, jpeg, webp, mp4, mov, m4v."
|
|
1307
|
+
);
|
|
1308
|
+
}
|
|
1309
|
+
let head;
|
|
1310
|
+
try {
|
|
1311
|
+
head = await readHead(ref, HEADER_BYTES);
|
|
1312
|
+
} catch (e) {
|
|
1313
|
+
throw new ProbeError(`N\xE3o consegui ler o arquivo: ${e instanceof Error ? e.message : String(e)}`);
|
|
1314
|
+
}
|
|
1315
|
+
if (!head.length) throw new ProbeError("Arquivo vazio.");
|
|
1316
|
+
if (kind === "image") {
|
|
1317
|
+
const dim = probePng(head) ?? probeJpeg(head) ?? probeWebp(head);
|
|
1318
|
+
if (!dim) throw new ProbeError("N\xE3o consegui ler as dimens\xF5es da imagem pelo cabe\xE7alho.");
|
|
1319
|
+
return { kind, width: dim.width, height: dim.height };
|
|
1320
|
+
}
|
|
1321
|
+
let info = probeMp4(head);
|
|
1322
|
+
if (!info && !isRemote(ref)) {
|
|
1323
|
+
try {
|
|
1324
|
+
const whole = await readFile2(toLocalPath2(ref));
|
|
1325
|
+
info = probeMp4(whole.subarray(0, Math.min(whole.length, MAX_SCAN_BYTES))) ?? probeMp4(whole.subarray(Math.max(0, whole.length - MAX_SCAN_BYTES)));
|
|
1326
|
+
} catch {
|
|
1327
|
+
}
|
|
1328
|
+
}
|
|
1329
|
+
if (!info) {
|
|
1330
|
+
throw new ProbeError(
|
|
1331
|
+
"N\xE3o consegui ler as dimens\xF5es do v\xEDdeo pelo cabe\xE7alho (o \xEDndice pode estar no fim do arquivo)."
|
|
1332
|
+
);
|
|
1333
|
+
}
|
|
1334
|
+
return { kind, width: info.width, height: info.height, duration: info.duration, fps: info.fps };
|
|
1335
|
+
}
|
|
1336
|
+
|
|
1337
|
+
// src/tools/upscale.ts
|
|
1338
|
+
var IMAGE_MODEL = "topaz/upscale/image/generative";
|
|
1339
|
+
var VIDEO_MODEL = "topaz/upscale/video/generative";
|
|
1340
|
+
var MAX_VIDEO_SECONDS = 30;
|
|
1341
|
+
var MAX_OUTPUT_HEIGHT = 1080;
|
|
1342
|
+
var IMAGE_BUDGET_MS2 = 9e4;
|
|
1343
|
+
var VIDEO_BUDGET_MS2 = 24e4;
|
|
1344
|
+
var DEFAULT_IMAGE_VARIANT = "Wonder 3";
|
|
1345
|
+
var DEFAULT_VIDEO_VARIANT = "Starlight Precise 2.6";
|
|
1346
|
+
var SOFTNESS_ONLY = "Starlight Precise 2.6";
|
|
1347
|
+
function registerUpscaleTools(server, client, cfg) {
|
|
1348
|
+
server.registerTool(
|
|
1349
|
+
"upscale",
|
|
1350
|
+
{
|
|
1351
|
+
title: "Melhorar resolu\xE7\xE3o (upscale Topaz)",
|
|
1352
|
+
description: "Aumenta a resolu\xE7\xE3o de uma IMAGEM ou de um V\xCDDEO que j\xE1 existe, com o Topaz. Passe o arquivo em `file` (caminho local ou URL) \u2014 o tipo \xE9 detectado pela extens\xE3o e define o modelo e os ajustes v\xE1lidos. Imagem: at\xE9 4x, ~20s, a partir de 6\u{1F48E}. V\xEDdeo: no m\xE1ximo 30s de dura\xE7\xE3o e sa\xEDda at\xE9 1080p, custa por bloco de 10s (5s \u2248 87\u{1F48E}, o teto de 30s chega a 260\u{1F48E}), leva alguns minutos e o \xC1UDIO N\xC3O \xC9 MANTIDO. \u26A0\uFE0F Melhorar v\xEDdeo \xE9 restrito a quem tem a tag de budget admin. Use `estimate_cost` antes se quiser confirmar o pre\xE7o." + TEAM_GATING_NOTE,
|
|
1353
|
+
inputSchema: {
|
|
1354
|
+
file: z2.string().min(1).describe(
|
|
1355
|
+
"Arquivo a melhorar: caminho local (/foto.png, ~/Desktop/clipe.mp4) OU URL p\xFAblica. Caminho local sobe pro R2 automaticamente. Imagem: png, jpg, jpeg, webp. V\xEDdeo: mp4, mov, m4v."
|
|
1356
|
+
),
|
|
1357
|
+
factor: z2.number().int().min(1).max(4).optional().describe("Quantas vezes aumentar (1 a 4). Padr\xE3o 2. Em 1x s\xF3 melhora a qualidade, sem ampliar."),
|
|
1358
|
+
variant: z2.string().optional().describe(
|
|
1359
|
+
"Modelo do Topaz. Imagem: Wonder 3 (padr\xE3o, serve para quase tudo), Wonder 3.5 (detalhe fino), Wonder 2, Wonder, Recover 3 (imagem muito danificada), Standard MAX (fiel ao original), Redefine (usa a descri\xE7\xE3o), Recovery V2, Recovery. V\xEDdeo: Starlight Precise 2.6 (padr\xE3o), Starlight HQ, Starlight Mini, Starlight Sharp, Starlight Fast 2 (mais r\xE1pido e custa metade)."
|
|
1360
|
+
),
|
|
1361
|
+
project: z2.string().max(64).optional().describe('Projeto de destino (uuid OU code curto, ex.: "VRAO-26"). Ausente \u2192 projeto ativo.'),
|
|
1362
|
+
// ── só imagem ────────────────────────────────────────────────────────
|
|
1363
|
+
output_format: z2.enum(["jpeg", "png"]).optional().describe("[imagem] Formato da sa\xEDda. Padr\xE3o png."),
|
|
1364
|
+
subject_detection: z2.enum(["All", "Foreground", "Background"]).optional().describe("[imagem] Onde aplicar: imagem toda, s\xF3 o primeiro plano ou s\xF3 o fundo. Padr\xE3o All."),
|
|
1365
|
+
face_enhancement: z2.boolean().optional().describe("[imagem] Tratar rostos separadamente. Padr\xE3o true."),
|
|
1366
|
+
face_enhancement_strength: z2.number().min(0).max(1).optional().describe("[imagem] For\xE7a nos rostos (0 a 1). Padr\xE3o 0.8."),
|
|
1367
|
+
face_enhancement_creativity: z2.number().min(0).max(1).optional().describe("[imagem] Liberdade nos rostos (0 a 1). Padr\xE3o 0."),
|
|
1368
|
+
enhancement_strength: z2.enum(["low", "medium", "high"]).optional().describe("[imagem] Intensidade geral do tratamento."),
|
|
1369
|
+
creativity: z2.number().int().min(1).max(6).optional().describe("[imagem] Quanto detalhe o modelo pode inventar (1 a 6)."),
|
|
1370
|
+
texture: z2.number().int().min(1).max(5).optional().describe("[imagem] Textura (1 a 5)."),
|
|
1371
|
+
sharpen: z2.number().min(0).max(1).optional().describe("[imagem] Nitidez (0 a 1)."),
|
|
1372
|
+
denoise: z2.number().min(0).max(1).optional().describe("[imagem] Redu\xE7\xE3o de ru\xEDdo (0 a 1)."),
|
|
1373
|
+
detail: z2.number().min(0).max(1).optional().describe("[imagem] Detalhe (0 a 1)."),
|
|
1374
|
+
crop_to_fill: z2.boolean().optional().describe("[imagem] Cortar para preencher em vez de encaixar."),
|
|
1375
|
+
autoprompt: z2.boolean().optional().describe("[imagem] O modelo escreve sozinho a orienta\xE7\xE3o a partir da imagem."),
|
|
1376
|
+
prompt: z2.string().max(2e3).optional().describe("[imagem] Orienta\xE7\xE3o para guiar a reconstru\xE7\xE3o (o que a imagem mostra). \xDAtil no Redefine."),
|
|
1377
|
+
// ── só vídeo ─────────────────────────────────────────────────────────
|
|
1378
|
+
softness: z2.number().int().min(1).max(5).optional().describe("[v\xEDdeo] Suavidade, 1 mais n\xEDtido e 5 mais suave. S\xF3 vale no Starlight Precise 2.6."),
|
|
1379
|
+
target_fps: z2.number().int().min(16).max(60).optional().describe("[v\xEDdeo] Quadros por segundo da sa\xEDda. Mudar o valor liga a interpola\xE7\xE3o de quadros."),
|
|
1380
|
+
h264_output: z2.boolean().optional().describe(
|
|
1381
|
+
"[v\xEDdeo] Exportar em H.264. Padr\xE3o true \u2014 desligado sai em H.265, que n\xE3o toca no Firefox e depende de hardware no Chrome."
|
|
1382
|
+
),
|
|
1383
|
+
// ── medidas ──────────────────────────────────────────────────────────
|
|
1384
|
+
source_width: z2.number().int().positive().optional().describe("Largura do arquivo. S\xF3 se a leitura autom\xE1tica falhar."),
|
|
1385
|
+
source_height: z2.number().int().positive().optional().describe("Altura do arquivo. S\xF3 se a leitura autom\xE1tica falhar."),
|
|
1386
|
+
source_duration: z2.number().positive().optional().describe("[v\xEDdeo] Dura\xE7\xE3o em segundos. S\xF3 se a leitura autom\xE1tica falhar."),
|
|
1387
|
+
source_fps: z2.number().positive().optional().describe("[v\xEDdeo] Quadros por segundo do arquivo. S\xF3 se a leitura autom\xE1tica falhar.")
|
|
1388
|
+
}
|
|
1389
|
+
},
|
|
1390
|
+
async (args) => {
|
|
1391
|
+
try {
|
|
1392
|
+
const kind = kindFromExt(args.file);
|
|
1393
|
+
if (!kind) {
|
|
1394
|
+
return errorResult(
|
|
1395
|
+
"N\xE3o reconheci o tipo do arquivo pela extens\xE3o. Imagem: png, jpg, jpeg, webp. V\xEDdeo: mp4, mov, m4v."
|
|
1396
|
+
);
|
|
1397
|
+
}
|
|
1398
|
+
const isVideo = kind === "video";
|
|
1399
|
+
const factor = args.factor ?? 2;
|
|
1400
|
+
const variant = args.variant ?? (isVideo ? DEFAULT_VIDEO_VARIANT : DEFAULT_IMAGE_VARIANT);
|
|
1401
|
+
const allowed = isVideo ? UPSCALE_VIDEO_VARIANTS : UPSCALE_IMAGE_VARIANTS;
|
|
1402
|
+
if (!allowed.includes(variant)) {
|
|
1403
|
+
return errorResult(
|
|
1404
|
+
`"${variant}" n\xE3o \xE9 um modelo de ${isVideo ? "v\xEDdeo" : "imagem"}. Use um destes: ${allowed.join(", ")}.`
|
|
1405
|
+
);
|
|
1406
|
+
}
|
|
1407
|
+
let width = args.source_width;
|
|
1408
|
+
let height = args.source_height;
|
|
1409
|
+
let duration = args.source_duration;
|
|
1410
|
+
let fps = args.source_fps;
|
|
1411
|
+
if (!width || !height || isVideo && !duration) {
|
|
1412
|
+
try {
|
|
1413
|
+
const probed = await probeMedia(args.file);
|
|
1414
|
+
width = width ?? probed.width;
|
|
1415
|
+
height = height ?? probed.height;
|
|
1416
|
+
duration = duration ?? probed.duration;
|
|
1417
|
+
fps = fps ?? probed.fps;
|
|
1418
|
+
} catch (e) {
|
|
1419
|
+
if (e instanceof ProbeError) {
|
|
1420
|
+
return errorResult(
|
|
1421
|
+
`${e.message}
|
|
1422
|
+
|
|
1423
|
+
\u{1F4A1} Passe as medidas na m\xE3o para seguir: source_width, source_height` + (isVideo ? ", source_duration e source_fps" : "") + ". Sem elas o custo sai errado e a trava de tamanho n\xE3o funciona."
|
|
1424
|
+
);
|
|
1425
|
+
}
|
|
1426
|
+
throw e;
|
|
1427
|
+
}
|
|
1428
|
+
}
|
|
1429
|
+
if (isVideo) {
|
|
1430
|
+
if (duration && duration > MAX_VIDEO_SECONDS) {
|
|
1431
|
+
return errorResult(
|
|
1432
|
+
`Este v\xEDdeo tem ${Math.round(duration)}s e o limite para melhorar \xE9 ${MAX_VIDEO_SECONDS}s. Corte um trecho menor e tente de novo.`
|
|
1433
|
+
);
|
|
1434
|
+
}
|
|
1435
|
+
if (height && height * factor > MAX_OUTPUT_HEIGHT) {
|
|
1436
|
+
const maxFactor = Math.floor(MAX_OUTPUT_HEIGHT / height);
|
|
1437
|
+
return errorResult(
|
|
1438
|
+
maxFactor >= 2 ? `Neste v\xEDdeo o aumento m\xE1ximo \xE9 ${maxFactor}x \u2014 acima disso a sa\xEDda passa de 1080p.` : "Este v\xEDdeo j\xE1 est\xE1 em 1080p ou acima, ent\xE3o n\xE3o d\xE1 para aumentar mais."
|
|
1439
|
+
);
|
|
1440
|
+
}
|
|
1441
|
+
}
|
|
1442
|
+
const url = await resolveRef(cfg, args.file);
|
|
1443
|
+
const label = [isVideo ? "V\xEDdeo" : "Imagem", `${factor}x`, variant].join(" \xB7 ");
|
|
1444
|
+
const input = compact({
|
|
1445
|
+
[isVideo ? "video" : "image"]: url,
|
|
1446
|
+
model: variant,
|
|
1447
|
+
upscale_factor: factor,
|
|
1448
|
+
prompt: args.prompt || label,
|
|
1449
|
+
source_width: width,
|
|
1450
|
+
source_height: height,
|
|
1451
|
+
source_duration: duration,
|
|
1452
|
+
source_fps: fps,
|
|
1453
|
+
...isVideo ? {
|
|
1454
|
+
target_fps: args.target_fps,
|
|
1455
|
+
// O backend descarta softness fora desta variante; filtrar aqui
|
|
1456
|
+
// deixa o motivo visível em vez de sumir em silêncio.
|
|
1457
|
+
softness: variant === SOFTNESS_ONLY ? args.softness : void 0,
|
|
1458
|
+
H264_output: args.h264_output ?? true
|
|
1459
|
+
} : {
|
|
1460
|
+
output_format: args.output_format,
|
|
1461
|
+
subject_detection: args.subject_detection,
|
|
1462
|
+
face_enhancement: args.face_enhancement,
|
|
1463
|
+
face_enhancement_strength: args.face_enhancement_strength,
|
|
1464
|
+
face_enhancement_creativity: args.face_enhancement_creativity,
|
|
1465
|
+
enhancement_strength: args.enhancement_strength,
|
|
1466
|
+
creativity: args.creativity,
|
|
1467
|
+
texture: args.texture,
|
|
1468
|
+
sharpen: args.sharpen,
|
|
1469
|
+
denoise: args.denoise,
|
|
1470
|
+
detail: args.detail,
|
|
1471
|
+
crop_to_fill: args.crop_to_fill,
|
|
1472
|
+
autoprompt: args.autoprompt
|
|
1473
|
+
}
|
|
1474
|
+
});
|
|
1475
|
+
const start = await client.startGeneration(
|
|
1476
|
+
compact({
|
|
1477
|
+
model: isVideo ? VIDEO_MODEL : IMAGE_MODEL,
|
|
1478
|
+
input,
|
|
1479
|
+
project: args.project ?? cfg.activeProject ?? void 0
|
|
1480
|
+
})
|
|
1481
|
+
);
|
|
1482
|
+
if (!start.success || !start.generation_id) {
|
|
1483
|
+
if (isTeamGatingError(start.message)) return errorResult(await buildPickProjectMessage(client));
|
|
1484
|
+
return errorResult(start.message || "Falha ao iniciar o upscale.");
|
|
1485
|
+
}
|
|
1486
|
+
const footer = costFooter(start, cfg);
|
|
1487
|
+
const { settled, status } = await pollUntilDone(
|
|
1488
|
+
client,
|
|
1489
|
+
start.generation_id,
|
|
1490
|
+
isVideo ? VIDEO_BUDGET_MS2 : IMAGE_BUDGET_MS2
|
|
1491
|
+
);
|
|
1492
|
+
if (!settled) {
|
|
1493
|
+
return textResult(
|
|
1494
|
+
`\u23F3 Upscale ainda em processamento (id: ${start.generation_id}, ${status.progress ?? 0}%). ` + (isVideo ? "V\xEDdeo costuma levar de 3 a 15 minutos, dependendo da dura\xE7\xE3o e da resolu\xE7\xE3o de sa\xEDda \u2014 " : "") + `use check_generation com esse id.
|
|
1495
|
+
${footer}`
|
|
1496
|
+
);
|
|
1497
|
+
}
|
|
1498
|
+
if (status.status === "failed") {
|
|
1499
|
+
return errorResult(`Upscale falhou: ${status.error_message || "erro desconhecido"}.
|
|
1500
|
+
${footer}`);
|
|
1501
|
+
}
|
|
1502
|
+
const urls = outputUrls(status);
|
|
1503
|
+
const list = urls.map((u, i) => `${i + 1}. ${u}`).join("\n") || "(sem URLs)";
|
|
1504
|
+
const saida = width && height ? ` \u2014 sa\xEDda ${width * factor} \xD7 ${height * factor}` : "";
|
|
1505
|
+
const semAudio = isVideo ? "\nO \xE1udio n\xE3o foi mantido (o Topaz descarta a faixa)." : "";
|
|
1506
|
+
return textResult(
|
|
1507
|
+
`\u2705 ${isVideo ? "V\xEDdeo" : "Imagem"} melhorado em ${factor}x com ${variant}${saida}.
|
|
1508
|
+
${list}${semAudio}
|
|
1509
|
+
${footer}`
|
|
1510
|
+
);
|
|
1511
|
+
} catch (e) {
|
|
1512
|
+
const msg = e instanceof ApiError ? e.message : e instanceof Error ? e.message : "";
|
|
1513
|
+
if (isTeamGatingError(msg)) return errorResult(await buildPickProjectMessage(client));
|
|
1514
|
+
if (isBudgetBlockedError(msg)) {
|
|
1515
|
+
return errorResult(`${apiErrorToUserMessage(e)}
|
|
1516
|
+
${BUDGET_BLOCKED_GUIDANCE}`);
|
|
1517
|
+
}
|
|
1518
|
+
return errorResult(apiErrorToUserMessage(e));
|
|
1519
|
+
}
|
|
1520
|
+
}
|
|
1521
|
+
);
|
|
1522
|
+
}
|
|
1523
|
+
|
|
1524
|
+
// src/tools/audio.ts
|
|
1525
|
+
import { z as z3 } from "zod";
|
|
1103
1526
|
function registerAudioTools(server, client, cfg) {
|
|
1104
1527
|
server.registerTool(
|
|
1105
1528
|
"generate_audio",
|
|
@@ -1107,15 +1530,15 @@ function registerAudioTools(server, client, cfg) {
|
|
|
1107
1530
|
title: "Gerar \xE1udio (TTS)",
|
|
1108
1531
|
description: 'Sintetiza fala a partir de texto (s\xEDncrono \u2014 retorna a URL na hora) usando o IA Lab da BePlus. ~30 vozes. Opcional `style_instruction` (notas de dire\xE7\xE3o, ex.: "fale animado e pausado") e di\xE1logo com 2 locutores via `multi_speaker`. Consome diamantes por segundo de \xE1udio.' + TEAM_GATING_NOTE,
|
|
1109
1532
|
inputSchema: {
|
|
1110
|
-
text:
|
|
1111
|
-
voice:
|
|
1112
|
-
model:
|
|
1113
|
-
style_instruction:
|
|
1114
|
-
multi_speaker:
|
|
1115
|
-
speaker1:
|
|
1116
|
-
speaker2:
|
|
1533
|
+
text: z3.string().min(1).max(1e4).describe("Texto a ser falado."),
|
|
1534
|
+
voice: z3.enum(VOICES).default(DEFAULTS.voice).describe("Voz (ex.: Kore, Puck, Charon)."),
|
|
1535
|
+
model: z3.enum(TTS_MODELS).default(DEFAULTS.ttsModel).describe("Modelo TTS."),
|
|
1536
|
+
style_instruction: z3.string().max(2e3).optional().describe("Instru\xE7\xE3o de estilo/tom aplicada \xE0 fala."),
|
|
1537
|
+
multi_speaker: z3.object({
|
|
1538
|
+
speaker1: z3.object({ name: z3.string().min(1).max(50), voice: z3.enum(VOICES) }),
|
|
1539
|
+
speaker2: z3.object({ name: z3.string().min(1).max(50), voice: z3.enum(VOICES) })
|
|
1117
1540
|
}).optional().describe('Di\xE1logo de 2 locutores. Marque as falas no texto como "Nome: ...".'),
|
|
1118
|
-
project:
|
|
1541
|
+
project: z3.string().max(64).optional().describe('Projeto (uuid OU code curto, ex.: "VRAO-26"). Omita para usar o projeto ativo da conta.')
|
|
1119
1542
|
}
|
|
1120
1543
|
},
|
|
1121
1544
|
async (args) => {
|
|
@@ -1156,7 +1579,7 @@ ${BUDGET_BLOCKED_GUIDANCE}`);
|
|
|
1156
1579
|
}
|
|
1157
1580
|
|
|
1158
1581
|
// src/tools/music.ts
|
|
1159
|
-
import { z as
|
|
1582
|
+
import { z as z4 } from "zod";
|
|
1160
1583
|
var MUSIC_BUDGET_MS = 15e4;
|
|
1161
1584
|
function registerMusicTools(server, client, cfg) {
|
|
1162
1585
|
server.registerTool(
|
|
@@ -1165,18 +1588,18 @@ function registerMusicTools(server, client, cfg) {
|
|
|
1165
1588
|
title: "Gerar m\xFAsica",
|
|
1166
1589
|
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,
|
|
1167
1590
|
inputSchema: {
|
|
1168
|
-
model:
|
|
1169
|
-
custom_mode:
|
|
1170
|
-
description:
|
|
1171
|
-
lyrics:
|
|
1172
|
-
title:
|
|
1173
|
-
tags:
|
|
1174
|
-
make_instrumental:
|
|
1175
|
-
vocal_gender:
|
|
1176
|
-
negative_tags:
|
|
1177
|
-
style_weight:
|
|
1178
|
-
weirdness_constraint:
|
|
1179
|
-
project:
|
|
1591
|
+
model: z4.enum(MUSIC_MODELS).default(DEFAULTS.musicModel).describe("suno/v5-5 = mais novo; suno/v4-5 = r\xE1pido/consistente (\xFAnico que aceita vocal_gender)."),
|
|
1592
|
+
custom_mode: z4.boolean().optional().describe("false (padr\xE3o) = gera a partir de `description`; true = usa sua `lyrics` pr\xF3pria."),
|
|
1593
|
+
description: z4.string().max(2500).optional().describe("Modo descri\xE7\xE3o (custom_mode=false): descreva a m\xFAsica desejada. Obrigat\xF3rio nesse modo."),
|
|
1594
|
+
lyrics: z4.string().max(5e3).optional().describe("Modo letra (custom_mode=true): a letra completa da m\xFAsica. Obrigat\xF3rio nesse modo."),
|
|
1595
|
+
title: z4.string().max(120).optional().describe("T\xEDtulo da m\xFAsica (modo letra)."),
|
|
1596
|
+
tags: z4.string().max(1e3).optional().describe('G\xEAnero/estilo, separados por v\xEDrgula (ex.: "pop, energ\xE9tico, vocal masculino").'),
|
|
1597
|
+
make_instrumental: z4.boolean().optional().describe("true = sem vocais (apenas instrumental). Padr\xE3o false."),
|
|
1598
|
+
vocal_gender: z4.enum(VOCAL_GENDERS).optional().describe("G\xEAnero do vocal: f (feminino) ou m (masculino). S\xF3 Suno v4.5; ignorado se instrumental."),
|
|
1599
|
+
negative_tags: z4.string().max(1e3).optional().describe("Estilos/elementos a evitar."),
|
|
1600
|
+
style_weight: z4.number().min(0).max(1).optional().describe("Peso do estilo (0\u20131). Maior = segue mais as tags. Omita para o modelo decidir."),
|
|
1601
|
+
weirdness_constraint: z4.number().min(0).max(1).optional().describe("Criatividade/imprevisibilidade (0\u20131). Omita para o modelo decidir."),
|
|
1602
|
+
project: z4.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.')
|
|
1180
1603
|
}
|
|
1181
1604
|
},
|
|
1182
1605
|
async (args) => {
|
|
@@ -1243,7 +1666,7 @@ ${BUDGET_BLOCKED_GUIDANCE}`);
|
|
|
1243
1666
|
}
|
|
1244
1667
|
|
|
1245
1668
|
// src/tools/account.ts
|
|
1246
|
-
import { z as
|
|
1669
|
+
import { z as z5 } from "zod";
|
|
1247
1670
|
function registerAccountTools(server, client, _cfg) {
|
|
1248
1671
|
server.registerTool(
|
|
1249
1672
|
"whoami",
|
|
@@ -1296,7 +1719,7 @@ ${budget}` : "")
|
|
|
1296
1719
|
title: "Listar modelos",
|
|
1297
1720
|
description: "Lista os modelos dispon\xEDveis com pre\xE7o-base e limites por usu\xE1rio. Use quando estiver em d\xFAvida sobre qual modelo/params usar ou quanto custa. \xC9 a fonte de verdade em runtime.",
|
|
1298
1721
|
inputSchema: {
|
|
1299
|
-
kind:
|
|
1722
|
+
kind: z5.enum(["image", "video", "audio", "all"]).default("all").optional().describe("Filtrar por tipo.")
|
|
1300
1723
|
}
|
|
1301
1724
|
},
|
|
1302
1725
|
async () => {
|
|
@@ -1330,16 +1753,45 @@ Limites simult\xE2neos por usu\xE1rio: imagem=${m.limits.perUserMaxImage}, v\xED
|
|
|
1330
1753
|
title: "Estimar custo",
|
|
1331
1754
|
description: "Retorna o custo autoritativo em diamantes ANTES de gerar. Para \xE1udio (TTS) o custo \xE9 por segundo e s\xF3 \xE9 conhecido ap\xF3s a s\xEDntese.",
|
|
1332
1755
|
inputSchema: {
|
|
1333
|
-
model:
|
|
1334
|
-
resolution:
|
|
1335
|
-
duration:
|
|
1336
|
-
mode:
|
|
1337
|
-
size:
|
|
1338
|
-
quality:
|
|
1756
|
+
model: z5.string().min(1).describe("Id do modelo (ex.: google/nano-banana-pro, bytedance/seedance-2.0)."),
|
|
1757
|
+
resolution: z5.string().optional(),
|
|
1758
|
+
duration: z5.number().int().optional(),
|
|
1759
|
+
mode: z5.string().optional(),
|
|
1760
|
+
size: z5.string().optional(),
|
|
1761
|
+
quality: z5.string().optional(),
|
|
1762
|
+
// Upscale: o preço não sai de uma chave fixa — vem das medidas do
|
|
1763
|
+
// arquivo (megapixel de saída na imagem, bloco de 10s no vídeo). Sem
|
|
1764
|
+
// elas o backend responde pelo padrão de 1024²/10s, que não é o preço
|
|
1765
|
+
// que vai ser cobrado.
|
|
1766
|
+
file: z5.string().optional().describe(
|
|
1767
|
+
"[upscale] Arquivo a medir (caminho local ou URL) \u2014 preenche sozinho as medidas abaixo. Use com model=topaz/upscale/image/generative ou .../video/generative."
|
|
1768
|
+
),
|
|
1769
|
+
upscale_factor: z5.number().int().min(1).max(4).optional().describe("[upscale] Fator de aumento (1 a 4). Padr\xE3o 2."),
|
|
1770
|
+
model_variant: z5.string().optional().describe("[upscale] Variante do Topaz (ex.: Wonder 3, Starlight Fast 2) \u2014 muda o pre\xE7o."),
|
|
1771
|
+
source_width: z5.number().int().positive().optional().describe("[upscale] Largura do arquivo."),
|
|
1772
|
+
source_height: z5.number().int().positive().optional().describe("[upscale] Altura do arquivo."),
|
|
1773
|
+
source_duration: z5.number().positive().optional().describe("[upscale] Dura\xE7\xE3o em segundos (v\xEDdeo)."),
|
|
1774
|
+
source_fps: z5.number().positive().optional().describe("[upscale] Quadros por segundo do arquivo (v\xEDdeo).")
|
|
1339
1775
|
}
|
|
1340
1776
|
},
|
|
1341
1777
|
async (args) => {
|
|
1342
1778
|
try {
|
|
1779
|
+
let width = args.source_width;
|
|
1780
|
+
let height = args.source_height;
|
|
1781
|
+
let duration = args.source_duration;
|
|
1782
|
+
let fps = args.source_fps;
|
|
1783
|
+
if (args.file && (!width || !height)) {
|
|
1784
|
+
try {
|
|
1785
|
+
const probed = await probeMedia(args.file);
|
|
1786
|
+
width = width ?? probed.width;
|
|
1787
|
+
height = height ?? probed.height;
|
|
1788
|
+
duration = duration ?? probed.duration;
|
|
1789
|
+
fps = fps ?? probed.fps;
|
|
1790
|
+
} catch (e) {
|
|
1791
|
+
if (e instanceof ProbeError) return errorResult(e.message);
|
|
1792
|
+
throw e;
|
|
1793
|
+
}
|
|
1794
|
+
}
|
|
1343
1795
|
const c = await client.calculateCost(
|
|
1344
1796
|
compact({
|
|
1345
1797
|
model: args.model,
|
|
@@ -1347,7 +1799,13 @@ Limites simult\xE2neos por usu\xE1rio: imagem=${m.limits.perUserMaxImage}, v\xED
|
|
|
1347
1799
|
duration: args.duration,
|
|
1348
1800
|
mode: args.mode,
|
|
1349
1801
|
size: args.size,
|
|
1350
|
-
quality: args.quality
|
|
1802
|
+
quality: args.quality,
|
|
1803
|
+
upscale_factor: args.upscale_factor,
|
|
1804
|
+
model_variant: args.model_variant,
|
|
1805
|
+
source_width: width,
|
|
1806
|
+
source_height: height,
|
|
1807
|
+
source_duration: duration,
|
|
1808
|
+
source_fps: fps
|
|
1351
1809
|
})
|
|
1352
1810
|
);
|
|
1353
1811
|
const note = c.fallback ? " [modelo desconhecido \u2014 custo m\xEDnimo]" : "";
|
|
@@ -1360,7 +1818,7 @@ Limites simult\xE2neos por usu\xE1rio: imagem=${m.limits.perUserMaxImage}, v\xED
|
|
|
1360
1818
|
}
|
|
1361
1819
|
|
|
1362
1820
|
// src/tools/analyze.ts
|
|
1363
|
-
import { z as
|
|
1821
|
+
import { z as z6 } from "zod";
|
|
1364
1822
|
function registerAnalyzeTools(server, client, cfg) {
|
|
1365
1823
|
server.registerTool(
|
|
1366
1824
|
"analyze_media",
|
|
@@ -1368,12 +1826,12 @@ function registerAnalyzeTools(server, client, cfg) {
|
|
|
1368
1826
|
title: "Analisar m\xEDdia (Gemini)",
|
|
1369
1827
|
description: "Envia um prompt + m\xEDdia (imagem, v\xEDdeo, \xE1udio ou PDF) para o Gemini e devolve uma resposta em texto. Use para descrever/analisar/transcrever/resumir/extrair informa\xE7\xE3o de m\xEDdia, ou perguntar algo sobre ela. A m\xEDdia entra como `media_urls` (URLs p\xFAblicas \u2014 tudo que o Google aceita: imagens, v\xEDdeos, \xE1udios, PDFs). V\xEDdeo/\xE1udio grandes funcionam (v\xE3o pela Files API do Google). Consome diamantes (reembolsados se falhar). Veja custo/limites com list_models.",
|
|
1370
1828
|
inputSchema: {
|
|
1371
|
-
prompt:
|
|
1372
|
-
media_urls:
|
|
1829
|
+
prompt: z6.string().min(1).max(5e4).describe('O que voc\xEA quer do modelo sobre a m\xEDdia (ex.: "descreva este v\xEDdeo", "transcreva este \xE1udio").'),
|
|
1830
|
+
media_urls: z6.array(z6.string().min(1)).max(50).optional().describe(
|
|
1373
1831
|
"M\xEDdia (imagem/v\xEDdeo/\xE1udio/PDF): URLs p\xFAblicas OU caminhos de arquivo local (o MCP sobe pro R2 automaticamente). Pode misturar tipos. Opcional (prompt s\xF3-texto tamb\xE9m funciona)."
|
|
1374
1832
|
),
|
|
1375
|
-
model:
|
|
1376
|
-
project:
|
|
1833
|
+
model: z6.enum(["gemini-flash", "gemini-pro"]).default("gemini-flash").optional().describe("gemini-flash = r\xE1pido/barato (Gemini 3.5 Flash); gemini-pro = mais capaz (Gemini 3.1 Pro)."),
|
|
1834
|
+
project: z6.string().optional().describe(
|
|
1377
1835
|
'Projeto (uuid ou code curto, ex.: "ICONS") de cujo budget cobrar. Omitido \u2192 usa o projeto ativo da conta.'
|
|
1378
1836
|
)
|
|
1379
1837
|
}
|
|
@@ -1402,7 +1860,7 @@ function registerAnalyzeTools(server, client, cfg) {
|
|
|
1402
1860
|
}
|
|
1403
1861
|
|
|
1404
1862
|
// src/tools/projects.ts
|
|
1405
|
-
import { z as
|
|
1863
|
+
import { z as z7 } from "zod";
|
|
1406
1864
|
function projectLine(p, active) {
|
|
1407
1865
|
const marker = active ? "\u25CF " : "\u25CB ";
|
|
1408
1866
|
const counts = `${p.generation_count ?? 0} gera\xE7\xF5es`;
|
|
@@ -1446,11 +1904,11 @@ ${lines.join("\n")}`);
|
|
|
1446
1904
|
title: "Criar projeto",
|
|
1447
1905
|
description: "Cria um projeto novo no IA Lab (workspace compartilhado), SEMPRE dentro de um cliente/marca, e por padr\xE3o j\xE1 o deixa ativo \u2014 gera\xE7\xF5es seguintes entram nele. S\xF3 membros da equipe podem criar. O campo `client` \xE9 obrigat\xF3rio: passe o code/uuid de um cliente existente (veja list_clients) OU o NOME de um cliente novo (ele \xE9 criado automaticamente). Projetos s\xE3o globais: todos da equipe usam.",
|
|
1448
1906
|
inputSchema: {
|
|
1449
|
-
name:
|
|
1450
|
-
client:
|
|
1451
|
-
description:
|
|
1452
|
-
color:
|
|
1453
|
-
set_active:
|
|
1907
|
+
name: z7.string().min(1).max(256).describe('Nome do projeto (ex.: "Ladeira Abaixo").'),
|
|
1908
|
+
client: z7.string().min(1).max(256).describe('Cliente/marca dono: code/uuid existente (ex.: "RDBL") OU nome novo (ex.: "Red Bull"). Obrigat\xF3rio.'),
|
|
1909
|
+
description: z7.string().max(4e3).optional().describe("Descri\xE7\xE3o do projeto (opcional)."),
|
|
1910
|
+
color: z7.string().regex(/^#[0-9a-fA-F]{6}$/).optional().describe("Cor em hex #RRGGBB para o badge (opcional)."),
|
|
1911
|
+
set_active: z7.boolean().optional().describe("Deixar o novo projeto como ativo (padr\xE3o true).")
|
|
1454
1912
|
}
|
|
1455
1913
|
},
|
|
1456
1914
|
async (args) => {
|
|
@@ -1475,7 +1933,7 @@ ${lines.join("\n")}`);
|
|
|
1475
1933
|
title: "Definir projeto ativo",
|
|
1476
1934
|
description: 'Define o projeto ativo da conta (por uuid OU code curto, ex.: "VRAO-26"). A partir da\xED, gera\xE7\xF5es sem `project` expl\xEDcito s\xE3o vinculadas a ele \u2014 espelha o seletor de projeto da web. Passe project=null para limpar.',
|
|
1477
1935
|
inputSchema: {
|
|
1478
|
-
project:
|
|
1936
|
+
project: z7.string().max(64).nullable().describe("uuid OU code curto do projeto. null para nenhum projeto.")
|
|
1479
1937
|
}
|
|
1480
1938
|
},
|
|
1481
1939
|
async ({ project }) => {
|
|
@@ -1493,7 +1951,7 @@ ${budget}` : ""));
|
|
|
1493
1951
|
}
|
|
1494
1952
|
|
|
1495
1953
|
// src/tools/clients.ts
|
|
1496
|
-
import { z as
|
|
1954
|
+
import { z as z8 } from "zod";
|
|
1497
1955
|
function clientLine(c, active) {
|
|
1498
1956
|
const marker = active ? "\u25CF " : "\u25CB ";
|
|
1499
1957
|
const counts = `${c.project_count ?? 0} projetos`;
|
|
@@ -1532,7 +1990,7 @@ ${lines.join("\n")}`);
|
|
|
1532
1990
|
title: "Criar cliente",
|
|
1533
1991
|
description: 'Cria um cliente/marca novo (ex.: "Red Bull"). S\xF3 membros da equipe. Clientes s\xE3o globais: qualquer colega passa a v\xEA-lo e us\xE1-lo. Depois crie projetos dentro dele com create_project.',
|
|
1534
1992
|
inputSchema: {
|
|
1535
|
-
name:
|
|
1993
|
+
name: z8.string().min(1).max(256).describe('Nome do cliente/marca (ex.: "Red Bull").')
|
|
1536
1994
|
}
|
|
1537
1995
|
},
|
|
1538
1996
|
async (args) => {
|
|
@@ -1550,7 +2008,7 @@ ${lines.join("\n")}`);
|
|
|
1550
2008
|
title: "Definir cliente ativo",
|
|
1551
2009
|
description: 'Define o cliente ativo da conta (por uuid OU code curto, ex.: "RDBL"). Trocar de cliente limpa o projeto ativo se ele pertencia a outro cliente. Passe client=null para limpar.',
|
|
1552
2010
|
inputSchema: {
|
|
1553
|
-
client:
|
|
2011
|
+
client: z8.string().max(64).nullable().describe("uuid OU code curto do cliente. null para nenhum.")
|
|
1554
2012
|
}
|
|
1555
2013
|
},
|
|
1556
2014
|
async ({ client: ref }) => {
|
|
@@ -1566,7 +2024,7 @@ ${lines.join("\n")}`);
|
|
|
1566
2024
|
}
|
|
1567
2025
|
|
|
1568
2026
|
// src/tools/calls.ts
|
|
1569
|
-
import { z as
|
|
2027
|
+
import { z as z9 } from "zod";
|
|
1570
2028
|
var VIS_LABEL = {
|
|
1571
2029
|
team: "equipe",
|
|
1572
2030
|
private: "privada",
|
|
@@ -1632,14 +2090,14 @@ function registerCallTools(server, client, _cfg) {
|
|
|
1632
2090
|
title: "Buscar calls",
|
|
1633
2091
|
description: 'Busca nas calls (reuni\xF5es gravadas e transcritas) vis\xEDveis pra conta \u2014 suas, da equipe e compartilhadas. Todos os filtros s\xE3o opcionais e combin\xE1veis: `query` (texto no t\xEDtulo/projeto/cliente/falante), `project` (code/uuid/nome), `client` (code/nome), `company` (empresa/equipe do dono, ex.: "BePlus"), `visibility` (team|private|workspace|link), `speaker` (nome do falante), `mine` (s\xF3 as que EU gravei). Retorna uma lista resumida \u2014 use get_call com o id pra ler a transcri\xE7\xE3o completa.',
|
|
1634
2092
|
inputSchema: {
|
|
1635
|
-
query:
|
|
1636
|
-
project:
|
|
1637
|
-
client:
|
|
1638
|
-
company:
|
|
1639
|
-
visibility:
|
|
1640
|
-
speaker:
|
|
1641
|
-
mine:
|
|
1642
|
-
limit:
|
|
2093
|
+
query: z9.string().max(200).optional().describe("Texto livre \u2014 casa t\xEDtulo, projeto, cliente ou falante."),
|
|
2094
|
+
project: z9.string().max(64).optional().describe('Projeto: code (ex.: "CONTEUDO-INS"), uuid ou parte do nome.'),
|
|
2095
|
+
client: z9.string().max(128).optional().describe("Cliente: code ou parte do nome."),
|
|
2096
|
+
company: z9.string().max(128).optional().describe('Empresa/equipe do dono da call (ex.: "BePlus"). Filtra por parte do nome.'),
|
|
2097
|
+
visibility: z9.enum(["team", "private", "workspace", "link"]).optional().describe("Privacidade: team (equipe), private (privada), workspace (projeto) ou link."),
|
|
2098
|
+
speaker: z9.string().max(128).optional().describe("Nome do falante."),
|
|
2099
|
+
mine: z9.boolean().optional().describe('true = s\xF3 as calls que EU gravei (sou o dono). Use pra "minhas grava\xE7\xF5es".'),
|
|
2100
|
+
limit: z9.number().int().min(1).max(50).optional().describe("M\xE1ximo de resultados (padr\xE3o 20).")
|
|
1643
2101
|
}
|
|
1644
2102
|
},
|
|
1645
2103
|
async (args) => {
|
|
@@ -1661,7 +2119,7 @@ ${calls.map(callLine).join("\n\n")}`);
|
|
|
1661
2119
|
title: "Ler call",
|
|
1662
2120
|
description: "Retorna o conte\xFAdo completo de uma call pelo id (use search_calls pra achar): t\xEDtulo, resumo, falantes, projeto/cliente, links da grava\xE7\xE3o e a TRANSCRI\xC7\xC3O inteira por falante. Respeita a visibilidade \u2014 s\xF3 retorna o que a conta pode ver.",
|
|
1663
2121
|
inputSchema: {
|
|
1664
|
-
id:
|
|
2122
|
+
id: z9.string().min(1).max(64).describe("id (uuid) da call, obtido via search_calls.")
|
|
1665
2123
|
}
|
|
1666
2124
|
},
|
|
1667
2125
|
async ({ id }) => {
|
|
@@ -1693,6 +2151,7 @@ function buildServer(cfg) {
|
|
|
1693
2151
|
const server = new McpServer({ name: "beplus", version: VERSION });
|
|
1694
2152
|
const client = new BeplusClient(cfg);
|
|
1695
2153
|
registerGenerationTools(server, client, cfg);
|
|
2154
|
+
registerUpscaleTools(server, client, cfg);
|
|
1696
2155
|
registerAudioTools(server, client, cfg);
|
|
1697
2156
|
registerMusicTools(server, client, cfg);
|
|
1698
2157
|
registerAccountTools(server, client, cfg);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "beplus-mcp",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.19.0",
|
|
4
4
|
"description": "Conector MCP da equipe BePlus — gere imagens/vídeos/áudio e consulte calls, projetos e clientes pela sua conta BePlus (diamantes, limites e histórico aplicados pela plataforma).",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|