@productmaker/mcp 12.2.6 → 12.2.7

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/stdio.js CHANGED
@@ -3612,7 +3612,19 @@ var identity = {
3612
3612
  requestDigest: digest,
3613
3613
  nativeMessagesBudget: NativeMessagesBudgetSchema.optional()
3614
3614
  };
3615
- var ModelRouteAttemptRequestSchema = z4.object(identity).strict();
3615
+ var ModelRouteAttemptRequestSchema = z4.object({
3616
+ ...identity,
3617
+ /** Trusted proxy evidence of a pure reasoning cap. Recovery only: this never
3618
+ * grants a changed body permission to reuse an older paid execution. Absent
3619
+ * on historical entries; retained on new entries to identify normalized work.
3620
+ */
3621
+ reasoningNormalization: z4.object({
3622
+ version: z4.literal(1),
3623
+ originalCallId: id,
3624
+ originalRequestDigest: digest,
3625
+ originalNativeMessagesBudget: NativeMessagesBudgetSchema.optional()
3626
+ }).strict().optional()
3627
+ }).strict();
3616
3628
  var ModelRouteCostSchema = z4.discriminatedUnion("source", [
3617
3629
  z4.object({
3618
3630
  source: z4.literal("unknown")
@@ -20680,7 +20692,7 @@ var CAPABILITY_MODEL_GUIDANCE = {
20680
20692
  ]
20681
20693
  },
20682
20694
  generate_free_video: {
20683
- summary: "Genera UN video libre con prompt/guion exacto, opcionalmente con imagen de referencia.",
20695
+ summary: "Creates custom-script video using paid credits; legacy 'free' means free-form.",
20684
20696
  useWhen: [
20685
20697
  "genera un video libre creativo",
20686
20698
  "la persona pide un video libre con un prompt o guion espec\xEDfico"
@@ -29188,35 +29200,535 @@ var ApiClient = class {
29188
29200
  };
29189
29201
 
29190
29202
  // src/index.ts
29191
- var MCP_VERSION = "12.2.6";
29203
+ var MCP_VERSION = "12.2.7";
29192
29204
 
29193
29205
  // src/meta-upload-app.ts
29194
29206
  import { readFileSync } from "fs";
29207
+ import { z as z131 } from "zod";
29208
+
29209
+ // src/public-https.ts
29210
+ import { lookup } from "dns/promises";
29211
+ import https from "https";
29212
+ import { isIP } from "net";
29213
+ import { createGunzip, createInflate, createBrotliDecompress } from "zlib";
29214
+
29215
+ // ../../packages/utils/src/ip-classification.ts
29216
+ import net from "net";
29217
+ function parseIPv4(ip) {
29218
+ const parts = ip.split(".");
29219
+ if (parts.length !== 4) return null;
29220
+ const nums = [
29221
+ Number(parts[0]),
29222
+ Number(parts[1]),
29223
+ Number(parts[2]),
29224
+ Number(parts[3])
29225
+ ];
29226
+ if (nums.some((n) => !Number.isInteger(n) || n < 0 || n > 255)) return null;
29227
+ return nums;
29228
+ }
29229
+ __name(parseIPv4, "parseIPv4");
29230
+ function classifyPrivateIPv4(ip, includeAdditionalRanges) {
29231
+ const p = parseIPv4(ip);
29232
+ if (!p) return false;
29233
+ const [a, b] = p;
29234
+ return a === 0 || a === 10 || a === 127 || a === 169 && b === 254 || a === 172 && b >= 16 && b <= 31 || a === 192 && b === 168 || a >= 240 || includeAdditionalRanges && (a === 100 && b >= 64 && b <= 127 || a === 198 && (b === 18 || b === 19) || a >= 224);
29235
+ }
29236
+ __name(classifyPrivateIPv4, "classifyPrivateIPv4");
29237
+ function isPrivateIPv4(ip) {
29238
+ return classifyPrivateIPv4(ip, true);
29239
+ }
29240
+ __name(isPrivateIPv4, "isPrivateIPv4");
29241
+ function is6to4PrivateIPv4(ipv6) {
29242
+ const groups = ipv6.split(":");
29243
+ if (groups.length < 3) return false;
29244
+ return isPrivateIPv4Words(groups[1] || "0", groups[2] || "0");
29245
+ }
29246
+ __name(is6to4PrivateIPv4, "is6to4PrivateIPv4");
29247
+ function isPrivateIPv4Words(high, low) {
29248
+ const hi = parseInt(high, 16);
29249
+ const lo = parseInt(low, 16);
29250
+ if (Number.isNaN(hi) || Number.isNaN(lo)) return false;
29251
+ const a = hi >> 8 & 255;
29252
+ const b = hi & 255;
29253
+ const c = lo >> 8 & 255;
29254
+ const d = lo & 255;
29255
+ return isPrivateIPv4(`${a}.${b}.${c}.${d}`);
29256
+ }
29257
+ __name(isPrivateIPv4Words, "isPrivateIPv4Words");
29258
+ function isPrivateIPv6(ip) {
29259
+ let normalized;
29260
+ try {
29261
+ normalized = new URL(`http://[${ip.split("%", 1)[0]}]/`).hostname.slice(1, -1).toLowerCase();
29262
+ } catch {
29263
+ return false;
29264
+ }
29265
+ if (normalized === "::" || /^::[0-9a-f]+$/.test(normalized)) return true;
29266
+ if (normalized.startsWith("64:ff9b:")) return true;
29267
+ if (/^fe[89a-f][0-9a-f]:/.test(normalized)) return true;
29268
+ if (/^f[cd][0-9a-f]{2}:/.test(normalized)) return true;
29269
+ if (normalized.startsWith("ff")) return true;
29270
+ if (/^2001:0*:/.test(normalized)) return true;
29271
+ if (normalized.startsWith("2001:db8:")) return true;
29272
+ if (normalized.startsWith("2002:") && is6to4PrivateIPv4(normalized)) return true;
29273
+ const embedded = /^::(?:ffff:(?:0:)?)?([0-9a-f]+):([0-9a-f]+)$/.exec(normalized);
29274
+ if (embedded?.[1] && embedded[2]) return isPrivateIPv4Words(embedded[1], embedded[2]);
29275
+ return false;
29276
+ }
29277
+ __name(isPrivateIPv6, "isPrivateIPv6");
29278
+ function isPrivateIp(ip) {
29279
+ const kind2 = net.isIP(ip);
29280
+ if (kind2 === 4) return isPrivateIPv4(ip);
29281
+ if (kind2 === 6) return isPrivateIPv6(ip);
29282
+ return false;
29283
+ }
29284
+ __name(isPrivateIp, "isPrivateIp");
29285
+ function looksLikeNonCanonicalIp(host) {
29286
+ if (net.isIP(host) !== 0) return false;
29287
+ if (/^\d+$/.test(host)) return true;
29288
+ if (/^0x[0-9a-f]+$/i.test(host)) return true;
29289
+ if (host.includes(".")) {
29290
+ const labels = host.split(".");
29291
+ const allNumeric = labels.every((label) => {
29292
+ if (label.length === 0) return false;
29293
+ return /^\d+$/.test(label) || // decimal / octal-with-leading-zero
29294
+ /^0x[0-9a-f]+$/i.test(label);
29295
+ });
29296
+ if (allNumeric) return true;
29297
+ }
29298
+ return false;
29299
+ }
29300
+ __name(looksLikeNonCanonicalIp, "looksLikeNonCanonicalIp");
29301
+
29302
+ // src/public-host.ts
29303
+ function isPrivateHost(value) {
29304
+ let host = value.toLowerCase().replace(/^\[|\]$/g, "").replace(/\.$/, "");
29305
+ if (host === "localhost" || host === "metadata.google.internal") return true;
29306
+ if (host.includes(":")) {
29307
+ try {
29308
+ host = new URL(`https://[${host}]/`).hostname.slice(1, -1);
29309
+ } catch {
29310
+ return true;
29311
+ }
29312
+ }
29313
+ return looksLikeNonCanonicalIp(host) || isPrivateIp(host);
29314
+ }
29315
+ __name(isPrivateHost, "isPrivateHost");
29316
+
29317
+ // src/public-https.ts
29318
+ var PublicHttpsError = class extends Error {
29319
+ static {
29320
+ __name(this, "PublicHttpsError");
29321
+ }
29322
+ code;
29323
+ constructor(code, message) {
29324
+ super(message), this.code = code;
29325
+ this.name = "PublicHttpsError";
29326
+ }
29327
+ };
29328
+ function pinnedLookup(pin) {
29329
+ return (_hostname, options, callback) => {
29330
+ if (options.all) callback(null, [
29331
+ pin
29332
+ ]);
29333
+ else callback(null, pin.address, pin.family);
29334
+ };
29335
+ }
29336
+ __name(pinnedLookup, "pinnedLookup");
29337
+ async function resolvePublicAddress(url2, signal) {
29338
+ const hostname = url2.hostname.replace(/^\[|\]$/g, "");
29339
+ if (isPrivateHost(hostname)) throw new PublicHttpsError("INVALID_INPUT", "media URL host is private");
29340
+ const literalFamily = isIP(hostname);
29341
+ const records = literalFamily ? [
29342
+ {
29343
+ address: hostname,
29344
+ family: literalFamily
29345
+ }
29346
+ ] : await new Promise((resolve4, reject) => {
29347
+ const abort = /* @__PURE__ */ __name(() => reject(signal.reason), "abort");
29348
+ if (signal.aborted) return abort();
29349
+ signal.addEventListener("abort", abort, {
29350
+ once: true
29351
+ });
29352
+ lookup(hostname, {
29353
+ all: true
29354
+ }).then(resolve4, reject).finally(() => {
29355
+ signal.removeEventListener("abort", abort);
29356
+ });
29357
+ });
29358
+ if (!Array.isArray(records) || !records.length || records.some((entry) => !isIP(entry.address) || isIP(entry.address) !== entry.family || isPrivateHost(entry.address))) {
29359
+ throw new PublicHttpsError("INVALID_INPUT", "media URL DNS answers are not exclusively public IP addresses");
29360
+ }
29361
+ const first = records[0];
29362
+ if (!first || first.family !== 4 && first.family !== 6) throw new PublicHttpsError("INVALID_INPUT", "media URL has no usable DNS address");
29363
+ return {
29364
+ address: first.address,
29365
+ family: first.family
29366
+ };
29367
+ }
29368
+ __name(resolvePublicAddress, "resolvePublicAddress");
29369
+ async function withPinnedHttpsResponse(url2, pin, signal, consume, tls = {}) {
29370
+ const agent = new https.Agent({
29371
+ keepAlive: false,
29372
+ lookup: pinnedLookup(pin),
29373
+ ...tls
29374
+ });
29375
+ let body;
29376
+ let incoming;
29377
+ let req;
29378
+ try {
29379
+ const response = await new Promise((resolve4, reject) => {
29380
+ req = https.request(url2, {
29381
+ agent,
29382
+ signal,
29383
+ headers: {
29384
+ "accept-encoding": "gzip, deflate, br"
29385
+ }
29386
+ }, (res) => {
29387
+ try {
29388
+ incoming = res;
29389
+ const encoding = res.headers["content-encoding"]?.toLowerCase();
29390
+ const decoder = encoding === "gzip" ? createGunzip() : encoding === "deflate" ? createInflate() : encoding === "br" ? createBrotliDecompress() : void 0;
29391
+ body = decoder ? res.pipe(decoder) : res;
29392
+ if (decoder) res.on("error", (error) => decoder.destroy(error));
29393
+ const iterator = body[Symbol.asyncIterator]();
29394
+ const stream = new ReadableStream({
29395
+ async pull(controller) {
29396
+ try {
29397
+ const next = await iterator.next();
29398
+ if (next.done) controller.close();
29399
+ else controller.enqueue(new Uint8Array(next.value));
29400
+ } catch (error) {
29401
+ controller.error(error);
29402
+ }
29403
+ },
29404
+ cancel() {
29405
+ body?.destroy();
29406
+ res.destroy();
29407
+ }
29408
+ });
29409
+ const headers = new Headers();
29410
+ for (const [key2, value] of Object.entries(res.headers)) {
29411
+ if (value !== void 0) headers.set(key2, Array.isArray(value) ? value.join(", ") : value);
29412
+ }
29413
+ const status = res.statusCode ?? 502;
29414
+ resolve4(new Response([
29415
+ 204,
29416
+ 205,
29417
+ 304
29418
+ ].includes(status) ? null : stream, {
29419
+ status,
29420
+ headers
29421
+ }));
29422
+ } catch (error) {
29423
+ res.destroy();
29424
+ reject(error);
29425
+ }
29426
+ });
29427
+ req.on("error", reject);
29428
+ req.end();
29429
+ });
29430
+ if (response.status >= 300 && response.status < 400) throw new PublicHttpsError("DOWNLOAD_FAILED", "media URL redirects are not allowed");
29431
+ return await consume(response, url2);
29432
+ } finally {
29433
+ body?.destroy();
29434
+ incoming?.destroy();
29435
+ req?.destroy();
29436
+ agent.destroy();
29437
+ }
29438
+ }
29439
+ __name(withPinnedHttpsResponse, "withPinnedHttpsResponse");
29440
+ async function withPublicHttpsResponse(rawUrl, timeoutMs, consume) {
29441
+ let url2;
29442
+ try {
29443
+ url2 = new URL(rawUrl);
29444
+ } catch {
29445
+ throw new PublicHttpsError("INVALID_INPUT", "media URL is not valid");
29446
+ }
29447
+ if (url2.protocol !== "https:" || url2.username || url2.password) throw new PublicHttpsError("INVALID_INPUT", "media URL must be HTTPS without embedded credentials");
29448
+ const controller = new AbortController();
29449
+ const timer = setTimeout(() => controller.abort(new Error("media download timed out")), timeoutMs);
29450
+ timer.unref();
29451
+ try {
29452
+ let pin;
29453
+ try {
29454
+ pin = await resolvePublicAddress(url2, controller.signal);
29455
+ } catch (error) {
29456
+ if (error instanceof PublicHttpsError || controller.signal.aborted) throw error;
29457
+ throw new PublicHttpsError("INVALID_INPUT", "media URL DNS lookup failed");
29458
+ }
29459
+ return await withPinnedHttpsResponse(url2, pin, controller.signal, consume);
29460
+ } finally {
29461
+ clearTimeout(timer);
29462
+ }
29463
+ }
29464
+ __name(withPublicHttpsResponse, "withPublicHttpsResponse");
29465
+
29466
+ // src/resolve-image.ts
29467
+ import { promises as fs, createReadStream } from "fs";
29468
+ import * as path from "path";
29469
+ import * as os from "os";
29470
+ var MAX_BYTES = 20 * 1024 * 1024;
29471
+ var IMAGE_FORMATS = [
29472
+ {
29473
+ mime: "image/jpeg",
29474
+ exts: [
29475
+ ".jpg",
29476
+ ".jpeg"
29477
+ ]
29478
+ },
29479
+ {
29480
+ mime: "image/png",
29481
+ exts: [
29482
+ ".png"
29483
+ ]
29484
+ },
29485
+ {
29486
+ mime: "image/webp",
29487
+ exts: [
29488
+ ".webp"
29489
+ ]
29490
+ }
29491
+ ];
29492
+ var ALLOWED_MIMES = new Set(IMAGE_FORMATS.map((f) => f.mime));
29493
+ var ALLOWED_EXTS = new Set(IMAGE_FORMATS.flatMap((f) => f.exts));
29494
+ var ResolveImageError = class extends Error {
29495
+ static {
29496
+ __name(this, "ResolveImageError");
29497
+ }
29498
+ code;
29499
+ constructor(code, message) {
29500
+ super(message), this.code = code;
29501
+ this.name = "ResolveImageError";
29502
+ }
29503
+ };
29504
+ function sniffMime(buf) {
29505
+ if (buf.length < 4) return null;
29506
+ if (buf.readUInt8(0) === 137 && buf.readUInt8(1) === 80 && buf.readUInt8(2) === 78 && buf.readUInt8(3) === 71) return "image/png";
29507
+ if (buf.readUInt8(0) === 255 && buf.readUInt8(1) === 216 && buf.readUInt8(2) === 255) return "image/jpeg";
29508
+ if (buf.length < 12) return null;
29509
+ if (buf.readUInt8(0) === 82 && buf.readUInt8(1) === 73 && buf.readUInt8(2) === 70 && buf.readUInt8(3) === 70 && buf.readUInt8(8) === 87 && buf.readUInt8(9) === 69 && buf.readUInt8(10) === 66 && buf.readUInt8(11) === 80) return "image/webp";
29510
+ return null;
29511
+ }
29512
+ __name(sniffMime, "sniffMime");
29513
+ function expandUserPath(raw) {
29514
+ let s = raw.trim();
29515
+ if (s === "~") return os.homedir();
29516
+ if (s.startsWith("~/") || s.startsWith("~\\")) s = path.join(os.homedir(), s.slice(2));
29517
+ s = s.replace(/^\$HOME(?=$|[/\\])/, os.homedir());
29518
+ s = s.replace(/^%USERPROFILE%(?=$|[/\\])/i, os.homedir());
29519
+ return s;
29520
+ }
29521
+ __name(expandUserPath, "expandUserPath");
29522
+ async function readFileWithCap(absPath, max) {
29523
+ const chunks = [];
29524
+ let total = 0;
29525
+ let exceeded = false;
29526
+ const stream = createReadStream(absPath);
29527
+ try {
29528
+ for await (const chunk of stream) {
29529
+ total += chunk.length;
29530
+ if (total > max) {
29531
+ exceeded = true;
29532
+ break;
29533
+ }
29534
+ chunks.push(chunk);
29535
+ }
29536
+ } catch (e) {
29537
+ const code = e.code;
29538
+ if (code === "ENOENT") throw new ResolveImageError("IMAGE_PATH_NOT_FOUND", "file disappeared during read");
29539
+ if (code === "EACCES" || code === "EPERM") throw new ResolveImageError("IMAGE_PATH_NOT_READABLE", `cannot read file (${code})`);
29540
+ throw new ResolveImageError("IMAGE_PATH_NOT_READABLE", e.message);
29541
+ } finally {
29542
+ if (!stream.destroyed) stream.destroy();
29543
+ }
29544
+ if (exceeded) throw new ResolveImageError("IMAGE_TOO_LARGE", `file exceeds ${max} bytes`);
29545
+ return Buffer.concat(chunks, total);
29546
+ }
29547
+ __name(readFileWithCap, "readFileWithCap");
29548
+ async function resolvePath(rawPath) {
29549
+ const expanded = expandUserPath(rawPath);
29550
+ const abs = path.resolve(expanded);
29551
+ const ext = path.extname(abs).toLowerCase();
29552
+ if (!ALLOWED_EXTS.has(ext)) {
29553
+ throw new ResolveImageError("IMAGE_TYPE_UNSUPPORTED", `path extension ${ext || "(none)"} is not in the image allowlist (.jpg .jpeg .png .webp)`);
29554
+ }
29555
+ let stat2;
29556
+ try {
29557
+ stat2 = await fs.stat(abs);
29558
+ } catch (e) {
29559
+ const code = e.code;
29560
+ if (code === "ENOENT") {
29561
+ throw new ResolveImageError("IMAGE_PATH_NOT_FOUND", `no such file: ${abs}`);
29562
+ }
29563
+ if (code === "EACCES" || code === "EPERM") {
29564
+ throw new ResolveImageError("IMAGE_PATH_NOT_READABLE", `cannot access ${abs} (${code})`);
29565
+ }
29566
+ throw new ResolveImageError("IMAGE_PATH_NOT_READABLE", `stat failed: ${e.message}`);
29567
+ }
29568
+ if (!stat2.isFile()) {
29569
+ throw new ResolveImageError("IMAGE_PATH_NOT_A_FILE", `${abs} is not a regular file`);
29570
+ }
29571
+ if (stat2.size > MAX_BYTES) {
29572
+ throw new ResolveImageError("IMAGE_TOO_LARGE", `${stat2.size} bytes (cap ${MAX_BYTES})`);
29573
+ }
29574
+ const buf = await readFileWithCap(abs, MAX_BYTES);
29575
+ const sniffed = sniffMime(buf);
29576
+ if (!sniffed) {
29577
+ throw new ResolveImageError("IMAGE_TYPE_UNSUPPORTED", `magic bytes do not match PNG/JPEG/WEBP for ${abs}`);
29578
+ }
29579
+ return {
29580
+ buffer: buf,
29581
+ mimetype: sniffed
29582
+ };
29583
+ }
29584
+ __name(resolvePath, "resolvePath");
29585
+ async function resolveImageUrl(rawUrl, maxBytes) {
29586
+ try {
29587
+ return await withPublicHttpsResponse(rawUrl, 3e4, async (res) => {
29588
+ if (!res.ok) throw new ResolveImageError("IMAGE_DOWNLOAD_FAILED", `HTTP ${res.status}`);
29589
+ const cl = res.headers.get("content-length");
29590
+ if (cl && Number(cl) > maxBytes) {
29591
+ throw new ResolveImageError("IMAGE_TOO_LARGE", `content-length ${cl}`);
29592
+ }
29593
+ const ct = (res.headers.get("content-type") ?? "").split(";")[0]?.trim() ?? "";
29594
+ if (!ALLOWED_MIMES.has(ct)) throw new ResolveImageError("IMAGE_TYPE_UNSUPPORTED", `content-type ${ct}`);
29595
+ let buf;
29596
+ try {
29597
+ buf = Buffer.from(await readResponseBytes(res, maxBytes));
29598
+ } catch (error) {
29599
+ if (error instanceof ResponseBodyTooLargeError) {
29600
+ throw new ResolveImageError("IMAGE_TOO_LARGE", `image URL exceeds ${maxBytes} bytes`);
29601
+ }
29602
+ throw new ResolveImageError("IMAGE_DOWNLOAD_FAILED", "image response could not be read");
29603
+ }
29604
+ const sniffed = sniffMime(buf);
29605
+ if (!sniffed || sniffed !== ct) {
29606
+ throw new ResolveImageError("IMAGE_TYPE_UNSUPPORTED", `magic bytes (${sniffed ?? "unknown"}) do not match content-type (${ct})`);
29607
+ }
29608
+ return {
29609
+ buffer: buf,
29610
+ mimetype: sniffed
29611
+ };
29612
+ });
29613
+ } catch (error) {
29614
+ if (error instanceof ResolveImageError) throw error;
29615
+ throw new ResolveImageError(error instanceof PublicHttpsError && error.code === "INVALID_INPUT" ? "INVALID_INPUT" : "IMAGE_DOWNLOAD_FAILED", error instanceof Error ? error.message : "image download failed");
29616
+ }
29617
+ }
29618
+ __name(resolveImageUrl, "resolveImageUrl");
29619
+ async function resolveImages(images, opts = {
29620
+ transport: "http"
29621
+ }) {
29622
+ if (!images.length) throw new ResolveImageError("INVALID_INPUT", "images: at least one entry required");
29623
+ if (images.length > 5) throw new ResolveImageError("INVALID_INPUT", "images: max 5 per request");
29624
+ const maxBytes = opts.maxBytes ?? MAX_BYTES;
29625
+ if (!Number.isSafeInteger(maxBytes) || maxBytes <= 0 || maxBytes > MAX_BYTES) {
29626
+ throw new ResolveImageError("INVALID_INPUT", `maxBytes must be between 1 and ${MAX_BYTES}`);
29627
+ }
29628
+ return Promise.all(images.map((ref) => resolveOne(ref, opts.transport, maxBytes)));
29629
+ }
29630
+ __name(resolveImages, "resolveImages");
29631
+ async function resolveOne(ref, transport, maxBytes) {
29632
+ const hasUrl = Boolean(ref.url);
29633
+ const hasPath = Boolean(ref.path);
29634
+ if (hasUrl === hasPath) {
29635
+ throw new ResolveImageError("INVALID_INPUT", "each image entry must have exactly one of `url` or `path`");
29636
+ }
29637
+ if (hasPath) {
29638
+ if (transport === "http") {
29639
+ throw new ResolveImageError("PATH_NOT_SUPPORTED_IN_HTTP_MODE", "Local file paths are only available when running the MCP locally (npm). For the hosted endpoint at mcp.productmaker.app, pass a public HTTPS `url` instead, or ask the user to host the photo first.");
29640
+ }
29641
+ return resolvePath(ref.path);
29642
+ }
29643
+ return resolveImageUrl(ref.url, maxBytes);
29644
+ }
29645
+ __name(resolveOne, "resolveOne");
29646
+
29647
+ // src/validate-api-url.ts
29648
+ function assertSafePublicBaseUrl(envName, url2) {
29649
+ let u;
29650
+ try {
29651
+ u = new URL(url2);
29652
+ } catch {
29653
+ throw new Error(`${envName} is not a valid URL: ${url2}`);
29654
+ }
29655
+ if (u.protocol !== "https:") {
29656
+ throw new Error(`${envName} must use https (got ${u.protocol})`);
29657
+ }
29658
+ if (u.username || u.password || u.search || u.hash) {
29659
+ throw new Error(`${envName} must not contain credentials, query parameters, or fragments`);
29660
+ }
29661
+ if (isPrivateHost(u.hostname)) {
29662
+ throw new Error(`${envName} must not point to a private/loopback host: ${u.hostname}`);
29663
+ }
29664
+ }
29665
+ __name(assertSafePublicBaseUrl, "assertSafePublicBaseUrl");
29666
+ function assertSafeApiBaseUrl(url2) {
29667
+ assertSafePublicBaseUrl("PM_API_URL", url2);
29668
+ }
29669
+ __name(assertSafeApiBaseUrl, "assertSafeApiBaseUrl");
29670
+
29671
+ // src/meta-upload-app.ts
29195
29672
  import { registerAppResource, RESOURCE_MIME_TYPE } from "@modelcontextprotocol/ext-apps/server";
29196
29673
  var MEDIA_UPLOAD_APP_RESOURCE_URI = "ui://media-upload/v1.html";
29197
29674
  var MEDIA_UPLOAD_APP_NAME = "ProductMaker media upload";
29675
+ var PRODUCTMAKER_APP_ORIGIN = "https://productmaker.app";
29198
29676
  var MEDIA_UPLOAD_APP_HTML_URL = new URL("./ui/meta-upload-app.html", import.meta.url);
29199
- var MEDIA_UPLOAD_APP_CSP = {
29200
- connectDomains: [
29201
- "https://api.productmaker.app",
29202
- "https://productmaker.app",
29203
- "https://www.productmaker.app",
29204
- "https://*.r2.cloudflarestorage.com",
29205
- "https://*.oaiusercontent.com"
29206
- ]
29207
- };
29208
- function buildMediaUploadAppCsp(resolveEndpoint) {
29677
+ var PRODUCTMAKER_UPLOAD_STORAGE_ORIGIN = "https://825805a8113ddb98b68b9b0ddbb5d72a.r2.cloudflarestorage.com";
29678
+ var MEDIA_UPLOAD_CONNECT_DOMAINS = [
29679
+ "https://api.productmaker.app",
29680
+ PRODUCTMAKER_APP_ORIGIN,
29681
+ "https://www.productmaker.app",
29682
+ // The file bridge supplies temporary URLs. Keep this host-owned scope until
29683
+ // a real ChatGPT/library upload establishes every required exact origin.
29684
+ "https://*.oaiusercontent.com"
29685
+ ];
29686
+ var STORAGE_ORIGINS_ERROR = "PM_MEDIA_UPLOAD_STORAGE_ORIGINS must be a JSON array of exact public HTTPS origins";
29687
+ function normalizeStorageOrigins(value) {
29688
+ try {
29689
+ const origins = z131.array(z131.string().trim().min(1)).max(16).parse(value);
29690
+ return [
29691
+ ...new Set(origins.map((origin) => {
29692
+ assertSafePublicBaseUrl("PM_MEDIA_UPLOAD_STORAGE_ORIGINS", origin);
29693
+ const parsed = new URL(origin);
29694
+ if (parsed.pathname !== "/" || parsed.hostname.includes("*")) throw new Error(STORAGE_ORIGINS_ERROR);
29695
+ return parsed.origin;
29696
+ }))
29697
+ ];
29698
+ } catch {
29699
+ throw new Error(STORAGE_ORIGINS_ERROR);
29700
+ }
29701
+ }
29702
+ __name(normalizeStorageOrigins, "normalizeStorageOrigins");
29703
+ function parseMediaUploadStorageOrigins(value) {
29704
+ if (value === void 0) return void 0;
29705
+ try {
29706
+ return normalizeStorageOrigins(JSON.parse(value));
29707
+ } catch {
29708
+ throw new Error(STORAGE_ORIGINS_ERROR);
29709
+ }
29710
+ }
29711
+ __name(parseMediaUploadStorageOrigins, "parseMediaUploadStorageOrigins");
29712
+ function buildMediaUploadAppCsp(resolveEndpoint, storageOrigins) {
29209
29713
  const resolveOrigin = new URL(resolveEndpoint).origin;
29714
+ const approvedStorage = storageOrigins === void 0 ? resolveOrigin === "https://api.productmaker.app" ? [
29715
+ PRODUCTMAKER_UPLOAD_STORAGE_ORIGIN
29716
+ ] : [] : normalizeStorageOrigins(storageOrigins);
29210
29717
  return {
29211
29718
  connectDomains: [
29212
29719
  .../* @__PURE__ */ new Set([
29213
- ...MEDIA_UPLOAD_APP_CSP.connectDomains,
29720
+ ...MEDIA_UPLOAD_CONNECT_DOMAINS,
29721
+ ...approvedStorage,
29214
29722
  resolveOrigin
29215
29723
  ])
29216
- ]
29724
+ ],
29725
+ // This standalone HTML has no remote static assets. Local image previews
29726
+ // use File/createImageBitmap and data: URLs; host-library bytes use fetch.
29727
+ resourceDomains: []
29217
29728
  };
29218
29729
  }
29219
29730
  __name(buildMediaUploadAppCsp, "buildMediaUploadAppCsp");
29731
+ var MEDIA_UPLOAD_APP_CSP = buildMediaUploadAppCsp("https://api.productmaker.app/v1/uploaded-assets/sessions/resolve");
29220
29732
  function readMediaUploadAppHtml(htmlUrl = MEDIA_UPLOAD_APP_HTML_URL) {
29221
29733
  try {
29222
29734
  return readFileSync(htmlUrl, "utf8");
@@ -29231,28 +29743,37 @@ function renderMediaUploadAppHtml(resolveEndpoint) {
29231
29743
  return readMediaUploadAppHtml().replace("__PRODUCTMAKER_RESOLVE_ENDPOINT__", encodedEndpoint);
29232
29744
  }
29233
29745
  __name(renderMediaUploadAppHtml, "renderMediaUploadAppHtml");
29234
- function registerMediaUploadAppResource(server, resolveEndpoint) {
29746
+ function registerMediaUploadAppResource(server, resolveEndpoint, storageOrigins) {
29235
29747
  const html = renderMediaUploadAppHtml(resolveEndpoint);
29236
- const csp = buildMediaUploadAppCsp(resolveEndpoint);
29748
+ const csp = buildMediaUploadAppCsp(resolveEndpoint, storageOrigins);
29749
+ const meta = {
29750
+ ui: {
29751
+ csp
29752
+ },
29753
+ // OpenAI requires a dedicated per-plugin UI origin. Keep its compatibility
29754
+ // alias separate from the host-derived standard domain used by Claude.
29755
+ "openai/widgetDomain": PRODUCTMAKER_APP_ORIGIN,
29756
+ "openai/widgetCSP": {
29757
+ connect_domains: csp.connectDomains,
29758
+ resource_domains: csp.resourceDomains,
29759
+ // Trust only the existing secure-upload fallback for OpenAI external opens.
29760
+ // The current HTML uses an ordinary anchor; no navigation behavior changes.
29761
+ redirect_domains: [
29762
+ PRODUCTMAKER_APP_ORIGIN
29763
+ ]
29764
+ }
29765
+ };
29237
29766
  registerAppResource(server, MEDIA_UPLOAD_APP_NAME, MEDIA_UPLOAD_APP_RESOURCE_URI, {
29238
29767
  title: "Upload media",
29239
29768
  mimeType: RESOURCE_MIME_TYPE,
29240
- _meta: {
29241
- ui: {
29242
- csp
29243
- }
29244
- }
29769
+ _meta: meta
29245
29770
  }, async () => ({
29246
29771
  contents: [
29247
29772
  {
29248
29773
  uri: MEDIA_UPLOAD_APP_RESOURCE_URI,
29249
29774
  mimeType: RESOURCE_MIME_TYPE,
29250
29775
  text: html,
29251
- _meta: {
29252
- ui: {
29253
- csp
29254
- }
29255
- }
29776
+ _meta: meta
29256
29777
  }
29257
29778
  ]
29258
29779
  }));
@@ -29375,247 +29896,7 @@ function registerTaskResultsAppResource(server, proxyBaseUrl) {
29375
29896
  __name(registerTaskResultsAppResource, "registerTaskResultsAppResource");
29376
29897
 
29377
29898
  // src/tools/tasks.ts
29378
- import { z as z136 } from "zod";
29379
-
29380
- // src/resolve-image.ts
29381
- import { lookup as dnsLookup } from "dns/promises";
29382
- import { promises as fs, createReadStream } from "fs";
29383
- import * as path from "path";
29384
- import * as os from "os";
29385
- var MAX_BYTES = 20 * 1024 * 1024;
29386
- var IMAGE_FORMATS = [
29387
- {
29388
- mime: "image/jpeg",
29389
- exts: [
29390
- ".jpg",
29391
- ".jpeg"
29392
- ]
29393
- },
29394
- {
29395
- mime: "image/png",
29396
- exts: [
29397
- ".png"
29398
- ]
29399
- },
29400
- {
29401
- mime: "image/webp",
29402
- exts: [
29403
- ".webp"
29404
- ]
29405
- }
29406
- ];
29407
- var ALLOWED_MIMES = new Set(IMAGE_FORMATS.map((f) => f.mime));
29408
- var ALLOWED_EXTS = new Set(IMAGE_FORMATS.flatMap((f) => f.exts));
29409
- var ResolveImageError = class extends Error {
29410
- static {
29411
- __name(this, "ResolveImageError");
29412
- }
29413
- code;
29414
- constructor(code, message) {
29415
- super(message), this.code = code;
29416
- this.name = "ResolveImageError";
29417
- }
29418
- };
29419
- function sniffMime(buf) {
29420
- if (buf.length < 4) return null;
29421
- if (buf.readUInt8(0) === 137 && buf.readUInt8(1) === 80 && buf.readUInt8(2) === 78 && buf.readUInt8(3) === 71) return "image/png";
29422
- if (buf.readUInt8(0) === 255 && buf.readUInt8(1) === 216 && buf.readUInt8(2) === 255) return "image/jpeg";
29423
- if (buf.length < 12) return null;
29424
- if (buf.readUInt8(0) === 82 && buf.readUInt8(1) === 73 && buf.readUInt8(2) === 70 && buf.readUInt8(3) === 70 && buf.readUInt8(8) === 87 && buf.readUInt8(9) === 69 && buf.readUInt8(10) === 66 && buf.readUInt8(11) === 80) return "image/webp";
29425
- return null;
29426
- }
29427
- __name(sniffMime, "sniffMime");
29428
- function isPrivateHost(host) {
29429
- if (host === "localhost" || host === "0.0.0.0") return true;
29430
- if (host === "metadata.google.internal" || host === "169.254.169.254") return true;
29431
- if (/^127\./.test(host)) return true;
29432
- if (/^10\./.test(host)) return true;
29433
- if (/^192\.168\./.test(host)) return true;
29434
- if (/^172\.(1[6-9]|2\d|3[0-1])\./.test(host)) return true;
29435
- if (host.startsWith("169.254.")) return true;
29436
- if (host === "[::1]" || host === "::1") return true;
29437
- if (/^\[?0*:(?:0*:){6}0*1\]?$/i.test(host)) return true;
29438
- const ipv4Mapped = host.match(/^\[?::ffff:([0-9a-f.:]+)\]?$/i);
29439
- if (ipv4Mapped && ipv4Mapped[1]) {
29440
- const inner = ipv4Mapped[1];
29441
- if (inner.includes(".")) return isPrivateHost(inner);
29442
- const parts = inner.split(":").map((h) => parseInt(h, 16));
29443
- if (parts.length === 2 && parts.every((n) => !Number.isNaN(n))) {
29444
- const bytes = [
29445
- parts[0] >> 8,
29446
- parts[0] & 255,
29447
- parts[1] >> 8,
29448
- parts[1] & 255
29449
- ];
29450
- return isPrivateHost(bytes.join("."));
29451
- }
29452
- }
29453
- if (/^\[?f[cd][0-9a-f]{2}:/i.test(host)) return true;
29454
- if (/^\[?fe[89ab][0-9a-f]:/i.test(host)) return true;
29455
- return false;
29456
- }
29457
- __name(isPrivateHost, "isPrivateHost");
29458
- async function assertHostResolvesPublic(hostname) {
29459
- let records;
29460
- try {
29461
- records = await dnsLookup(hostname, {
29462
- all: true
29463
- });
29464
- } catch {
29465
- if (process.env.NODE_ENV === "test" || process.env.VITEST) return;
29466
- throw new ResolveImageError("INVALID_INPUT", `imageUrl DNS lookup failed for ${hostname}`);
29467
- }
29468
- for (const { address } of records) {
29469
- if (isPrivateHost(address)) {
29470
- throw new ResolveImageError("INVALID_INPUT", `imageUrl host resolves to a private address (${address})`);
29471
- }
29472
- }
29473
- }
29474
- __name(assertHostResolvesPublic, "assertHostResolvesPublic");
29475
- function expandUserPath(raw) {
29476
- let s = raw.trim();
29477
- if (s === "~") return os.homedir();
29478
- if (s.startsWith("~/") || s.startsWith("~\\")) s = path.join(os.homedir(), s.slice(2));
29479
- s = s.replace(/^\$HOME(?=$|[/\\])/, os.homedir());
29480
- s = s.replace(/^%USERPROFILE%(?=$|[/\\])/i, os.homedir());
29481
- return s;
29482
- }
29483
- __name(expandUserPath, "expandUserPath");
29484
- async function readFileWithCap(absPath, max) {
29485
- const chunks = [];
29486
- let total = 0;
29487
- let exceeded = false;
29488
- const stream = createReadStream(absPath);
29489
- try {
29490
- for await (const chunk of stream) {
29491
- total += chunk.length;
29492
- if (total > max) {
29493
- exceeded = true;
29494
- break;
29495
- }
29496
- chunks.push(chunk);
29497
- }
29498
- } catch (e) {
29499
- const code = e.code;
29500
- if (code === "ENOENT") throw new ResolveImageError("IMAGE_PATH_NOT_FOUND", "file disappeared during read");
29501
- if (code === "EACCES" || code === "EPERM") throw new ResolveImageError("IMAGE_PATH_NOT_READABLE", `cannot read file (${code})`);
29502
- throw new ResolveImageError("IMAGE_PATH_NOT_READABLE", e.message);
29503
- } finally {
29504
- if (!stream.destroyed) stream.destroy();
29505
- }
29506
- if (exceeded) throw new ResolveImageError("IMAGE_TOO_LARGE", `file exceeds ${max} bytes`);
29507
- return Buffer.concat(chunks, total);
29508
- }
29509
- __name(readFileWithCap, "readFileWithCap");
29510
- async function resolvePath(rawPath) {
29511
- const expanded = expandUserPath(rawPath);
29512
- const abs = path.resolve(expanded);
29513
- const ext = path.extname(abs).toLowerCase();
29514
- if (!ALLOWED_EXTS.has(ext)) {
29515
- throw new ResolveImageError("IMAGE_TYPE_UNSUPPORTED", `path extension ${ext || "(none)"} is not in the image allowlist (.jpg .jpeg .png .webp)`);
29516
- }
29517
- let stat2;
29518
- try {
29519
- stat2 = await fs.stat(abs);
29520
- } catch (e) {
29521
- const code = e.code;
29522
- if (code === "ENOENT") {
29523
- throw new ResolveImageError("IMAGE_PATH_NOT_FOUND", `no such file: ${abs}`);
29524
- }
29525
- if (code === "EACCES" || code === "EPERM") {
29526
- throw new ResolveImageError("IMAGE_PATH_NOT_READABLE", `cannot access ${abs} (${code})`);
29527
- }
29528
- throw new ResolveImageError("IMAGE_PATH_NOT_READABLE", `stat failed: ${e.message}`);
29529
- }
29530
- if (!stat2.isFile()) {
29531
- throw new ResolveImageError("IMAGE_PATH_NOT_A_FILE", `${abs} is not a regular file`);
29532
- }
29533
- if (stat2.size > MAX_BYTES) {
29534
- throw new ResolveImageError("IMAGE_TOO_LARGE", `${stat2.size} bytes (cap ${MAX_BYTES})`);
29535
- }
29536
- const buf = await readFileWithCap(abs, MAX_BYTES);
29537
- const sniffed = sniffMime(buf);
29538
- if (!sniffed) {
29539
- throw new ResolveImageError("IMAGE_TYPE_UNSUPPORTED", `magic bytes do not match PNG/JPEG/WEBP for ${abs}`);
29540
- }
29541
- return {
29542
- buffer: buf,
29543
- mimetype: sniffed
29544
- };
29545
- }
29546
- __name(resolvePath, "resolvePath");
29547
- async function resolveImageUrl(rawUrl, maxBytes) {
29548
- let u;
29549
- try {
29550
- u = new URL(rawUrl);
29551
- } catch {
29552
- throw new ResolveImageError("INVALID_INPUT", "imageUrl is not a valid URL");
29553
- }
29554
- if (u.protocol !== "https:") throw new ResolveImageError("INVALID_INPUT", "imageUrl must be HTTPS");
29555
- if (isPrivateHost(u.hostname)) throw new ResolveImageError("INVALID_INPUT", "imageUrl host not allowed");
29556
- await assertHostResolvesPublic(u.hostname);
29557
- let res;
29558
- try {
29559
- res = await fetch(rawUrl, {
29560
- signal: AbortSignal.timeout(3e4),
29561
- redirect: "error"
29562
- });
29563
- } catch (e) {
29564
- throw new ResolveImageError("IMAGE_DOWNLOAD_FAILED", e.message);
29565
- }
29566
- if (!res.ok) throw new ResolveImageError("IMAGE_DOWNLOAD_FAILED", `HTTP ${res.status}`);
29567
- const cl = res.headers.get("content-length");
29568
- if (cl && Number(cl) > maxBytes) {
29569
- throw new ResolveImageError("IMAGE_TOO_LARGE", `content-length ${cl}`);
29570
- }
29571
- const ct = (res.headers.get("content-type") ?? "").split(";")[0]?.trim() ?? "";
29572
- if (!ALLOWED_MIMES.has(ct)) throw new ResolveImageError("IMAGE_TYPE_UNSUPPORTED", `content-type ${ct}`);
29573
- let buf;
29574
- try {
29575
- buf = Buffer.from(await readResponseBytes(res, maxBytes));
29576
- } catch (error) {
29577
- if (error instanceof ResponseBodyTooLargeError) {
29578
- throw new ResolveImageError("IMAGE_TOO_LARGE", `image URL exceeds ${maxBytes} bytes`);
29579
- }
29580
- throw new ResolveImageError("IMAGE_DOWNLOAD_FAILED", "image response could not be read");
29581
- }
29582
- const sniffed = sniffMime(buf);
29583
- if (!sniffed || sniffed !== ct) {
29584
- throw new ResolveImageError("IMAGE_TYPE_UNSUPPORTED", `magic bytes (${sniffed ?? "unknown"}) do not match content-type (${ct})`);
29585
- }
29586
- return {
29587
- buffer: buf,
29588
- mimetype: sniffed
29589
- };
29590
- }
29591
- __name(resolveImageUrl, "resolveImageUrl");
29592
- async function resolveImages(images, opts = {
29593
- transport: "http"
29594
- }) {
29595
- if (!images.length) throw new ResolveImageError("INVALID_INPUT", "images: at least one entry required");
29596
- if (images.length > 5) throw new ResolveImageError("INVALID_INPUT", "images: max 5 per request");
29597
- const maxBytes = opts.maxBytes ?? MAX_BYTES;
29598
- if (!Number.isSafeInteger(maxBytes) || maxBytes <= 0 || maxBytes > MAX_BYTES) {
29599
- throw new ResolveImageError("INVALID_INPUT", `maxBytes must be between 1 and ${MAX_BYTES}`);
29600
- }
29601
- return Promise.all(images.map((ref) => resolveOne(ref, opts.transport, maxBytes)));
29602
- }
29603
- __name(resolveImages, "resolveImages");
29604
- async function resolveOne(ref, transport, maxBytes) {
29605
- const hasUrl = Boolean(ref.url);
29606
- const hasPath = Boolean(ref.path);
29607
- if (hasUrl === hasPath) {
29608
- throw new ResolveImageError("INVALID_INPUT", "each image entry must have exactly one of `url` or `path`");
29609
- }
29610
- if (hasPath) {
29611
- if (transport === "http") {
29612
- throw new ResolveImageError("PATH_NOT_SUPPORTED_IN_HTTP_MODE", "Local file paths are only available when running the MCP locally (npm). For the hosted endpoint at mcp.productmaker.app, pass a public HTTPS `url` instead, or ask the user to host the photo first.");
29613
- }
29614
- return resolvePath(ref.path);
29615
- }
29616
- return resolveImageUrl(ref.url, maxBytes);
29617
- }
29618
- __name(resolveOne, "resolveOne");
29899
+ import { z as z137 } from "zod";
29619
29900
 
29620
29901
  // src/status-poller.ts
29621
29902
  var TERMINAL = /* @__PURE__ */ new Set([
@@ -29693,7 +29974,6 @@ __name(buildPollingContinuation, "buildPollingContinuation");
29693
29974
  import { McpError, ErrorCode } from "@modelcontextprotocol/sdk/types.js";
29694
29975
 
29695
29976
  // src/resolve-media.ts
29696
- import { lookup as dnsLookup2 } from "dns/promises";
29697
29977
  import { promises as fs2, createReadStream as createReadStream2 } from "fs";
29698
29978
  import * as os2 from "os";
29699
29979
  import * as path2 from "path";
@@ -29830,32 +30110,6 @@ async function readFileWithCap2(absPath) {
29830
30110
  return Buffer.concat(chunks, total);
29831
30111
  }
29832
30112
  __name(readFileWithCap2, "readFileWithCap");
29833
- async function assertPublicUrl(rawUrl) {
29834
- let url2;
29835
- try {
29836
- url2 = new URL(rawUrl);
29837
- } catch {
29838
- throw new ResolveMediaError("INVALID_INPUT", "media URL is not valid");
29839
- }
29840
- if (url2.protocol !== "https:") throw new ResolveMediaError("INVALID_INPUT", "media URL must be HTTPS");
29841
- if (url2.username || url2.password) throw new ResolveMediaError("INVALID_INPUT", "media URL must not contain embedded credentials");
29842
- if (isPrivateHost(url2.hostname)) throw new ResolveMediaError("INVALID_INPUT", "media URL host is private");
29843
- try {
29844
- const records = await dnsLookup2(url2.hostname, {
29845
- all: true
29846
- });
29847
- if (records.some((record) => isPrivateHost(record.address))) {
29848
- throw new ResolveMediaError("INVALID_INPUT", "media URL resolves to a private address");
29849
- }
29850
- } catch (error) {
29851
- if (error instanceof ResolveMediaError) throw error;
29852
- if (process.env.NODE_ENV !== "test" && !process.env.VITEST) {
29853
- throw new ResolveMediaError("INVALID_INPUT", "media URL DNS lookup failed");
29854
- }
29855
- }
29856
- return url2;
29857
- }
29858
- __name(assertPublicUrl, "assertPublicUrl");
29859
30113
  async function resolvePath2(rawPath, expectedKind) {
29860
30114
  const absolutePath = path2.resolve(expandUserPath2(rawPath));
29861
30115
  const extension = path2.extname(absolutePath).toLowerCase();
@@ -29899,46 +30153,48 @@ async function resolveMediaRef(ref, opts) {
29899
30153
  return resolvePath2(ref.path, opts.expectedKind);
29900
30154
  }
29901
30155
  if (!ref.url) throw new ResolveMediaError("INVALID_INPUT", "media URL is empty");
29902
- const url2 = await assertPublicUrl(ref.url);
29903
- const extension = path2.extname(url2.pathname).toLowerCase();
29904
- const format = MEDIA_FORMATS.find((entry) => entry.exts.some((candidate) => candidate === extension));
29905
- if (format && opts.expectedKind && format.kind !== opts.expectedKind) {
29906
- throw new ResolveMediaError("MEDIA_TYPE_UNSUPPORTED", "media URL extension does not match the requested type");
29907
- }
29908
- const response = await fetch(url2, {
29909
- redirect: "error",
29910
- signal: AbortSignal.timeout(12e4)
29911
- });
29912
- if (!response.ok) throw new ResolveMediaError("MEDIA_URL_UNAVAILABLE", `media URL returned HTTP ${response.status}`);
29913
- const contentLength = Number(response.headers.get("content-length") ?? 0);
29914
- if (contentLength > MAX_MEDIA_BYTES) {
29915
- void response.body?.cancel().catch(() => void 0);
29916
- throw new ResolveMediaError("MEDIA_TOO_LARGE", `media URL exceeds ${MAX_MEDIA_BYTES} bytes`);
29917
- }
29918
- const buffer = await readResponseWithCap(response);
29919
- const sniffed = sniffMime2(buffer, extension);
29920
- if (!sniffed) throw new ResolveMediaError("MEDIA_TYPE_UNSUPPORTED", "media URL magic bytes are unsupported");
29921
- const kind2 = sniffed.startsWith("video/") ? "video" : "image";
29922
- if (opts.expectedKind && kind2 !== opts.expectedKind) {
29923
- throw new ResolveMediaError("MEDIA_TYPE_UNSUPPORTED", `expected ${opts.expectedKind} but received ${kind2}`);
29924
- }
29925
- if (format && sniffed !== format.mime) {
29926
- throw new ResolveMediaError("MEDIA_TYPE_UNSUPPORTED", `media URL magic bytes do not match ${format.mime}`);
30156
+ try {
30157
+ return await withPublicHttpsResponse(ref.url, 12e4, async (response, url2) => {
30158
+ const extension = path2.extname(url2.pathname).toLowerCase();
30159
+ const format = MEDIA_FORMATS.find((entry) => entry.exts.some((candidate) => candidate === extension));
30160
+ if (format && opts.expectedKind && format.kind !== opts.expectedKind) {
30161
+ throw new ResolveMediaError("MEDIA_TYPE_UNSUPPORTED", "media URL extension does not match the requested type");
30162
+ }
30163
+ if (!response.ok) throw new ResolveMediaError("MEDIA_URL_UNAVAILABLE", `media URL returned HTTP ${response.status}`);
30164
+ const contentLength = Number(response.headers.get("content-length") ?? 0);
30165
+ if (contentLength > MAX_MEDIA_BYTES) {
30166
+ void response.body?.cancel().catch(() => void 0);
30167
+ throw new ResolveMediaError("MEDIA_TOO_LARGE", `media URL exceeds ${MAX_MEDIA_BYTES} bytes`);
30168
+ }
30169
+ const buffer = await readResponseWithCap(response);
30170
+ const sniffed = sniffMime2(buffer, extension);
30171
+ if (!sniffed) throw new ResolveMediaError("MEDIA_TYPE_UNSUPPORTED", "media URL magic bytes are unsupported");
30172
+ const kind2 = sniffed.startsWith("video/") ? "video" : "image";
30173
+ if (opts.expectedKind && kind2 !== opts.expectedKind) {
30174
+ throw new ResolveMediaError("MEDIA_TYPE_UNSUPPORTED", `expected ${opts.expectedKind} but received ${kind2}`);
30175
+ }
30176
+ if (format && sniffed !== format.mime) {
30177
+ throw new ResolveMediaError("MEDIA_TYPE_UNSUPPORTED", `media URL magic bytes do not match ${format.mime}`);
30178
+ }
30179
+ const filename = path2.basename(url2.pathname) || `media.${kind2 === "video" ? "mp4" : "jpg"}`;
30180
+ return {
30181
+ kind: kind2,
30182
+ mimetype: sniffed,
30183
+ filename,
30184
+ buffer
30185
+ };
30186
+ });
30187
+ } catch (error) {
30188
+ if (error instanceof ResolveMediaError) throw error;
30189
+ throw new ResolveMediaError(error instanceof PublicHttpsError && error.code === "INVALID_INPUT" ? "INVALID_INPUT" : "MEDIA_URL_UNAVAILABLE", error instanceof Error ? error.message : "media download failed");
29927
30190
  }
29928
- const filename = path2.basename(url2.pathname) || `media.${kind2 === "video" ? "mp4" : "jpg"}`;
29929
- return {
29930
- kind: kind2,
29931
- mimetype: sniffed,
29932
- filename,
29933
- buffer
29934
- };
29935
30191
  }
29936
30192
  __name(resolveMediaRef, "resolveMediaRef");
29937
30193
 
29938
30194
  // src/tools/shared.ts
29939
- import { z as z131 } from "zod";
29940
- var Lang = z131.string().regex(/^[a-z]{2}(?:[-_][a-zA-Z]{2})?$/, "Usa un c\xF3digo de idioma BCP-47, por ejemplo es, en-US o pt-BR").max(10);
29941
- var Country = z131.string().min(2).max(3);
30195
+ import { z as z132 } from "zod";
30196
+ var Lang = z132.string().regex(/^[a-z]{2}(?:[-_][a-zA-Z]{2})?$/, "Usa un c\xF3digo de idioma BCP-47, por ejemplo es, en-US o pt-BR").max(10);
30197
+ var Country = z132.string().min(2).max(3);
29942
30198
  var LandingStrategyEnum = LandingStrategySchema;
29943
30199
  var LandingImageFormatEnum = LandingImageFormatSchema;
29944
30200
  var VideoPresetEnum = VideoPreset.extract([
@@ -30042,7 +30298,7 @@ function animationStyleApplies(videoPreset, angleNarratives) {
30042
30298
  return ANIMATED_VIDEO_PRESETS.has(videoPreset ?? "") || (angleNarratives ?? []).some((n) => ANIMATED_NARRATIVE_STYLES.has(n));
30043
30299
  }
30044
30300
  __name(animationStyleApplies, "animationStyleApplies");
30045
- var VideoTierEnum = z131.enum([
30301
+ var VideoTierEnum = z132.enum([
30046
30302
  "basic"
30047
30303
  ]);
30048
30304
  function resolveVideoTier(_tier) {
@@ -30100,10 +30356,10 @@ var ImageCreativeStyleEnum = ImageCreativeStyle;
30100
30356
  var ImageCreativeArchetypeEnum = ImageCreativeArchetype;
30101
30357
  var ImageCreativeVisualTreatmentEnum = ImageCreativeVisualTreatment;
30102
30358
  var ImageCreativeAspectRatioEnum = ImageCreativeAspectRatio;
30103
- var ImageRefSchema = z131.object({
30104
- url: z131.string().url().optional().describe('URL p\xFAblica HTTPS de la foto. Ej: una URL de Shopify CDN, Cloudinary, Imgur. \xDAsala cuando el usuario te diga "est\xE1 en X.com/foto.jpg" o cuando la foto ya est\xE9 hospedada.'),
30105
- path: z131.string().min(1).optional().describe('Ruta del archivo en el computador del usuario. Ej: "/Users/maria/Desktop/foto.jpg", "~/Pictures/camiseta.png", "C:\\Users\\Maria\\Desktop\\foto.jpg". El MCP local la lee del disco y la sube. SOLO funciona cuando el MCP corre local (stdio). NO funciona en mcp.productmaker.app (HTTP) \u2014 ah\xED usa `url`.'),
30106
- assetId: z131.string().uuid().optional().describe("ID opaco devuelto por check_media_upload. \xDAsalo para una imagen subida desde el MCP App; ProductMaker adopta el archivo directamente y el MCP nunca descarga sus bytes.")
30359
+ var ImageRefSchema = z132.object({
30360
+ url: z132.string().url().optional().describe('URL p\xFAblica HTTPS de la foto. Ej: una URL de Shopify CDN, Cloudinary, Imgur. \xDAsala cuando el usuario te diga "est\xE1 en X.com/foto.jpg" o cuando la foto ya est\xE9 hospedada.'),
30361
+ path: z132.string().min(1).optional().describe('Ruta del archivo en el computador del usuario. Ej: "/Users/maria/Desktop/foto.jpg", "~/Pictures/camiseta.png", "C:\\Users\\Maria\\Desktop\\foto.jpg". El MCP local la lee del disco y la sube. SOLO funciona cuando el MCP corre local (stdio). NO funciona en mcp.productmaker.app (HTTP) \u2014 ah\xED usa `url`.'),
30362
+ assetId: z132.string().uuid().optional().describe("ID opaco devuelto por check_media_upload. \xDAsalo para una imagen subida desde el MCP App; ProductMaker adopta el archivo directamente y el MCP nunca descarga sus bytes.")
30107
30363
  }).refine((v) => [
30108
30364
  v.url,
30109
30365
  v.path,
@@ -30111,11 +30367,11 @@ var ImageRefSchema = z131.object({
30111
30367
  ].filter(Boolean).length === 1, {
30112
30368
  message: "each image entry must have exactly one of `url`, `path`, or `assetId`"
30113
30369
  });
30114
- var ImagesInput = z131.array(ImageRefSchema).min(1).max(5).superRefine((images, ctx) => {
30370
+ var ImagesInput = z132.array(ImageRefSchema).min(1).max(5).superRefine((images, ctx) => {
30115
30371
  const assetCount = images.filter((image2) => Boolean(image2.assetId)).length;
30116
30372
  if (assetCount > 0 && assetCount !== images.length) {
30117
30373
  ctx.addIssue({
30118
- code: z131.ZodIssueCode.custom,
30374
+ code: z132.ZodIssueCode.custom,
30119
30375
  message: "all image entries must use assetId, or all must use URL/path sources"
30120
30376
  });
30121
30377
  }
@@ -30196,14 +30452,14 @@ async function submitCreative(api, opts) {
30196
30452
  });
30197
30453
  }
30198
30454
  __name(submitCreative, "submitCreative");
30199
- var ResearchTargetCountryEnum = z131.enum([
30455
+ var ResearchTargetCountryEnum = z132.enum([
30200
30456
  "CO",
30201
30457
  "MX",
30202
30458
  "PE",
30203
30459
  "EC",
30204
30460
  "CL"
30205
30461
  ]);
30206
- var ResearchCategoryEnum = z131.enum([
30462
+ var ResearchCategoryEnum = z132.enum([
30207
30463
  "auto",
30208
30464
  "automotive",
30209
30465
  "kitchen",
@@ -30998,7 +31254,7 @@ function approveRefusalMessage(reason) {
30998
31254
  __name(approveRefusalMessage, "approveRefusalMessage");
30999
31255
 
31000
31256
  // src/tools/task-outputs.ts
31001
- import { z as z132 } from "zod";
31257
+ import { z as z133 } from "zod";
31002
31258
 
31003
31259
  // src/asset-proxy.ts
31004
31260
  import { createHmac, timingSafeEqual } from "crypto";
@@ -31182,12 +31438,12 @@ function serviceVideoResultMedia(result) {
31182
31438
  };
31183
31439
  }
31184
31440
  __name(serviceVideoResultMedia, "serviceVideoResultMedia");
31185
- var CreativeVariantIdentity = z132.object({
31186
- variantTaskId: z132.string().uuid(),
31187
- sourceTaskId: z132.string().uuid(),
31441
+ var CreativeVariantIdentity = z133.object({
31442
+ variantTaskId: z133.string().uuid(),
31443
+ sourceTaskId: z133.string().uuid(),
31188
31444
  kind: TaskCreativeVariantKind,
31189
31445
  status: TaskStatusSchema,
31190
- error: z132.string().optional()
31446
+ error: z133.string().optional()
31191
31447
  });
31192
31448
  function proxifyOutputs(outputs, proxyBaseUrl) {
31193
31449
  if (!proxyBaseUrl || !isProxyEnabled()) return outputs;
@@ -31791,29 +32047,29 @@ ${parsed.data.error ? `${escapeMarkdownText(parsed.data.error)}
31791
32047
  __name(renderVideoScriptMarkdown, "renderVideoScriptMarkdown");
31792
32048
 
31793
32049
  // src/tools/media-presentation.ts
31794
- import { z as z133 } from "zod";
31795
- var HttpsPresentationUrlSchema = z133.string().url().max(2048).refine((value) => new URL(value).protocol === "https:");
32050
+ import { z as z134 } from "zod";
32051
+ var HttpsPresentationUrlSchema = z134.string().url().max(2048).refine((value) => new URL(value).protocol === "https:");
31796
32052
  var ProductMakerPreviewUrlSchema = HttpsPresentationUrlSchema.refine((value) => {
31797
32053
  const hostname = new URL(value).hostname.toLowerCase();
31798
32054
  return hostname === "productmaker.app" || hostname.endsWith(".productmaker.app");
31799
32055
  }, "preview URL must be owned by ProductMaker");
31800
- var positiveIntAtMost = /* @__PURE__ */ __name((maximum) => z133.number().int().positive().max(maximum), "positiveIntAtMost");
31801
- var PresentationImageSchema = z133.object({
31802
- kind: z133.literal("image"),
32056
+ var positiveIntAtMost = /* @__PURE__ */ __name((maximum) => z134.number().int().positive().max(maximum), "positiveIntAtMost");
32057
+ var PresentationImageSchema = z134.object({
32058
+ kind: z134.literal("image"),
31803
32059
  url: HttpsPresentationUrlSchema,
31804
- mimeType: z133.enum([
32060
+ mimeType: z134.enum([
31805
32061
  "image/jpeg",
31806
32062
  "image/png"
31807
32063
  ]),
31808
32064
  byteSize: positiveIntAtMost(5 * 1024 * 1024),
31809
32065
  width: positiveIntAtMost(16384),
31810
32066
  height: positiveIntAtMost(16384),
31811
- alt: z133.string().trim().min(1).max(240)
32067
+ alt: z134.string().trim().min(1).max(240)
31812
32068
  }).strict();
31813
32069
  var McpMediaPresentationSchema = PresentationImageSchema;
31814
- var GalleryImageSchema = z133.object({
32070
+ var GalleryImageSchema = z134.object({
31815
32071
  url: ProductMakerPreviewUrlSchema,
31816
- alt: z133.string().trim().min(1).max(240)
32072
+ alt: z134.string().trim().min(1).max(240)
31817
32073
  });
31818
32074
  var MCP_GALLERY_IMAGE_MAX_BYTES = 10 * 1024 * 1024;
31819
32075
  async function imageBlocks(image2, transport) {
@@ -31870,12 +32126,12 @@ async function buildMcpMediaPresentationContent(rawPresentation, transport = "ht
31870
32126
  ];
31871
32127
  }
31872
32128
  __name(buildMcpMediaPresentationContent, "buildMcpMediaPresentationContent");
31873
- var OwnedImagePreviewSchema = z133.object({
32129
+ var OwnedImagePreviewSchema = z134.object({
31874
32130
  url: HttpsPresentationUrlSchema.refine((value) => {
31875
32131
  const url2 = new URL(value);
31876
32132
  return !url2.username && !url2.password && !isPrivateHost(url2.hostname);
31877
32133
  }, "Preview URL must be public HTTPS without credentials"),
31878
- alt: z133.string().trim().min(1).max(240)
32134
+ alt: z134.string().trim().min(1).max(240)
31879
32135
  });
31880
32136
  async function buildMcpImagePreviewContent(rawPreview, transport = "http") {
31881
32137
  const preview2 = OwnedImagePreviewSchema.safeParse(rawPreview);
@@ -32171,7 +32427,7 @@ async function observeMcpMediaDelivery(fingerprint, work) {
32171
32427
  __name(observeMcpMediaDelivery, "observeMcpMediaDelivery");
32172
32428
 
32173
32429
  // src/tools/animated-video.ts
32174
- import { z as z134 } from "zod";
32430
+ import { z as z135 } from "zod";
32175
32431
 
32176
32432
  // src/tools/paid-confirmation.ts
32177
32433
  function describeMarketStudyChoice(enabled) {
@@ -32420,30 +32676,30 @@ function hookVariantsLabel(count) {
32420
32676
  return count === 1 ? "1 versi\xF3n del gancho" : `${count} versiones del gancho`;
32421
32677
  }
32422
32678
  __name(hookVariantsLabel, "hookVariantsLabel");
32423
- var DurationInput = z134.union(MOTION_GRAPHICS_DURATIONS.map((d) => z134.literal(d)));
32424
- var CreateAnimatedVideoInput = z134.object({
32425
- action: z134.enum([
32679
+ var DurationInput = z135.union(MOTION_GRAPHICS_DURATIONS.map((d) => z135.literal(d)));
32680
+ var CreateAnimatedVideoInput = z135.object({
32681
+ action: z135.enum([
32426
32682
  "prepare",
32427
32683
  "status",
32428
32684
  "cancel",
32429
32685
  "generate"
32430
32686
  ]).default("prepare").describe("prepare inicia una captura gratuita; status consulta websiteCaptureId; cancel la cancela; generate requiere captura ready y confirmaci\xF3n expl\xEDcita del usuario despu\xE9s de mostrar vista previa y precio."),
32431
- brandId: z134.string().uuid().optional().describe("ID de un servicio propio y listo (de create_brand). Obligatorio al preparar y crear videos nuevos. Conserva el mismo brandId en reintentos; si recuperas una generaci\xF3n hist\xF3rica aceptada sin este campo, om\xEDtelo como en la petici\xF3n original. status/cancel no lo necesitan."),
32432
- websiteCaptureId: z134.string().uuid().optional().describe("ID de la captura preparada. Obligatorio para status/cancel/generate; no inventarlo."),
32433
- generationRequestId: z134.string().uuid().optional().describe("Identidad opcional de una generaci\xF3n confirmada. Repite el mismo UUID al recuperar una respuesta perdida; usa otro \xFAnicamente cuando el usuario pida y confirme otro video, nunca por un error de transporte."),
32434
- confirmed: z134.boolean().optional().describe("Solo true despu\xE9s de que el usuario confirme esta captura, formato, duraci\xF3n, idioma y precio. Obligatorio para generate; preparar no genera ni cobra."),
32687
+ brandId: z135.string().uuid().optional().describe("ID de un servicio propio y listo (de create_brand). Obligatorio al preparar y crear videos nuevos. Conserva el mismo brandId en reintentos; si recuperas una generaci\xF3n hist\xF3rica aceptada sin este campo, om\xEDtelo como en la petici\xF3n original. status/cancel no lo necesitan."),
32688
+ websiteCaptureId: z135.string().uuid().optional().describe("ID de la captura preparada. Obligatorio para status/cancel/generate; no inventarlo."),
32689
+ generationRequestId: z135.string().uuid().optional().describe("Identidad opcional de una generaci\xF3n confirmada. Repite el mismo UUID al recuperar una respuesta perdida; usa otro \xFAnicamente cuando el usuario pida y confirme otro video, nunca por un error de transporte."),
32690
+ confirmed: z135.boolean().optional().describe("Solo true despu\xE9s de que el usuario confirme esta captura, formato, duraci\xF3n, idioma y precio. Obligatorio para generate; preparar no genera ni cobra."),
32435
32691
  // Plain text, not `.url()`: users type «cal.com», and the web accepts it.
32436
32692
  // normalizeWebAddress (shared with the web) adds https:// and refuses
32437
32693
  // anything that is not a public http(s) address.
32438
- landingUrl: z134.string().trim().min(1).optional().describe("Enlace p\xFAblico de la p\xE1gina del servicio, como lo dio el usuario (\xABcal.com\xBB o \xABhttps://cal.com\xBB). El video sale de lo que muestra esa p\xE1gina: su marca, colores y mensajes."),
32694
+ landingUrl: z135.string().trim().min(1).optional().describe("Enlace p\xFAblico de la p\xE1gina del servicio, como lo dio el usuario (\xABcal.com\xBB o \xABhttps://cal.com\xBB). El video sale de lo que muestra esa p\xE1gina: su marca, colores y mensajes."),
32439
32695
  format: MotionGraphicsFormatSchema.optional().describe('Formato: "vertical" (9:16, Reels/TikTok), "square" (1:1, feed) u "horizontal" (16:9, YouTube/web). Si el usuario no lo dijo, PREG\xDANTALE.'),
32440
32696
  durationSec: DurationInput.optional().describe(`Duraci\xF3n en segundos: ${MOTION_GRAPHICS_DURATIONS.join(", ")}. Fija el precio (ver la descripci\xF3n de la herramienta). Si el usuario no la dijo, PREG\xDANTALE.`),
32441
32697
  language: MotionGraphicsLanguageSchema.optional().describe('Idioma de los textos del video: "es", "en" o "pt".'),
32442
32698
  videoQuality: MotionGraphicsVideoQualitySchema.default(DEFAULT_MOTION_GRAPHICS_VIDEO_QUALITY).describe('Calidad IA: "economy" (econ\xF3mica) o "pro" (alta, por defecto). Afecta el precio de los clips IA.'),
32443
32699
  videoResolution: MotionGraphicsVideoResolutionSchema.default(DEFAULT_MOTION_GRAPHICS_VIDEO_RESOLUTION).describe('Resoluci\xF3n: "480p" (por defecto), "720p" o "1080p". 1080p solo est\xE1 disponible con videoQuality="pro".'),
32444
- notes: z134.string().trim().max(MOTION_GRAPHICS_NOTES_MAX_CHARS).optional().describe("Opcional. Idea creativa, escenarios, acciones, di\xE1logo, voz o m\xFAsica en lenguaje natural. El director puede hacer solo motion graphics, escenas reales o escenas imposibles seg\xFAn la idea; las frases gu\xEDan el guion sin garantizar reproducci\xF3n exacta."),
32700
+ notes: z135.string().trim().max(MOTION_GRAPHICS_NOTES_MAX_CHARS).optional().describe("Opcional. Idea creativa, escenarios, acciones, di\xE1logo, voz o m\xFAsica en lenguaje natural. El director puede hacer solo motion graphics, escenas reales o escenas imposibles seg\xFAn la idea; las frases gu\xEDan el guion sin garantizar reproducci\xF3n exacta."),
32445
32701
  hookVariants: MotionGraphicsHookVariantsSchema.optional().describe(`Opcional. Cantidad de videos independientes, de 1 a ${MOTION_GRAPHICS_HOOK_VARIANTS_MAX} (por defecto 1). Cada uno tiene su propio \xE1ngulo, guion y estilo visual, respetando notes, y reserva el precio completo de un video (ver la descripci\xF3n); \xFAsalo solo si el usuario lo pide.`),
32446
- music: z134.boolean().optional().describe("Opcional. M\xFAsica y efectos dirigidos seg\xFAn el negocio y el video (por defecto true), con una copia adicional solo con voces. false entrega solo voces, sin m\xFAsica ni efectos a\xF1adidos ni el cargo de m\xFAsica. \xDAsalo solo si el usuario lo pide.")
32702
+ music: z135.boolean().optional().describe("Opcional. M\xFAsica y efectos dirigidos seg\xFAn el negocio y el video (por defecto true), con una copia adicional solo con voces. false entrega solo voces, sin m\xFAsica ni efectos a\xF1adidos ni el cargo de m\xFAsica. \xDAsalo solo si el usuario lo pide.")
32447
32703
  }).strict();
32448
32704
  function captureNextAction(capture) {
32449
32705
  if (capture.status === "ready") return "Muestra la vista previa al usuario y confirma formato, duraci\xF3n, idioma, calidad IA, resoluci\xF3n y precio. Solo despu\xE9s llama create_animated_video con action=generate, este websiteCaptureId y confirmed=true.";
@@ -32496,33 +32752,33 @@ var FORMAT_LABELS = {
32496
32752
  square: "cuadrado 1:1",
32497
32753
  horizontal: "horizontal 16:9"
32498
32754
  };
32499
- var AnimatedVideoResultSchema = z134.object({
32500
- status: z134.enum([
32755
+ var AnimatedVideoResultSchema = z135.object({
32756
+ status: z135.enum([
32501
32757
  "pending",
32502
32758
  "done",
32503
32759
  "error"
32504
32760
  ]),
32505
- serviceMode: z134.literal("motion_graphics"),
32506
- finalMp4Url: z134.string().optional(),
32507
- adCopy: z134.string().optional(),
32508
- adCopies: z134.array(z134.string()).optional(),
32509
- error: z134.string().optional(),
32510
- motionGraphics: z134.object({
32761
+ serviceMode: z135.literal("motion_graphics"),
32762
+ finalMp4Url: z135.string().optional(),
32763
+ adCopy: z135.string().optional(),
32764
+ adCopies: z135.array(z135.string()).optional(),
32765
+ error: z135.string().optional(),
32766
+ motionGraphics: z135.object({
32511
32767
  stage: MotionGraphicsStageSchema,
32512
32768
  format: MotionGraphicsFormatSchema,
32513
- durationSec: z134.number(),
32514
- width: z134.number().optional(),
32515
- height: z134.number().optional(),
32769
+ durationSec: z135.number(),
32770
+ width: z135.number().optional(),
32771
+ height: z135.number().optional(),
32516
32772
  language: MotionGraphicsLanguageSchema.optional(),
32517
32773
  videoQuality: MotionGraphicsVideoQualitySchema.optional().catch(void 0),
32518
32774
  videoResolution: MotionGraphicsVideoResolutionSchema.optional().catch(void 0),
32519
- creativeBatch: z134.object({
32775
+ creativeBatch: z135.object({
32520
32776
  index: MotionGraphicsHookVariantsSchema,
32521
32777
  count: MotionGraphicsHookVariantsSchema,
32522
- tasks: z134.array(z134.object({
32523
- taskId: z134.string().uuid(),
32778
+ tasks: z135.array(z135.object({
32779
+ taskId: z135.string().uuid(),
32524
32780
  index: MotionGraphicsHookVariantsSchema,
32525
- status: z134.enum([
32781
+ status: z135.enum([
32526
32782
  "queued",
32527
32783
  "processing",
32528
32784
  "done",
@@ -32530,29 +32786,29 @@ var AnimatedVideoResultSchema = z134.object({
32530
32786
  ])
32531
32787
  })).max(MOTION_GRAPHICS_HOOK_VARIANTS_MAX)
32532
32788
  }).optional().catch(void 0),
32533
- queuedAt: z134.string().optional(),
32534
- jobsAhead: z134.number().int().nonnegative().optional(),
32535
- stageStartedAt: z134.string().optional(),
32536
- renderProgress: z134.number().optional(),
32789
+ queuedAt: z135.string().optional(),
32790
+ jobsAhead: z135.number().int().nonnegative().optional(),
32791
+ stageStartedAt: z135.string().optional(),
32792
+ renderProgress: z135.number().optional(),
32537
32793
  // AI clip fields (absent on a video paid before clips, or from an API
32538
32794
  // that predates them). Lenient: an unreadable value is dropped
32539
32795
  // instead of hiding the status.
32540
- clipCount: z134.number().int().nonnegative().optional().catch(void 0),
32541
- clipsUsed: z134.number().int().nonnegative().optional().catch(void 0),
32542
- clipPriceCents: z134.number().finite().nonnegative().optional().catch(void 0),
32543
- clipsRefunded: z134.number().int().nonnegative().optional().catch(void 0),
32796
+ clipCount: z135.number().int().nonnegative().optional().catch(void 0),
32797
+ clipsUsed: z135.number().int().nonnegative().optional().catch(void 0),
32798
+ clipPriceCents: z135.number().finite().nonnegative().optional().catch(void 0),
32799
+ clipsRefunded: z135.number().int().nonnegative().optional().catch(void 0),
32544
32800
  // Hook versions (absent from an API or a video before them): the
32545
32801
  // count ordered and, once done, the signed URLs of the EXTRA versions
32546
32802
  // (hook 2, hook 3); `finalMp4Url` is hook 1.
32547
- hookVariants: z134.number().int().min(1).max(MOTION_GRAPHICS_HOOK_VARIANTS_MAX).optional().catch(void 0),
32548
- cleanVideoUrl: z134.string().url().optional().catch(void 0),
32549
- cleanVariantUrls: z134.array(z134.string().url()).max(MOTION_GRAPHICS_HOOK_VARIANTS_MAX - 1).optional().catch(void 0),
32550
- variantUrls: z134.array(z134.string().min(1)).max(MOTION_GRAPHICS_HOOK_VARIANTS_MAX - 1).optional().catch(void 0),
32803
+ hookVariants: z135.number().int().min(1).max(MOTION_GRAPHICS_HOOK_VARIANTS_MAX).optional().catch(void 0),
32804
+ cleanVideoUrl: z135.string().url().optional().catch(void 0),
32805
+ cleanVariantUrls: z135.array(z135.string().url()).max(MOTION_GRAPHICS_HOOK_VARIANTS_MAX - 1).optional().catch(void 0),
32806
+ variantUrls: z135.array(z135.string().min(1)).max(MOTION_GRAPHICS_HOOK_VARIANTS_MAX - 1).optional().catch(void 0),
32551
32807
  // Extra hook versions given back, as the task recorded it once the
32552
32808
  // refunds landed (absent until then, and from an API before it).
32553
- variantsRefunded: z134.number().int().nonnegative().optional().catch(void 0),
32554
- activity: z134.array(MotionGraphicsActivitySchema).optional().catch(void 0),
32555
- previewUrl: z134.string().optional()
32809
+ variantsRefunded: z135.number().int().nonnegative().optional().catch(void 0),
32810
+ activity: z135.array(MotionGraphicsActivitySchema).optional().catch(void 0),
32811
+ previewUrl: z135.string().optional()
32556
32812
  }).passthrough()
32557
32813
  }).passthrough();
32558
32814
  function parseAnimatedVideoResult(value) {
@@ -32831,7 +33087,7 @@ function registerAnimatedVideoTools(server, api) {
32831
33087
  nowMs: 0
32832
33088
  })
32833
33089
  });
32834
- const parsedTaskIds = z134.array(z134.string().uuid()).min(1).max(MOTION_GRAPHICS_HOOK_VARIANTS_MAX).safeParse(created.taskIds);
33090
+ const parsedTaskIds = z135.array(z135.string().uuid()).min(1).max(MOTION_GRAPHICS_HOOK_VARIANTS_MAX).safeParse(created.taskIds);
32835
33091
  const taskIds = parsedTaskIds.success && parsedTaskIds.data[0] === created.taskId ? parsedTaskIds.data : void 0;
32836
33092
  const receipt = {
32837
33093
  taskId: created.taskId,
@@ -32873,10 +33129,10 @@ function registerAnimatedVideoTools(server, api) {
32873
33129
  __name(registerAnimatedVideoTools, "registerAnimatedVideoTools");
32874
33130
 
32875
33131
  // src/tools/winning-product-image.ts
32876
- import { z as z135 } from "zod";
32877
- var WinnerReferenceInput = z135.object({
32878
- runId: z135.string().uuid(),
32879
- candidateSourceId: z135.string().trim().min(1).max(160)
33132
+ import { z as z136 } from "zod";
33133
+ var WinnerReferenceInput = z136.object({
33134
+ runId: z136.string().uuid(),
33135
+ candidateSourceId: z136.string().trim().min(1).max(160)
32880
33136
  }).strict().describe("Referencia opaca de un producto devuelto por find_winning_products/get_product_research_run.");
32881
33137
  async function resolveWinnerImages(api, winnerReference, images) {
32882
33138
  if (!winnerReference) return images;
@@ -32913,62 +33169,62 @@ var createProductTaskSingleOutputDescription = renderCreateProductTaskAvoidWhenF
32913
33169
  var CREATE_SERVICE_VIDEO_ON_MCP = PRODUCTMAKER_MCP_CAPABILITY_NAMES.includes("create_service_video");
32914
33170
  var listTasksGuidance = getProductMakerCapabilityModelGuidance("list_tasks");
32915
33171
  var editTaskDraftGuidance = getProductMakerCapabilityModelGuidance("edit_task_draft");
32916
- var CreateProductTaskInput = z136.object({
33172
+ var CreateProductTaskInput = z137.object({
32917
33173
  images: ImagesInput.optional().describe("1\u20135 fotos del mismo producto. Omitir \xFAnicamente cuando projectId apunta a un proyecto propio con imagen guardada."),
32918
33174
  winnerReference: WinnerReferenceInput.optional().describe(WINNER_IMAGE_CHOICE_GUIDANCE),
32919
- projectId: z136.string().uuid().optional().describe("Opcional. Proyecto guiado propio. Permite continuar sin volver a subir la imagen guardada; nunca evita cr\xE9ditos, cuotas ni confirmaciones."),
32920
- productUrl: z136.string().url().optional().describe("Opcional. URL de la p\xE1gina del producto (Shopify, AliExpress, Amazon, etc.) para enriquecer el contexto. Si se omite, la IA infiere todo desde la imagen."),
33175
+ projectId: z137.string().uuid().optional().describe("Opcional. Proyecto guiado propio. Permite continuar sin volver a subir la imagen guardada; nunca evita cr\xE9ditos, cuotas ni confirmaciones."),
33176
+ productUrl: z137.string().url().optional().describe("Opcional. URL de la p\xE1gina del producto (Shopify, AliExpress, Amazon, etc.) para enriquecer el contexto. Si se omite, la IA infiere todo desde la imagen."),
32921
33177
  language: Lang.describe('C\xF3digo de idioma para el contenido generado. Ejemplos: "es", "en", "pt". REQUERIDO \u2014 si el usuario no lo especific\xF3, PREG\xDANTALE antes de invocar. NUNCA infieras desde el contexto del chat ni asumas "es" por default.'),
32922
33178
  country: Country.describe('C\xF3digo ISO del pa\xEDs objetivo. Ejemplos: "CO", "MX", "US", "AR". Define moneda, m\xE9todos de pago locales y tono. REQUERIDO \u2014 si el usuario no lo especific\xF3, PREG\xDANTALE antes de invocar. NUNCA infieras desde el contexto del chat.'),
32923
- price: z136.string().min(1).max(120).optional().describe('Precio que se mostrar\xE1 en la landing y video. Formato libre \u2014 ej. "$89.900", "USD 29.99", "MXN 599". Opcional pero MUY IMPACTANTE: si el usuario no lo mencion\xF3, PREG\xDANTALE antes de invocar. Si te dice "t\xFA elige", om\xEDtelo y el backend sugiere uno.'),
32924
- compareAtPrice: z136.string().min(1).max(120).optional().describe('Opcional. Precio anterior para comunicar un descuento real en landing e im\xE1genes. Ej. "$129.000" cuando price="$89.900".'),
32925
- offer: z136.string().min(1).max(240).optional().describe('Oferta o promoci\xF3n a destacar. Ej. "2x1", "Env\xEDo gratis hoy", "50% OFF". Opcional pero MUY IMPACTANTE: si el usuario no lo mencion\xF3, PREG\xDANTALE antes de invocar (es v\xE1lido que diga "sin oferta"). Si te dice "t\xFA elige", om\xEDtelo.'),
32926
- paymentMethods: z136.string().min(1).max(240).optional().describe('Opcional. M\xE9todos de pago que puede mostrar la landing. Ej. "Tarjeta, PSE, Nequi, Contraentrega". Si se omite, la landing no muestra m\xE9todos de pago.'),
32927
- productContext: z136.string().max(PRODUCT_CONTEXT_MAX_CHARS).optional().describe("Opcional. Informacion adicional del producto que la IA debe usar como contexto prioritario: uso real, comprador, restricciones, diferenciadores o direccion creativa. No reemplaza la investigacion de competencia; si hay competencia, guia el primer angulo y los outputs no deben contradecirlo."),
32928
- productLabel: z136.string().trim().max(80).optional().describe('Opcional. Qu\xE9 es el producto, en pocas palabras (ej. "quita pelusas para ropa"). Es AUTORITATIVO: si lo env\xEDas, la IA usa esta identidad y no la reemplaza por otra categor\xEDa. Si lo omites, la IA detecta el producto desde la imagen.'),
33179
+ price: z137.string().min(1).max(120).optional().describe('Precio que se mostrar\xE1 en la landing y video. Formato libre \u2014 ej. "$89.900", "USD 29.99", "MXN 599". Opcional pero MUY IMPACTANTE: si el usuario no lo mencion\xF3, PREG\xDANTALE antes de invocar. Si te dice "t\xFA elige", om\xEDtelo y el backend sugiere uno.'),
33180
+ compareAtPrice: z137.string().min(1).max(120).optional().describe('Opcional. Precio anterior para comunicar un descuento real en landing e im\xE1genes. Ej. "$129.000" cuando price="$89.900".'),
33181
+ offer: z137.string().min(1).max(240).optional().describe('Oferta o promoci\xF3n a destacar. Ej. "2x1", "Env\xEDo gratis hoy", "50% OFF". Opcional pero MUY IMPACTANTE: si el usuario no lo mencion\xF3, PREG\xDANTALE antes de invocar (es v\xE1lido que diga "sin oferta"). Si te dice "t\xFA elige", om\xEDtelo.'),
33182
+ paymentMethods: z137.string().min(1).max(240).optional().describe('Opcional. M\xE9todos de pago que puede mostrar la landing. Ej. "Tarjeta, PSE, Nequi, Contraentrega". Si se omite, la landing no muestra m\xE9todos de pago.'),
33183
+ productContext: z137.string().max(PRODUCT_CONTEXT_MAX_CHARS).optional().describe("Opcional. Informacion adicional del producto que la IA debe usar como contexto prioritario: uso real, comprador, restricciones, diferenciadores o direccion creativa. No reemplaza la investigacion de competencia; si hay competencia, guia el primer angulo y los outputs no deben contradecirlo."),
33184
+ productLabel: z137.string().trim().max(80).optional().describe('Opcional. Qu\xE9 es el producto, en pocas palabras (ej. "quita pelusas para ropa"). Es AUTORITATIVO: si lo env\xEDas, la IA usa esta identidad y no la reemplaza por otra categor\xEDa. Si lo omites, la IA detecta el producto desde la imagen.'),
32929
33185
  videoPreset: VideoPresetEnum.default("auto").describe('Opcional. Tipo/narrativa del video \u2014 \xFAnico campo de "estilo" que el usuario controla. Default "auto" (la IA escoge tras analizar el producto). Todos los presets funcionan a 15s (single_call) y 30s (Multiescena); "problem_solution" en single_call abre sin el producto y el producto entra por referencia en su beat de soluci\xF3n. Opciones: "auto", "ugc_dynamic" (review con experiencia y prueba), "ugc_how_to" (tutorial con pasos y resultado visible), "ugc_unboxing_chaos" (apertura y primera prueba), "problem_solution" (problema concreto, producto como soluci\xF3n y CTA), "product_showcase" (producto ejecutando una funci\xF3n real), "wearable_showcase" (prenda o accesorio modelado), "cinematic" (TV spot con receta cinematogr\xE1fica), "wild_card" (idea imposible: un truco visual que en la vida real no podr\xEDa pasar, y al cierre la invitaci\xF3n a comprar), "pixar_problem" (problema animado 3D), "pixar_sassy" (producto que habla). Si el usuario pide algo fuera de esta lista, p\xEDdele una opci\xF3n v\xE1lida.'),
32930
33186
  animationStyle: AnimationStyleEnum.optional().describe('Opcional. Estilo visual SOLO para presets animados ("pixar_problem" o "pixar_sassy") \u2014 la API lo rechaza con cualquier otro preset. Opciones: "pixar_3d" (default, look 3D de pel\xEDcula animada), "claymation" (plastilina stop-motion artesanal), "wes_anderson" (tableau sim\xE9trico pastel), "sci_fi" (futurista con HUDs y ne\xF3n), "realista" (fotorrealista, fiel al producto). Si el usuario pide un look animado sin nombrar el estilo, omite el campo.'),
32931
- durationSeconds: z136.union([
32932
- z136.literal(15),
32933
- z136.literal(30)
33187
+ durationSeconds: z137.union([
33188
+ z137.literal(15),
33189
+ z137.literal(30)
32934
33190
  ]).default(15).describe("Opcional. Duraci\xF3n objetivo del video: 15s (single_call, default) o 30s (Multiescena, aproximadamente 30s y facturado por escena)."),
32935
- pauseForAngleReview: z136.boolean().optional().describe("Compatibilidad heredada. MCP deriva esta pausa de aiDecidesAll: manual siempre pausa y autom\xE1tico siempre la omite. No uses este campo para saltar reviews."),
32936
- aiDecidesAll: z136.boolean().optional().describe("Mapea el toggle web \u201CDejar que la IA decida todo\u201D. Si true, ProductMaker omite reviews. Act\xEDvalo \xFAnicamente cuando el usuario lo pida expl\xEDcitamente. Omitido/false = reviews conversacionales."),
33191
+ pauseForAngleReview: z137.boolean().optional().describe("Compatibilidad heredada. MCP deriva esta pausa de aiDecidesAll: manual siempre pausa y autom\xE1tico siempre la omite. No uses este campo para saltar reviews."),
33192
+ aiDecidesAll: z137.boolean().optional().describe("Mapea el toggle web \u201CDejar que la IA decida todo\u201D. Si true, ProductMaker omite reviews. Act\xEDvalo \xFAnicamente cuando el usuario lo pida expl\xEDcitamente. Omitido/false = reviews conversacionales."),
32937
33193
  videoTier: VideoTierEnum.optional().describe('Opcional. Estilo del video. "basic" = "Realista" para 15s; al pedir durationSeconds=30 el sistema selecciona internamente Multiescena. No se expone el tier interno directamente.'),
32938
33194
  videoStructure: VideoStructureEnum.default("single_take").describe('Opcional. Formato: "single_take" = Un solo clip (generado completo; puede incluir cambios de plano) o "multi_scene" = Multiescena. Default: single_take.'),
32939
33195
  videoQuality: VideoQualityEnum.default("economy").describe('Opcional. Calidad: "economy" = Econ\xF3mico, "medium" = Medio o "pro" = Pro. El proveedor se elige internamente. Default: economy.'),
32940
33196
  videoResolution: VideoResolutionEnum.optional().describe('Opcional. Resoluci\xF3n del render. En la web se pregunta "\xBFResoluci\xF3n del video?". "480p" = excelente calidad y m\xE1s econ\xF3mico (RECOMENDADO, default si se omite); "720p" = m\xE1xima nitidez, entre 3% y 15% m\xE1s caro seg\xFAn formato y tier. Si el usuario no menciona resoluci\xF3n, OMITE este campo (el backend usa 480p).'),
32941
- videoAngleCount: z136.number().int().min(1).max(3).optional().describe("Cantidad de videos a renderizar: 1, 2 o 3. REQUERIDO cuando outputs incluye video (o se omite outputs, cuyo default incluye video); om\xEDtelo s\xF3lo cuando no se generar\xE1 video. Preg\xFAntalo incluso con aiDecidesAll=true porque cambia el costo."),
33197
+ videoAngleCount: z137.number().int().min(1).max(3).optional().describe("Cantidad de videos a renderizar: 1, 2 o 3. REQUERIDO cuando outputs incluye video (o se omite outputs, cuyo default incluye video); om\xEDtelo s\xF3lo cuando no se generar\xE1 video. Preg\xFAntalo incluso con aiDecidesAll=true porque cambia el costo."),
32942
33198
  angleNarratives: PublicNarrativeStyleEnum.array().max(3).optional().describe('Opcional. Narrativa POR \xC1NGULO (posicional). Valores actuales: "review", "ugc_how_to", "ugc_unboxing_chaos", "problem_product_cta" (problema concreto, producto como soluci\xF3n y CTA), "product_showcase", "wearable_showcase", "cinematic", "wild_card", "animated_problem_pixar", "sassy_product_pixar", "market" (narrativa an\xE1lisis de mercado: adapta el video a los patrones ganadores estudiados de la competencia; requiere competitorScanEnabled: true). angleNarratives[i] fuerza la narrativa del \xE1ngulo i; las posiciones restantes las elige la IA. La lista no puede superar videoAngleCount.'),
32943
- outputs: z136.array(z136.enum([
33199
+ outputs: z137.array(z137.enum([
32944
33200
  "landing",
32945
33201
  "video",
32946
33202
  "image"
32947
33203
  ])).min(1).max(3).optional().describe(`Opcional. Qu\xE9 outputs generar. Default: todos ["landing","video","image"]. Para 2 outputs: ["landing","image"] (sin video), ["video","image"] (sin landing), ["landing","video"] (sin imagen). ${createProductTaskSingleOutputDescription}`),
32948
- includeInitialLandingGif: z136.boolean().optional().describe("Opcional. En nuevas landings cl\xE1sicas habilitadas, omitirlo incluye el primer GIF del producto y su coste en el total. Env\xEDa false si el usuario lo excluye: no se genera ni se reserva su coste. Conserva la elecci\xF3n al confirmar y al reintentar."),
32949
- competitorScanEnabled: z136.boolean().optional().describe("Opcional. Default false: omitirlo significa SIN estudio de mercado. Antes de crear, llama estimate_market_study_cost con kind=product_task, muestra al usuario el costo con y sin estudio y preg\xFAntale si lo quiere; env\xEDa true solo si lo acepta (o si solicita la narrativa an\xE1lisis de mercado). Si la tarea incluye video, true analiza anuncios de la competencia y desbloquea la narrativa an\xE1lisis de mercado; si el estudio no encuentra datos suficientes, degrada al planner cl\xE1sico."),
33204
+ includeInitialLandingGif: z137.boolean().optional().describe("Opcional. En nuevas landings cl\xE1sicas habilitadas, omitirlo incluye el primer GIF del producto y su coste en el total. Env\xEDa false si el usuario lo excluye: no se genera ni se reserva su coste. Conserva la elecci\xF3n al confirmar y al reintentar."),
33205
+ competitorScanEnabled: z137.boolean().optional().describe("Opcional. Default false: omitirlo significa SIN estudio de mercado. Antes de crear, llama estimate_market_study_cost con kind=product_task, muestra al usuario el costo con y sin estudio y preg\xFAntale si lo quiere; env\xEDa true solo si lo acepta (o si solicita la narrativa an\xE1lisis de mercado). Si la tarea incluye video, true analiza anuncios de la competencia y desbloquea la narrativa an\xE1lisis de mercado; si el estudio no encuentra datos suficientes, degrada al planner cl\xE1sico."),
32950
33206
  generateAiEdit: AiEditRequestSchema.optional().describe('Opcional. Pide la Edici\xF3n con IA: una pasada que a\xF1ade texto, efectos y m\xFAsica sobre cada video entregado (variante with_ai_edit; se cobra al entregar, por video). Nivel: "economy" (Econ\xF3mica, Sonnet 5.5, m\xE1s barata) o "pro" (Opus 5.5, mejor calidad, ~10 min). true equivale a "pro"; false/omitido = sin edici\xF3n.'),
32951
- replicateWinnerWhenFound: z136.boolean().optional().describe("Conserva la preferencia de copiar un ganador si existe. false estudia el mercado sin reemplazar un \xE1ngulo por el anuncio ganador. No a\xF1ade una pregunta obligatoria."),
32952
- brandId: z136.string().uuid().optional().describe("Opcional. ID de una marca del usuario; si se env\xEDa, el sistema usa su voz, colores y persona para enriquecer los outputs generados."),
32953
- actorId: z136.string().trim().min(1).max(120).optional().describe("Opcional. ID de un personaje guardado (usa list_actors para verlos). Si lo env\xEDas, su foto y su voz mandan sobre la descripci\xF3n de texto."),
33207
+ replicateWinnerWhenFound: z137.boolean().optional().describe("Conserva la preferencia de copiar un ganador si existe. false estudia el mercado sin reemplazar un \xE1ngulo por el anuncio ganador. No a\xF1ade una pregunta obligatoria."),
33208
+ brandId: z137.string().uuid().optional().describe("Opcional. ID de una marca del usuario; si se env\xEDa, el sistema usa su voz, colores y persona para enriquecer los outputs generados."),
33209
+ actorId: z137.string().trim().min(1).max(120).optional().describe("Opcional. ID de un personaje guardado (usa list_actors para verlos). Si lo env\xEDas, su foto y su voz mandan sobre la descripci\xF3n de texto."),
32954
33210
  creativeGender: CreativeGenderEnum.optional().describe('Opcional. QUI\xC9N APARECE EN PANTALLA en todos los creativos de la task: el presentador del video, las personas fotografiadas en la landing y la persona del anuncio. NO es la voz. M\xE1ndalo en cuanto el usuario lo diga en cualquier momento de la conversaci\xF3n y con cualquier redacci\xF3n ("quiero una mujer", "que salga un hombre", "esto es para hombres"): si no lo mandas, el g\xE9nero lo inventa la IA y el usuario recibe lo contrario de lo que pidi\xF3 sin ning\xFAn aviso. "mixed" solo cuando pida expl\xEDcitamente ambos. Si adem\xE1s mandas actorId, el personaje guardado manda sobre este campo.'),
32955
- creativeDirection: z136.string().max(500).optional().describe('Opcional. Direcci\xF3n creativa en texto libre (colores, mood, estilo) que aplica a los outputs de IMAGEN y LANDING (no al video \u2014 el estilo/ambiente del video se dirige con scriptNotes). M\xC1XIMA PRIORIDAD: anula marca, colores y estilo por defecto. Ej. "amarillo y azul, minimalista". M\xE1x 500 caracteres.'),
32956
- headline: z136.string().max(120).optional().describe("Opcional. Titular EXACTO a renderizar. Se imprime tal cual: la IA no lo reescribe, no lo acorta y no lo traduce. Se aplica a las IM\xC1GENES y a la portada de la landing de esta task (el video no lo usa; para el video usa scriptNotes). \xDAsalo cuando el copy es de marca y no es negociable. M\xE1x 120 caracteres. Si lo omites, la IA escribe el titular."),
32957
- subheadline: z136.string().max(160).optional().describe("Opcional. Subt\xEDtulo EXACTO a renderizar, misma regla que headline. Independiente: puedes fijar solo el subt\xEDtulo. M\xE1x 160 caracteres."),
33211
+ creativeDirection: z137.string().max(500).optional().describe('Opcional. Direcci\xF3n creativa en texto libre (colores, mood, estilo) que aplica a los outputs de IMAGEN y LANDING (no al video \u2014 el estilo/ambiente del video se dirige con scriptNotes). M\xC1XIMA PRIORIDAD: anula marca, colores y estilo por defecto. Ej. "amarillo y azul, minimalista". M\xE1x 500 caracteres.'),
33212
+ headline: z137.string().max(120).optional().describe("Opcional. Titular EXACTO a renderizar. Se imprime tal cual: la IA no lo reescribe, no lo acorta y no lo traduce. Se aplica a las IM\xC1GENES y a la portada de la landing de esta task (el video no lo usa; para el video usa scriptNotes). \xDAsalo cuando el copy es de marca y no es negociable. M\xE1x 120 caracteres. Si lo omites, la IA escribe el titular."),
33213
+ subheadline: z137.string().max(160).optional().describe("Opcional. Subt\xEDtulo EXACTO a renderizar, misma regla que headline. Independiente: puedes fijar solo el subt\xEDtulo. M\xE1x 160 caracteres."),
32958
33214
  landingStrategy: LandingStrategyEnum.optional().describe('Opcional. Estructura de la landing cl\xE1sica cuando landing est\xE1 entre outputs: "classic" (estructura original probada y recomendada; default del primer rollout), "auto" (la IA decide; experimental), "pain_solution", "social_proof" (Landing Viral: UGC, WhatsApp, Instagram y evidencia social), "benefits_clarity", "offer_value" o "lifestyle_identity". Default "classic".'),
32959
33215
  landingImageFormat: LandingImageFormatEnum.default("9:16").describe('Opcional. Formato/relaci\xF3n de aspecto de las im\xE1genes de la landing cl\xE1sica cuando landing est\xE1 entre outputs: "9:16" (vertical, por defecto), "4:5" (vertical compacto para feed) o "1:1" (cuadrado). Default "9:16".')
32960
33216
  }).strict();
32961
- var CreateServiceVideoInput = z136.object({
32962
- landingUrl: z136.string().url().describe("URL p\xFAblica de la landing del servicio. Es suficiente para generar el video; los medios son opcionales en la web."),
32963
- additionalContext: z136.string().max(3e3).optional().describe('Instrucci\xF3n natural prioritaria. Ej.: "adapta el ritmo de estos competidores", "enfatiza reservas de demos" o restricciones de claims.'),
33217
+ var CreateServiceVideoInput = z137.object({
33218
+ landingUrl: z137.string().url().describe("URL p\xFAblica de la landing del servicio. Es suficiente para generar el video; los medios son opcionales en la web."),
33219
+ additionalContext: z137.string().max(3e3).optional().describe('Instrucci\xF3n natural prioritaria. Ej.: "adapta el ritmo de estos competidores", "enfatiza reservas de demos" o restricciones de claims.'),
32964
33220
  language: Lang.describe("Idioma del video: es, en o pt."),
32965
- voiceId: z136.string().trim().min(1).pipe(VoiceId).optional().describe('Opcional. Voz para toda la narraci\xF3n. Usa "auto" para que la IA la alinee con el presentador. Las voces del banco compartido fueron retiradas.'),
32966
- brandId: z136.string().uuid().optional().describe("Marca guardada del usuario. Aplica voz, mensajes, paleta y persona predeterminada."),
32967
- reviewMode: z136.enum([
33221
+ voiceId: z137.string().trim().min(1).pipe(VoiceId).optional().describe('Opcional. Voz para toda la narraci\xF3n. Usa "auto" para que la IA la alinee con el presentador. Las voces del banco compartido fueron retiradas.'),
33222
+ brandId: z137.string().uuid().optional().describe("Marca guardada del usuario. Aplica voz, mensajes, paleta y persona predeterminada."),
33223
+ reviewMode: z137.enum([
32968
33224
  "automatic",
32969
33225
  "guided"
32970
33226
  ]).default("automatic").describe("automatic = la IA elige concepto y guion sin pausas; guided = el usuario aprueba concepto y storyboard."),
32971
- videoPreset: z136.enum([
33227
+ videoPreset: z137.enum([
32972
33228
  "auto",
32973
33229
  "ugc_dynamic",
32974
33230
  "ugc_how_to",
@@ -32978,128 +33234,128 @@ var CreateServiceVideoInput = z136.object({
32978
33234
  videoQuality: VideoQualityEnum.default("economy").describe('Opcional. Calidad: "economy" = Econ\xF3mico, "medium" = Medio o "pro" = Pro. El proveedor se elige internamente. Default: economy.'),
32979
33235
  videoResolution: VideoResolutionEnum.default("480p").describe('Opcional. Resoluci\xF3n: "480p" o "720p". 720p cuesta m\xE1s por segundo y solo est\xE1 disponible en "economy" y "pro" (Medio = 768P). Default: 480p.')
32980
33236
  }).strict();
32981
- var GetTaskStatusInput = z136.object({
32982
- taskId: z136.string().min(1),
32983
- waitSeconds: z136.number().int().min(0).max(120).optional()
33237
+ var GetTaskStatusInput = z137.object({
33238
+ taskId: z137.string().min(1),
33239
+ waitSeconds: z137.number().int().min(0).max(120).optional()
32984
33240
  });
32985
- var ListTasksInput = z136.object({
32986
- limit: z136.number().int().min(1).max(50).optional(),
32987
- offset: z136.number().int().min(0).optional(),
32988
- search: z136.string().optional()
33241
+ var ListTasksInput = z137.object({
33242
+ limit: z137.number().int().min(1).max(50).optional(),
33243
+ offset: z137.number().int().min(0).optional(),
33244
+ search: z137.string().optional()
32989
33245
  });
32990
33246
  var MCP_REVIEW_LIST_FIELD_MAX = IMAGE_WIZARD_LIST_FIELD_MAX;
32991
33247
  var MCP_REVIEW_FEATURE_ITEM_MAX = IMAGE_FEATURE_ITEM_MAX;
32992
- var EditTaskDraftInput = z136.object({
32993
- taskId: z136.string().min(1),
32994
- angleIndex: z136.number().int().min(0),
32995
- angle: z136.object({
32996
- angle: z136.string().min(1).max(480),
32997
- buyerPersona: z136.string().min(1).max(360),
32998
- actor: z136.string().min(1).max(360).optional(),
32999
- actorId: z136.string().trim().min(1).optional().describe("Opcional. ID de un personaje guardado (usa list_actors para verlos). Si lo env\xEDas, su foto y su voz mandan sobre la descripci\xF3n de texto."),
33000
- gender: z136.enum([
33248
+ var EditTaskDraftInput = z137.object({
33249
+ taskId: z137.string().min(1),
33250
+ angleIndex: z137.number().int().min(0),
33251
+ angle: z137.object({
33252
+ angle: z137.string().min(1).max(480),
33253
+ buyerPersona: z137.string().min(1).max(360),
33254
+ actor: z137.string().min(1).max(360).optional(),
33255
+ actorId: z137.string().trim().min(1).optional().describe("Opcional. ID de un personaje guardado (usa list_actors para verlos). Si lo env\xEDas, su foto y su voz mandan sobre la descripci\xF3n de texto."),
33256
+ gender: z137.enum([
33001
33257
  "female",
33002
33258
  "male"
33003
33259
  ]).optional(),
33004
- scriptNotes: z136.string().max(2e3).optional()
33260
+ scriptNotes: z137.string().max(2e3).optional()
33005
33261
  })
33006
33262
  });
33007
- var PendingActionPayload = z136.discriminatedUnion("kind", [
33008
- z136.object({
33009
- kind: z136.literal("product_reference_set_approval"),
33263
+ var PendingActionPayload = z137.discriminatedUnion("kind", [
33264
+ z137.object({
33265
+ kind: z137.literal("product_reference_set_approval"),
33010
33266
  scope: ProductReferenceScopeEnum.optional(),
33011
- scopeKey: z136.string().min(1).max(120).optional(),
33012
- scopeSnapshotId: z136.string().uuid().optional(),
33013
- expectedScopeHash: z136.string().min(1).max(128).optional(),
33267
+ scopeKey: z137.string().min(1).max(120).optional(),
33268
+ scopeSnapshotId: z137.string().uuid().optional(),
33269
+ expectedScopeHash: z137.string().min(1).max(128).optional(),
33014
33270
  decision: ProductReferenceResolutionDecisionEnum,
33015
33271
  reason: ProductReferenceRejectionReasonEnum.optional(),
33016
- affectedRefKeys: z136.array(z136.string().min(1).max(200)).min(1).optional(),
33017
- affectedPhotoNumbers: z136.array(z136.number().int().positive()).min(1).optional(),
33018
- recoveryAction: z136.enum([
33272
+ affectedRefKeys: z137.array(z137.string().min(1).max(200)).min(1).optional(),
33273
+ affectedPhotoNumbers: z137.array(z137.number().int().positive()).min(1).optional(),
33274
+ recoveryAction: z137.enum([
33019
33275
  "remove",
33020
33276
  "replace",
33021
33277
  "upload",
33022
33278
  "return_to_selection"
33023
33279
  ]).optional()
33024
33280
  }),
33025
- z136.object({
33026
- kind: z136.literal("product_reference_confirmation"),
33027
- selectedPhotoNumbers: z136.array(z136.number().int().positive().max(5)).min(1).max(5).optional().describe("Opcional. N\xFAmeros visibles de las fotos que el usuario quiere conservar; las dem\xE1s se deseleccionan. Debe quedar al menos una."),
33281
+ z137.object({
33282
+ kind: z137.literal("product_reference_confirmation"),
33283
+ selectedPhotoNumbers: z137.array(z137.number().int().positive().max(5)).min(1).max(5).optional().describe("Opcional. N\xFAmeros visibles de las fotos que el usuario quiere conservar; las dem\xE1s se deseleccionan. Debe quedar al menos una."),
33028
33284
  userChoice: ProductReferenceUserChoiceEnum.optional(),
33029
- confirmedProductNames: z136.array(z136.string().min(1).max(120)).min(2).max(8).optional(),
33285
+ confirmedProductNames: z137.array(z137.string().min(1).max(120)).min(2).max(8).optional(),
33030
33286
  videoStructure: VideoStructureEnum.optional().describe("Opcional. Formato para reanudar el video: single_take (Un solo clip, generado completo; puede incluir cambios de plano) o multi_scene (Multiescena). El backend vuelve a validar que sea compatible con el agrupamiento confirmado."),
33031
33287
  videoQuality: VideoQualityEnum.optional().describe("Opcional. Calidad para reanudar el video: economy (Econ\xF3mico), medium (Medio) o pro. El proveedor se elige internamente y el backend vuelve a cotizar la ruta."),
33032
- useMultiSceneShowcase: z136.boolean().optional().describe("Compatibilidad con confirmaciones antiguas: si es true junto con userChoice=variants, conserva el showcase multiescena.")
33288
+ useMultiSceneShowcase: z137.boolean().optional().describe("Compatibilidad con confirmaciones antiguas: si es true junto con userChoice=variants, conserva el showcase multiescena.")
33033
33289
  }),
33034
- z136.object({
33035
- kind: z136.literal("angle_review_approval"),
33290
+ z137.object({
33291
+ kind: z137.literal("angle_review_approval"),
33036
33292
  // All edits are optional — empty action body just approves the
33037
33293
  // backend-generated draft as-is. Field shapes mirror the angle-review
33038
33294
  // request contracts in @packages/types/wizard.ts; the handler routes to
33039
33295
  // the dedicated video/image/autopilot endpoint based on StatusResponse.kind.
33040
- customAngle: z136.string().min(1).max(480).optional(),
33296
+ customAngle: z137.string().min(1).max(480).optional(),
33041
33297
  // Keep the shared MCP action within the strictest downstream contract:
33042
33298
  // autopilot/image resume endpoints cap these fields at 1500 characters.
33043
- painPoints: z136.string().max(MCP_REVIEW_LIST_FIELD_MAX).optional(),
33044
- benefits: z136.string().max(MCP_REVIEW_LIST_FIELD_MAX).optional(),
33299
+ painPoints: z137.string().max(MCP_REVIEW_LIST_FIELD_MAX).optional(),
33300
+ benefits: z137.string().max(MCP_REVIEW_LIST_FIELD_MAX).optional(),
33045
33301
  // Same policy as the two fields above: the strictest downstream contract
33046
33302
  // wins. The image endpoint caps each feature at 240 while landing/video
33047
33303
  // accept 300, and this single action routes to whichever the task is — so
33048
33304
  // accepting 300 here turns a 250-character feature on an image task into
33049
33305
  // an opaque HTTP 400 after the model already believed it had been saved.
33050
- features: z136.array(z136.string().min(1).max(MCP_REVIEW_FEATURE_ITEM_MAX)).max(10).optional(),
33051
- buyerPersona: z136.string().min(1).max(360).optional(),
33306
+ features: z137.array(z137.string().min(1).max(MCP_REVIEW_FEATURE_ITEM_MAX)).max(10).optional(),
33307
+ buyerPersona: z137.string().min(1).max(360).optional(),
33052
33308
  narrativeStyle: PublicNarrativeStyleEnum.optional().describe('Narrativa del video. "problem_product_cta" (Problema y Soluci\xF3n) funciona en ambos formatos \u2014 en Un solo clip abre sin producto y el producto entra por referencia en su beat. La familia storyteller ("animated_storyteller", "animated_inner_journey", "animated_object_soul", "animated_timeline") no est\xE1 disponible por ahora.'),
33053
- actor: z136.string().min(1).max(360).optional().describe("Nueva descripci\xF3n del presentador. Al enviarla sin actorId, se elimina autom\xE1ticamente cualquier actor guardado previo de esos \xE1ngulos."),
33054
- actorId: z136.string().max(120).nullable().optional(),
33055
- creativeDirection: z136.string().max(2e3).optional(),
33056
- selectedAngleNumbers: z136.array(z136.number().int().positive()).min(1).max(3).optional().describe("N\xFAmeros visibles (1-based) de los \xE1ngulos que el usuario quiere renderizar. Deben ser \xFAnicos, existir en la review y no superar el videoAngleCount autorizado."),
33057
- angleEdits: z136.array(z136.object({
33058
- angleNumber: z136.number().int().positive(),
33059
- narrativeSelection: z136.enum([
33309
+ actor: z137.string().min(1).max(360).optional().describe("Nueva descripci\xF3n del presentador. Al enviarla sin actorId, se elimina autom\xE1ticamente cualquier actor guardado previo de esos \xE1ngulos."),
33310
+ actorId: z137.string().max(120).nullable().optional(),
33311
+ creativeDirection: z137.string().max(2e3).optional(),
33312
+ selectedAngleNumbers: z137.array(z137.number().int().positive()).min(1).max(3).optional().describe("N\xFAmeros visibles (1-based) de los \xE1ngulos que el usuario quiere renderizar. Deben ser \xFAnicos, existir en la review y no superar el videoAngleCount autorizado."),
33313
+ angleEdits: z137.array(z137.object({
33314
+ angleNumber: z137.number().int().positive(),
33315
+ narrativeSelection: z137.enum([
33060
33316
  "market",
33061
33317
  "guided"
33062
33318
  ]).optional().describe("Elecci\xF3n expl\xEDcita: market usa el estudio disponible; guided usa la narrativa elegida para este \xE1ngulo."),
33063
- angle: z136.string().min(1).max(480).optional(),
33064
- buyerPersona: z136.string().min(1).max(360).optional(),
33319
+ angle: z137.string().min(1).max(480).optional(),
33320
+ buyerPersona: z137.string().min(1).max(360).optional(),
33065
33321
  narrativeStyle: PublicNarrativeStyleEnum.optional().describe('Narrativa del video. "problem_product_cta" (Problema y Soluci\xF3n) funciona en ambos formatos \u2014 en Un solo clip abre sin producto y el producto entra por referencia en su beat. La familia storyteller ("animated_storyteller", "animated_inner_journey", "animated_object_soul", "animated_timeline") no est\xE1 disponible por ahora.'),
33066
- actor: z136.string().min(1).max(360).optional(),
33067
- actorId: z136.string().max(120).nullable().optional(),
33068
- creativeDirection: z136.string().max(2e3).optional()
33322
+ actor: z137.string().min(1).max(360).optional(),
33323
+ actorId: z137.string().max(120).nullable().optional(),
33324
+ creativeDirection: z137.string().max(2e3).optional()
33069
33325
  }).strict()).max(3).optional().describe("Ediciones independientes por n\xFAmero visible de \xE1ngulo. S\xF3lo se aceptan para \xE1ngulos incluidos en selectedAngleNumbers (o en la selecci\xF3n por defecto)."),
33070
- gapDurationSeconds: z136.union([
33071
- z136.literal(15),
33072
- z136.literal(30)
33326
+ gapDurationSeconds: z137.union([
33327
+ z137.literal(15),
33328
+ z137.literal(30)
33073
33329
  ]).optional().describe("Opcional. Solo para aceptar expl\xEDcitamente la recomendaci\xF3n gapDurationChoice del status. 30 env\xEDa el override double_30s para el \xE1ngulo recomendado; 15 u omitido conserva la duraci\xF3n cotizada.")
33074
33330
  }),
33075
- z136.object({
33076
- kind: z136.literal("service_angle_approval"),
33077
- angleId: z136.string().uuid().optional(),
33078
- angleNumber: z136.number().int().positive().optional(),
33079
- content: z136.string().min(1).max(480).optional(),
33080
- creativeDirection: z136.string().min(1).max(1500).optional(),
33081
- actor: z136.string().min(1).max(360).optional()
33331
+ z137.object({
33332
+ kind: z137.literal("service_angle_approval"),
33333
+ angleId: z137.string().uuid().optional(),
33334
+ angleNumber: z137.number().int().positive().optional(),
33335
+ content: z137.string().min(1).max(480).optional(),
33336
+ creativeDirection: z137.string().min(1).max(1500).optional(),
33337
+ actor: z137.string().min(1).max(360).optional()
33082
33338
  }),
33083
- z136.object({
33084
- kind: z136.literal("service_script_approval"),
33085
- voiceovers: z136.record(z136.string(), z136.string().min(1).max(600)).optional(),
33086
- visualDirections: z136.record(z136.string(), z136.string().min(1).max(1200)).optional(),
33087
- voiceoversByScene: z136.record(z136.string(), z136.string().min(1).max(600)).optional(),
33088
- visualDirectionsByScene: z136.record(z136.string(), z136.string().min(1).max(1200)).optional()
33339
+ z137.object({
33340
+ kind: z137.literal("service_script_approval"),
33341
+ voiceovers: z137.record(z137.string(), z137.string().min(1).max(600)).optional(),
33342
+ visualDirections: z137.record(z137.string(), z137.string().min(1).max(1200)).optional(),
33343
+ voiceoversByScene: z137.record(z137.string(), z137.string().min(1).max(600)).optional(),
33344
+ visualDirectionsByScene: z137.record(z137.string(), z137.string().min(1).max(1200)).optional()
33089
33345
  })
33090
33346
  ]);
33091
- var ResolvePendingActionInput = z136.object({
33092
- taskId: z136.string().min(1),
33347
+ var ResolvePendingActionInput = z137.object({
33348
+ taskId: z137.string().min(1),
33093
33349
  action: PendingActionPayload
33094
33350
  }).strict();
33095
33351
  function resolveVariantConfirmVideoTier(action) {
33096
33352
  return action.useMultiSceneShowcase && action.userChoice === "variants" ? "standard" : void 0;
33097
33353
  }
33098
33354
  __name(resolveVariantConfirmVideoTier, "resolveVariantConfirmVideoTier");
33099
- var StatusForResolveSchema = z136.object({
33355
+ var StatusForResolveSchema = z137.object({
33100
33356
  // Metadata used to select the backend resume contract. Older API responses
33101
33357
  // may omit these fields, so the resolver keeps the generic fallback.
33102
- kind: z136.enum([
33358
+ kind: z137.enum([
33103
33359
  "autopilot",
33104
33360
  "video",
33105
33361
  "image",
@@ -33107,18 +33363,18 @@ var StatusForResolveSchema = z136.object({
33107
33363
  "mix",
33108
33364
  "service"
33109
33365
  ]).optional(),
33110
- selectedOutputs: z136.array(z136.enum([
33366
+ selectedOutputs: z137.array(z137.enum([
33111
33367
  "landing",
33112
33368
  "video",
33113
33369
  "image"
33114
33370
  ])).optional(),
33115
- videoAngleCount: z136.number().int().min(1).max(3).optional(),
33116
- ugcFormat: z136.enum([
33371
+ videoAngleCount: z137.number().int().min(1).max(3).optional(),
33372
+ ugcFormat: z137.enum([
33117
33373
  "single_8s",
33118
33374
  "single_15s",
33119
33375
  "double_30s"
33120
33376
  ]).optional(),
33121
- videoStyle: z136.enum([
33377
+ videoStyle: z137.enum([
33122
33378
  "ugc",
33123
33379
  "cartoon_3d",
33124
33380
  "product_only",
@@ -33128,13 +33384,13 @@ var StatusForResolveSchema = z136.object({
33128
33384
  videoQuality: VideoQualityEnum.optional(),
33129
33385
  videoRouteId: VideoRouteIdEnum.optional(),
33130
33386
  videoModel: TaskVideoModelEnum.optional(),
33131
- videoTier: z136.enum([
33387
+ videoTier: z137.enum([
33132
33388
  "legacy",
33133
33389
  "standard",
33134
33390
  "pro",
33135
33391
  "single_call"
33136
33392
  ]).optional(),
33137
- videoResolution: z136.enum([
33393
+ videoResolution: z137.enum([
33138
33394
  "480p",
33139
33395
  "720p"
33140
33396
  ]).optional(),
@@ -33142,60 +33398,60 @@ var StatusForResolveSchema = z136.object({
33142
33398
  // A task created before a narrative was blocked/hidden must still parse,
33143
33399
  // or resolve_pending_action could never approve it.
33144
33400
  narrativeStyle: NarrativeStyle.optional(),
33145
- pauseForVideoScriptReview: z136.boolean().optional(),
33146
- pendingUserAction: z136.object({
33147
- kind: z136.string(),
33148
- candidateGrouping: z136.string().optional(),
33149
- selectedImageUrls: z136.array(z136.string()).optional(),
33150
- proposedProductNames: z136.array(z136.string()).optional(),
33401
+ pauseForVideoScriptReview: z137.boolean().optional(),
33402
+ pendingUserAction: z137.object({
33403
+ kind: z137.string(),
33404
+ candidateGrouping: z137.string().optional(),
33405
+ selectedImageUrls: z137.array(z137.string()).optional(),
33406
+ proposedProductNames: z137.array(z137.string()).optional(),
33151
33407
  // Adaptive reference questions are server-owned. Echo only the evidence
33152
33408
  // identity and the choices shown by the current snapshot; never accept a
33153
33409
  // client-supplied reference set as authorization.
33154
- options: z136.array(ProductReferenceUserChoiceEnum).max(4).optional(),
33155
- mode: z136.enum([
33410
+ options: z137.array(ProductReferenceUserChoiceEnum).max(4).optional(),
33411
+ mode: z137.enum([
33156
33412
  "question",
33157
33413
  "visual_recovery"
33158
33414
  ]).optional(),
33159
33415
  /** Hand-mirrored from packages/types result.ts — see CLAUDE.md § MCP Server Sync. */
33160
- canKeepSelection: z136.boolean().optional(),
33161
- decisionKey: z136.string().optional(),
33162
- questionOrdinal: z136.number().int().min(1).max(5).optional(),
33163
- questionCeiling: z136.union([
33164
- z136.literal(3),
33165
- z136.literal(5)
33416
+ canKeepSelection: z137.boolean().optional(),
33417
+ decisionKey: z137.string().optional(),
33418
+ questionOrdinal: z137.number().int().min(1).max(5).optional(),
33419
+ questionCeiling: z137.union([
33420
+ z137.literal(3),
33421
+ z137.literal(5)
33166
33422
  ]).optional(),
33167
- referenceEvidenceVersion: z136.number().int().positive().optional(),
33168
- expectedEvidenceHash: z136.string().optional(),
33169
- question: z136.object({
33170
- prompt: z136.string().min(1).max(240),
33171
- helpText: z136.string().min(1).max(300).optional(),
33172
- options: z136.array(z136.object({
33423
+ referenceEvidenceVersion: z137.number().int().positive().optional(),
33424
+ expectedEvidenceHash: z137.string().optional(),
33425
+ question: z137.object({
33426
+ prompt: z137.string().min(1).max(240),
33427
+ helpText: z137.string().min(1).max(300).optional(),
33428
+ options: z137.array(z137.object({
33173
33429
  value: ProductReferenceUserChoiceEnum,
33174
- label: z136.string().min(1).max(120),
33175
- description: z136.string().min(1).max(240).optional()
33430
+ label: z137.string().min(1).max(120),
33431
+ description: z137.string().min(1).max(240).optional()
33176
33432
  })).min(2).max(4)
33177
33433
  }).optional(),
33178
33434
  scope: ProductReferenceScopeEnum.optional(),
33179
- scopeKey: z136.string().optional(),
33180
- scopeSnapshotId: z136.string().uuid().optional(),
33181
- scopeHash: z136.string().optional(),
33182
- baseReferenceSetHash: z136.string().optional(),
33183
- phase: z136.enum([
33435
+ scopeKey: z137.string().optional(),
33436
+ scopeSnapshotId: z137.string().uuid().optional(),
33437
+ scopeHash: z137.string().optional(),
33438
+ baseReferenceSetHash: z137.string().optional(),
33439
+ phase: z137.enum([
33184
33440
  "preparation",
33185
33441
  "final"
33186
33442
  ]).optional(),
33187
- selectedRefKeys: z136.array(z136.string().min(1).max(200)).optional(),
33188
- allowedDecisions: z136.array(ProductReferenceResolutionDecisionEnum).optional(),
33189
- previews: z136.array(z136.object({
33190
- refKey: z136.string().min(1).max(200)
33443
+ selectedRefKeys: z137.array(z137.string().min(1).max(200)).optional(),
33444
+ allowedDecisions: z137.array(ProductReferenceResolutionDecisionEnum).optional(),
33445
+ previews: z137.array(z137.object({
33446
+ refKey: z137.string().min(1).max(200)
33191
33447
  }).passthrough()).optional(),
33192
- customAngleDraft: z136.string().optional(),
33193
- painPointsDraft: z136.string().optional(),
33194
- benefitsDraft: z136.string().optional(),
33195
- featuresDraft: z136.array(z136.string()).optional(),
33196
- angleOptions: z136.array(z136.object({
33197
- index: z136.number().int().nonnegative(),
33198
- angle: z136.string().min(1)
33448
+ customAngleDraft: z137.string().optional(),
33449
+ painPointsDraft: z137.string().optional(),
33450
+ benefitsDraft: z137.string().optional(),
33451
+ featuresDraft: z137.array(z137.string()).optional(),
33452
+ angleOptions: z137.array(z137.object({
33453
+ index: z137.number().int().nonnegative(),
33454
+ angle: z137.string().min(1)
33199
33455
  }).passthrough()).min(1).max(5).optional(),
33200
33456
  // Recommendation consumed only when the caller explicitly sends
33201
33457
  // gapDurationSeconds: 30 in the angle approval action.
@@ -33205,17 +33461,17 @@ var StatusForResolveSchema = z136.object({
33205
33461
  // keys. `actorId` (the saved custom actor the picker stored) was silently
33206
33462
  // dropped here, so the approval handler below could not echo it back on the
33207
33463
  // PATCH — the selection looked accepted and reached no render.
33208
- angles: z136.array(z136.object({
33209
- id: z136.string().uuid(),
33210
- content: z136.string(),
33211
- creativeDirection: z136.string(),
33212
- actor: z136.string(),
33213
- actorId: z136.string().nullable().optional()
33464
+ angles: z137.array(z137.object({
33465
+ id: z137.string().uuid(),
33466
+ content: z137.string(),
33467
+ creativeDirection: z137.string(),
33468
+ actor: z137.string(),
33469
+ actorId: z137.string().nullable().optional()
33214
33470
  }).passthrough()).optional(),
33215
- panels: z136.array(z136.object({
33216
- beatId: z136.string(),
33217
- voiceover: z136.string(),
33218
- visualDirection: z136.string()
33471
+ panels: z137.array(z137.object({
33472
+ beatId: z137.string(),
33473
+ voiceover: z137.string(),
33474
+ visualDirection: z137.string()
33219
33475
  }).passthrough()).optional()
33220
33476
  }).optional()
33221
33477
  }).passthrough();
@@ -33568,8 +33824,8 @@ ANTES de invocar, resume outputs, cantidad de videos y datos comerciales, explic
33568
33824
  server.registerTool("verify_landing_commerce", {
33569
33825
  description: getProductMakerCapabilityModelGuidance("verify_landing_commerce").summary + " Verifica una correcci\xF3n manual de precios, tachados o packs de una landing editable existente. Solo relee Shopify y los recibos externos; no cambia precios, inventario, SKU ni compra una edici\xF3n. Pasa taskId y editId de la edici\xF3n ya conocida; nunca inventes IDs ni pidas repetir el cambio. Devuelve el estado can\xF3nico por proveedor: conserva el aviso pendiente hasta confirmar los valores reales. Solo despu\xE9s de confirmar todos los valores, el servidor puede volver a habilitar la compra. Web guardada no demuestra publicaci\xF3n p\xFAblica ni checkout; muestra las correcciones y enlaces pendientes sin c\xF3digos t\xE9cnicos. La disponibilidad del editor conserva sus permisos actuales.",
33570
33826
  inputSchema: {
33571
- taskId: z136.string().uuid(),
33572
- editId: z136.string().uuid()
33827
+ taskId: z137.string().uuid(),
33828
+ editId: z137.string().uuid()
33573
33829
  },
33574
33830
  annotations: OVERWRITES_EXTERNAL
33575
33831
  }, async (input) => {
@@ -33840,16 +34096,16 @@ ANTES de invocar, resume outputs, cantidad de videos y datos comerciales, explic
33840
34096
  if (input.search) qs.set("search", input.search);
33841
34097
  const path3 = `/v1/tasks${qs.toString() ? "?" + qs.toString() : ""}`;
33842
34098
  const r = await api.getJson(path3);
33843
- const page = z136.object({
33844
- items: z136.array(z136.unknown())
34099
+ const page = z137.object({
34100
+ items: z137.array(z137.unknown())
33845
34101
  }).passthrough().safeParse(r);
33846
34102
  if (!page.success) return asText(r);
33847
34103
  return asText({
33848
34104
  ...page.data,
33849
34105
  items: page.data.items.map((item) => {
33850
- const servicePage = z136.object({
33851
- taskId: z136.string().uuid(),
33852
- resourceType: z136.literal("service_page")
34106
+ const servicePage = z137.object({
34107
+ taskId: z137.string().uuid(),
34108
+ resourceType: z137.literal("service_page")
33853
34109
  }).passthrough().safeParse(item);
33854
34110
  if (!servicePage.success) return item;
33855
34111
  return {
@@ -34339,28 +34595,28 @@ ANTES de invocar, resume outputs, cantidad de videos y datos comerciales, explic
34339
34595
  __name(registerTaskTools, "registerTaskTools");
34340
34596
 
34341
34597
  // src/tools/brands.ts
34342
- import { z as z137 } from "zod";
34598
+ import { z as z138 } from "zod";
34343
34599
  var guidance2 = getProductMakerCapabilityModelGuidance("create_brand");
34344
34600
  var BRAND_START_REPLAY_WINDOW_MS = 15 * 60 * 1e3;
34345
34601
  var BRAND_NOTES_MAX_CHARS = 2e4;
34346
34602
  var BRAND_AMBIGUOUS_REQUEST_RETENTION_MS = 24 * 60 * 60 * 1e3;
34347
34603
  var BRAND_CREATION_REQUEST_LIMIT = 500;
34348
34604
  var brandCreationRequests = /* @__PURE__ */ new Map();
34349
- var CreateBrandInput = z137.object({
34350
- action: z137.enum([
34605
+ var CreateBrandInput = z138.object({
34606
+ action: z138.enum([
34351
34607
  "start",
34352
34608
  "status",
34353
34609
  "list"
34354
34610
  ]).default("start").describe("start cotiza (sin confirmed) o inicia (confirmed=true) el an\xE1lisis; status consulta brandId hasta ready o failed; list muestra las marcas que el usuario ya tiene."),
34355
- sourceUrl: z137.string().trim().min(1).max(BRAND_SOURCE_URL_MAX_LENGTH).optional().describe("Instagram, Facebook, TikTok o web p\xFAblica del negocio, como la dio el usuario (\xAB@sanlucasbarberia\xBB, \xABinstagram.com/sanlucasbarberia\xBB, \xABmibarberia.com\xBB). Obligatorio para start."),
34356
- businessNotes: z137.string().trim().max(BRAND_NOTES_MAX_CHARS).optional().describe("Opcional. Lo que el usuario cuente de su negocio en el chat (servicios y precios, WhatsApp, ciudad o zona, horarios). Se guarda como documento del negocio y llega tal cual a la marca y a su landing. Copia sus palabras; no inventes datos."),
34357
- language: z137.enum([
34611
+ sourceUrl: z138.string().trim().min(1).max(BRAND_SOURCE_URL_MAX_LENGTH).optional().describe("Instagram, Facebook, TikTok o web p\xFAblica del negocio, como la dio el usuario (\xAB@sanlucasbarberia\xBB, \xABinstagram.com/sanlucasbarberia\xBB, \xABmibarberia.com\xBB). Obligatorio para start."),
34612
+ businessNotes: z138.string().trim().max(BRAND_NOTES_MAX_CHARS).optional().describe("Opcional. Lo que el usuario cuente de su negocio en el chat (servicios y precios, WhatsApp, ciudad o zona, horarios). Se guarda como documento del negocio y llega tal cual a la marca y a su landing. Copia sus palabras; no inventes datos."),
34613
+ language: z138.enum([
34358
34614
  "es",
34359
34615
  "en",
34360
34616
  "pt"
34361
34617
  ]).optional().describe("Idioma de la conversaci\xF3n. Solo se usa si la p\xE1gina del negocio no deja claro su idioma."),
34362
- confirmed: z137.boolean().optional().describe("Solo true despu\xE9s de mostrar el precio y que el usuario acepte. Sin confirmed, start solo cotiza y no cobra."),
34363
- brandId: z137.string().uuid().optional().describe("ID de la marca. Obligatorio para status; no lo inventes.")
34618
+ confirmed: z138.boolean().optional().describe("Solo true despu\xE9s de mostrar el precio y que el usuario acepte. Sin confirmed, start solo cotiza y no cobra."),
34619
+ brandId: z138.string().uuid().optional().describe("ID de la marca. Obligatorio para status; no lo inventes.")
34364
34620
  }).strict();
34365
34621
  function normalizeBrandSourceUrl(raw) {
34366
34622
  const value = raw.trim();
@@ -34644,24 +34900,24 @@ __name(registerBrandTools, "registerBrandTools");
34644
34900
 
34645
34901
  // src/tools/service-landing.ts
34646
34902
  import { createHash as createHash3, randomUUID } from "crypto";
34647
- import { z as z138 } from "zod";
34903
+ import { z as z139 } from "zod";
34648
34904
  var guidance3 = getProductMakerCapabilityModelGuidance("create_service_landing");
34649
34905
  var SERVICE_LANDING_KEY_NAMESPACE = "6f1d8c2a-3b4e-5f60-8a7b-9c0d1e2f3a4b";
34650
34906
  var SERVICE_LANDING_INFO_MAX_CHARS = 2e4;
34651
- var CreateServiceLandingInput = z138.object({
34652
- action: z138.enum([
34907
+ var CreateServiceLandingInput = z139.object({
34908
+ action: z139.enum([
34653
34909
  "prepare",
34654
34910
  "build",
34655
34911
  "status",
34656
34912
  "publish"
34657
34913
  ]).default("prepare").describe("prepare (gratis) crea o retoma la landing de la marca y da el precio; build (pagado, confirmed=true) genera la p\xE1gina directamente con la marca y los datos guardados; status sigue el avance; publish la publica y devuelve su enlace."),
34658
- brandId: z138.string().uuid().optional().describe("ID de la marca (de create_brand). Obligatorio para prepare. Nunca un taskId de producto."),
34659
- servicePageId: z138.string().uuid().optional().describe("ID de la landing que devolvi\xF3 prepare. Obligatorio para build, status y publish."),
34660
- businessInfo: z138.string().trim().max(SERVICE_LANDING_INFO_MAX_CHARS).optional().describe("Opcional. Lo que el usuario cont\xF3 del negocio y no estaba en su marca: servicios y precios, WhatsApp o tel\xE9fono, ciudad o zona, horarios. Copia sus palabras; no inventes datos."),
34661
- confirmed: z138.boolean().optional().describe("Solo true despu\xE9s de mostrar el precio total y que el usuario acepte. Obligatorio para build."),
34662
- maxCredits: z138.number().int().positive().optional().describe("El total en cr\xE9ditos que el usuario acept\xF3 (el que devolvi\xF3 prepare). Obligatorio para build: ninguna etapa se inicia si su precio supera este total."),
34663
- operationId: z138.string().uuid().optional().describe("Opcional en status: la operaci\xF3n que devolvi\xF3 build."),
34664
- slug: z138.string().trim().toLowerCase().regex(/^[a-z0-9]+(?:-[a-z0-9]+)*$/).max(64).optional().describe("Opcional en publish: la direcci\xF3n corta de la p\xE1gina (ej. sanlucas-barberia). Si falta se usa la sugerida desde el nombre del negocio.")
34914
+ brandId: z139.string().uuid().optional().describe("ID de la marca (de create_brand). Obligatorio para prepare. Nunca un taskId de producto."),
34915
+ servicePageId: z139.string().uuid().optional().describe("ID de la landing que devolvi\xF3 prepare. Obligatorio para build, status y publish."),
34916
+ businessInfo: z139.string().trim().max(SERVICE_LANDING_INFO_MAX_CHARS).optional().describe("Opcional. Lo que el usuario cont\xF3 del negocio y no estaba en su marca: servicios y precios, WhatsApp o tel\xE9fono, ciudad o zona, horarios. Copia sus palabras; no inventes datos."),
34917
+ confirmed: z139.boolean().optional().describe("Solo true despu\xE9s de mostrar el precio total y que el usuario acepte. Obligatorio para build."),
34918
+ maxCredits: z139.number().int().positive().optional().describe("El total en cr\xE9ditos que el usuario acept\xF3 (el que devolvi\xF3 prepare). Obligatorio para build: ninguna etapa se inicia si su precio supera este total."),
34919
+ operationId: z139.string().uuid().optional().describe("Opcional en status: la operaci\xF3n que devolvi\xF3 build."),
34920
+ slug: z139.string().trim().toLowerCase().regex(/^[a-z0-9]+(?:-[a-z0-9]+)*$/).max(64).optional().describe("Opcional en publish: la direcci\xF3n corta de la p\xE1gina (ej. sanlucas-barberia). Si falta se usa la sugerida desde el nombre del negocio.")
34665
34921
  }).strict();
34666
34922
  function serviceKey(...parts) {
34667
34923
  return deriveUuidV5(parts.join(":"), SERVICE_LANDING_KEY_NAMESPACE);
@@ -34858,7 +35114,11 @@ function registerServiceLandingTools(server, api) {
34858
35114
  server.registerTool("create_service_landing", {
34859
35115
  description: guidance3.summary + " Parte del brandId de create_brand y termina con el enlace p\xFAblico de la p\xE1gina. NO es para productos de dropshipping (para eso generate_landing / create_product_task). Flujo: 1) action=prepare con brandId (gratis): prepara la landing y devuelve el precio estimado. 2) Pregunta una sola vez los datos que falten; puede omitirlos. 3) Cuando acepte el precio, action=build con servicePageId, confirmed=true, maxCredits y businessInfo: inicia una sola generaci\xF3n con la marca y las fuentes guardadas, sin una lectura pagada previa. 4) action=status hasta generated o error; generando se sigue en Tareas y contin\xFAa aunque cierre el chat. No llames build para continuar una etapa. 5) Muestra el enlace para verla y, si acepta, action=publish. Repetir una llamada no cobra dos veces. Con el enlace publicado sigue create_animated_video si quiere un video y create_service_images con el mismo brandId si quiere im\xE1genes.",
34860
35116
  inputSchema: CreateServiceLandingInput.shape,
34861
- annotations: CREATES_PAID
35117
+ // action=publish exposes the page to the public internet.
35118
+ annotations: {
35119
+ ...CREATES_PAID,
35120
+ openWorldHint: true
35121
+ }
34862
35122
  }, async (rawInput) => {
34863
35123
  try {
34864
35124
  const input = CreateServiceLandingInput.parse(rawInput);
@@ -35036,7 +35296,7 @@ function registerServiceLandingTools(server, api) {
35036
35296
  __name(registerServiceLandingTools, "registerServiceLandingTools");
35037
35297
 
35038
35298
  // src/tools/service-images.ts
35039
- import { z as z139 } from "zod";
35299
+ import { z as z140 } from "zod";
35040
35300
  var guidance4 = getProductMakerCapabilityModelGuidance("create_service_images");
35041
35301
  var SERVICE_IMAGE_FORMATS = [
35042
35302
  "4:5",
@@ -35049,23 +35309,23 @@ var LANGUAGE_TAGS = {
35049
35309
  en: "en-US",
35050
35310
  pt: "pt-BR"
35051
35311
  };
35052
- var CreateServiceImagesInput = z139.object({
35053
- brandId: z139.string().uuid().optional().describe("ID de un servicio propio y listo (de create_brand), obligatorio para cotizar y crear im\xE1genes nuevas. En un retry hist\xF3rico ya confirmado que originalmente no ten\xEDa este campo, conserva la petici\xF3n original. Nunca un taskId de producto."),
35054
- instruction: z139.string().trim().max(4e3).optional().describe("Opcional. Qu\xE9 servicio o promoci\xF3n mostrar, con las palabras del usuario (ej. \xABcorte + barba a $35.000 los martes\xBB). Si se omite, la IA propone la imagen usando la informaci\xF3n del servicio."),
35055
- delegateToAi: z139.boolean().optional().describe("true si el usuario prefiere que la IA elija qu\xE9 mostrar a partir de su servicio. Tambi\xE9n se delega cuando instruction est\xE1 vac\xEDo."),
35056
- format: z139.enum(SERVICE_IMAGE_FORMATS).optional().describe('Formato: "4:5" (feed, por defecto), "9:16" (historias/reels) o "1:1" (cuadrado).'),
35057
- count: z139.number().int().min(1).max(SERVICE_IMAGE_MAX_COUNT).optional().describe("Cu\xE1ntas im\xE1genes, de 1 a 3 (por defecto 1)."),
35058
- quality: z139.enum([
35312
+ var CreateServiceImagesInput = z140.object({
35313
+ brandId: z140.string().uuid().optional().describe("ID de un servicio propio y listo (de create_brand), obligatorio para cotizar y crear im\xE1genes nuevas. En un retry hist\xF3rico ya confirmado que originalmente no ten\xEDa este campo, conserva la petici\xF3n original. Nunca un taskId de producto."),
35314
+ instruction: z140.string().trim().max(4e3).optional().describe("Opcional. Qu\xE9 servicio o promoci\xF3n mostrar, con las palabras del usuario (ej. \xABcorte + barba a $35.000 los martes\xBB). Si se omite, la IA propone la imagen usando la informaci\xF3n del servicio."),
35315
+ delegateToAi: z140.boolean().optional().describe("true si el usuario prefiere que la IA elija qu\xE9 mostrar a partir de su servicio. Tambi\xE9n se delega cuando instruction est\xE1 vac\xEDo."),
35316
+ format: z140.enum(SERVICE_IMAGE_FORMATS).optional().describe('Formato: "4:5" (feed, por defecto), "9:16" (historias/reels) o "1:1" (cuadrado).'),
35317
+ count: z140.number().int().min(1).max(SERVICE_IMAGE_MAX_COUNT).optional().describe("Cu\xE1ntas im\xE1genes, de 1 a 3 (por defecto 1)."),
35318
+ quality: z140.enum([
35059
35319
  "standard",
35060
35320
  "high"
35061
35321
  ]).optional().describe('Calidad: "standard" (por defecto) o "high" (m\xE1s cara; no disponible en 4:5).'),
35062
- language: z139.enum([
35322
+ language: z140.enum([
35063
35323
  "es",
35064
35324
  "en",
35065
35325
  "pt"
35066
35326
  ]).optional().describe("Idioma de los textos de la imagen (por defecto es)."),
35067
- adCopy: z139.boolean().optional().describe("Compatibilidad con clientes anteriores: el copy para publicar siempre est\xE1 incluido en cada imagen de servicios."),
35068
- confirmed: z139.boolean().optional().describe("Solo true despu\xE9s de mostrar el precio y que el usuario acepte. Sin confirmed, solo cotiza y no cobra.")
35327
+ adCopy: z140.boolean().optional().describe("Compatibilidad con clientes anteriores: el copy para publicar siempre est\xE1 incluido en cada imagen de servicios."),
35328
+ confirmed: z140.boolean().optional().describe("Solo true despu\xE9s de mostrar el precio y que el usuario acepte. Sin confirmed, solo cotiza y no cobra.")
35069
35329
  }).strict();
35070
35330
  function serviceImageModel(quality) {
35071
35331
  return quality === "high" ? AD_CREATIVE_MODELS.PREMIUM : AD_CREATIVE_MODELS.STANDARD;
@@ -35182,7 +35442,7 @@ function registerServiceImageTools(server, api, options = {}) {
35182
35442
  nextAction: "Dile al usuario que est\xE1s creando sus im\xE1genes. Llama get_task_status con este taskId (waitSeconds=45) hasta done (trae las im\xE1genes reales de ProductMaker) o error. Muestra los recursos de imagen devueltos directamente en el chat. Nunca generes ni recrees una imagen sustituta con otra herramienta. Si la vista previa falla, explica el fallo y conserva los enlaces protegidos devueltos."
35183
35443
  });
35184
35444
  } catch (error) {
35185
- if (error instanceof z139.ZodError) throw invalidInputFromZod(error);
35445
+ if (error instanceof z140.ZodError) throw invalidInputFromZod(error);
35186
35446
  throw translateError(error);
35187
35447
  }
35188
35448
  });
@@ -35190,7 +35450,7 @@ function registerServiceImageTools(server, api, options = {}) {
35190
35450
  __name(registerServiceImageTools, "registerServiceImageTools");
35191
35451
 
35192
35452
  // src/tools/creatives.ts
35193
- import { z as z140 } from "zod";
35453
+ import { z as z141 } from "zod";
35194
35454
  var generateFreeVideoGuidance = getProductMakerCapabilityModelGuidance("generate_free_video");
35195
35455
  var generateVideoCreativeGuidance = getProductMakerCapabilityModelGuidance("generate_video_creative");
35196
35456
  var generateImageCreativesGuidance = getProductMakerCapabilityModelGuidance("generate_image_creatives");
@@ -35201,72 +35461,72 @@ var CreativeRefs = {
35201
35461
  images: ImagesInput,
35202
35462
  language: Lang.describe('Idioma del contenido. Ej. "es", "en", "pt". REQUERIDO \u2014 si el usuario no lo dijo, PREG\xDANTALE antes de invocar. NUNCA infieras desde el contexto del chat ni asumas un default.'),
35203
35463
  country: Country.describe('Pa\xEDs objetivo ISO. Ej. "CO", "MX", "US", "AR". Define moneda, m\xE9todos de pago y tono. REQUERIDO \u2014 si el usuario no lo dijo, PREG\xDANTALE antes de invocar. NUNCA infieras desde el contexto del chat ni asumas un default.'),
35204
- price: z140.string().min(1).max(120).optional().describe('Precio en formato libre \u2014 ej. "$89.900", "USD 29.99". Opcional pero MUY IMPACTANTE para conversi\xF3n: si el usuario no lo mencion\xF3, PREG\xDANTALE antes de invocar. Si el usuario te dice "t\xFA elige", omite el campo y la IA del backend sugiere uno.'),
35205
- offer: z140.string().min(1).max(240).optional().describe('Oferta a destacar \u2014 ej. "2x1", "Env\xEDo gratis", "50% OFF". Opcional pero MUY IMPACTANTE: si el usuario no lo mencion\xF3, PREG\xDANTALE antes de invocar (es v\xE1lido que diga "sin oferta"). Si el usuario te dice "t\xFA elige", omite el campo.'),
35206
- compareAtPrice: z140.string().min(1).max(120).optional().describe('Opcional. Precio anterior o de referencia para mostrar descuento. Ej. "$129.000" cuando price="$89.900".'),
35207
- productContext: z140.string().max(3e3).optional().describe("Opcional. Informacion adicional del producto que la IA debe usar como contexto prioritario: uso real, comprador, restricciones, diferenciadores o direccion creativa. No reemplaza la investigacion de competencia; si hay competencia, guia el primer angulo y los outputs no deben contradecirlo."),
35208
- productLabel: z140.string().trim().max(80).optional().describe('Opcional. Nombre o tipo del producto en pocas palabras (ej. "collar de oro", "quita pelusas para ropa"). Es AUTORITATIVO: la IA usa esta identidad y no la reemplaza por otra categor\xEDa. Si lo omites, la IA lo detecta desde la imagen. NO salta la extracci\xF3n.'),
35209
- buyerPersona: z140.string().optional().describe('Opcional. Descripci\xF3n del comprador objetivo. Ej. "Mujeres 35-50, profesionales urbanas". Si se omite, la IA infiere desde el \xE1ngulo y producto.'),
35210
- isCombo: z140.boolean().optional().describe("Opcional. Si true, el creativo presenta el producto como combo/pack. Default false."),
35211
- competitorScanEnabled: z140.boolean().optional().describe("Opcional. Default false: omitirlo significa SIN estudio de mercado. Antes de crear, llama estimate_market_study_cost, muestra al usuario el costo con y sin estudio y preg\xFAntale si lo quiere; env\xEDa true solo si lo acepta (o si solicita la narrativa an\xE1lisis de mercado). En video, true analiza anuncios de la competencia y desbloquea la narrativa an\xE1lisis de mercado; si el estudio no encuentra datos suficientes, degrada al planner cl\xE1sico."),
35212
- brandId: z140.string().uuid().optional().describe("Opcional. ID de una marca del usuario; si se env\xEDa, el sistema usa su voz, colores y persona para enriquecer el creativo.")
35464
+ price: z141.string().min(1).max(120).optional().describe('Precio en formato libre \u2014 ej. "$89.900", "USD 29.99". Opcional pero MUY IMPACTANTE para conversi\xF3n: si el usuario no lo mencion\xF3, PREG\xDANTALE antes de invocar. Si el usuario te dice "t\xFA elige", omite el campo y la IA del backend sugiere uno.'),
35465
+ offer: z141.string().min(1).max(240).optional().describe('Oferta a destacar \u2014 ej. "2x1", "Env\xEDo gratis", "50% OFF". Opcional pero MUY IMPACTANTE: si el usuario no lo mencion\xF3, PREG\xDANTALE antes de invocar (es v\xE1lido que diga "sin oferta"). Si el usuario te dice "t\xFA elige", omite el campo.'),
35466
+ compareAtPrice: z141.string().min(1).max(120).optional().describe('Opcional. Precio anterior o de referencia para mostrar descuento. Ej. "$129.000" cuando price="$89.900".'),
35467
+ productContext: z141.string().max(3e3).optional().describe("Opcional. Informacion adicional del producto que la IA debe usar como contexto prioritario: uso real, comprador, restricciones, diferenciadores o direccion creativa. No reemplaza la investigacion de competencia; si hay competencia, guia el primer angulo y los outputs no deben contradecirlo."),
35468
+ productLabel: z141.string().trim().max(80).optional().describe('Opcional. Nombre o tipo del producto en pocas palabras (ej. "collar de oro", "quita pelusas para ropa"). Es AUTORITATIVO: la IA usa esta identidad y no la reemplaza por otra categor\xEDa. Si lo omites, la IA lo detecta desde la imagen. NO salta la extracci\xF3n.'),
35469
+ buyerPersona: z141.string().optional().describe('Opcional. Descripci\xF3n del comprador objetivo. Ej. "Mujeres 35-50, profesionales urbanas". Si se omite, la IA infiere desde el \xE1ngulo y producto.'),
35470
+ isCombo: z141.boolean().optional().describe("Opcional. Si true, el creativo presenta el producto como combo/pack. Default false."),
35471
+ competitorScanEnabled: z141.boolean().optional().describe("Opcional. Default false: omitirlo significa SIN estudio de mercado. Antes de crear, llama estimate_market_study_cost, muestra al usuario el costo con y sin estudio y preg\xFAntale si lo quiere; env\xEDa true solo si lo acepta (o si solicita la narrativa an\xE1lisis de mercado). En video, true analiza anuncios de la competencia y desbloquea la narrativa an\xE1lisis de mercado; si el estudio no encuentra datos suficientes, degrada al planner cl\xE1sico."),
35472
+ brandId: z141.string().uuid().optional().describe("Opcional. ID de una marca del usuario; si se env\xEDa, el sistema usa su voz, colores y persona para enriquecer el creativo.")
35213
35473
  };
35214
- var VideoPresenter = z140.discriminatedUnion("mode", [
35215
- z140.object({
35216
- mode: z140.literal("none")
35474
+ var VideoPresenter = z141.discriminatedUnion("mode", [
35475
+ z141.object({
35476
+ mode: z141.literal("none")
35217
35477
  }).strict(),
35218
- z140.object({
35219
- mode: z140.literal("saved_actor"),
35220
- actorId: z140.string().trim().min(1).max(120).describe("ID devuelto por list_actors para un actor guardado del usuario.")
35478
+ z141.object({
35479
+ mode: z141.literal("saved_actor"),
35480
+ actorId: z141.string().trim().min(1).max(120).describe("ID devuelto por list_actors para un actor guardado del usuario.")
35221
35481
  }).strict(),
35222
- z140.object({
35223
- mode: z140.literal("described"),
35224
- description: z140.string().trim().min(1).max(500).describe("Descripci\xF3n concreta de la persona que presenta el producto.")
35482
+ z141.object({
35483
+ mode: z141.literal("described"),
35484
+ description: z141.string().trim().min(1).max(500).describe("Descripci\xF3n concreta de la persona que presenta el producto.")
35225
35485
  }).strict()
35226
35486
  ]);
35227
- var VideoCreativeSetup = z140.discriminatedUnion("mode", [
35228
- z140.object({
35229
- mode: z140.literal("automatic").describe('S\xF3lo cuando el usuario pidi\xF3 expl\xEDcitamente "decide todo".')
35487
+ var VideoCreativeSetup = z141.discriminatedUnion("mode", [
35488
+ z141.object({
35489
+ mode: z141.literal("automatic").describe('S\xF3lo cuando el usuario pidi\xF3 expl\xEDcitamente "decide todo".')
35230
35490
  }).strict(),
35231
- z140.object({
35232
- mode: z140.literal("guided"),
35491
+ z141.object({
35492
+ mode: z141.literal("guided"),
35233
35493
  videoPreset: VideoPresetEnum.default("auto").describe('Narrativa opcional. Con estudio de mercado no la preguntes: se usa "auto" y el estudio define la estructura. Sin estudio conserva la elecci\xF3n del usuario. Todas las narrativas funcionan en Un solo clip y Multiescena.'),
35234
- buyerPersona: z140.string().trim().min(1).max(360),
35494
+ buyerPersona: z141.string().trim().min(1).max(360),
35235
35495
  animationStyle: AnimationStyleEnum.optional().describe('Opcional. Estilo visual SOLO para presets animados ("pixar_problem" o "pixar_sassy") \u2014 la API lo rechaza con cualquier otro preset. Opciones: "pixar_3d" (default, look 3D de pel\xEDcula animada), "claymation" (plastilina stop-motion artesanal), "wes_anderson" (tableau sim\xE9trico pastel), "sci_fi" (futurista con HUDs y ne\xF3n), "realista" (fotorrealista, fiel al producto). Si el usuario pide un look animado sin nombrar el estilo, omite el campo.'),
35236
35496
  presenter: VideoPresenter,
35237
- creativeDirection: z140.string().trim().min(1).max(500).nullable().describe("Direcci\xF3n visual elegida. null significa que el usuario deleg\xF3 expl\xEDcitamente esta decisi\xF3n.")
35497
+ creativeDirection: z141.string().trim().min(1).max(500).nullable().describe("Direcci\xF3n visual elegida. null significa que el usuario deleg\xF3 expl\xEDcitamente esta decisi\xF3n.")
35238
35498
  }).strict()
35239
35499
  ]);
35240
- var VideoInput = z140.object({
35500
+ var VideoInput = z141.object({
35241
35501
  images: CreativeRefs.images.optional(),
35242
35502
  winnerReference: WinnerReferenceInput.optional().describe(WINNER_IMAGE_CHOICE_GUIDANCE),
35243
- title: z140.string().min(1).describe('T\xEDtulo corto del producto. Aparece en la primera vista del video. Ej. "Crema Anti-Edad Premium".'),
35244
- primaryAngle: z140.string().min(1).describe('\xC1ngulo de venta principal (frase del beneficio). Ej. "Reduce arrugas en 14 d\xEDas" o "Para quienes odian las cremas grasosas".'),
35503
+ title: z141.string().min(1).describe('T\xEDtulo corto del producto. Aparece en la primera vista del video. Ej. "Crema Anti-Edad Premium".'),
35504
+ primaryAngle: z141.string().min(1).describe('\xC1ngulo de venta principal (frase del beneficio). Ej. "Reduce arrugas en 14 d\xEDas" o "Para quienes odian las cremas grasosas".'),
35245
35505
  language: CreativeRefs.language,
35246
35506
  country: CreativeRefs.country,
35247
- aspectRatio: z140.enum([
35507
+ aspectRatio: z141.enum([
35248
35508
  "9:16",
35249
35509
  "16:9",
35250
35510
  "Auto"
35251
35511
  ]).describe('Relaci\xF3n de aspecto. "9:16" = vertical (TikTok/Reels \u2014 default recomendado para ads). "16:9" = horizontal (YouTube). "Auto" = decide la IA.'),
35252
35512
  generateAiEdit: AiEditRequestSchema.optional().describe('Opcional. Pide la Edici\xF3n con IA: una pasada que a\xF1ade texto, efectos y m\xFAsica sobre cada video entregado (variante with_ai_edit; se cobra al entregar, por video). Nivel: "economy" (Econ\xF3mica, Sonnet 5.5, m\xE1s barata) o "pro" (Opus 5.5, mejor calidad, ~10 min). true equivale a "pro"; false/omitido = sin edici\xF3n.'),
35253
- durationSeconds: z140.number().int().min(4).max(30).default(15).describe("Opcional. Duraci\xF3n objetivo: Un solo clip Econ\xF3mico o Medio usa 15s; Un solo clip Pro usa 15s o 30s; Multiescena usa 15s o 30s totales. La combinaci\xF3n de formato y calidad determina la ruta y el costo."),
35254
- videoAngleCount: z140.number().int().min(1).max(3).describe("Cantidad de videos a renderizar: 1, 2 o 3. Es una decisi\xF3n de costo obligatoria: preg\xFAntala incluso cuando creativeSetup.mode=automatic."),
35513
+ durationSeconds: z141.number().int().min(4).max(30).default(15).describe("Opcional. Duraci\xF3n objetivo: Un solo clip Econ\xF3mico o Medio usa 15s; Un solo clip Pro usa 15s o 30s; Multiescena usa 15s o 30s totales. La combinaci\xF3n de formato y calidad determina la ruta y el costo."),
35514
+ videoAngleCount: z141.number().int().min(1).max(3).describe("Cantidad de videos a renderizar: 1, 2 o 3. Es una decisi\xF3n de costo obligatoria: preg\xFAntala incluso cuando creativeSetup.mode=automatic."),
35255
35515
  angleNarratives: PublicNarrativeStyleEnum.array().max(3).optional().describe('Opcional. Narrativa POR \xC1NGULO (posicional). Valores actuales: "review", "ugc_how_to", "ugc_unboxing_chaos", "problem_product_cta" (problema concreto, producto como soluci\xF3n y CTA), "product_showcase", "wearable_showcase", "cinematic", "wild_card", "animated_problem_pixar", "sassy_product_pixar", "market" (narrativa an\xE1lisis de mercado: adapta el video a los patrones ganadores estudiados de la competencia; requiere competitorScanEnabled: true). angleNarratives[i] fuerza la narrativa del \xE1ngulo i; las posiciones restantes las elige la IA. La lista no puede superar videoAngleCount.'),
35256
35516
  creativeSetup: VideoCreativeSetup.describe("Configuraci\xF3n creativa obligatoria. En guided pregunta narrativa, p\xFAblico, presentador y direcci\xF3n antes de confirmar. Usa automatic s\xF3lo si el usuario deleg\xF3 todo expl\xEDcitamente."),
35257
35517
  videoStructure: VideoStructureEnum.default("single_take").describe('Opcional. Formato: "single_take" = Un solo clip (generado completo; puede incluir cambios de plano) o "multi_scene" = Multiescena (varias tomas concatenadas). Default: single_take.'),
35258
35518
  videoQuality: VideoQualityEnum.default("economy").describe('Opcional. Calidad: "economy" = Econ\xF3mico, "medium" = Medio o "pro" = Pro. El sistema elige el proveedor internamente. Default: economy.'),
35259
35519
  videoResolution: VideoResolutionEnum.optional().describe('Opcional. Resoluci\xF3n del render: "480p" o "720p". Si se omite, se usa 480p; elige 720p expl\xEDcitamente si necesitas m\xE1s nitidez.'),
35260
- features: z140.array(z140.string()).optional().describe('Opcional. Lista de features/beneficios clave a mencionar. Ej. ["resistente al agua", "bater\xEDa 24h", "carga inal\xE1mbrica"].'),
35520
+ features: z141.array(z141.string()).optional().describe('Opcional. Lista de features/beneficios clave a mencionar. Ej. ["resistente al agua", "bater\xEDa 24h", "carga inal\xE1mbrica"].'),
35261
35521
  price: CreativeRefs.price,
35262
35522
  offer: CreativeRefs.offer,
35263
35523
  productContext: CreativeRefs.productContext,
35264
35524
  productLabel: CreativeRefs.productLabel,
35265
- cta: z140.string().optional().describe('Opcional. Call-to-action final del video. Ej. "Compra ahora", "Pide el tuyo hoy", "Link en bio". Si se omite, la IA escribe uno.'),
35266
- scriptNotes: z140.string().optional().describe('Opcional. Notas/restricciones para el guion (la IA las usa al redactar). Ej. "No mencionar la marca X", "Tono divertido, no cl\xEDnico", "Mencionar 2x1". NO es el guion literal: creativeSetup.mode=guided activa siempre la revisi\xF3n palabra-por-palabra con get_video_script / approve_video_script.'),
35525
+ cta: z141.string().optional().describe('Opcional. Call-to-action final del video. Ej. "Compra ahora", "Pide el tuyo hoy", "Link en bio". Si se omite, la IA escribe uno.'),
35526
+ scriptNotes: z141.string().optional().describe('Opcional. Notas/restricciones para el guion (la IA las usa al redactar). Ej. "No mencionar la marca X", "Tono divertido, no cl\xEDnico", "Mencionar 2x1". NO es el guion literal: creativeSetup.mode=guided activa siempre la revisi\xF3n palabra-por-palabra con get_video_script / approve_video_script.'),
35267
35527
  creativeGender: CreativeGenderEnum.optional().describe('Opcional. QUI\xC9N APARECE EN PANTALLA en el video: mujer ("female"), hombre ("male") o ambos ("mixed"). NO es la voz; cuando se fija, la voz se alinea sola con el personaje. M\xE1ndalo en cuanto el usuario diga qui\xE9n debe SALIR en el video, con cualquier redacci\xF3n ("quiero una mujer", "que salga un hombre", "protagonista femenina"): si no lo mandas, el g\xE9nero lo inventa la IA y el usuario puede recibir lo contrario de lo que pidi\xF3 sin ning\xFAn aviso. NO lo mandes cuando hable del P\xDABLICO COMPRADOR ("esto es para hombres", "mi p\xFAblico son mujeres", "va dirigido a mam\xE1s"): eso es a qui\xE9n se le vende, no qui\xE9n act\xFAa, y fijarlo ah\xED pinea un protagonista que nadie pidi\xF3. "mixed" solo cuando pida expl\xEDcitamente ambos, y ten en cuenta que no garantiza una mezcla: no fija ning\xFAn g\xE9nero y cada creativo elige su protagonista. No lo preguntes como paso obligatorio. Si creativeSetup.presenter.mode="saved_actor", el personaje guardado manda sobre este campo.'),
35268
35528
  competitorScanEnabled: CreativeRefs.competitorScanEnabled,
35269
- replicateWinnerWhenFound: z140.boolean().optional().describe("Conserva la preferencia de copiar un ganador si existe. false permite estudiar el mercado sin reemplazar un \xE1ngulo por el anuncio ganador. No a\xF1adas una pregunta obligatoria para este campo."),
35529
+ replicateWinnerWhenFound: z141.boolean().optional().describe("Conserva la preferencia de copiar un ganador si existe. false permite estudiar el mercado sin reemplazar un \xE1ngulo por el anuncio ganador. No a\xF1adas una pregunta obligatoria para este campo."),
35270
35530
  brandId: CreativeRefs.brandId
35271
35531
  }).strict();
35272
35532
  var FreeVideoQualityEnum = VideoQualityEnum;
@@ -35284,7 +35544,7 @@ var FREE_VIDEO_DURATIONS_BY_QUALITY = {
35284
35544
  length: 29
35285
35545
  }, (_, index) => index + 2)
35286
35546
  };
35287
- var FreeVideoModelIdEnum = z140.enum([
35547
+ var FreeVideoModelIdEnum = z141.enum([
35288
35548
  "grok-imagine-video-1-5-preview",
35289
35549
  "minimax-h3/reference-to-video",
35290
35550
  "wan/3-0-video",
@@ -35295,66 +35555,66 @@ var FREE_SELECTABLE_MODEL_IDS = [
35295
35555
  "minimax-h3/reference-to-video",
35296
35556
  "wan/3-0-video"
35297
35557
  ];
35298
- var FreeVideoInput = z140.object({
35558
+ var FreeVideoInput = z141.object({
35299
35559
  images: ImagesInput.optional().describe("Opcional. 1-5 fotos del producto. Om\xEDtelo para generar desde texto puro."),
35300
- rawPrompt: z140.string().trim().min(1).max(2e3).describe("Prompt o guion exacto del video. Si activas subt\xEDtulos, este mismo texto se usa como guion de subt\xEDtulos."),
35560
+ rawPrompt: z141.string().trim().min(1).max(2e3).describe("Prompt o guion exacto del video. Si activas subt\xEDtulos, este mismo texto se usa como guion de subt\xEDtulos."),
35301
35561
  language: CreativeRefs.language,
35302
35562
  quality: FreeVideoQualityEnum.describe('Calidad del video: "economy" = Econ\xF3mico, "medium" = Medio o "pro" = Pro (Wan).'),
35303
35563
  // Retired in MCP 7.0.0 (Task 11): kept in the schema, accepted, and
35304
35564
  // parsed ONLY so the handler can reject it with a message naming its
35305
35565
  // replacement — dropping it silently would turn a caller's explicit
35306
35566
  // Pro++ pick into a quiet fallback to Wan.
35307
- freeProModel: z140.enum([
35567
+ freeProModel: z141.enum([
35308
35568
  "wan",
35309
35569
  "seedance"
35310
35570
  ]).optional().describe("ELIMINADO en MCP 7.0.0 \u2014 usa `modelId`. Enviar este campo produce un error."),
35311
35571
  modelId: FreeVideoModelIdEnum.optional().describe('Opcional. Elige expl\xEDcitamente el modelo del registro en vez del default de la calidad: "grok-imagine-video-1-5-preview" (Grok 1.5), "minimax-h3/reference-to-video" (H3 Max) o "wan/3-0-video" (Wan 3.0). Om\xEDtelo para usar el modelo por defecto de la calidad elegida. El modelo determina la duraci\xF3n permitida (Grok 6/8/10/15s, H3 Max 4-15s, Wan 2-30s) y el precio. "bytedance/seedance-2-5" est\xE1 inactivo y se rechaza con un error claro, nunca un 500.'),
35312
- durationSeconds: z140.number().int().min(2).max(30).describe("Duraci\xF3n exacta. Econ\xF3mico: 6, 8, 10 o 15s; Medio: enteros de 4 a 15s; Pro Wan: 2 a 30s; Pro++ Seedance: 4 a 30s."),
35572
+ durationSeconds: z141.number().int().min(2).max(30).describe("Duraci\xF3n exacta. Econ\xF3mico: 6, 8, 10 o 15s; Medio: enteros de 4 a 15s; Pro Wan: 2 a 30s; Pro++ Seedance: 4 a 30s."),
35313
35573
  videoResolution: VideoResolutionEnum.describe("Resoluci\xF3n del video: 480p o 720p."),
35314
- generateAiMusic: z140.boolean().default(false).describe('Opcional. Agrega m\xFAsica instrumental IA debajo del audio nativo. Default false. Equivale a generateAiEdit (nivel "pro"): la Edici\xF3n con IA trae su propia m\xFAsica.'),
35574
+ generateAiMusic: z141.boolean().default(false).describe('Opcional. Agrega m\xFAsica instrumental IA debajo del audio nativo. Default false. Equivale a generateAiEdit (nivel "pro"): la Edici\xF3n con IA trae su propia m\xFAsica.'),
35315
35575
  generateAiEdit: AiEditRequestSchema.optional().describe('Opcional. Pide la Edici\xF3n con IA: una pasada que a\xF1ade texto, gr\xE1ficos y m\xFAsica sobre el video ya entregado (variante with_ai_edit; se cobra al entregar). Nivel: "economy" (Econ\xF3mica, Sonnet 5.5, m\xE1s barata) o "pro" (Opus 5.5, mejor calidad, ~10 min). true equivale a "pro". Default sin edici\xF3n.'),
35316
- addSubtitles: z140.boolean().default(false).describe("Opcional. Quema subt\xEDtulos usando el rawPrompt exacto. Default false."),
35317
- idempotencyKey: z140.string().uuid().optional().describe("Opcional. Se acepta por compatibilidad pero se ignora: la identidad del reintento se deriva autom\xE1ticamente de los argumentos sem\xE1nticos, as\xED que un retry con los mismos argumentos ya es idempotente sin este campo.")
35576
+ addSubtitles: z141.boolean().default(false).describe("Opcional. Quema subt\xEDtulos usando el rawPrompt exacto. Default false."),
35577
+ idempotencyKey: z141.string().uuid().optional().describe("Opcional. Se acepta por compatibilidad pero se ignora: la identidad del reintento se deriva autom\xE1ticamente de los argumentos sem\xE1nticos, as\xED que un retry con los mismos argumentos ya es idempotente sin este campo.")
35318
35578
  }).strict();
35319
35579
  var imageStyleList = ImageCreativeStyleEnum.options.join(" | ");
35320
- var ImageCreativeSetup = z140.discriminatedUnion("mode", [
35321
- z140.object({
35322
- mode: z140.literal("automatic")
35580
+ var ImageCreativeSetup = z141.discriminatedUnion("mode", [
35581
+ z141.object({
35582
+ mode: z141.literal("automatic")
35323
35583
  }).strict(),
35324
- z140.object({
35325
- mode: z140.literal("guided"),
35326
- creativeArchetype: z140.union([
35584
+ z141.object({
35585
+ mode: z141.literal("guided"),
35586
+ creativeArchetype: z141.union([
35327
35587
  ImageCreativeArchetypeEnum,
35328
- z140.literal("ai_decides")
35588
+ z141.literal("ai_decides")
35329
35589
  ]),
35330
- visualTreatment: z140.union([
35590
+ visualTreatment: z141.union([
35331
35591
  ImageCreativeVisualTreatmentEnum,
35332
- z140.literal("ai_decides")
35592
+ z141.literal("ai_decides")
35333
35593
  ]),
35334
35594
  aspectRatio: ImageCreativeAspectRatioEnum,
35335
- nVariants: z140.union([
35336
- z140.literal(1),
35337
- z140.literal(2),
35338
- z140.literal(3),
35339
- z140.literal(4),
35340
- z140.literal(6)
35595
+ nVariants: z141.union([
35596
+ z141.literal(1),
35597
+ z141.literal(2),
35598
+ z141.literal(3),
35599
+ z141.literal(4),
35600
+ z141.literal(6)
35341
35601
  ]),
35342
- imageModel: z140.enum(PUBLIC_IMAGE_QUALITY_INPUTS).describe('Calidad: "image:1k" (Est\xE1ndar) o "image:2k" (Premium). Los tokens gpt-image-2-5:{1k,2k} y gpt-image-2:{1k,2k} se siguen aceptando.'),
35343
- styleMix: z140.boolean().nullable().describe("Decisi\xF3n s\xF3lo para 2\u20134 variantes. Usa null para 1 o 6, cuando la UI no hace esta pregunta."),
35344
- generateAdCopy: z140.boolean(),
35345
- competitorScanEnabled: z140.boolean(),
35346
- productContext: z140.string().trim().min(1).max(3e3).nullable(),
35347
- creativeDirection: z140.string().trim().min(1).max(500).nullable(),
35348
- brandColors: z140.string().trim().min(1).max(240).nullable().describe('Colores en lenguaje natural, igual que en el chat web. Ej. "Azul y Dorado". null significa sin colores espec\xEDficos.'),
35349
- headline: z140.string().trim().min(1).max(120).nullable(),
35350
- subheadline: z140.string().trim().min(1).max(160).nullable()
35602
+ imageModel: z141.enum(PUBLIC_IMAGE_QUALITY_INPUTS).describe('Calidad: "image:1k" (Est\xE1ndar) o "image:2k" (Premium). Los tokens gpt-image-2-5:{1k,2k} y gpt-image-2:{1k,2k} se siguen aceptando.'),
35603
+ styleMix: z141.boolean().nullable().describe("Decisi\xF3n s\xF3lo para 2\u20134 variantes. Usa null para 1 o 6, cuando la UI no hace esta pregunta."),
35604
+ generateAdCopy: z141.boolean(),
35605
+ competitorScanEnabled: z141.boolean(),
35606
+ productContext: z141.string().trim().min(1).max(3e3).nullable(),
35607
+ creativeDirection: z141.string().trim().min(1).max(500).nullable(),
35608
+ brandColors: z141.string().trim().min(1).max(240).nullable().describe('Colores en lenguaje natural, igual que en el chat web. Ej. "Azul y Dorado". null significa sin colores espec\xEDficos.'),
35609
+ headline: z141.string().trim().min(1).max(120).nullable(),
35610
+ subheadline: z141.string().trim().min(1).max(160).nullable()
35351
35611
  }).strict()
35352
35612
  ]).superRefine((setup, context) => {
35353
35613
  if (setup.mode !== "guided") return;
35354
35614
  const needsStyleMix = setup.nVariants >= 2 && setup.nVariants <= 4;
35355
35615
  if (needsStyleMix && setup.styleMix === null) {
35356
35616
  context.addIssue({
35357
- code: z140.ZodIssueCode.custom,
35617
+ code: z141.ZodIssueCode.custom,
35358
35618
  path: [
35359
35619
  "styleMix"
35360
35620
  ],
@@ -35363,7 +35623,7 @@ var ImageCreativeSetup = z140.discriminatedUnion("mode", [
35363
35623
  }
35364
35624
  if (!needsStyleMix && setup.styleMix !== null) {
35365
35625
  context.addIssue({
35366
- code: z140.ZodIssueCode.custom,
35626
+ code: z141.ZodIssueCode.custom,
35367
35627
  path: [
35368
35628
  "styleMix"
35369
35629
  ],
@@ -35372,7 +35632,7 @@ var ImageCreativeSetup = z140.discriminatedUnion("mode", [
35372
35632
  }
35373
35633
  if (imageResolutionOf(setup.imageModel) === "2k" && setup.aspectRatio === "4:5") {
35374
35634
  context.addIssue({
35375
- code: z140.ZodIssueCode.custom,
35635
+ code: z141.ZodIssueCode.custom,
35376
35636
  path: [
35377
35637
  "aspectRatio"
35378
35638
  ],
@@ -35380,55 +35640,55 @@ var ImageCreativeSetup = z140.discriminatedUnion("mode", [
35380
35640
  });
35381
35641
  }
35382
35642
  });
35383
- var LandingCreativeSetup = z140.discriminatedUnion("mode", [
35384
- z140.object({
35385
- mode: z140.literal("automatic")
35643
+ var LandingCreativeSetup = z141.discriminatedUnion("mode", [
35644
+ z141.object({
35645
+ mode: z141.literal("automatic")
35386
35646
  }).strict(),
35387
- z140.object({
35388
- mode: z140.literal("guided"),
35647
+ z141.object({
35648
+ mode: z141.literal("guided"),
35389
35649
  landingStrategy: LandingStrategyEnum,
35390
35650
  landingImageFormat: LandingImageFormatEnum,
35391
- competitorScanEnabled: z140.boolean(),
35392
- paymentMethods: z140.string().trim().min(1).max(240).nullable(),
35393
- productContext: z140.string().trim().min(1).max(3e3).nullable(),
35394
- creativeDirection: z140.string().trim().min(1).max(500).nullable(),
35395
- headline: z140.string().trim().min(1).max(120).nullable(),
35396
- subheadline: z140.string().trim().min(1).max(160).nullable()
35651
+ competitorScanEnabled: z141.boolean(),
35652
+ paymentMethods: z141.string().trim().min(1).max(240).nullable(),
35653
+ productContext: z141.string().trim().min(1).max(3e3).nullable(),
35654
+ creativeDirection: z141.string().trim().min(1).max(500).nullable(),
35655
+ headline: z141.string().trim().min(1).max(120).nullable(),
35656
+ subheadline: z141.string().trim().min(1).max(160).nullable()
35397
35657
  }).strict()
35398
35658
  ]);
35399
- var ImageInput = z140.object({
35659
+ var ImageInput = z141.object({
35400
35660
  images: CreativeRefs.images.optional(),
35401
35661
  winnerReference: WinnerReferenceInput.optional().describe(WINNER_IMAGE_CHOICE_GUIDANCE),
35402
- productTitle: z140.string().min(1).describe('Nombre del producto. Ej. "Crema Anti-Edad Premium".'),
35403
- primaryAngle: z140.string().min(1).optional().describe('Opcional. \xC1ngulo de venta principal. Si se omite, ProductMaker lo genera y creativeSetup.mode="guided" lo muestra para revisi\xF3n antes de crear las im\xE1genes.'),
35662
+ productTitle: z141.string().min(1).describe('Nombre del producto. Ej. "Crema Anti-Edad Premium".'),
35663
+ primaryAngle: z141.string().min(1).optional().describe('Opcional. \xC1ngulo de venta principal. Si se omite, ProductMaker lo genera y creativeSetup.mode="guided" lo muestra para revisi\xF3n antes de crear las im\xE1genes.'),
35404
35664
  buyerPersona: CreativeRefs.buyerPersona,
35405
35665
  language: CreativeRefs.language,
35406
35666
  country: CreativeRefs.country,
35407
35667
  creativeSetup: ImageCreativeSetup.describe("Configuraci\xF3n obligatoria equivalente al wizard web. Usa guided salvo que el usuario delegue expl\xEDcitamente todo; presenta las decisiones guiadas en un \xFAnico checklist compacto."),
35408
35668
  imageStyle: ImageCreativeStyleEnum.optional().describe(`Opcional. Estilo del shot del producto. Valores: ${imageStyleList}. Si se omite, la IA escoge seg\xFAn el archetype.`),
35409
- features: z140.array(z140.string()).optional().describe('Opcional. Features/beneficios a destacar visualmente. Ej. ["resistente al agua", "ultra liviano"].'),
35669
+ features: z141.array(z141.string()).optional().describe('Opcional. Features/beneficios a destacar visualmente. Ej. ["resistente al agua", "ultra liviano"].'),
35410
35670
  price: CreativeRefs.price,
35411
- omitPrice: z140.boolean().optional().describe("Pon true \xFAnicamente cuando el usuario pida expl\xEDcitamente una imagen sin precio. Evita que el backend reutilice un precio extra\xEDdo del producto. No lo combines con price ni compareAtPrice."),
35671
+ omitPrice: z141.boolean().optional().describe("Pon true \xFAnicamente cuando el usuario pida expl\xEDcitamente una imagen sin precio. Evita que el backend reutilice un precio extra\xEDdo del producto. No lo combines con price ni compareAtPrice."),
35412
35672
  compareAtPrice: CreativeRefs.compareAtPrice,
35413
35673
  offer: CreativeRefs.offer,
35414
35674
  productLabel: CreativeRefs.productLabel,
35415
35675
  isCombo: CreativeRefs.isCombo,
35416
35676
  brandId: CreativeRefs.brandId
35417
35677
  });
35418
- var EditImageCreativeInput = z140.object({
35419
- taskId: z140.string().uuid().describe("ID de la tarea de imagen ya finalizada."),
35420
- variantIndex: z140.number().int().min(0).max(10).describe("\xCDndice de la variante a corregir (0 = primera imagen)."),
35421
- clientRequestId: z140.string().min(1).max(160).optional().describe("Identidad estable de esta correcci\xF3n; reutil\xEDzala al reconciliar una respuesta perdida, no para otra instrucci\xF3n."),
35422
- instruction: z140.string().min(1).max(1e3).describe('Qu\xE9 cambiar en esa imagen. Ej. "quita el badge de env\xEDo gratis", "cambia el fondo a blanco".')
35678
+ var EditImageCreativeInput = z141.object({
35679
+ taskId: z141.string().uuid().describe("ID de la tarea de imagen ya finalizada."),
35680
+ variantIndex: z141.number().int().min(0).max(10).describe("\xCDndice de la variante a corregir (0 = primera imagen)."),
35681
+ clientRequestId: z141.string().min(1).max(160).optional().describe("Identidad estable de esta correcci\xF3n; reutil\xEDzala al reconciliar una respuesta perdida, no para otra instrucci\xF3n."),
35682
+ instruction: z141.string().min(1).max(1e3).describe('Qu\xE9 cambiar en esa imagen. Ej. "quita el badge de env\xEDo gratis", "cambia el fondo a blanco".')
35423
35683
  });
35424
- var LandingInput = z140.object({
35425
- includeInitialLandingGif: z140.boolean().optional().describe("Opcional. En nuevas landings cl\xE1sicas habilitadas, omitirlo incluye el primer GIF del producto y su coste en el total. Env\xEDa false si el usuario lo excluye: no se genera ni se reserva su coste. Conserva la elecci\xF3n al confirmar y al reintentar."),
35684
+ var LandingInput = z141.object({
35685
+ includeInitialLandingGif: z141.boolean().optional().describe("Opcional. En nuevas landings cl\xE1sicas habilitadas, omitirlo incluye el primer GIF del producto y su coste en el total. Env\xEDa false si el usuario lo excluye: no se genera ni se reserva su coste. Conserva la elecci\xF3n al confirmar y al reintentar."),
35426
35686
  images: CreativeRefs.images.optional(),
35427
35687
  winnerReference: WinnerReferenceInput.optional().describe(WINNER_IMAGE_CHOICE_GUIDANCE),
35428
35688
  language: CreativeRefs.language,
35429
35689
  country: CreativeRefs.country,
35430
35690
  creativeSetup: LandingCreativeSetup.describe("Configuraci\xF3n obligatoria de landing cl\xE1sica equivalente al wizard web. No expone landing editable/code mientras siga siendo admin-only."),
35431
- salesAngle: z140.string().optional().describe('Opcional. \xC1ngulo de venta principal. Ej. "Reduce arrugas en 14 d\xEDas". Si se omite, la IA lo deduce de la imagen.'),
35691
+ salesAngle: z141.string().optional().describe('Opcional. \xC1ngulo de venta principal. Ej. "Reduce arrugas en 14 d\xEDas". Si se omite, la IA lo deduce de la imagen.'),
35432
35692
  buyerPersona: CreativeRefs.buyerPersona,
35433
35693
  price: CreativeRefs.price,
35434
35694
  compareAtPrice: CreativeRefs.compareAtPrice,
@@ -35498,6 +35758,7 @@ function resolveLandingSetup(setup) {
35498
35758
  __name(resolveLandingSetup, "resolveLandingSetup");
35499
35759
  function registerCreativeTools(server, api, { transport = "http" } = {}) {
35500
35760
  server.registerTool("generate_free_video", {
35761
+ title: "Custom-script video",
35501
35762
  description: generateFreeVideoGuidance.summary + ' Si el usuario adjunta o menciona una imagen local, llama primero request_media_upload con mediaKind=image y usa el assetId devuelto; no pidas path ni URL. Devuelve un taskId y procesa el job de forma as\xEDncrona.\n\nREQUERIDOS: rawPrompt, quality, durationSeconds y videoResolution.\n\nCALIDAD: "economy" = Econ\xF3mico, "medium" = Medio o "pro" = Pro (Wan). Economy admite 6/8/10/15s; Medium admite cualquier duraci\xF3n entera de 4 a 15s; Pro admite enteros de 2 a 30s y audio natural. `freeProModel` se elimin\xF3 \u2014 usa `modelId` (ver su descripci\xF3n); Seedance 2.5 est\xE1 inactivo y se rechaza con un error claro.\n\nSUBT\xCDTULOS: si addSubtitles=true, el rawPrompt completo se usa como texto exacto de subt\xEDtulos y debe tener m\xE1ximo 500 caracteres; no existe una pregunta adicional de transcripci\xF3n.\n\nPara videos normales con narrativa/\xE1ngulo usa generate_video_creative. Para tareas combinadas de producto usa create_product_task.',
35502
35763
  inputSchema: FreeVideoInput.shape,
35503
35764
  annotations: CREATES_PAID_OPEN_WORLD
@@ -35710,8 +35971,8 @@ function registerCreativeTools(server, api, { transport = "http" } = {}) {
35710
35971
  server.registerTool("get_image_edit_status", {
35711
35972
  description: imageEditStatusGuidance.summary,
35712
35973
  inputSchema: {
35713
- taskId: z140.string().uuid(),
35714
- operationId: z140.string().uuid()
35974
+ taskId: z141.string().uuid(),
35975
+ operationId: z141.string().uuid()
35715
35976
  },
35716
35977
  annotations: READS_INTERNAL
35717
35978
  }, async (input) => {
@@ -35768,10 +36029,10 @@ function registerCreativeTools(server, api, { transport = "http" } = {}) {
35768
36029
  __name(registerCreativeTools, "registerCreativeTools");
35769
36030
 
35770
36031
  // src/tools/creative-variants.ts
35771
- import { z as z141 } from "zod";
36032
+ import { z as z142 } from "zod";
35772
36033
  var estimateCreativeVariantGuidance = getProductMakerCapabilityModelGuidance("estimate_creative_variant");
35773
36034
  var createCreativeVariantGuidance = getProductMakerCapabilityModelGuidance("create_creative_variant");
35774
- var LandingSectionKeyEnum = z141.enum([
36035
+ var LandingSectionKeyEnum = z142.enum([
35775
36036
  "problem_hook",
35776
36037
  "problem_cost",
35777
36038
  "solution_mechanism",
@@ -35811,24 +36072,24 @@ var LandingSectionKeyEnum = z141.enum([
35811
36072
  "lifestyle_fit",
35812
36073
  "join_lifestyle"
35813
36074
  ]);
35814
- var CreativeVariantKindEnum = z141.enum([
36075
+ var CreativeVariantKindEnum = z142.enum([
35815
36076
  "video",
35816
36077
  "image",
35817
36078
  "landing"
35818
36079
  ]);
35819
- var EstimateCreativeVariantInputBase = z141.object({
35820
- sourceTaskId: z141.string().uuid().describe("ID de la tarea original completada."),
36080
+ var EstimateCreativeVariantInputBase = z142.object({
36081
+ sourceTaskId: z142.string().uuid().describe("ID de la tarea original completada."),
35821
36082
  kind: CreativeVariantKindEnum,
35822
36083
  videoPreset: TaskCreativeVariantVideoPresetEnum.optional().describe("Opcional para video. Solo cambia el preset p\xFAblico; el servidor conserva el modelo/tier del task origen."),
35823
36084
  videoResolution: VideoResolutionEnum.optional().describe("Opcional para video. Resoluci\xF3n del nuevo render."),
35824
- nVariants: z141.number().int().min(1).max(3).optional().describe("Opcional para imagen. N\xFAmero de im\xE1genes adicionales (1-3)."),
36085
+ nVariants: z142.number().int().min(1).max(3).optional().describe("Opcional para imagen. N\xFAmero de im\xE1genes adicionales (1-3)."),
35825
36086
  sectionKey: LandingSectionKeyEnum.optional().describe("Opcional para landing. Si ya conoces la secci\xF3n, evita cotizar la clasificaci\xF3n con IA."),
35826
- instructions: z141.string().trim().min(1).max(2e3).optional().describe("Opcional para landing. Si se env\xEDa sin sectionKey, el estimado suma la clasificaci\xF3n pagada con IA.")
36087
+ instructions: z142.string().trim().min(1).max(2e3).optional().describe("Opcional para landing. Si se env\xEDa sin sectionKey, el estimado suma la clasificaci\xF3n pagada con IA.")
35827
36088
  }).strict();
35828
36089
  var EstimateCreativeVariantInput = EstimateCreativeVariantInputBase.superRefine((input, ctx) => {
35829
36090
  if (input.kind === "video" && input.nVariants !== void 0) {
35830
36091
  ctx.addIssue({
35831
- code: z141.ZodIssueCode.custom,
36092
+ code: z142.ZodIssueCode.custom,
35832
36093
  path: [
35833
36094
  "nVariants"
35834
36095
  ],
@@ -35838,7 +36099,7 @@ var EstimateCreativeVariantInput = EstimateCreativeVariantInputBase.superRefine(
35838
36099
  if (input.kind !== "video") {
35839
36100
  if (input.videoPreset !== void 0) {
35840
36101
  ctx.addIssue({
35841
- code: z141.ZodIssueCode.custom,
36102
+ code: z142.ZodIssueCode.custom,
35842
36103
  path: [
35843
36104
  "videoPreset"
35844
36105
  ],
@@ -35847,7 +36108,7 @@ var EstimateCreativeVariantInput = EstimateCreativeVariantInputBase.superRefine(
35847
36108
  }
35848
36109
  if (input.videoResolution !== void 0) {
35849
36110
  ctx.addIssue({
35850
- code: z141.ZodIssueCode.custom,
36111
+ code: z142.ZodIssueCode.custom,
35851
36112
  path: [
35852
36113
  "videoResolution"
35853
36114
  ],
@@ -35857,7 +36118,7 @@ var EstimateCreativeVariantInput = EstimateCreativeVariantInputBase.superRefine(
35857
36118
  }
35858
36119
  if (input.kind !== "image" && input.nVariants !== void 0) {
35859
36120
  ctx.addIssue({
35860
- code: z141.ZodIssueCode.custom,
36121
+ code: z142.ZodIssueCode.custom,
35861
36122
  path: [
35862
36123
  "nVariants"
35863
36124
  ],
@@ -35867,7 +36128,7 @@ var EstimateCreativeVariantInput = EstimateCreativeVariantInputBase.superRefine(
35867
36128
  if (input.kind !== "landing") {
35868
36129
  if (input.sectionKey !== void 0) {
35869
36130
  ctx.addIssue({
35870
- code: z141.ZodIssueCode.custom,
36131
+ code: z142.ZodIssueCode.custom,
35871
36132
  path: [
35872
36133
  "sectionKey"
35873
36134
  ],
@@ -35876,7 +36137,7 @@ var EstimateCreativeVariantInput = EstimateCreativeVariantInputBase.superRefine(
35876
36137
  }
35877
36138
  if (input.instructions !== void 0) {
35878
36139
  ctx.addIssue({
35879
- code: z141.ZodIssueCode.custom,
36140
+ code: z142.ZodIssueCode.custom,
35880
36141
  path: [
35881
36142
  "instructions"
35882
36143
  ],
@@ -35885,31 +36146,31 @@ var EstimateCreativeVariantInput = EstimateCreativeVariantInputBase.superRefine(
35885
36146
  }
35886
36147
  }
35887
36148
  });
35888
- var CreateCreativeVariantInputBase = z141.object({
35889
- sourceTaskId: z141.string().uuid().describe("ID de la tarea original completada."),
36149
+ var CreateCreativeVariantInputBase = z142.object({
36150
+ sourceTaskId: z142.string().uuid().describe("ID de la tarea original completada."),
35890
36151
  kind: CreativeVariantKindEnum,
35891
- confirm: z141.literal(true).describe("Confirmaci\xF3n expl\xEDcita del usuario despu\xE9s de mostrarle el estimado de cr\xE9ditos."),
35892
- clientRequestId: z141.string().uuid().describe("UUID estable para esta intenci\xF3n (obligatorio por compatibilidad de contrato; el servidor deriva la identidad real del request desde los argumentos sem\xE1nticos, as\xED que un UUID nuevo en un retry NO crea una variante duplicada). Si la API devuelve status=error, una nueva generaci\xF3n exige otra confirmaci\xF3n."),
35893
- angle: z141.string().trim().min(1).max(500).optional().describe("\xC1ngulo nuevo para video o imagen."),
35894
- buyerPersona: z141.string().trim().min(1).max(360).optional().describe("Buyer persona coherente con el \xE1ngulo."),
35895
- scriptNotes: z141.string().trim().min(1).max(2e3).optional().describe("Direcci\xF3n breve del guion para una variante de video."),
35896
- creativeDirection: z141.string().trim().min(1).max(2e3).optional().describe("Direcci\xF3n creativa para una variante de imagen."),
36152
+ confirm: z142.literal(true).describe("Confirmaci\xF3n expl\xEDcita del usuario despu\xE9s de mostrarle el estimado de cr\xE9ditos."),
36153
+ clientRequestId: z142.string().uuid().describe("UUID estable para esta intenci\xF3n (obligatorio por compatibilidad de contrato; el servidor deriva la identidad real del request desde los argumentos sem\xE1nticos, as\xED que un UUID nuevo en un retry NO crea una variante duplicada). Si la API devuelve status=error, una nueva generaci\xF3n exige otra confirmaci\xF3n."),
36154
+ angle: z142.string().trim().min(1).max(500).optional().describe("\xC1ngulo nuevo para video o imagen."),
36155
+ buyerPersona: z142.string().trim().min(1).max(360).optional().describe("Buyer persona coherente con el \xE1ngulo."),
36156
+ scriptNotes: z142.string().trim().min(1).max(2e3).optional().describe("Direcci\xF3n breve del guion para una variante de video."),
36157
+ creativeDirection: z142.string().trim().min(1).max(2e3).optional().describe("Direcci\xF3n creativa para una variante de imagen."),
35897
36158
  videoPreset: TaskCreativeVariantVideoPresetEnum.optional().describe("Opcional para video. Preset p\xFAblico; el servidor resuelve modelo/tier desde el task origen."),
35898
36159
  videoResolution: VideoResolutionEnum.optional().describe("Opcional para video."),
35899
- generateAiMusic: z141.boolean().optional().describe("Opcional para video."),
35900
- nVariants: z141.number().int().min(1).max(3).optional().describe("Opcional para imagen. N\xFAmero de im\xE1genes adicionales (1-3)."),
36160
+ generateAiMusic: z142.boolean().optional().describe("Opcional para video."),
36161
+ nVariants: z142.number().int().min(1).max(3).optional().describe("Opcional para imagen. N\xFAmero de im\xE1genes adicionales (1-3)."),
35901
36162
  imageStyle: ImageCreativeStyleEnum.optional().describe("Opcional para imagen."),
35902
36163
  creativeArchetype: ImageCreativeArchetypeEnum.optional().describe("Opcional para imagen."),
35903
36164
  visualTreatment: ImageCreativeVisualTreatmentEnum.optional().describe("Opcional para imagen."),
35904
36165
  aspectRatio: ImageCreativeAspectRatioEnum.optional().describe("Opcional para imagen."),
35905
- styleMix: z141.boolean().optional().describe("Opcional para imagen."),
36166
+ styleMix: z142.boolean().optional().describe("Opcional para imagen."),
35906
36167
  sectionKey: LandingSectionKeyEnum.optional().describe("Opcional para landing. Si se omite, se clasifica con IA a partir de instructions."),
35907
- instructions: z141.string().trim().min(1).max(2e3).optional().describe("Solicitud libre para una nueva secci\xF3n de landing. La IA la clasifica si no env\xEDas sectionKey.")
36168
+ instructions: z142.string().trim().min(1).max(2e3).optional().describe("Solicitud libre para una nueva secci\xF3n de landing. La IA la clasifica si no env\xEDas sectionKey.")
35908
36169
  }).strict();
35909
36170
  var CreateCreativeVariantInput = CreateCreativeVariantInputBase.superRefine((input, ctx) => {
35910
36171
  if ((input.kind === "video" || input.kind === "image") && !input.angle) {
35911
36172
  ctx.addIssue({
35912
- code: z141.ZodIssueCode.custom,
36173
+ code: z142.ZodIssueCode.custom,
35913
36174
  path: [
35914
36175
  "angle"
35915
36176
  ],
@@ -35918,7 +36179,7 @@ var CreateCreativeVariantInput = CreateCreativeVariantInputBase.superRefine((inp
35918
36179
  }
35919
36180
  if (input.kind === "landing" && !input.sectionKey && !input.instructions) {
35920
36181
  ctx.addIssue({
35921
- code: z141.ZodIssueCode.custom,
36182
+ code: z142.ZodIssueCode.custom,
35922
36183
  path: [
35923
36184
  "instructions"
35924
36185
  ],
@@ -35964,7 +36225,7 @@ var CreateCreativeVariantInput = CreateCreativeVariantInputBase.superRefine((inp
35964
36225
  for (const field of fieldsByKind[input.kind]) {
35965
36226
  if (input[field] !== void 0) {
35966
36227
  ctx.addIssue({
35967
- code: z141.ZodIssueCode.custom,
36228
+ code: z142.ZodIssueCode.custom,
35968
36229
  path: [
35969
36230
  field
35970
36231
  ],
@@ -36122,13 +36383,13 @@ function registerCreativeVariantTools(server, api) {
36122
36383
  __name(registerCreativeVariantTools, "registerCreativeVariantTools");
36123
36384
 
36124
36385
  // src/tools/script-review.ts
36125
- import { z as z142 } from "zod";
36386
+ import { z as z143 } from "zod";
36126
36387
  var getVideoScriptGuidance = getProductMakerCapabilityModelGuidance("get_video_script");
36127
36388
  var regenerateVideoScriptGuidance = getProductMakerCapabilityModelGuidance("regenerate_video_script");
36128
36389
  var approveVideoScriptGuidance = getProductMakerCapabilityModelGuidance("approve_video_script");
36129
- var GetVideoScriptInput = z142.object({
36130
- taskId: z142.string().min(1).describe("ID de la tarea (la misma que devolvi\xF3 create_product_task / generate_video_creative)."),
36131
- angleKey: z142.string().min(1).optional().describe('Clave persistida del \xE1ngulo cuyo guion se quiere leer (formato "angle_0", "angle_1", ...). Usa exactamente el angleKey recibido en reviewNextAction.calls al aprobar \xE1ngulos, incluso si s\xF3lo elegiste uno. Si se omite, el backend resuelve a "angle_0" literal; elegir el segundo candidato no crea angle_0. No derives esta clave del n\xFAmero visible del candidato.')
36390
+ var GetVideoScriptInput = z143.object({
36391
+ taskId: z143.string().min(1).describe("ID de la tarea (la misma que devolvi\xF3 create_product_task / generate_video_creative)."),
36392
+ angleKey: z143.string().min(1).optional().describe('Clave persistida del \xE1ngulo cuyo guion se quiere leer (formato "angle_0", "angle_1", ...). Usa exactamente el angleKey recibido en reviewNextAction.calls al aprobar \xE1ngulos, incluso si s\xF3lo elegiste uno. Si se omite, el backend resuelve a "angle_0" literal; elegir el segundo candidato no crea angle_0. No derives esta clave del n\xFAmero visible del candidato.')
36132
36393
  }).strict();
36133
36394
  var ApproveVideoScriptInput = ApproveVideoScriptCapabilityRequest;
36134
36395
  var RegenerateVideoScriptInput = RegenerateVideoScriptCapabilityRequest.extend({
@@ -36234,7 +36495,7 @@ Consulta get_task_status con esta tarea antes de reintentar. Si la tarea termin\
36234
36495
  __name(registerScriptReviewTools, "registerScriptReviewTools");
36235
36496
 
36236
36497
  // src/tools/connections.ts
36237
- import { z as z143 } from "zod";
36498
+ import { z as z144 } from "zod";
36238
36499
  var listShopifyShopsGuidance = getProductMakerCapabilityModelGuidance("list_shopify_shops");
36239
36500
  var listShopifyProductsGuidance = getProductMakerCapabilityModelGuidance("list_shopify_products");
36240
36501
  var listMetaAdAccountsGuidance = getProductMakerCapabilityModelGuidance("list_meta_ad_accounts");
@@ -36243,7 +36504,7 @@ var listTikTokAdvertisersGuidance = getProductMakerCapabilityModelGuidance("list
36243
36504
  function registerConnectionTools(server, api) {
36244
36505
  server.registerTool("list_shopify_shops", {
36245
36506
  description: listShopifyShopsGuidance.summary + " Si la lista est\xE1 vac\xEDa, dile al usuario que conecte una tienda en productmaker.app/integrations antes de intentar publicar.",
36246
- inputSchema: z143.object({}).shape,
36507
+ inputSchema: z144.object({}).shape,
36247
36508
  annotations: READS_INTERNAL
36248
36509
  }, async () => {
36249
36510
  try {
@@ -36255,10 +36516,10 @@ function registerConnectionTools(server, api) {
36255
36516
  });
36256
36517
  server.registerTool("list_shopify_products", {
36257
36518
  description: listShopifyProductsGuidance.summary + ' Usa el `id` devuelto junto con `publicationMode="existing"` en `publish_to_shopify`. Si `taskId` se proporciona, la respuesta tambi\xE9n indica el producto publicado actualmente para esa tarea y tienda. No modifica Shopify.',
36258
- inputSchema: z143.object({
36259
- shopDomain: z143.string().min(3).max(120),
36260
- query: z143.string().trim().max(120).optional(),
36261
- taskId: z143.string().uuid().optional()
36519
+ inputSchema: z144.object({
36520
+ shopDomain: z144.string().min(3).max(120),
36521
+ query: z144.string().trim().max(120).optional(),
36522
+ taskId: z144.string().uuid().optional()
36262
36523
  }).shape,
36263
36524
  annotations: READS_EXTERNAL
36264
36525
  }, async ({ shopDomain, query, taskId }) => {
@@ -36276,7 +36537,7 @@ function registerConnectionTools(server, api) {
36276
36537
  });
36277
36538
  server.registerTool("list_meta_ad_accounts", {
36278
36539
  description: listMetaAdAccountsGuidance.summary + " Para publicar, prefiere `list_meta_destinations`, que tambi\xE9n devuelve el portfolio y las p\xE1ginas disponibles. Si est\xE1 vac\xEDa, ind\xEDcale al usuario conectar Meta en productmaker.app/integrations.",
36279
- inputSchema: z143.object({}).shape,
36540
+ inputSchema: z144.object({}).shape,
36280
36541
  annotations: READS_EXTERNAL
36281
36542
  }, async () => {
36282
36543
  try {
@@ -36288,7 +36549,7 @@ function registerConnectionTools(server, api) {
36288
36549
  });
36289
36550
  server.registerTool("list_meta_destinations", {
36290
36551
  description: listMetaDestinationsGuidance.summary + " Es obligatorio llamar esta herramienta antes de publicar, mostrar las opciones y enviar al publicador el `portfolioId`, `adAccountId` y `pageId` elegidos; nunca elijas silenciosamente la primera p\xE1gina o cuenta. No modifica Meta.",
36291
- inputSchema: z143.object({}).shape,
36552
+ inputSchema: z144.object({}).shape,
36292
36553
  annotations: READS_EXTERNAL,
36293
36554
  _meta: {
36294
36555
  "openai/widgetAccessible": true
@@ -36303,7 +36564,7 @@ function registerConnectionTools(server, api) {
36303
36564
  });
36304
36565
  server.registerTool("list_tiktok_advertisers", {
36305
36566
  description: listTikTokAdvertisersGuidance.summary + " Si est\xE1 vac\xEDa, ind\xEDcale al usuario conectar TikTok en productmaker.app/integrations.",
36306
- inputSchema: z143.object({}).shape,
36567
+ inputSchema: z144.object({}).shape,
36307
36568
  annotations: READS_EXTERNAL
36308
36569
  }, async () => {
36309
36570
  try {
@@ -36317,7 +36578,7 @@ function registerConnectionTools(server, api) {
36317
36578
  __name(registerConnectionTools, "registerConnectionTools");
36318
36579
 
36319
36580
  // src/tools/actors.ts
36320
- import { z as z144 } from "zod";
36581
+ import { z as z145 } from "zod";
36321
36582
  var listActorsGuidance = getProductMakerCapabilityModelGuidance("list_actors");
36322
36583
  var actorVoiceGuidance = {
36323
36584
  capabilities: getProductMakerCapabilityModelGuidance("get_actor_voice_capabilities"),
@@ -36357,7 +36618,7 @@ function sanitizeVoiceResult(value) {
36357
36618
  __name(sanitizeVoiceResult, "sanitizeVoiceResult");
36358
36619
  function registerActorTools(server, api) {
36359
36620
  const actor = {
36360
- actorId: z144.string().min(1).max(64)
36621
+ actorId: z145.string().min(1).max(64)
36361
36622
  };
36362
36623
  const safe = /* @__PURE__ */ __name(async (request) => {
36363
36624
  try {
@@ -36386,8 +36647,8 @@ function registerActorTools(server, api) {
36386
36647
  inputSchema: {
36387
36648
  ...actor,
36388
36649
  draft: ActorVoiceDraftSchema,
36389
- requestKey: z144.string().trim().min(1).max(128),
36390
- acceptedQuoteId: z144.string().trim().min(1).max(128)
36650
+ requestKey: z145.string().trim().min(1).max(128),
36651
+ acceptedQuoteId: z145.string().trim().min(1).max(128)
36391
36652
  },
36392
36653
  annotations: CREATES_PAID
36393
36654
  }, async ({ actorId, ...body }) => safe(api.postJson(`/v1/actors/${encodeURIComponent(actorId)}/voice/previews`, body)));
@@ -36395,7 +36656,7 @@ function registerActorTools(server, api) {
36395
36656
  description: actorVoiceGuidance.get.summary,
36396
36657
  inputSchema: {
36397
36658
  ...actor,
36398
- candidateId: z144.string().min(1).max(64)
36659
+ candidateId: z145.string().min(1).max(64)
36399
36660
  },
36400
36661
  // Polling can resume the first paid submission of an accepted quote.
36401
36662
  // The immutable candidate/operation key keeps recovery idempotent.
@@ -36408,16 +36669,16 @@ function registerActorTools(server, api) {
36408
36669
  description: actorVoiceGuidance.approve.summary,
36409
36670
  inputSchema: {
36410
36671
  ...actor,
36411
- candidateId: z144.string().min(1).max(64),
36412
- expectedRevision: z144.number().int().positive(),
36413
- expectedApprovedRevision: z144.number().int().positive().nullable().optional()
36672
+ candidateId: z145.string().min(1).max(64),
36673
+ expectedRevision: z145.number().int().positive(),
36674
+ expectedApprovedRevision: z145.number().int().positive().nullable().optional()
36414
36675
  },
36415
36676
  // Replaces the selected voice and withdraws the old catalog grants.
36416
36677
  annotations: OVERWRITES_INTERNAL
36417
36678
  }, async ({ actorId, ...body }) => safe(api.postJson(`/v1/actors/${encodeURIComponent(actorId)}/voice/approve`, body)));
36418
36679
  server.registerTool("list_actors", {
36419
36680
  description: listActorsGuidance.summary + " Devuelve id, nombre, descripci\xF3n, g\xE9nero y voz privada del personaje. Cuando el personaje tiene una voz personalizada, `voice.identityId` y `voice.revision` identifican la versi\xF3n aprobada \u2014 conserva la revisi\xF3n al llamar `approve_actor_voice` (expectedApprovedRevision) para reemplazarla. Los IDs de proveedor nunca se exponen por MCP. Usa el `id` como `actorId` al crear o editar una tarea. Solo aparecen los personajes listos para usar; si la lista est\xE1 vac\xEDa, dile al usuario que cree uno en productmaker.app antes de intentar asignarlo. No modifica nada.",
36420
- inputSchema: z144.object({}).shape,
36681
+ inputSchema: z145.object({}).shape,
36421
36682
  annotations: READS_INTERNAL
36422
36683
  }, async () => {
36423
36684
  try {
@@ -36450,7 +36711,7 @@ function registerActorTools(server, api) {
36450
36711
  __name(registerActorTools, "registerActorTools");
36451
36712
 
36452
36713
  // src/tools/publish.ts
36453
- import { z as z145 } from "zod";
36714
+ import { z as z146 } from "zod";
36454
36715
 
36455
36716
  // src/tools/meta-publish-attempt-id.ts
36456
36717
  var META_PUBLISH_NAMESPACE = "7b19c9ac-b4eb-4db1-970e-c2f24ce30b98";
@@ -36489,40 +36750,40 @@ var publishToShopifyGuidance = getProductMakerCapabilityModelGuidance("publish_t
36489
36750
  var publishToMetaGuidance = getProductMakerCapabilityModelGuidance("publish_to_meta");
36490
36751
  var publishCreativesToMetaGuidance = getProductMakerCapabilityModelGuidance("publish_creatives_to_meta");
36491
36752
  var publishToTikTokGuidance = getProductMakerCapabilityModelGuidance("publish_to_tiktok");
36492
- var ShopifyInput = z145.object({
36493
- taskId: z145.string().min(1),
36494
- shopDomain: z145.string().min(1),
36753
+ var ShopifyInput = z146.object({
36754
+ taskId: z146.string().min(1),
36755
+ shopDomain: z146.string().min(1),
36495
36756
  /** Override: number of landing images to include. Default = ALL images the
36496
36757
  * task generated (the MCP fetches /v1/results first to count). Bounded
36497
36758
  * generously here; the backend validates each index against
36498
36759
  * landing.images.length and rejects out-of-range. */
36499
- numLandingImages: z145.number().int().min(1).max(20).optional(),
36500
- variantIndex: z145.number().int().min(0).optional(),
36760
+ numLandingImages: z146.number().int().min(1).max(20).optional(),
36761
+ variantIndex: z146.number().int().min(0).optional(),
36501
36762
  /** ASK THE USER before setting: "¿Tu producto tiene variantes (talla, color, etc.)?".
36502
36763
  * When true, the landing renders variant-picker pills; when false, it ships
36503
36764
  * a single-option product page. Defaults to false. */
36504
- showVariantPicker: z145.boolean().optional(),
36765
+ showVariantPicker: z146.boolean().optional(),
36505
36766
  /** Default `auto` updates the product previously published for this task and
36506
36767
  * shop. Use `existing` with targetProductId to choose another product, or
36507
36768
  * `new` only when the user explicitly wants a duplicate/new listing. */
36508
- publicationMode: z145.enum([
36769
+ publicationMode: z146.enum([
36509
36770
  "auto",
36510
36771
  "existing",
36511
36772
  "new"
36512
36773
  ]).optional(),
36513
36774
  /** Shopify product GID, numeric id, or admin product URL. Required when
36514
36775
  * publicationMode is `existing`. */
36515
- targetProductId: z145.string().trim().min(1).max(300).optional(),
36776
+ targetProductId: z146.string().trim().min(1).max(300).optional(),
36516
36777
  /** Add an announcement bar text above add-to-cart (optional). */
36517
- announcementBarEnabled: z145.boolean().optional(),
36518
- announcementBarText: z145.string().trim().min(1).max(SHOPIFY_ANNOUNCEMENT_BAR_MAX_LENGTH).optional(),
36778
+ announcementBarEnabled: z146.boolean().optional(),
36779
+ announcementBarText: z146.string().trim().min(1).max(SHOPIFY_ANNOUNCEMENT_BAR_MAX_LENGTH).optional(),
36519
36780
  commerceCorrection: PublishCommerceCorrectionSchema.optional().describe("Complete seller-confirmed commerce values, with the revision and fingerprint returned in recovery. Never invent missing prices or convert currency."),
36520
- confirmed: z145.boolean().optional().describe("Required as true only when submitting commerceCorrection, after showing its values and obtaining explicit seller confirmation.")
36781
+ confirmed: z146.boolean().optional().describe("Required as true only when submitting commerceCorrection, after showing its values and obtaining explicit seller confirmation.")
36521
36782
  });
36522
36783
  var ShopifyInputWithAnnouncementBarValidation = ShopifyInput.superRefine((value, ctx) => {
36523
36784
  if (value.commerceCorrection && value.confirmed !== true) {
36524
36785
  ctx.addIssue({
36525
- code: z145.ZodIssueCode.custom,
36786
+ code: z146.ZodIssueCode.custom,
36526
36787
  path: [
36527
36788
  "confirmed"
36528
36789
  ],
@@ -36531,7 +36792,7 @@ var ShopifyInputWithAnnouncementBarValidation = ShopifyInput.superRefine((value,
36531
36792
  }
36532
36793
  if (value.announcementBarEnabled && !value.announcementBarText) {
36533
36794
  ctx.addIssue({
36534
- code: z145.ZodIssueCode.custom,
36795
+ code: z146.ZodIssueCode.custom,
36535
36796
  path: [
36536
36797
  "announcementBarText"
36537
36798
  ],
@@ -36540,7 +36801,7 @@ var ShopifyInputWithAnnouncementBarValidation = ShopifyInput.superRefine((value,
36540
36801
  }
36541
36802
  if (!value.announcementBarEnabled && value.announcementBarText) {
36542
36803
  ctx.addIssue({
36543
- code: z145.ZodIssueCode.custom,
36804
+ code: z146.ZodIssueCode.custom,
36544
36805
  path: [
36545
36806
  "announcementBarText"
36546
36807
  ],
@@ -36554,90 +36815,90 @@ function parseShopifyInput(rawInput) {
36554
36815
  return parsed.data;
36555
36816
  }
36556
36817
  __name(parseShopifyInput, "parseShopifyInput");
36557
- var McpCampaignConfig = z145.object({
36818
+ var McpCampaignConfig = z146.object({
36558
36819
  /** Legacy USD amount. Meta retains its existing conversion; the TikTok
36559
36820
  * schema below restricts this input to USD advertiser accounts. */
36560
- dailyBudgetUsd: z145.number().positive().optional(),
36821
+ dailyBudgetUsd: z146.number().positive().optional(),
36561
36822
  /** ISO-3166-1 alpha-2 country codes or resolvable names (CountryIso2Schema
36562
36823
  * normalizes at validation). Meta accepts max 1 (single-country ad set).
36563
36824
  * TikTok takes only the first entry. */
36564
- targetCountries: z145.array(CountryIso2Schema).max(1).optional(),
36565
- campaignName: z145.string().trim().min(1).max(100).optional()
36825
+ targetCountries: z146.array(CountryIso2Schema).max(1).optional(),
36826
+ campaignName: z146.string().trim().min(1).max(100).optional()
36566
36827
  });
36567
36828
  var McpAdAccountId = MetaAdAccountId.describe('Meta ad account id. Accepts "act_1234567890" or the bare numeric id "1234567890".');
36568
- var MetaInput = z145.object({
36569
- taskId: z145.string().min(1),
36829
+ var MetaInput = z146.object({
36830
+ taskId: z146.string().min(1),
36570
36831
  /** Exact Meta Business portfolio selected from `list_meta_destinations`. */
36571
- portfolioId: z145.string().trim().min(1),
36832
+ portfolioId: z146.string().trim().min(1),
36572
36833
  /** Exact Facebook Page selected from `list_meta_destinations`. */
36573
- pageId: z145.string().trim().min(1),
36834
+ pageId: z146.string().trim().min(1),
36574
36835
  adAccountId: McpAdAccountId,
36575
- pixelId: z145.string().min(1).optional(),
36576
- landingUrl: z145.string().url().optional(),
36836
+ pixelId: z146.string().min(1).optional(),
36837
+ landingUrl: z146.string().url().optional(),
36577
36838
  /** Select which angles to publish by 0-based index. Omit to publish all
36578
36839
  * successful angles. Example: a task with 3 angles → `[0,2]` publishes #0 + #2. */
36579
- angleIndexes: z145.array(z145.number().int().nonnegative()).optional(),
36840
+ angleIndexes: z146.array(z146.number().int().nonnegative()).optional(),
36580
36841
  campaignConfig: McpCampaignConfig.optional(),
36581
36842
  /** Override the ad's primary text (body copy). If omitted, ProductMaker
36582
36843
  * uses the auto-generated copy attached to the task. Capped at 500 chars
36583
36844
  * (Meta's primary-text limit). */
36584
- primaryText: z145.string().trim().min(1).max(500).optional(),
36845
+ primaryText: z146.string().trim().min(1).max(500).optional(),
36585
36846
  /** Optional WhatsApp number. When set, the ad button opens a WhatsApp chat
36586
36847
  * (wa.me) instead of a landing page. */
36587
- phoneNumber: z145.string().min(8).max(32).optional()
36848
+ phoneNumber: z146.string().min(8).max(32).optional()
36588
36849
  });
36589
- var GroupedTaskSource = z145.object({
36590
- type: z145.literal("task"),
36591
- taskId: z145.string().min(1),
36592
- angleIndex: z145.number().int().min(0).optional(),
36593
- videoVariant: z145.enum([
36850
+ var GroupedTaskSource = z146.object({
36851
+ type: z146.literal("task"),
36852
+ taskId: z146.string().min(1),
36853
+ angleIndex: z146.number().int().min(0).optional(),
36854
+ videoVariant: z146.enum([
36594
36855
  "with_subtitles",
36595
36856
  "without_subtitles"
36596
36857
  ]).optional(),
36597
- imageVariantIndex: z145.number().int().min(0).optional()
36598
- });
36599
- var GroupedMediaSource = z145.object({
36600
- type: z145.literal("media"),
36601
- url: z145.string().url().optional().describe("Public HTTPS media URL. For a file on the user laptop, use request_media_upload on the hosted MCP instead."),
36602
- path: z145.string().min(1).optional().describe('Local file path only when this MCP runs over stdio. On the hosted MCP, call request_media_upload and then use source.type="asset".'),
36603
- thumbnailUrl: z145.string().url().optional(),
36604
- thumbnailPath: z145.string().min(1).optional()
36858
+ imageVariantIndex: z146.number().int().min(0).optional()
36859
+ });
36860
+ var GroupedMediaSource = z146.object({
36861
+ type: z146.literal("media"),
36862
+ url: z146.string().url().optional().describe("Public HTTPS media URL. For a file on the user laptop, use request_media_upload on the hosted MCP instead."),
36863
+ path: z146.string().min(1).optional().describe('Local file path only when this MCP runs over stdio. On the hosted MCP, call request_media_upload and then use source.type="asset".'),
36864
+ thumbnailUrl: z146.string().url().optional(),
36865
+ thumbnailPath: z146.string().min(1).optional()
36605
36866
  }).refine((value) => Boolean(value.url) !== Boolean(value.path), {
36606
36867
  message: "media source needs exactly one of `url` or `path`"
36607
36868
  }).refine((value) => !(value.thumbnailUrl && value.thumbnailPath), {
36608
36869
  message: "media source needs at most one of `thumbnailUrl` or `thumbnailPath`"
36609
36870
  });
36610
- var GroupedUploadedAssetSource = z145.object({
36611
- type: z145.literal("asset"),
36871
+ var GroupedUploadedAssetSource = z146.object({
36872
+ type: z146.literal("asset"),
36612
36873
  /** Returned by check_media_upload. ProductMaker resolves ownership server-side. */
36613
- assetId: z145.string().uuid(),
36614
- thumbnailAssetId: z145.string().uuid().optional()
36874
+ assetId: z146.string().uuid(),
36875
+ thumbnailAssetId: z146.string().uuid().optional()
36615
36876
  });
36616
- var GroupedCreative = z145.object({
36617
- type: z145.enum([
36877
+ var GroupedCreative = z146.object({
36878
+ type: z146.enum([
36618
36879
  "image",
36619
36880
  "video"
36620
36881
  ]),
36621
- source: z145.union([
36882
+ source: z146.union([
36622
36883
  GroupedTaskSource,
36623
36884
  GroupedMediaSource,
36624
36885
  GroupedUploadedAssetSource
36625
36886
  ]),
36626
- title: z145.string().max(120).optional(),
36627
- description: z145.string().max(500).optional(),
36887
+ title: z146.string().max(120).optional(),
36888
+ description: z146.string().max(500).optional(),
36628
36889
  cta: MetaCtaType.optional(),
36629
- creativeId: z145.string().min(1).optional()
36890
+ creativeId: z146.string().min(1).optional()
36630
36891
  });
36631
- var GroupedCampaignConfigBase = z145.object({
36892
+ var GroupedCampaignConfigBase = z146.object({
36632
36893
  campaignType: MetaCampaignType.default("sales"),
36633
36894
  optimizationMode: MetaOptimizationMode.default("advantage_plus"),
36634
36895
  budgetMode: MetaBudgetMode.optional(),
36635
- campaignDailyBudget: z145.number().int().positive().optional(),
36636
- adSets: z145.array(z145.object({
36637
- tempId: z145.string().min(1).max(80),
36638
- name: z145.string().min(1).max(100),
36639
- dailyBudget: z145.number().int().positive().optional(),
36640
- creativeIds: z145.array(z145.string().min(1)).min(1)
36896
+ campaignDailyBudget: z146.number().int().positive().optional(),
36897
+ adSets: z146.array(z146.object({
36898
+ tempId: z146.string().min(1).max(80),
36899
+ name: z146.string().min(1).max(100),
36900
+ dailyBudget: z146.number().int().positive().optional(),
36901
+ creativeIds: z146.array(z146.string().min(1)).min(1)
36641
36902
  })).min(1).max(CAMPAIGN_LIMITS.MAX_AD_SETS),
36642
36903
  /**
36643
36904
  * Valid values are 'landing' and 'whatsapp' — there is no 'website'. This
@@ -36645,17 +36906,17 @@ var GroupedCampaignConfigBase = z145.object({
36645
36906
  * opaque `invalid_literal` and made the WhatsApp destination — supported by
36646
36907
  * the API since #849 — unreachable from this tool.
36647
36908
  */
36648
- destination: z145.discriminatedUnion("type", [
36649
- z145.object({
36650
- type: z145.literal("landing"),
36651
- landingUrl: z145.string().url()
36909
+ destination: z146.discriminatedUnion("type", [
36910
+ z146.object({
36911
+ type: z146.literal("landing"),
36912
+ landingUrl: z146.string().url()
36652
36913
  }),
36653
- z145.object({
36654
- type: z145.literal("whatsapp"),
36655
- phoneNumber: z145.string().min(8).max(32)
36914
+ z146.object({
36915
+ type: z146.literal("whatsapp"),
36916
+ phoneNumber: z146.string().min(8).max(32)
36656
36917
  })
36657
36918
  ]),
36658
- saveAsDefault: z145.boolean().default(false),
36919
+ saveAsDefault: z146.boolean().default(false),
36659
36920
  budgetPolicy: MetaBudgetPolicy.optional()
36660
36921
  });
36661
36922
  function resolveBudgetMode(value) {
@@ -36669,7 +36930,7 @@ var GroupedCampaignConfig = GroupedCampaignConfigBase.superRefine((value, ctx) =
36669
36930
  const budgetMode = resolveBudgetMode(value);
36670
36931
  if (budgetMode === "campaign" && value.campaignDailyBudget === void 0) {
36671
36932
  ctx.addIssue({
36672
- code: z145.ZodIssueCode.custom,
36933
+ code: z146.ZodIssueCode.custom,
36673
36934
  path: [
36674
36935
  "campaignDailyBudget"
36675
36936
  ],
@@ -36680,7 +36941,7 @@ var GroupedCampaignConfig = GroupedCampaignConfigBase.superRefine((value, ctx) =
36680
36941
  value.adSets.forEach((adSet, index) => {
36681
36942
  if (adSet.dailyBudget === void 0) {
36682
36943
  ctx.addIssue({
36683
- code: z145.ZodIssueCode.custom,
36944
+ code: z146.ZodIssueCode.custom,
36684
36945
  path: [
36685
36946
  "adSets",
36686
36947
  index,
@@ -36695,25 +36956,25 @@ var GroupedCampaignConfig = GroupedCampaignConfigBase.superRefine((value, ctx) =
36695
36956
  ...value,
36696
36957
  budgetMode: resolveBudgetMode(value)
36697
36958
  }));
36698
- var PublishCreativesToMetaInputBase = z145.object({
36699
- creatives: z145.array(GroupedCreative).min(1).max(CAMPAIGN_LIMITS.MAX_CREATIVES),
36959
+ var PublishCreativesToMetaInputBase = z146.object({
36960
+ creatives: z146.array(GroupedCreative).min(1).max(CAMPAIGN_LIMITS.MAX_CREATIVES),
36700
36961
  /** Exact Meta Business portfolio selected from `list_meta_destinations`. */
36701
- portfolioId: z145.string().trim().min(1),
36962
+ portfolioId: z146.string().trim().min(1),
36702
36963
  /** Exact Facebook Page selected from `list_meta_destinations`. */
36703
- pageId: z145.string().trim().min(1),
36964
+ pageId: z146.string().trim().min(1),
36704
36965
  adAccountId: McpAdAccountId,
36705
- pixelId: z145.string().min(1).optional(),
36966
+ pixelId: z146.string().min(1).optional(),
36706
36967
  /** Required unless `campaignConfig.destination` is whatsapp or a creative
36707
36968
  * comes from a task (the API then lands on the published service landing
36708
36969
  * of its service tasks) — see the superRefine below. Mirrors the API's
36709
36970
  * `FacebookCreateCampaignFromTasksBodySchema`. */
36710
- landingUrl: z145.string().url().optional(),
36971
+ landingUrl: z146.string().url().optional(),
36711
36972
  /** ISO-2 code or resolvable country name — mirrors the API contract
36712
36973
  * (CountryIso2Schema), which normalizes names to alpha-2 at validation.
36713
36974
  * Omitted: the brand country of its service tasks (the API answers
36714
36975
  * META_NO_TARGET_COUNTRY when it has none); "US" for products. */
36715
36976
  targetCountry: CountryIso2Schema.optional(),
36716
- campaignName: z145.string().trim().min(1).max(100),
36977
+ campaignName: z146.string().trim().min(1).max(100),
36717
36978
  campaignConfig: GroupedCampaignConfig.optional()
36718
36979
  });
36719
36980
  var PublishCreativesToMetaInput = PublishCreativesToMetaInputBase.superRefine((value, ctx) => {
@@ -36721,7 +36982,7 @@ var PublishCreativesToMetaInput = PublishCreativesToMetaInputBase.superRefine((v
36721
36982
  const hasTaskCreative = value.creatives.some((creative) => creative.source.type === "task");
36722
36983
  if (destination?.type !== "whatsapp" && destination?.type !== "landing" && !value.landingUrl && !hasTaskCreative) {
36723
36984
  ctx.addIssue({
36724
- code: z145.ZodIssueCode.custom,
36985
+ code: z146.ZodIssueCode.custom,
36725
36986
  path: [
36726
36987
  "landingUrl"
36727
36988
  ],
@@ -36730,7 +36991,7 @@ var PublishCreativesToMetaInput = PublishCreativesToMetaInputBase.superRefine((v
36730
36991
  }
36731
36992
  if (destination?.type === "landing" && value.landingUrl && destination.landingUrl !== value.landingUrl) {
36732
36993
  ctx.addIssue({
36733
- code: z145.ZodIssueCode.custom,
36994
+ code: z146.ZodIssueCode.custom,
36734
36995
  path: [
36735
36996
  "campaignConfig",
36736
36997
  "destination",
@@ -36749,16 +37010,16 @@ var PublishCreativesToMetaInput = PublishCreativesToMetaInputBase.superRefine((v
36749
37010
  }
36750
37011
  return value;
36751
37012
  });
36752
- var PresignUploadResponseSchema = z145.object({
36753
- uploadUrl: z145.string().url().refine((value) => {
37013
+ var PresignUploadResponseSchema = z146.object({
37014
+ uploadUrl: z146.string().url().refine((value) => {
36754
37015
  const url2 = new URL(value);
36755
37016
  return !url2.username && !url2.password && url2.protocol === "https:";
36756
37017
  }, "presigned upload URL must be HTTPS without embedded credentials"),
36757
- publicUrl: z145.string().url().refine((value) => {
37018
+ publicUrl: z146.string().url().refine((value) => {
36758
37019
  const url2 = new URL(value);
36759
37020
  return !url2.username && !url2.password;
36760
37021
  }, "public media URL must not contain embedded credentials"),
36761
- stagingAssetId: z145.string().uuid()
37022
+ stagingAssetId: z146.string().uuid()
36762
37023
  });
36763
37024
  async function uploadResolvedMedia(api, media, assetRole) {
36764
37025
  if (!media.buffer) throw new ResolveMediaError("INVALID_INPUT", "resolved media has no bytes");
@@ -36784,14 +37045,14 @@ async function resolveAndUploadMedia(api, ref, expectedKind, assetRole, transpor
36784
37045
  }
36785
37046
  __name(resolveAndUploadMedia, "resolveAndUploadMedia");
36786
37047
  var TikTokMcpCampaignConfig = McpCampaignConfig.extend({
36787
- dailyBudgetUsd: z145.number().finite().positive().optional().describe("Legacy USD amount, supported only for USD advertiser accounts. No FX conversion."),
36788
- dailyBudget: z145.number().finite().positive().optional().describe("Daily budget in the selected advertiser currency major units; pass budgetCurrency together."),
37048
+ dailyBudgetUsd: z146.number().finite().positive().optional().describe("Legacy USD amount, supported only for USD advertiser accounts. No FX conversion."),
37049
+ dailyBudget: z146.number().finite().positive().optional().describe("Daily budget in the selected advertiser currency major units; pass budgetCurrency together."),
36789
37050
  budgetCurrency: TikTokBudgetCurrencySchema.optional(),
36790
37051
  destination: TikTokCampaignDestinationSchema.optional()
36791
37052
  }).superRefine((value, ctx) => {
36792
37053
  if (value.dailyBudgetUsd !== void 0 && (value.dailyBudget !== void 0 || value.budgetCurrency !== void 0)) {
36793
37054
  ctx.addIssue({
36794
- code: z145.ZodIssueCode.custom,
37055
+ code: z146.ZodIssueCode.custom,
36795
37056
  path: [
36796
37057
  "dailyBudgetUsd"
36797
37058
  ],
@@ -36800,7 +37061,7 @@ var TikTokMcpCampaignConfig = McpCampaignConfig.extend({
36800
37061
  }
36801
37062
  if (value.dailyBudget !== void 0 !== (value.budgetCurrency !== void 0)) {
36802
37063
  ctx.addIssue({
36803
- code: z145.ZodIssueCode.custom,
37064
+ code: z146.ZodIssueCode.custom,
36804
37065
  path: [
36805
37066
  "budgetCurrency"
36806
37067
  ],
@@ -36808,23 +37069,27 @@ var TikTokMcpCampaignConfig = McpCampaignConfig.extend({
36808
37069
  });
36809
37070
  }
36810
37071
  });
36811
- var TikTokInput = z145.object({
36812
- taskId: z145.string().min(1),
36813
- advertiserId: z145.string().min(1),
36814
- pixelId: z145.string().min(1).optional(),
36815
- landingUrl: z145.string().url().optional(),
36816
- angleIndexes: z145.array(z145.number().int().nonnegative()).optional(),
37072
+ var TikTokInput = z146.object({
37073
+ taskId: z146.string().min(1),
37074
+ advertiserId: z146.string().min(1),
37075
+ pixelId: z146.string().min(1).optional(),
37076
+ landingUrl: z146.string().url().optional(),
37077
+ angleIndexes: z146.array(z146.number().int().nonnegative()).optional(),
36817
37078
  campaignConfig: TikTokMcpCampaignConfig.optional(),
36818
37079
  /** Override the ad's description (TikTok's body copy). If omitted,
36819
37080
  * ProductMaker uses the auto-generated copy attached to the task. */
36820
- primaryText: z145.string().trim().min(1).max(500).optional()
37081
+ primaryText: z146.string().trim().min(1).max(500).optional()
36821
37082
  });
36822
37083
  function registerPublishTools(server, api, opts = {}) {
36823
37084
  const transport = opts.transport ?? "http";
36824
37085
  server.registerTool("publish_to_shopify", {
36825
37086
  description: publishToShopifyGuidance.summary + " Precondici\xF3n: la tarea debe estar en status='complete' \u2014 usa `get_task_status` antes. Por defecto `publicationMode=auto`: si esta tarea ya fue publicada en esa tienda, actualiza ESE MISMO producto y conserva URL, precio, variantes, inventario y SKU; si no, crea uno. Usa `existing` + `targetProductId` cuando el usuario elija otro producto y `new` s\xF3lo si pide crear uno nuevo. ANTES de llamar preg\xFAntale \"\xBFTu producto tiene variantes (talla, color, etc.)?\" y pasa `showVariantPicker: true` si responde que s\xED. Por defecto publica TODAS las im\xE1genes generadas; pasa `numLandingImages` s\xF3lo si el usuario pide menos. En code landings con una oferta expl\xEDcita de packs, ProductMaker s\xF3lo publica el selector si variantes reales de Shopify cobran los totales exactos; la respuesta explica si las cre\xF3, verific\xF3 o descart\xF3 por no existir ahorro. Si devuelve SHOPIFY_PUBLISH_INPUT_REQUIRED, muestra todos los problemas de recovery y solicita los importes faltantes. Solo env\xEDa commerceCorrection con su revisi\xF3n/huella y confirmed:true despu\xE9s de confirmar los valores exactos; nunca inventes importes, conviertas moneda ni elimines ofertas. Si adem\xE1s trae correctionRejected, lo que el vendedor confirm\xF3 fue rechazado: l\xE9ele `reason` ANTES de volver a pedir datos. Un contexto obsoleto requiere volver a revisar; open_editor (incluido SHOPIFY_PUBLISH_CORRECTION_NOT_CONVERGED) requiere el editor de ProductMaker y NO se reenv\xEDa la misma correcci\xF3n. SHOPIFY_PUBLISH_VERIFICATION_FAILED trae en `reason` el siguiente paso: reintenta una vez y si se repite sigue ese paso. Si devuelve SHOPIFY_THEME_SETUP_REQUIRED, el tema de la tienda no tiene el bloque Code Landing y nada se public\xF3: env\xEDa al usuario el themeEditorDeepLink del error para abrir el editor del tema de Shopify, p\xEDdele que agregue el bloque y pulse Guardar (una sola vez por tienda), y reintenta s\xF3lo despu\xE9s. Ante resultado incierto, verifica la tarea/tienda antes de volver a publicar, especialmente con new; commerceCorrectionSaved no prueba publicaci\xF3n. La respuesta es markdown con el link a la landing y al producto.",
36826
37087
  inputSchema: ShopifyInput.shape,
36827
- annotations: OVERWRITES_EXTERNAL
37088
+ // Publication reaches the public storefront, beyond private account data.
37089
+ annotations: {
37090
+ ...OVERWRITES_EXTERNAL,
37091
+ openWorldHint: true
37092
+ }
36828
37093
  }, async (input, extra) => {
36829
37094
  try {
36830
37095
  const parsed = parseShopifyInput(input);
@@ -36941,7 +37206,13 @@ function registerPublishTools(server, api, opts = {}) {
36941
37206
  server.registerTool("publish_creatives_to_meta", {
36942
37207
  description: publishCreativesToMetaGuidance.summary + ' Publica tus creativos en Meta sin gastar cr\xE9ditos de ProductMaker. El presupuesto de anuncios se paga directamente desde tu cuenta de Meta. Crear creativos o copy con IA sigue usando cr\xE9ditos. Si la petici\xF3n natural adjunta o menciona archivos locales, llama primero `request_media_upload`; tras OK usa cada resultado como `source.type="asset"` con su `assetId` y el `type` que coincide con `mediaKind`. No pidas path ni URL. Despu\xE9s llama `list_meta_destinations` y pregunta en el chat por el `portfolioId`, `adAccountId` y `pageId` exactos; el App es solo para archivos. Si la carga fue parcial, conserva los assets confirmados, procesa s\xF3lo los pendientes y publica \xFAnicamente assets confirmados; nunca inventes assets ni superes diez creativos. Las rutas locales s\xF3lo funcionan con MCP stdio. Los medios propios se eliminan despu\xE9s de publicar correctamente; si falla antes de enviar una mutaci\xF3n a Meta, quedan durante el TTL. Para videos la miniatura es opcional y ProductMaker puede generarla con FFmpeg. `campaignConfig.destination` acepta landing o WhatsApp. Para landing basta `campaignConfig.destination.landingUrl`; no dupliques la URL arriba. Con creativos de tarea de servicio puedes omitir `landingUrl` y `targetCountry`: se usan su landing de servicio publicada y el pa\xEDs de su marca; si falta alguno, pregunta al usuario (nunca asumas un pa\xEDs). Si omites `budgetMode`, se infiere `ad_set` cuando cada ad set trae `dailyBudget`, o `campaign` cuando env\xEDas `campaignDailyBudget`. No actives la campa\xF1a autom\xE1ticamente. Si el resultado es ambiguo, NO reenv\xEDes: revisa Meta Ads Manager.',
36943
37208
  inputSchema: PublishCreativesToMetaInputBase.shape,
36944
- annotations: CREATES_EXTERNAL
37209
+ // May fetch arbitrary public media URLs and consumes owned upload assets
37210
+ // after successful publication, even though the campaign starts paused.
37211
+ annotations: {
37212
+ ...CREATES_EXTERNAL,
37213
+ openWorldHint: true,
37214
+ destructiveHint: true
37215
+ }
36945
37216
  }, async (rawInput, extra) => {
36946
37217
  try {
36947
37218
  const parsed = PublishCreativesToMetaInput.safeParse(rawInput);
@@ -37177,39 +37448,40 @@ __name(toTikTokCampaignConfig, "toTikTokCampaignConfig");
37177
37448
 
37178
37449
  // src/tools/meta-upload.ts
37179
37450
  import { registerAppTool } from "@modelcontextprotocol/ext-apps/server";
37180
- import { z as z146 } from "zod";
37451
+ import { z as z147 } from "zod";
37181
37452
  var requestMediaUploadGuidance = getProductMakerCapabilityModelGuidance("request_media_upload");
37182
- var UploadSessionInputBase = z146.object({
37183
- mediaKind: z146.enum([
37453
+ var checkMediaUploadGuidance = getProductMakerCapabilityModelGuidance("check_media_upload");
37454
+ var UploadSessionInputBase = z147.object({
37455
+ mediaKind: z147.enum([
37184
37456
  "image",
37185
37457
  "video",
37186
37458
  "mixed"
37187
37459
  ]).default("mixed").describe("The kind of file the user will select. Use image for product references and landing pages, video for a single video, and mixed only when the consumer supports both."),
37188
- assetRole: z146.enum([
37460
+ assetRole: z147.enum([
37189
37461
  "media",
37190
37462
  "thumbnail"
37191
37463
  ]).default("media").describe("Use media for the creative itself; thumbnail only for an image thumbnail."),
37192
- uploadPurpose: z146.enum([
37464
+ uploadPurpose: z147.enum([
37193
37465
  "product_reference",
37194
37466
  "creative",
37195
37467
  "generic"
37196
37468
  ]).default("generic").describe("The user's purpose for these files. Use product_reference for a product photo used by tasks or landings, creative for media that will become an ad or creative, and generic only when neither applies."),
37197
- maxFiles: z146.number().int().min(1).max(10).optional().describe("Optional batch limit: image accepts 1-5, video exactly 1, and mixed 1-10.")
37469
+ maxFiles: z147.number().int().min(1).max(10).optional().describe("Optional batch limit: image accepts 1-5, video exactly 1, and mixed 1-10.")
37198
37470
  });
37199
- var OpenAiFileReference = z146.object({
37200
- download_url: z146.string().url().describe("Required by ChatGPT's openai/fileParams schema. The App prefers getFileDownloadUrl(file_id) for a fresh URL, and the server never fetches this value."),
37201
- file_id: z146.string().min(1),
37202
- mime_type: z146.string().min(1).optional(),
37203
- file_name: z146.string().min(1).optional()
37471
+ var OpenAiFileReference = z147.object({
37472
+ download_url: z147.string().url().describe("Required by ChatGPT's openai/fileParams schema. The App prefers getFileDownloadUrl(file_id) for a fresh URL, and the server never fetches this value."),
37473
+ file_id: z147.string().min(1),
37474
+ mime_type: z147.string().min(1).optional(),
37475
+ file_name: z147.string().min(1).optional()
37204
37476
  });
37205
37477
  var UploadSessionToolInputShape = {
37206
37478
  ...UploadSessionInputBase.shape,
37207
- files: z146.array(OpenAiFileReference).max(10).optional().describe("Optional OpenAI-hosted file references provided by ChatGPT. They are only metadata for the UI bridge and are never forwarded to ProductMaker.")
37479
+ files: z147.array(OpenAiFileReference).max(10).optional().describe("Optional OpenAI-hosted file references provided by ChatGPT. They are only metadata for the UI bridge and are never forwarded to ProductMaker.")
37208
37480
  };
37209
- var UploadSessionToolInput = z146.object(UploadSessionToolInputShape).superRefine((value, ctx) => {
37481
+ var UploadSessionToolInput = z147.object(UploadSessionToolInputShape).superRefine((value, ctx) => {
37210
37482
  if (value.assetRole === "thumbnail" && value.mediaKind !== "image") {
37211
37483
  ctx.addIssue({
37212
- code: z146.ZodIssueCode.custom,
37484
+ code: z147.ZodIssueCode.custom,
37213
37485
  path: [
37214
37486
  "mediaKind"
37215
37487
  ],
@@ -37218,7 +37490,7 @@ var UploadSessionToolInput = z146.object(UploadSessionToolInputShape).superRefin
37218
37490
  }
37219
37491
  if (value.mediaKind === "video" && value.maxFiles !== void 0 && value.maxFiles !== 1) {
37220
37492
  ctx.addIssue({
37221
- code: z146.ZodIssueCode.custom,
37493
+ code: z147.ZodIssueCode.custom,
37222
37494
  path: [
37223
37495
  "maxFiles"
37224
37496
  ],
@@ -37227,7 +37499,7 @@ var UploadSessionToolInput = z146.object(UploadSessionToolInputShape).superRefin
37227
37499
  }
37228
37500
  if (value.mediaKind === "image" && (value.maxFiles ?? 1) > 5) {
37229
37501
  ctx.addIssue({
37230
- code: z146.ZodIssueCode.custom,
37502
+ code: z147.ZodIssueCode.custom,
37231
37503
  path: [
37232
37504
  "maxFiles"
37233
37505
  ],
@@ -37235,38 +37507,38 @@ var UploadSessionToolInput = z146.object(UploadSessionToolInputShape).superRefin
37235
37507
  });
37236
37508
  }
37237
37509
  });
37238
- var UploadSessionResponse = z146.object({
37239
- sessionId: z146.string().uuid(),
37240
- uploadPageUrl: z146.string().url(),
37241
- mediaKind: z146.enum([
37510
+ var UploadSessionResponse = z147.object({
37511
+ sessionId: z147.string().uuid(),
37512
+ uploadPageUrl: z147.string().url(),
37513
+ mediaKind: z147.enum([
37242
37514
  "image",
37243
37515
  "video",
37244
37516
  "mixed"
37245
37517
  ]),
37246
- assetRole: z146.enum([
37518
+ assetRole: z147.enum([
37247
37519
  "media",
37248
37520
  "thumbnail"
37249
37521
  ]),
37250
- maxFiles: z146.number().int().positive(),
37251
- expiresAt: z146.string().datetime()
37522
+ maxFiles: z147.number().int().positive(),
37523
+ expiresAt: z147.string().datetime()
37252
37524
  });
37253
- var UploadStatusInput = z146.object({
37254
- sessionId: z146.string().uuid().describe("The sessionId returned by request_media_upload.")
37525
+ var UploadStatusInput = z147.object({
37526
+ sessionId: z147.string().uuid().describe("The sessionId returned by request_media_upload.")
37255
37527
  });
37256
- var UniversalUploadStatusResponse = z146.discriminatedUnion("status", [
37257
- z146.object({
37258
- status: z146.literal("pending"),
37259
- assets: z146.array(z146.never()).length(0)
37528
+ var UniversalUploadStatusResponse = z147.discriminatedUnion("status", [
37529
+ z147.object({
37530
+ status: z147.literal("pending"),
37531
+ assets: z147.array(z147.never()).length(0)
37260
37532
  }),
37261
- z146.object({
37262
- status: z146.literal("uploaded"),
37263
- assets: z146.array(z146.object({
37264
- assetId: z146.string().uuid(),
37265
- mediaKind: z146.enum([
37533
+ z147.object({
37534
+ status: z147.literal("uploaded"),
37535
+ assets: z147.array(z147.object({
37536
+ assetId: z147.string().uuid(),
37537
+ mediaKind: z147.enum([
37266
37538
  "image",
37267
37539
  "video"
37268
37540
  ]),
37269
- assetRole: z146.enum([
37541
+ assetRole: z147.enum([
37270
37542
  "media",
37271
37543
  "thumbnail"
37272
37544
  ])
@@ -37274,13 +37546,13 @@ var UniversalUploadStatusResponse = z146.discriminatedUnion("status", [
37274
37546
  })
37275
37547
  ]);
37276
37548
  var UniversalUploadStructuredResponse = UploadSessionResponse.extend({
37277
- uploadPurpose: z146.enum([
37549
+ uploadPurpose: z147.enum([
37278
37550
  "product_reference",
37279
37551
  "creative",
37280
37552
  "generic"
37281
37553
  ]),
37282
- checkToolName: z146.literal("check_media_upload"),
37283
- resolveEndpoint: z146.string().url()
37554
+ checkToolName: z147.literal("check_media_upload"),
37555
+ resolveEndpoint: z147.string().url()
37284
37556
  });
37285
37557
  function contextualizeUploadPageUrl(uploadPageUrl, uploadPurpose) {
37286
37558
  const url2 = new URL(uploadPageUrl);
@@ -37312,7 +37584,7 @@ __name(resolveEndpointForSession, "resolveEndpointForSession");
37312
37584
  function registerMediaUploadTools(server, api) {
37313
37585
  registerAppTool(server, "request_media_upload", {
37314
37586
  title: "Subir archivos",
37315
- description: requestMediaUploadGuidance.summary + " Cuando la foto o el video sea el siguiente dato faltante, ll\xE1malo en esa misma respuesta: no te limites a pedir que lo adjunte en el chat y esperar otro turno. Ll\xE1malo tambi\xE9n autom\xE1ticamente y antes de preguntar por campos faltantes adicionales cuando una petici\xF3n natural adjunte o mencione un archivo local, por ejemplo \u2018crea una landing con este producto\u2019, \u2018haz creativos con esta foto\u2019 o \u2018sube este video a Meta\u2019; hazlo aunque el usuario diga que todav\xEDa no generes ni publiques, porque esta herramienta solo prepara el archivo y no consume cr\xE9ditos, salvo que proh\xEDba expl\xEDcitamente subir, compartir o almacenar el archivo; no pidas una ruta local ni URL. Es la \xFAnica ruta de carga para create_product_task, generate_image_creatives, generate_video_creative, generate_free_video, generate_landing y publish_creatives_to_meta. Pasa las referencias de ChatGPT en files con download_url y file_id. Si otro host no puede proporcionar ambos, omite files para que el App abra el selector inline o el enlace de carga. Usa uploadPurpose=product_reference para fotos de producto y landings, creative para creativos o anuncios, y generic solo cuando ninguno aplique. Usa mediaKind=image para referencias de producto y landings; mixed solo cuando el consumidor acepte ambos tipos. El App NO te avisa cuando termina: en cuanto el usuario diga que ya subi\xF3 el archivo o que sigas, llama `check_media_upload` con este sessionId para obtener los assetId, y reanuda la intenci\xF3n original usando \xFAnicamente assetId y mediaKind, sin pedir que vuelva a subir ni mostrar datos t\xE9cnicos. El enlace externo es fallback solo si el App inline falla. Esta herramienta no genera, publica ni consume cr\xE9ditos.",
37587
+ description: requestMediaUploadGuidance.summary + " Prepara una sesi\xF3n privada de carga para usar im\xE1genes o videos nuevos en una operaci\xF3n de ProductMaker solicitada por la persona. El selector inline permite elegir archivos; el enlace seguro es una alternativa si ese selector no est\xE1 disponible. files acepta referencias del host con download_url y file_id; se puede omitir para usar el selector. uploadPurpose distingue product_reference, creative y generic. Las referencias de producto y landing usan mediaKind=image; mixed corresponde a consumidores compatibles con ambos tipos. La sesi\xF3n se consulta con check_media_upload despu\xE9s de que la persona termine. El resultado proporciona assetId y mediaKind para la operaci\xF3n original. No corresponde si ya hay un assetId reutilizable o si la persona proh\xEDbe subir, compartir o almacenar el archivo. No genera, publica ni consume cr\xE9ditos.",
37316
37588
  inputSchema: UploadSessionToolInputShape,
37317
37589
  annotations: CREATES_INTERNAL,
37318
37590
  outputSchema: UniversalUploadStructuredResponse.shape,
@@ -37353,7 +37625,7 @@ function registerMediaUploadTools(server, api) {
37353
37625
  }
37354
37626
  });
37355
37627
  server.registerTool("check_media_upload", {
37356
- description: "Comprueba la carga universal. Ll\xE1mala cuando el usuario diga que ya subi\xF3 sus archivos: es la \xFAnica forma de enterarte, porque el App no manda mensajes al chat. Si status=pending, el usuario todav\xEDa no ha terminado: d\xEDselo y espera, no inventes un assetId ni abras otra sesi\xF3n de carga. Si status=uploaded, conserva assets[] para la petici\xF3n anterior y pasa cada assetId \xFAnicamente a un consumidor compatible. Un asset de video solo sirve donde se admite video; las referencias de producto, imagen y landing requieren mediaKind=image. No muestres IDs t\xE9cnicos como JSON al usuario.",
37628
+ description: checkMediaUploadGuidance.summary + " Consulta el estado de una sesi\xF3n de carga existente. pending indica que todav\xEDa no hay archivos verificados; uploaded devuelve assets con assetId, mediaKind y assetRole. Cada archivo sirve \xFAnicamente para consumidores compatibles con su tipo. Es una lectura sin generaci\xF3n, publicaci\xF3n ni consumo de cr\xE9ditos.",
37357
37629
  inputSchema: UploadStatusInput.shape,
37358
37630
  annotations: READS_INTERNAL,
37359
37631
  _meta: {
@@ -37371,15 +37643,15 @@ function registerMediaUploadTools(server, api) {
37371
37643
  __name(registerMediaUploadTools, "registerMediaUploadTools");
37372
37644
 
37373
37645
  // src/tools/campaigns.ts
37374
- import { z as z147 } from "zod";
37646
+ import { z as z148 } from "zod";
37375
37647
  var getMetaCampaignStatusGuidance = getProductMakerCapabilityModelGuidance("get_meta_campaign_status");
37376
37648
  var getTikTokCampaignStatusGuidance = getProductMakerCapabilityModelGuidance("get_tiktok_campaign_status");
37377
- var MetaStatusInput = z147.object({
37378
- campaignId: z147.string().min(1).describe("Meta campaign ID returned by publish_to_meta.")
37649
+ var MetaStatusInput = z148.object({
37650
+ campaignId: z148.string().min(1).describe("Meta campaign ID returned by publish_to_meta.")
37379
37651
  });
37380
- var TikTokStatusInput = z147.object({
37381
- campaignId: z147.string().min(1).describe("TikTok campaign ID returned by publish_to_tiktok."),
37382
- advertiserId: z147.string().min(1).describe("The same advertiserId used to publish the campaign.")
37652
+ var TikTokStatusInput = z148.object({
37653
+ campaignId: z148.string().min(1).describe("TikTok campaign ID returned by publish_to_tiktok."),
37654
+ advertiserId: z148.string().min(1).describe("The same advertiserId used to publish the campaign.")
37383
37655
  });
37384
37656
  function registerCampaignTools(server, api) {
37385
37657
  server.registerTool("get_meta_campaign_status", {
@@ -37484,10 +37756,10 @@ function renderTikTokStatusMarkdown(r) {
37484
37756
  __name(renderTikTokStatusMarkdown, "renderTikTokStatusMarkdown");
37485
37757
 
37486
37758
  // ../../packages/product-economics/src/product-costing.ts
37487
- import { z as z149 } from "zod";
37759
+ import { z as z150 } from "zod";
37488
37760
 
37489
37761
  // ../../packages/product-economics/src/product-costing-countries.ts
37490
- import { z as z148 } from "zod";
37762
+ import { z as z149 } from "zod";
37491
37763
 
37492
37764
  // ../../packages/product-economics/src/product-costing-shipping-references.json
37493
37765
  var product_costing_shipping_references_default = {
@@ -37551,21 +37823,21 @@ var product_costing_shipping_references_default = {
37551
37823
  };
37552
37824
 
37553
37825
  // ../../packages/product-economics/src/product-costing-countries.ts
37554
- var ShippingReferenceSchema = z148.object({
37555
- reference: z148.number().positive(),
37556
- min: z148.number().nonnegative(),
37557
- max: z148.number().positive(),
37558
- updatedAt: z148.string().date(),
37559
- confidence: z148.enum([
37826
+ var ShippingReferenceSchema = z149.object({
37827
+ reference: z149.number().positive(),
37828
+ min: z149.number().nonnegative(),
37829
+ max: z149.number().positive(),
37830
+ updatedAt: z149.string().date(),
37831
+ confidence: z149.enum([
37560
37832
  "high",
37561
37833
  "medium",
37562
37834
  "medium-low"
37563
37835
  ]),
37564
- sources: z148.array(z148.string().url().startsWith("https://")).min(1).max(3)
37836
+ sources: z149.array(z149.string().url().startsWith("https://")).min(1).max(3)
37565
37837
  }).superRefine(({ reference, min, max }, context) => {
37566
37838
  if (min > reference || reference > max) {
37567
37839
  context.addIssue({
37568
- code: z148.ZodIssueCode.custom,
37840
+ code: z149.ZodIssueCode.custom,
37569
37841
  message: "shipping_reference_outside_range",
37570
37842
  path: [
37571
37843
  "reference"
@@ -37573,7 +37845,7 @@ var ShippingReferenceSchema = z148.object({
37573
37845
  });
37574
37846
  }
37575
37847
  });
37576
- var SHIPPING_REFERENCES = z148.record(ShippingReferenceSchema).parse(product_costing_shipping_references_default);
37848
+ var SHIPPING_REFERENCES = z149.record(ShippingReferenceSchema).parse(product_costing_shipping_references_default);
37577
37849
  var PRODUCT_COSTING_COUNTRY_CODES = Object.freeze(Object.keys(SHIPPING_REFERENCES));
37578
37850
  var SPANISH_LOCALE_COUNTRIES = /* @__PURE__ */ new Set([
37579
37851
  "MX",
@@ -37755,18 +38027,18 @@ function getProductCostingCountryName(country) {
37755
38027
  __name(getProductCostingCountryName, "getProductCostingCountryName");
37756
38028
 
37757
38029
  // ../../packages/product-economics/src/product-costing.ts
37758
- var ProductCostingInputSchema = z149.object({
37759
- country: z149.string().refine(isProductCostingCountryCode, "unsupported_country"),
37760
- supplierCost: z149.number().finite().max(Number.MAX_SAFE_INTEGER, "amount_too_large").positive("supplier_cost_positive"),
37761
- shippingCost: z149.number().finite().max(Number.MAX_SAFE_INTEGER, "amount_too_large").nonnegative("shipping_cost_nonnegative"),
37762
- returnRate: z149.number().finite().min(0, "rate_nonnegative").lt(1, "rate_below_one"),
37763
- cancellationRate: z149.number().finite().min(0, "rate_nonnegative").lt(1, "rate_below_one"),
37764
- adsManagerCpa: z149.number().finite().max(Number.MAX_SAFE_INTEGER, "amount_too_large").nonnegative("cpa_nonnegative"),
37765
- desiredMargin: z149.number().finite().min(0, "margin_nonnegative").lt(1, "margin_below_one")
38030
+ var ProductCostingInputSchema = z150.object({
38031
+ country: z150.string().refine(isProductCostingCountryCode, "unsupported_country"),
38032
+ supplierCost: z150.number().finite().max(Number.MAX_SAFE_INTEGER, "amount_too_large").positive("supplier_cost_positive"),
38033
+ shippingCost: z150.number().finite().max(Number.MAX_SAFE_INTEGER, "amount_too_large").nonnegative("shipping_cost_nonnegative"),
38034
+ returnRate: z150.number().finite().min(0, "rate_nonnegative").lt(1, "rate_below_one"),
38035
+ cancellationRate: z150.number().finite().min(0, "rate_nonnegative").lt(1, "rate_below_one"),
38036
+ adsManagerCpa: z150.number().finite().max(Number.MAX_SAFE_INTEGER, "amount_too_large").nonnegative("cpa_nonnegative"),
38037
+ desiredMargin: z150.number().finite().min(0, "margin_nonnegative").lt(1, "margin_below_one")
37766
38038
  }).superRefine(({ returnRate, cancellationRate }, context) => {
37767
38039
  if (returnRate + cancellationRate >= 1) {
37768
38040
  context.addIssue({
37769
- code: z149.ZodIssueCode.custom,
38041
+ code: z150.ZodIssueCode.custom,
37770
38042
  message: "delivery_rate_positive",
37771
38043
  path: [
37772
38044
  "cancellationRate"
@@ -37863,15 +38135,15 @@ function calculateProductCosting(rawInput) {
37863
38135
  __name(calculateProductCosting, "calculateProductCosting");
37864
38136
 
37865
38137
  // src/tools/product-costing.ts
37866
- import { z as z150 } from "zod";
38138
+ import { z as z151 } from "zod";
37867
38139
  var costProductGuidance = getProductMakerCapabilityModelGuidance("cost_product");
37868
- var optionalAmount = /* @__PURE__ */ __name((description) => z150.number().finite().max(Number.MAX_SAFE_INTEGER).nonnegative().optional().describe(description), "optionalAmount");
37869
- var optionalPercent = /* @__PURE__ */ __name((description) => z150.number().finite().min(0).lt(100).optional().describe(description), "optionalPercent");
37870
- var ProductCostingToolInput = z150.object({
37871
- country: z150.string().trim().toUpperCase().refine(isProductCostingCountryCode, {
38140
+ var optionalAmount = /* @__PURE__ */ __name((description) => z151.number().finite().max(Number.MAX_SAFE_INTEGER).nonnegative().optional().describe(description), "optionalAmount");
38141
+ var optionalPercent = /* @__PURE__ */ __name((description) => z151.number().finite().min(0).lt(100).optional().describe(description), "optionalPercent");
38142
+ var ProductCostingToolInput = z151.object({
38143
+ country: z151.string().trim().toUpperCase().refine(isProductCostingCountryCode, {
37872
38144
  message: "Pa\xEDs no soportado por ProductMaker."
37873
38145
  }).optional().describe("C\xF3digo ISO-2 de cualquiera de los pa\xEDses disponibles en ProductMaker. Omite para Colombia."),
37874
- supplierCost: z150.number().finite().max(Number.MAX_SAFE_INTEGER).positive().optional().describe("Costo real de una unidad con el proveedor, en la moneda del pa\xEDs. Requerido salvo que uses supplierCostCop para Colombia."),
38146
+ supplierCost: z151.number().finite().max(Number.MAX_SAFE_INTEGER).positive().optional().describe("Costo real de una unidad con el proveedor, en la moneda del pa\xEDs. Requerido salvo que uses supplierCostCop para Colombia."),
37875
38147
  supplierCostCop: optionalAmount("Alias anterior para costo del proveedor en Colombia. Prefiere supplierCost."),
37876
38148
  shippingCost: optionalAmount("Costo de un env\xEDo en la moneda del pa\xEDs. Omite para usar el valor gu\xEDa vigente."),
37877
38149
  shippingCostCop: optionalAmount("Alias anterior para flete en Colombia. Prefiere shippingCost."),
@@ -38034,7 +38306,7 @@ function formatPercent(value) {
38034
38306
  __name(formatPercent, "formatPercent");
38035
38307
 
38036
38308
  // src/tools/product-research.ts
38037
- import { z as z151 } from "zod";
38309
+ import { z as z152 } from "zod";
38038
38310
 
38039
38311
  // src/tools/product-research-idempotency.ts
38040
38312
  var RESEARCH_IDEMPOTENCY_WINDOW_MS = 2 * 60 * 60 * 1e3;
@@ -38405,17 +38677,17 @@ var TERMINAL2 = /* @__PURE__ */ new Set([
38405
38677
  ]);
38406
38678
  var POLL_INTERVAL_MS = 5e3;
38407
38679
  var MAX_WAIT_SECONDS = 120;
38408
- var FindInput = z151.object({
38680
+ var FindInput = z152.object({
38409
38681
  targetCountry: ResearchTargetCountryEnum.describe("Pa\xEDs objetivo. S\xF3lo CO, MX, PE, EC y CL tienen cobertura de investigaci\xF3n."),
38410
38682
  categoryKey: ResearchCategoryEnum.default("auto").describe('Categor\xEDa a investigar. Usa "auto" para que la IA elija seg\xFAn el mercado.'),
38411
- confirmed: z151.boolean().describe(`Ponlo en true SOLO despu\xE9s de que el usuario haya aceptado el cargo de ${PRICE_CREDITS} cr\xE9ditos. No lo asumas.`),
38412
- waitSeconds: z151.number().int().min(0).max(MAX_WAIT_SECONDS).optional().describe("Segundos a esperar el reporte antes de devolver el runId. M\xE1ximo 120.")
38683
+ confirmed: z152.boolean().describe(`Ponlo en true SOLO despu\xE9s de que el usuario haya aceptado el cargo de ${PRICE_CREDITS} cr\xE9ditos. No lo asumas.`),
38684
+ waitSeconds: z152.number().int().min(0).max(MAX_WAIT_SECONDS).optional().describe("Segundos a esperar el reporte antes de devolver el runId. M\xE1ximo 120.")
38413
38685
  });
38414
- var GetInput = z151.object({
38415
- runId: z151.string().uuid().describe("ID del run devuelto por find_winning_products.")
38686
+ var GetInput = z152.object({
38687
+ runId: z152.string().uuid().describe("ID del run devuelto por find_winning_products.")
38416
38688
  });
38417
- var ListInput = z151.object({
38418
- limit: z151.number().int().min(1).max(20).default(10)
38689
+ var ListInput = z152.object({
38690
+ limit: z152.number().int().min(1).max(20).default(10)
38419
38691
  });
38420
38692
  function textResult(text4) {
38421
38693
  return {
@@ -38554,17 +38826,17 @@ _Esta llamada no cobr\xF3 cr\xE9ditos: se reutiliz\xF3 una investigaci\xF3n reci
38554
38826
  __name(registerProductResearchTools, "registerProductResearchTools");
38555
38827
 
38556
38828
  // src/tools/dropshipping-projects.ts
38557
- import { z as z152 } from "zod";
38829
+ import { z as z153 } from "zod";
38558
38830
  var startDropshippingProjectGuidance = getProductMakerCapabilityModelGuidance("start_dropshipping_project");
38559
38831
  var costDropshippingProjectGuidance = getProductMakerCapabilityModelGuidance("cost_dropshipping_project");
38560
38832
  var guideDropshippingLaunchGuidance = getProductMakerCapabilityModelGuidance("guide_dropshipping_launch");
38561
- var ProjectId = z152.string().uuid();
38562
- var DropshippingProjectStatus = z152.enum([
38833
+ var ProjectId = z153.string().uuid();
38834
+ var DropshippingProjectStatus = z153.enum([
38563
38835
  "active",
38564
38836
  "completed",
38565
38837
  "archived"
38566
38838
  ]);
38567
- var DropshippingProjectPhase = z152.enum([
38839
+ var DropshippingProjectPhase = z153.enum([
38568
38840
  "research",
38569
38841
  "costing",
38570
38842
  "media",
@@ -38573,21 +38845,21 @@ var DropshippingProjectPhase = z152.enum([
38573
38845
  "advertising",
38574
38846
  "complete"
38575
38847
  ]);
38576
- var DropshippingRewardMilestone = z152.enum([
38848
+ var DropshippingRewardMilestone = z153.enum([
38577
38849
  "product_selected",
38578
38850
  "costing_saved",
38579
38851
  "product_media_ready",
38580
38852
  "creatives_completed",
38581
38853
  "campaign_created"
38582
38854
  ]);
38583
- var TargetCountry = z152.enum([
38855
+ var TargetCountry = z153.enum([
38584
38856
  "CO",
38585
38857
  "MX",
38586
38858
  "PE",
38587
38859
  "EC",
38588
38860
  "CL"
38589
38861
  ]);
38590
- var ResearchCategory = z152.enum([
38862
+ var ResearchCategory = z153.enum([
38591
38863
  "auto",
38592
38864
  "automotive",
38593
38865
  "kitchen",
@@ -38602,7 +38874,7 @@ var ResearchCategory = z152.enum([
38602
38874
  "garden",
38603
38875
  "cleaning"
38604
38876
  ]);
38605
- var HttpUrl3 = z152.string().url().max(2e3).refine((value) => {
38877
+ var HttpUrl3 = z153.string().url().max(2e3).refine((value) => {
38606
38878
  try {
38607
38879
  return [
38608
38880
  "http:",
@@ -38612,45 +38884,45 @@ var HttpUrl3 = z152.string().url().max(2e3).refine((value) => {
38612
38884
  return false;
38613
38885
  }
38614
38886
  }, "URL must use HTTP(S)");
38615
- var ResearchCandidateOrigin = z152.object({
38616
- type: z152.literal("research_candidate"),
38887
+ var ResearchCandidateOrigin = z153.object({
38888
+ type: z153.literal("research_candidate"),
38617
38889
  runId: ProjectId,
38618
- candidateSourceId: z152.string().min(1).max(160)
38890
+ candidateSourceId: z153.string().min(1).max(160)
38619
38891
  });
38620
- var TaskOrigin = z152.object({
38621
- type: z152.literal("task"),
38892
+ var TaskOrigin = z153.object({
38893
+ type: z153.literal("task"),
38622
38894
  taskId: ProjectId
38623
38895
  });
38624
- var ManualOrigin = z152.object({
38625
- type: z152.literal("manual"),
38896
+ var ManualOrigin = z153.object({
38897
+ type: z153.literal("manual"),
38626
38898
  clientRequestId: ProjectId,
38627
- country: z152.string().min(2).max(3).optional()
38899
+ country: z153.string().min(2).max(3).optional()
38628
38900
  });
38629
- var StartProjectInput = z152.object({
38630
- origin: z152.discriminatedUnion("type", [
38901
+ var StartProjectInput = z153.object({
38902
+ origin: z153.discriminatedUnion("type", [
38631
38903
  ResearchCandidateOrigin,
38632
38904
  TaskOrigin,
38633
38905
  ManualOrigin
38634
38906
  ]),
38635
- productName: z152.string().trim().min(1).max(240).optional(),
38907
+ productName: z153.string().trim().min(1).max(240).optional(),
38636
38908
  productUrl: HttpUrl3.optional(),
38637
- country: z152.string().min(2).max(3).optional(),
38638
- languageCode: z152.string().min(2).max(16).optional()
38909
+ country: z153.string().min(2).max(3).optional(),
38910
+ languageCode: z153.string().min(2).max(16).optional()
38639
38911
  }).strict();
38640
- var CostProjectInput = z152.object({
38912
+ var CostProjectInput = z153.object({
38641
38913
  projectId: ProjectId,
38642
- version: z152.number().int().positive().describe("Version le\xEDda del proyecto. Copia la \xFAltima que viste; no se la pidas al usuario."),
38643
- country: z152.string().trim().toUpperCase().refine(isProductCostingCountryCode, {
38914
+ version: z153.number().int().positive().describe("Version le\xEDda del proyecto. Copia la \xFAltima que viste; no se la pidas al usuario."),
38915
+ country: z153.string().trim().toUpperCase().refine(isProductCostingCountryCode, {
38644
38916
  message: "Pa\xEDs no soportado por ProductMaker."
38645
38917
  }).describe("C\xF3digo ISO-2 del pa\xEDs del proyecto. Usa el `targetCountry` que devuelve el proyecto."),
38646
- supplierCost: z152.number().finite().positive().describe("Costo real de una unidad con el proveedor, en la moneda del pa\xEDs. \xDANICO dato que debes pedirle al usuario: nunca lo inventes."),
38647
- shippingCost: z152.number().finite().nonnegative().optional().describe("Costo de un env\xEDo. Omite para usar el valor gu\xEDa del pa\xEDs en vez de inventarlo."),
38648
- returnRate: z152.number().finite().min(0).lt(1).optional().describe("Fracci\xF3n de pedidos devueltos (0.25 = 25%). Omite para usar el valor gu\xEDa del pa\xEDs."),
38649
- cancellationRate: z152.number().finite().min(0).lt(1).optional().describe("Fracci\xF3n de pedidos cancelados (0.15 = 15%). Omite para usar el valor gu\xEDa del pa\xEDs."),
38650
- adsManagerCpa: z152.number().finite().nonnegative().optional().describe("Costo por compra de Ads Manager. Omite para usar el valor gu\xEDa del pa\xEDs."),
38651
- desiredMargin: z152.number().finite().min(0).lt(1).optional().describe("Margen deseado (0.1 = 10%). Omite para usar el valor gu\xEDa del pa\xEDs.")
38652
- });
38653
- var GuideInput = z152.object({
38918
+ supplierCost: z153.number().finite().positive().describe("Costo real de una unidad con el proveedor, en la moneda del pa\xEDs. \xDANICO dato que debes pedirle al usuario: nunca lo inventes."),
38919
+ shippingCost: z153.number().finite().nonnegative().optional().describe("Costo de un env\xEDo. Omite para usar el valor gu\xEDa del pa\xEDs en vez de inventarlo."),
38920
+ returnRate: z153.number().finite().min(0).lt(1).optional().describe("Fracci\xF3n de pedidos devueltos (0.25 = 25%). Omite para usar el valor gu\xEDa del pa\xEDs."),
38921
+ cancellationRate: z153.number().finite().min(0).lt(1).optional().describe("Fracci\xF3n de pedidos cancelados (0.15 = 15%). Omite para usar el valor gu\xEDa del pa\xEDs."),
38922
+ adsManagerCpa: z153.number().finite().nonnegative().optional().describe("Costo por compra de Ads Manager. Omite para usar el valor gu\xEDa del pa\xEDs."),
38923
+ desiredMargin: z153.number().finite().min(0).lt(1).optional().describe("Margen deseado (0.1 = 10%). Omite para usar el valor gu\xEDa del pa\xEDs.")
38924
+ });
38925
+ var GuideInput = z153.object({
38654
38926
  projectId: ProjectId.optional()
38655
38927
  }).strict();
38656
38928
  var NEXT_ACTION_KINDS = [
@@ -38668,29 +38940,29 @@ var NEXT_ACTION_KINDS = [
38668
38940
  "repair_meta_campaign",
38669
38941
  "review_paused_campaign"
38670
38942
  ];
38671
- var DropshippingProjectNextAction = z152.discriminatedUnion("kind", NEXT_ACTION_KINDS.map((kind2) => z152.object({
38672
- kind: z152.literal(kind2),
38673
- href: z152.string().min(1)
38943
+ var DropshippingProjectNextAction = z153.discriminatedUnion("kind", NEXT_ACTION_KINDS.map((kind2) => z153.object({
38944
+ kind: z153.literal(kind2),
38945
+ href: z153.string().min(1)
38674
38946
  })));
38675
- var CostingInputSchema = z152.object({
38676
- country: z152.string().min(2).max(3),
38677
- supplierCost: z152.number().finite().positive(),
38678
- shippingCost: z152.number().finite().nonnegative(),
38679
- returnRate: z152.number().finite().min(0).lt(1),
38680
- cancellationRate: z152.number().finite().min(0).lt(1),
38681
- adsManagerCpa: z152.number().finite().nonnegative(),
38682
- desiredMargin: z152.number().finite().min(0).lt(1)
38947
+ var CostingInputSchema = z153.object({
38948
+ country: z153.string().min(2).max(3),
38949
+ supplierCost: z153.number().finite().positive(),
38950
+ shippingCost: z153.number().finite().nonnegative(),
38951
+ returnRate: z153.number().finite().min(0).lt(1),
38952
+ cancellationRate: z153.number().finite().min(0).lt(1),
38953
+ adsManagerCpa: z153.number().finite().nonnegative(),
38954
+ desiredMargin: z153.number().finite().min(0).lt(1)
38683
38955
  }).superRefine(({ returnRate, cancellationRate }, context) => {
38684
38956
  if (returnRate + cancellationRate >= 1) context.addIssue({
38685
- code: z152.ZodIssueCode.custom,
38957
+ code: z153.ZodIssueCode.custom,
38686
38958
  path: [
38687
38959
  "cancellationRate"
38688
38960
  ]
38689
38961
  });
38690
38962
  });
38691
- var FiniteCostingNumber = z152.number().finite();
38692
- var CostingResultSchema = z152.object({
38693
- country: z152.string().min(2).max(3),
38963
+ var FiniteCostingNumber = z153.number().finite();
38964
+ var CostingResultSchema = z153.object({
38965
+ country: z153.string().min(2).max(3),
38694
38966
  deliveryRate: FiniteCostingNumber,
38695
38967
  adjustedShippingCost: FiniteCostingNumber,
38696
38968
  adjustedCpa: FiniteCostingNumber,
@@ -38703,45 +38975,45 @@ var CostingResultSchema = z152.object({
38703
38975
  estimatedProfit: FiniteCostingNumber,
38704
38976
  actualMargin: FiniteCostingNumber,
38705
38977
  breakEvenAdsManagerCpa: FiniteCostingNumber,
38706
- wasPriceAutoAdjusted: z152.boolean(),
38707
- profitabilityStatus: z152.literal("healthy")
38708
- });
38709
- var RewardProgressSchema = z152.object({
38710
- campaignKey: z152.literal("dropshipping-launch-v1"),
38711
- totalCredits: z152.number().int().positive(),
38712
- earnedCredits: z152.number().int().nonnegative(),
38713
- milestones: z152.array(z152.object({
38978
+ wasPriceAutoAdjusted: z153.boolean(),
38979
+ profitabilityStatus: z153.literal("healthy")
38980
+ });
38981
+ var RewardProgressSchema = z153.object({
38982
+ campaignKey: z153.literal("dropshipping-launch-v1"),
38983
+ totalCredits: z153.number().int().positive(),
38984
+ earnedCredits: z153.number().int().nonnegative(),
38985
+ milestones: z153.array(z153.object({
38714
38986
  milestone: DropshippingRewardMilestone,
38715
- credits: z152.number().int().positive(),
38716
- available: z152.boolean().optional(),
38717
- earned: z152.boolean(),
38987
+ credits: z153.number().int().positive(),
38988
+ available: z153.boolean().optional(),
38989
+ earned: z153.boolean(),
38718
38990
  grantId: ProjectId.nullable(),
38719
- grantedAt: z152.string().datetime().nullable()
38991
+ grantedAt: z153.string().datetime().nullable()
38720
38992
  })).length(DropshippingRewardMilestone.options.length)
38721
38993
  });
38722
- var ProjectResponseSchema = z152.object({
38994
+ var ProjectResponseSchema = z153.object({
38723
38995
  id: ProjectId,
38724
38996
  taskId: ProjectId.nullable(),
38725
38997
  status: DropshippingProjectStatus,
38726
- originKey: z152.string().min(1),
38998
+ originKey: z153.string().min(1),
38727
38999
  researchRunId: ProjectId.nullable(),
38728
- candidateSourceId: z152.string().nullable(),
38729
- productName: z152.string().nullable(),
39000
+ candidateSourceId: z153.string().nullable(),
39001
+ productName: z153.string().nullable(),
38730
39002
  productUrl: HttpUrl3.nullable(),
38731
39003
  referenceImageUrl: HttpUrl3.nullable(),
38732
- targetCountry: z152.string().nullable(),
38733
- languageCode: z152.string().nullable(),
38734
- hasReferenceImage: z152.boolean(),
38735
- hasWinnerReferenceImage: z152.boolean(),
39004
+ targetCountry: z153.string().nullable(),
39005
+ languageCode: z153.string().nullable(),
39006
+ hasReferenceImage: z153.boolean(),
39007
+ hasWinnerReferenceImage: z153.boolean(),
38736
39008
  costingInput: CostingInputSchema.nullable(),
38737
39009
  costingResult: CostingResultSchema.nullable(),
38738
- costingVersion: z152.number().int().positive().nullable(),
38739
- version: z152.number().int().positive(),
39010
+ costingVersion: z153.number().int().positive().nullable(),
39011
+ version: z153.number().int().positive(),
38740
39012
  phase: DropshippingProjectPhase,
38741
39013
  nextAction: DropshippingProjectNextAction.nullable(),
38742
39014
  rewards: RewardProgressSchema,
38743
- createdAt: z152.string().datetime(),
38744
- updatedAt: z152.string().datetime()
39015
+ createdAt: z153.string().datetime(),
39016
+ updatedAt: z153.string().datetime()
38745
39017
  }).passthrough();
38746
39018
  var NEXT_ACTION_GUIDANCE = {
38747
39019
  cost_product: "Llama `cost_dropshipping_project` con el costo real del proveedor. Nunca lo inventes: p\xEDdeselo al usuario. Es el \xDANICO dato que debes pedirle: omite env\xEDo, devoluciones, cancelaciones, CPA y margen para que ProductMaker use los valores gu\xEDa del pa\xEDs, y luego dile al usuario cu\xE1les us\xF3 por si quiere corregirlos.",
@@ -38861,58 +39133,58 @@ function registerDropshippingProjectTools(server, api) {
38861
39133
  __name(registerDropshippingProjectTools, "registerDropshippingProjectTools");
38862
39134
 
38863
39135
  // src/tools/daily-control.ts
38864
- import { z as z153 } from "zod";
39136
+ import { z as z154 } from "zod";
38865
39137
  var getDailyControlGuidance = getProductMakerCapabilityModelGuidance("get_daily_control");
38866
39138
  var linkCampaignToProjectGuidance = getProductMakerCapabilityModelGuidance("link_campaign_to_project");
38867
39139
  var reportDailyReturnsGuidance = getProductMakerCapabilityModelGuidance("report_daily_returns");
38868
- var ProviderCampaignIdentifier = z153.string().min(1).max(64).regex(/^[A-Za-z0-9_-]+$/);
38869
- var DateOnly = z153.string().regex(/^\d{4}-\d{2}-\d{2}$/);
38870
- var DailyControlInput = z153.object({
39140
+ var ProviderCampaignIdentifier = z154.string().min(1).max(64).regex(/^[A-Za-z0-9_-]+$/);
39141
+ var DateOnly = z154.string().regex(/^\d{4}-\d{2}-\d{2}$/);
39142
+ var DailyControlInput = z154.object({
38871
39143
  date: DateOnly.optional().describe("D\xEDa final de la ventana (YYYY-MM-DD). Si se omite, la ventana termina AYER en UTC \u2014 nunca hoy, porque el d\xEDa en curso est\xE1 incompleto \u2014 y el servidor puede correrla un d\xEDa m\xE1s atr\xE1s si en la zona horaria de la tienda ese d\xEDa todav\xEDa no ha cerrado."),
38872
- days: z153.number().int().min(1).max(14).default(3).describe("Tama\xF1o de la ventana, 1 a 14 d\xEDas."),
38873
- projectId: z153.string().uuid().optional().describe("Opcional: limita el reporte a un solo proyecto en vez de todos los productos activos.")
39144
+ days: z154.number().int().min(1).max(14).default(3).describe("Tama\xF1o de la ventana, 1 a 14 d\xEDas."),
39145
+ projectId: z154.string().uuid().optional().describe("Opcional: limita el reporte a un solo proyecto en vez de todos los productos activos.")
38874
39146
  }).strict();
38875
- var LinkCampaignInput = z153.object({
38876
- projectId: z153.string().uuid(),
38877
- provider: z153.enum([
39147
+ var LinkCampaignInput = z154.object({
39148
+ projectId: z154.string().uuid(),
39149
+ provider: z154.enum([
38878
39150
  "meta",
38879
39151
  "tiktok"
38880
39152
  ]),
38881
39153
  accountId: ProviderCampaignIdentifier.describe("Cuenta publicitaria del proveedor. Para Meta el servidor la IGNORA y deriva la cuenta real de la campa\xF1a \u2014 se sigue pidiendo aqu\xED solo porque TikTok s\xED la necesita para resolver la campa\xF1a."),
38882
39154
  campaignId: ProviderCampaignIdentifier
38883
39155
  }).strict();
38884
- var ReportReturnsInput = z153.object({
38885
- projectId: z153.string().uuid(),
39156
+ var ReportReturnsInput = z154.object({
39157
+ projectId: z154.string().uuid(),
38886
39158
  date: DateOnly,
38887
- returnedOrders: z153.number().int().min(0).max(1e5),
38888
- note: z153.string().trim().max(280).optional()
38889
- }).strict();
38890
- var DailyControlStatusMirror = DailyControlStatusEnum.or(z153.string());
38891
- var DailyControlLightMirror = DailyControlLightEnum.or(z153.string());
38892
- var DailyControlSourceMirror = DailyControlSourceEnum.or(z153.string());
38893
- var DailyControlVerdictMirror = DailyControlVerdictEnum.or(z153.string());
38894
- var DailyControlDayMirror = z153.object({
38895
- date: z153.string(),
38896
- returnedOrders: z153.number(),
39159
+ returnedOrders: z154.number().int().min(0).max(1e5),
39160
+ note: z154.string().trim().max(280).optional()
39161
+ }).strict();
39162
+ var DailyControlStatusMirror = DailyControlStatusEnum.or(z154.string());
39163
+ var DailyControlLightMirror = DailyControlLightEnum.or(z154.string());
39164
+ var DailyControlSourceMirror = DailyControlSourceEnum.or(z154.string());
39165
+ var DailyControlVerdictMirror = DailyControlVerdictEnum.or(z154.string());
39166
+ var DailyControlDayMirror = z154.object({
39167
+ date: z154.string(),
39168
+ returnedOrders: z154.number(),
38897
39169
  returnedSource: DailyControlSourceMirror,
38898
39170
  cancelledSource: DailyControlSourceMirror,
38899
- hasEstimates: z153.boolean(),
39171
+ hasEstimates: z154.boolean(),
38900
39172
  light: DailyControlLightMirror
38901
39173
  }).passthrough();
38902
- var DailyControlProductMirror = z153.object({
38903
- projectId: z153.string().uuid(),
38904
- productName: z153.string().nullable(),
39174
+ var DailyControlProductMirror = z154.object({
39175
+ projectId: z154.string().uuid(),
39176
+ productName: z154.string().nullable(),
38905
39177
  status: DailyControlStatusMirror,
38906
- statusMessage: z153.string().nullable(),
38907
- days: z153.array(DailyControlDayMirror),
39178
+ statusMessage: z154.string().nullable(),
39179
+ days: z154.array(DailyControlDayMirror),
38908
39180
  verdict: DailyControlVerdictMirror.nullable(),
38909
- rationale: z153.string().nullable()
39181
+ rationale: z154.string().nullable()
38910
39182
  }).passthrough();
38911
- var DailyControlResponseMirror = z153.object({
38912
- since: z153.string(),
38913
- until: z153.string(),
38914
- products: z153.array(DailyControlProductMirror),
38915
- archivedProjectSpend: z153.array(z153.unknown())
39183
+ var DailyControlResponseMirror = z154.object({
39184
+ since: z154.string(),
39185
+ until: z154.string(),
39186
+ products: z154.array(DailyControlProductMirror),
39187
+ archivedProjectSpend: z154.array(z154.unknown())
38916
39188
  }).passthrough();
38917
39189
  var MCP_WARNINGS_KEY = "_mcpDailyControlWarnings";
38918
39190
  function withWarnings(response) {
@@ -38988,7 +39260,7 @@ __name(registerDailyControlTools, "registerDailyControlTools");
38988
39260
  import { createHash as createHash4 } from "crypto";
38989
39261
  import { readFile, stat } from "fs/promises";
38990
39262
  import { basename as basename2, isAbsolute, resolve as resolve3 } from "path";
38991
- import { z as z154 } from "zod";
39263
+ import { z as z155 } from "zod";
38992
39264
  var getProductControlGuidance = getProductMakerCapabilityModelGuidance("get_product_control");
38993
39265
  var listImportableProductsGuidance = getProductMakerCapabilityModelGuidance("list_importable_products");
38994
39266
  var importProductControlsGuidance = getProductMakerCapabilityModelGuidance("import_product_controls");
@@ -39010,42 +39282,42 @@ var listLinkableCampaignsGuidance = getProductMakerCapabilityModelGuidance("list
39010
39282
  var linkProductCampaignGuidance = getProductMakerCapabilityModelGuidance("link_product_campaign");
39011
39283
  var deleteMonthlyExpenseGuidance = getProductMakerCapabilityModelGuidance("delete_monthly_expense");
39012
39284
  var removeProductControlGuidance = getProductMakerCapabilityModelGuidance("remove_product_control");
39013
- var DateOnly2 = z154.string().regex(/^\d{4}-\d{2}-\d{2}$/);
39285
+ var DateOnly2 = z155.string().regex(/^\d{4}-\d{2}-\d{2}$/);
39014
39286
  var IMPORT_BATCH_MAX2 = 50;
39015
- var ProductControlListInput = z154.object({
39016
- days: z154.number().int().min(1).max(14).default(3).describe("Tama\xF1o de la ventana, 1 a 14 d\xEDas."),
39287
+ var ProductControlListInput = z155.object({
39288
+ days: z155.number().int().min(1).max(14).default(3).describe("Tama\xF1o de la ventana, 1 a 14 d\xEDas."),
39017
39289
  date: DateOnly2.optional().describe("D\xEDa final de la ventana (YYYY-MM-DD). Si se omite, termina en el \xFAltimo d\xEDa cerrado \u2014 nunca hoy, que est\xE1 incompleto."),
39018
- shopDomain: z154.string().min(1).optional().describe("Tienda, si tiene m\xE1s de una conectada.")
39290
+ shopDomain: z155.string().min(1).optional().describe("Tienda, si tiene m\xE1s de una conectada.")
39019
39291
  }).strict();
39020
- var ImportableProductsInput = z154.object({
39021
- q: z154.string().trim().max(120).optional().describe("B\xFAsqueda por t\xEDtulo. Sin esto un cat\xE1logo grande es inrecorrible."),
39022
- after: z154.string().max(512).optional().describe("Cursor de la p\xE1gina anterior, tal como vino en la respuesta."),
39023
- shopDomain: z154.string().min(1).optional()
39292
+ var ImportableProductsInput = z155.object({
39293
+ q: z155.string().trim().max(120).optional().describe("B\xFAsqueda por t\xEDtulo. Sin esto un cat\xE1logo grande es inrecorrible."),
39294
+ after: z155.string().max(512).optional().describe("Cursor de la p\xE1gina anterior, tal como vino en la respuesta."),
39295
+ shopDomain: z155.string().min(1).optional()
39024
39296
  }).strict();
39025
- var ImportProductControlsInput = z154.object({
39026
- shopifyProductIds: z154.array(z154.string().min(1).max(64)).min(1).max(IMPORT_BATCH_MAX2).describe("Identificadores tal como vinieron de list_importable_products. NO los inventes ni los deduzcas del t\xEDtulo."),
39297
+ var ImportProductControlsInput = z155.object({
39298
+ shopifyProductIds: z155.array(z155.string().min(1).max(64)).min(1).max(IMPORT_BATCH_MAX2).describe("Identificadores tal como vinieron de list_importable_products. NO los inventes ni los deduzcas del t\xEDtulo."),
39027
39299
  // Obligatorio, como en el endpoint: con dos tiendas conectadas, importar
39028
39300
  // sin decir a cuál archivaría el producto bajo la tienda equivocada.
39029
- shopDomain: z154.string().min(1)
39301
+ shopDomain: z155.string().min(1)
39030
39302
  }).strict();
39031
- var SetProductCostingInput = z154.object({
39303
+ var SetProductCostingInput = z155.object({
39032
39304
  /* La identidad, de UNA forma: la del producto nuestro, o la pareja
39033
39305
  tienda + id de Shopify. El esquema canónico también las admite juntas,
39034
39306
  pero pedirlas por separado evita que el modelo emita una mezcla que
39035
39307
  diga dos productos a la vez. */
39036
- productControlId: z154.string().uuid().optional().describe("El id nuestro (lo devuelven get_product_control y create_manual_product). Obligatorio para productos manuales o importados sin tienda."),
39037
- manualSellingPrice: z154.number().positive().nullable().optional().describe("Precio de venta \u2014 SOLO con productControlId: para el producto sin tienda no hay otra verdad. En un producto de Shopify NO se manda: el precio lo manda la tienda."),
39038
- shopDomain: z154.string().min(1).optional(),
39039
- shopifyProductId: z154.string().min(1).max(64).optional(),
39040
- targetCountry: z154.string().length(2).describe("Pa\xEDs de venta en ISO-2, que fija moneda y referencia de flete."),
39041
- supplierCost: z154.number().finite().positive().describe("Lo que le cuesta el producto al proveedor. Nunca cero."),
39042
- shippingCost: z154.number().finite().nonnegative(),
39043
- returnRate: z154.number().finite().min(0).lt(1).describe("Proporci\xF3n 0\u20131, no porcentaje: 0.15 es quince por ciento."),
39044
- cancellationRate: z154.number().finite().min(0).lt(1)
39045
- }).strict();
39046
- var Count = z154.number().int().min(0).max(1e5).nullable().optional();
39047
- var ReportProductCountsInput = z154.object({
39048
- productControlId: z154.string().uuid().describe("El identificador que devuelve get_product_control."),
39308
+ productControlId: z155.string().uuid().optional().describe("El id nuestro (lo devuelven get_product_control y create_manual_product). Obligatorio para productos manuales o importados sin tienda."),
39309
+ manualSellingPrice: z155.number().positive().nullable().optional().describe("Precio de venta \u2014 SOLO con productControlId: para el producto sin tienda no hay otra verdad. En un producto de Shopify NO se manda: el precio lo manda la tienda."),
39310
+ shopDomain: z155.string().min(1).optional(),
39311
+ shopifyProductId: z155.string().min(1).max(64).optional(),
39312
+ targetCountry: z155.string().length(2).describe("Pa\xEDs de venta en ISO-2, que fija moneda y referencia de flete."),
39313
+ supplierCost: z155.number().finite().positive().describe("Lo que le cuesta el producto al proveedor. Nunca cero."),
39314
+ shippingCost: z155.number().finite().nonnegative(),
39315
+ returnRate: z155.number().finite().min(0).lt(1).describe("Proporci\xF3n 0\u20131, no porcentaje: 0.15 es quince por ciento."),
39316
+ cancellationRate: z155.number().finite().min(0).lt(1)
39317
+ }).strict();
39318
+ var Count = z155.number().int().min(0).max(1e5).nullable().optional();
39319
+ var ReportProductCountsInput = z155.object({
39320
+ productControlId: z155.string().uuid().describe("El identificador que devuelve get_product_control."),
39049
39321
  date: DateOnly2,
39050
39322
  orders: Count.describe("Pedidos que entraron ese d\xEDa."),
39051
39323
  cancelledOrders: Count,
@@ -39055,64 +39327,64 @@ var ReportProductCountsInput = z154.object({
39055
39327
  }).strict();
39056
39328
  var MAX_ORDERS_FILE_BYTES = 8 * 1024 * 1024;
39057
39329
  var SyncShopifyOrdersInput = SyncShopifyOrdersSchema;
39058
- var ImportOrdersFileInput = z154.object({
39330
+ var ImportOrdersFileInput = z155.object({
39059
39331
  /** Ruta ABSOLUTA en la máquina de quien usa el MCP. */
39060
- path: z154.string().min(1),
39061
- operationId: z154.string().uuid().optional(),
39332
+ path: z155.string().min(1),
39333
+ operationId: z155.string().uuid().optional(),
39062
39334
  /** Solo mirar: devuelve el resumen y no escribe nada. */
39063
- dryRun: z154.boolean().optional()
39335
+ dryRun: z155.boolean().optional()
39064
39336
  }).strict();
39065
- var CreateOperationInput = z154.object({
39066
- countryCode: z154.string().length(2),
39067
- label: z154.string().trim().min(1).max(60).optional()
39337
+ var CreateOperationInput = z155.object({
39338
+ countryCode: z155.string().length(2),
39339
+ label: z155.string().trim().min(1).max(60).optional()
39068
39340
  }).strict();
39069
- var MergeProductsInput = z154.object({
39070
- sourceId: z154.string().uuid(),
39071
- targetId: z154.string().uuid()
39341
+ var MergeProductsInput = z155.object({
39342
+ sourceId: z155.string().uuid(),
39343
+ targetId: z155.string().uuid()
39072
39344
  }).strict();
39073
- var SetMonthlyExpenseInput = z154.object({
39074
- month: z154.string().regex(/^\d{4}-(0[1-9]|1[0-2])$/).optional(),
39345
+ var SetMonthlyExpenseInput = z155.object({
39346
+ month: z155.string().regex(/^\d{4}-(0[1-9]|1[0-2])$/).optional(),
39075
39347
  label: SaveMonthlyExpenseSchema.shape.label,
39076
- amount: z154.number().finite().min(0),
39077
- operationId: z154.string().uuid().optional()
39348
+ amount: z155.number().finite().min(0),
39349
+ operationId: z155.string().uuid().optional()
39078
39350
  }).strict();
39079
- var SetAdSpendInput = z154.object({
39080
- month: z154.string().regex(/^\d{4}-(0[1-9]|1[0-2])$/).optional(),
39081
- operationId: z154.string().uuid().optional(),
39351
+ var SetAdSpendInput = z155.object({
39352
+ month: z155.string().regex(/^\d{4}-(0[1-9]|1[0-2])$/).optional(),
39353
+ operationId: z155.string().uuid().optional(),
39082
39354
  /** null RETIRA la corrección; cero afirma que no pautó. */
39083
- amount: z154.number().finite().min(0).nullable()
39355
+ amount: z155.number().finite().min(0).nullable()
39084
39356
  }).strict();
39085
- var GetFinancesInput = z154.object({
39086
- month: z154.string().regex(/^\d{4}-(0[1-9]|1[0-2])$/).optional().describe("Mes en YYYY-MM. Si se omite, el mes en curso. Solo mes calendario."),
39087
- operationId: z154.string().uuid().optional().describe("El negocio, si tiene mas de uno. Con uno solo no hace falta decirlo.")
39357
+ var GetFinancesInput = z155.object({
39358
+ month: z155.string().regex(/^\d{4}-(0[1-9]|1[0-2])$/).optional().describe("Mes en YYYY-MM. Si se omite, el mes en curso. Solo mes calendario."),
39359
+ operationId: z155.string().uuid().optional().describe("El negocio, si tiene mas de uno. Con uno solo no hace falta decirlo.")
39088
39360
  }).strict();
39089
39361
  var GetDropiStatusInput = DropiCountryQuerySchema.extend({
39090
39362
  countryCode: DropiCountrySchema.optional().describe("Pa\xEDs de la cuenta (ISO-2). Si se omite, el servidor responde por Colombia (CO)."),
39091
- connectionId: z154.string().uuid().optional().describe("Una cuenta concreta cuando el pa\xEDs tiene varias. Sale de la lista de conexiones; nunca se inventa.")
39363
+ connectionId: z155.string().uuid().optional().describe("Una cuenta concreta cuando el pa\xEDs tiene varias. Sale de la lista de conexiones; nunca se inventa.")
39092
39364
  });
39093
39365
  var SyncDropiOrdersInput = SyncDropiFinancialSchema;
39094
39366
  var CreateManualProductInput = CreateManualProductControlSchema.innerType().omit({
39095
39367
  requestId: true
39096
39368
  });
39097
- var ListUnmatchedProductsInput = z154.object({
39098
- operationId: z154.string().uuid().optional().describe("El negocio, si tiene mas de uno. Con uno solo no hace falta decirlo.")
39369
+ var ListUnmatchedProductsInput = z155.object({
39370
+ operationId: z155.string().uuid().optional().describe("El negocio, si tiene mas de uno. Con uno solo no hace falta decirlo.")
39099
39371
  }).strict();
39100
39372
  var ResolveUnmatchedProductInput = ResolveUnmatchedProductSchema;
39101
- var ListLinkableCampaignsInput = z154.object({}).strict();
39102
- var LinkProductCampaignInput = z154.object({
39103
- provider: z154.enum([
39373
+ var ListLinkableCampaignsInput = z155.object({}).strict();
39374
+ var LinkProductCampaignInput = z155.object({
39375
+ provider: z155.enum([
39104
39376
  "meta",
39105
39377
  "tiktok"
39106
39378
  ]).describe("La red publicitaria de la campa\xF1a."),
39107
- accountId: z154.string().min(1).max(64).regex(/^[A-Za-z0-9_-]+$/).describe("La cuenta publicitaria. Sale de list_linkable_campaigns; nunca se inventa."),
39108
- campaignId: z154.string().min(1).max(64).regex(/^[A-Za-z0-9_-]+$/).describe("La campa\xF1a. Sale de list_linkable_campaigns; nunca se inventa."),
39109
- productControlId: z154.string().uuid().optional().describe("El id del producto en su control financiero."),
39110
- shopDomain: z154.string().min(1).optional().describe("La tienda del producto; con shopifyProductId es la otra forma de decir cu\xE1l es."),
39111
- shopifyProductId: z154.string().min(1).optional().describe("El id del producto en su tienda. Va junto a shopDomain.")
39379
+ accountId: z155.string().min(1).max(64).regex(/^[A-Za-z0-9_-]+$/).describe("La cuenta publicitaria. Sale de list_linkable_campaigns; nunca se inventa."),
39380
+ campaignId: z155.string().min(1).max(64).regex(/^[A-Za-z0-9_-]+$/).describe("La campa\xF1a. Sale de list_linkable_campaigns; nunca se inventa."),
39381
+ productControlId: z155.string().uuid().optional().describe("El id del producto en su control financiero."),
39382
+ shopDomain: z155.string().min(1).optional().describe("La tienda del producto; con shopifyProductId es la otra forma de decir cu\xE1l es."),
39383
+ shopifyProductId: z155.string().min(1).optional().describe("El id del producto en su tienda. Va junto a shopDomain.")
39112
39384
  }).strict();
39113
39385
  var DeleteMonthlyExpenseInput = DeleteMonthlyExpenseSchema;
39114
- var RemoveProductControlInput = z154.object({
39115
- productControlId: z154.string().uuid().describe("El id del producto en su control. Sale del listado; nunca se inventa.")
39386
+ var RemoveProductControlInput = z155.object({
39387
+ productControlId: z155.string().uuid().describe("El id del producto en su control. Sale del listado; nunca se inventa.")
39116
39388
  }).strict();
39117
39389
  function manualProductRequestId(input) {
39118
39390
  const digest3 = createHash4("sha256").update(JSON.stringify(input, [
@@ -39453,28 +39725,28 @@ function registerProductControlTools(server, api, { transport = "http" } = {}) {
39453
39725
  __name(registerProductControlTools, "registerProductControlTools");
39454
39726
 
39455
39727
  // src/tools/market-study-estimate.ts
39456
- import { z as z155 } from "zod";
39728
+ import { z as z156 } from "zod";
39457
39729
  var estimateMarketStudyGuidance = getProductMakerCapabilityModelGuidance("estimate_market_study_cost");
39458
- var ProductTaskOutputEnum = z155.enum([
39730
+ var ProductTaskOutputEnum = z156.enum([
39459
39731
  "landing",
39460
39732
  "video",
39461
39733
  "image"
39462
39734
  ]);
39463
- var EstimateMarketStudyInput = z155.object({
39464
- kind: z155.enum([
39735
+ var EstimateMarketStudyInput = z156.object({
39736
+ kind: z156.enum([
39465
39737
  "video",
39466
39738
  "product_task"
39467
39739
  ]).describe('"video" para generate_video_creative; "product_task" para create_product_task.'),
39468
- videoAngleCount: z155.number().int().min(1).max(3).describe("Los mismos 1, 2 o 3 videos que vas a pedir al crear."),
39469
- durationSeconds: z155.number().int().min(4).max(30).default(15).describe("La misma duraci\xF3n que enviar\xE1s al crear."),
39740
+ videoAngleCount: z156.number().int().min(1).max(3).describe("Los mismos 1, 2 o 3 videos que vas a pedir al crear."),
39741
+ durationSeconds: z156.number().int().min(4).max(30).default(15).describe("La misma duraci\xF3n que enviar\xE1s al crear."),
39470
39742
  videoStructure: VideoStructureEnum.default("single_take"),
39471
39743
  videoQuality: VideoQualityEnum.default("economy"),
39472
39744
  videoResolution: VideoResolutionEnum.default("480p"),
39473
- productImageCount: z155.number().int().min(1).max(5).optional().describe("Cantidad de fotos del producto que adjuntar\xE1s (1-5)."),
39745
+ productImageCount: z156.number().int().min(1).max(5).optional().describe("Cantidad de fotos del producto que adjuntar\xE1s (1-5)."),
39474
39746
  outputs: ProductTaskOutputEnum.array().min(1).max(3).optional().describe('Solo para kind="product_task": los mismos outputs de create_product_task. Default: todos.'),
39475
- generateAiMusic: z155.boolean().optional(),
39747
+ generateAiMusic: z156.boolean().optional(),
39476
39748
  generateAiEdit: AiEditRequestSchema.optional(),
39477
- replicateWinnerWhenFound: z155.boolean().optional().describe("La misma preferencia que enviar\xE1s al crear. false excluye la reserva de r\xE9plica del ganador de la cotizaci\xF3n con estudio.")
39749
+ replicateWinnerWhenFound: z156.boolean().optional().describe("La misma preferencia que enviar\xE1s al crear. false excluye la reserva de r\xE9plica del ganador de la cotizaci\xF3n con estudio.")
39478
39750
  }).strict();
39479
39751
  function buildMarketStudyEstimateRequests(input) {
39480
39752
  const quotesVideo = input.kind === "video" || (input.outputs ?? [
@@ -39596,11 +39868,11 @@ __name(registerMarketStudyEstimateTools, "registerMarketStudyEstimateTools");
39596
39868
  // src/guidance/productmaker-guidance.ts
39597
39869
  var START_DROPSHIPPING_PROMPT_NAME = "start_dropshipping";
39598
39870
  var PRODUCTMAKER_SERVER_INSTRUCTIONS = [
39599
- "Whenever a ProductMaker request needs a new image or video and no reusable ProductMaker assetId is available, call request_media_upload immediately in the same response\u2014whether media is the next missing input or the user attached, pasted, mentioned, or has the file locally. The inline uploader is how ProductMaker asks for the file. Never merely ask the user to attach or upload it in chat and wait for a later turn. Call it before asking for any missing business inputs, even when the user says not to generate or publish yet: uploading only stages the file and does not generate, publish, or consume credits, unless the user explicitly says not to upload, share, or store the file. Treat natural requests such as 'create a landing with this product', 'make creatives with this photo', or 'upload this video to Meta' as upload intent; never require the user to name the uploader.",
39871
+ "ProductMaker accepts new image or video inputs through request_media_upload, which opens an inline file picker or accepts supported host file references. It creates a private staging session and does not generate media, publish, or consume credits. Existing ProductMaker assetId values can be reused without another upload. A user's explicit refusal to upload, share, or store media takes precedence.",
39600
39872
  "For requests that do not need a new media upload, understand the user's intent before choosing a ProductMaker tool.",
39601
39873
  "For every concrete ProductMaker request to calculate how much to charge, profitability, or margin for a dropshipping product, you must call cost_product after collecting its missing country and real supplier cost. Never calculate or reproduce ProductMaker's costing formula yourself; answer from the tool result.",
39602
- "For remote MCP hosts, never ask for a local path, CDN URL, bytes, or base64. Pass supported host file references to request_media_upload; otherwise let its inline App open the file picker. The external upload page is fallback only.",
39603
- "After the upload App sends its OK continuation, resume the original request with the verified assetId values, do not ask for another upload, and ask only for remaining business inputs in chat. The host-inserted continuation may contain opaque asset IDs; do not repeat them in narrative prose or expose session IDs, tokens, private URLs, file names, or JSON handoff payloads.",
39874
+ "Remote file inputs use supported host file references or the inline picker. Local filesystem paths are available only to local stdio clients. The secure upload page is a fallback when the inline picker is unavailable.",
39875
+ "check_media_upload reads an existing session after the user reports completion. An uploaded result supplies owner-scoped assetId and mediaKind values for compatible ProductMaker operations; a pending result indicates that upload is unfinished. The upload App does not send chat messages. Session tokens and private handoff details are not user-facing content.",
39604
39876
  "Keep focused requests direct: creatives, landing pages, Shopify publication, Meta publication, and status checks do not require a DropshippingProject. Never search for, reuse, infer, or mention a DropshippingProject unless the user explicitly asks to continue the guided journey.",
39605
39877
  "Use the guided journey only for an explicit end-to-end launch.",
39606
39878
  "In guided mode, create a project only after a product is chosen, then call guide_dropshipping_launch after every mutation and follow its nextAction.",
@@ -39672,7 +39944,7 @@ function createServer(opts) {
39672
39944
  signal: opts.abortSignal
39673
39945
  });
39674
39946
  registerProductMakerPrompts(server);
39675
- registerMediaUploadAppResource(server, api.publicUrl("/v1/uploaded-assets/sessions/resolve"));
39947
+ registerMediaUploadAppResource(server, api.publicUrl("/v1/uploaded-assets/sessions/resolve"), opts.mediaUploadStorageOrigins);
39676
39948
  registerTaskResultsAppResource(server, proxyBaseUrl);
39677
39949
  registerProductCostingTools(server);
39678
39950
  registerProductResearchTools(server, api);
@@ -39706,30 +39978,6 @@ function createServer(opts) {
39706
39978
  }
39707
39979
  __name(createServer, "createServer");
39708
39980
 
39709
- // src/validate-api-url.ts
39710
- function assertSafePublicBaseUrl(envName, url2) {
39711
- let u;
39712
- try {
39713
- u = new URL(url2);
39714
- } catch {
39715
- throw new Error(`${envName} is not a valid URL: ${url2}`);
39716
- }
39717
- if (u.protocol !== "https:") {
39718
- throw new Error(`${envName} must use https (got ${u.protocol})`);
39719
- }
39720
- if (u.username || u.password || u.search || u.hash) {
39721
- throw new Error(`${envName} must not contain credentials, query parameters, or fragments`);
39722
- }
39723
- if (isPrivateHost(u.hostname)) {
39724
- throw new Error(`${envName} must not point to a private/loopback host: ${u.hostname}`);
39725
- }
39726
- }
39727
- __name(assertSafePublicBaseUrl, "assertSafePublicBaseUrl");
39728
- function assertSafeApiBaseUrl(url2) {
39729
- assertSafePublicBaseUrl("PM_API_URL", url2);
39730
- }
39731
- __name(assertSafeApiBaseUrl, "assertSafeApiBaseUrl");
39732
-
39733
39981
  // src/stdio.ts
39734
39982
  async function main() {
39735
39983
  const apiKey = process.env.PM_API_KEY;
@@ -39747,7 +39995,8 @@ async function main() {
39747
39995
  const server = createServer({
39748
39996
  credential: new StaticApiKeyCredential(apiKey),
39749
39997
  apiBaseUrl,
39750
- transport: "stdio"
39998
+ transport: "stdio",
39999
+ mediaUploadStorageOrigins: parseMediaUploadStorageOrigins(process.env.PM_MEDIA_UPLOAD_STORAGE_ORIGINS)
39751
40000
  });
39752
40001
  await server.connect(new StdioServerTransport());
39753
40002
  }