@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/http.js CHANGED
@@ -3837,7 +3837,19 @@ var identity = {
3837
3837
  requestDigest: digest,
3838
3838
  nativeMessagesBudget: NativeMessagesBudgetSchema.optional()
3839
3839
  };
3840
- var ModelRouteAttemptRequestSchema = z4.object(identity).strict();
3840
+ var ModelRouteAttemptRequestSchema = z4.object({
3841
+ ...identity,
3842
+ /** Trusted proxy evidence of a pure reasoning cap. Recovery only: this never
3843
+ * grants a changed body permission to reuse an older paid execution. Absent
3844
+ * on historical entries; retained on new entries to identify normalized work.
3845
+ */
3846
+ reasoningNormalization: z4.object({
3847
+ version: z4.literal(1),
3848
+ originalCallId: id,
3849
+ originalRequestDigest: digest,
3850
+ originalNativeMessagesBudget: NativeMessagesBudgetSchema.optional()
3851
+ }).strict().optional()
3852
+ }).strict();
3841
3853
  var ModelRouteCostSchema = z4.discriminatedUnion("source", [
3842
3854
  z4.object({
3843
3855
  source: z4.literal("unknown")
@@ -20905,7 +20917,7 @@ var CAPABILITY_MODEL_GUIDANCE = {
20905
20917
  ]
20906
20918
  },
20907
20919
  generate_free_video: {
20908
- summary: "Genera UN video libre con prompt/guion exacto, opcionalmente con imagen de referencia.",
20920
+ summary: "Creates custom-script video using paid credits; legacy 'free' means free-form.",
20909
20921
  useWhen: [
20910
20922
  "genera un video libre creativo",
20911
20923
  "la persona pide un video libre con un prompt o guion espec\xEDfico"
@@ -30375,11 +30387,11 @@ var OAuthOboCredential = class {
30375
30387
  // src/guidance/productmaker-guidance.ts
30376
30388
  var START_DROPSHIPPING_PROMPT_NAME = "start_dropshipping";
30377
30389
  var PRODUCTMAKER_SERVER_INSTRUCTIONS = [
30378
- "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.",
30390
+ "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.",
30379
30391
  "For requests that do not need a new media upload, understand the user's intent before choosing a ProductMaker tool.",
30380
30392
  "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.",
30381
- "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.",
30382
- "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.",
30393
+ "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.",
30394
+ "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.",
30383
30395
  "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.",
30384
30396
  "Use the guided journey only for an explicit end-to-end launch.",
30385
30397
  "In guided mode, create a project only after a product is chosen, then call guide_dropshipping_launch after every mutation and follow its nextAction.",
@@ -30861,197 +30873,270 @@ var ApiClient = class {
30861
30873
  };
30862
30874
 
30863
30875
  // src/index.ts
30864
- var MCP_VERSION = "12.2.6";
30876
+ var MCP_VERSION = "12.2.7";
30865
30877
 
30866
30878
  // src/meta-upload-app.ts
30867
30879
  import { readFileSync } from "fs";
30868
- import { registerAppResource, RESOURCE_MIME_TYPE } from "@modelcontextprotocol/ext-apps/server";
30869
- var MEDIA_UPLOAD_APP_RESOURCE_URI = "ui://media-upload/v1.html";
30870
- var MEDIA_UPLOAD_APP_NAME = "ProductMaker media upload";
30871
- var MEDIA_UPLOAD_APP_HTML_URL = new URL("./ui/meta-upload-app.html", import.meta.url);
30872
- var MEDIA_UPLOAD_APP_CSP = {
30873
- connectDomains: [
30874
- "https://api.productmaker.app",
30875
- "https://productmaker.app",
30876
- "https://www.productmaker.app",
30877
- "https://*.r2.cloudflarestorage.com",
30878
- "https://*.oaiusercontent.com"
30879
- ]
30880
- };
30881
- function buildMediaUploadAppCsp(resolveEndpoint) {
30882
- const resolveOrigin = new URL(resolveEndpoint).origin;
30883
- return {
30884
- connectDomains: [
30885
- .../* @__PURE__ */ new Set([
30886
- ...MEDIA_UPLOAD_APP_CSP.connectDomains,
30887
- resolveOrigin
30888
- ])
30889
- ]
30890
- };
30891
- }
30892
- __name(buildMediaUploadAppCsp, "buildMediaUploadAppCsp");
30893
- function readMediaUploadAppHtml(htmlUrl = MEDIA_UPLOAD_APP_HTML_URL) {
30880
+ import { z as z132 } from "zod";
30881
+
30882
+ // src/public-https.ts
30883
+ import { lookup } from "dns/promises";
30884
+ import https from "https";
30885
+ import { isIP } from "net";
30886
+ import { createGunzip, createInflate, createBrotliDecompress } from "zlib";
30887
+
30888
+ // ../../packages/utils/src/ip-classification.ts
30889
+ import net from "net";
30890
+ function parseIPv4(ip) {
30891
+ const parts = ip.split(".");
30892
+ if (parts.length !== 4) return null;
30893
+ const nums = [
30894
+ Number(parts[0]),
30895
+ Number(parts[1]),
30896
+ Number(parts[2]),
30897
+ Number(parts[3])
30898
+ ];
30899
+ if (nums.some((n) => !Number.isInteger(n) || n < 0 || n > 255)) return null;
30900
+ return nums;
30901
+ }
30902
+ __name(parseIPv4, "parseIPv4");
30903
+ function classifyPrivateIPv4(ip, includeAdditionalRanges) {
30904
+ const p = parseIPv4(ip);
30905
+ if (!p) return false;
30906
+ const [a, b] = p;
30907
+ 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);
30908
+ }
30909
+ __name(classifyPrivateIPv4, "classifyPrivateIPv4");
30910
+ function isPrivateIPv4(ip) {
30911
+ return classifyPrivateIPv4(ip, true);
30912
+ }
30913
+ __name(isPrivateIPv4, "isPrivateIPv4");
30914
+ function is6to4PrivateIPv4(ipv6) {
30915
+ const groups = ipv6.split(":");
30916
+ if (groups.length < 3) return false;
30917
+ return isPrivateIPv4Words(groups[1] || "0", groups[2] || "0");
30918
+ }
30919
+ __name(is6to4PrivateIPv4, "is6to4PrivateIPv4");
30920
+ function isPrivateIPv4Words(high, low) {
30921
+ const hi = parseInt(high, 16);
30922
+ const lo = parseInt(low, 16);
30923
+ if (Number.isNaN(hi) || Number.isNaN(lo)) return false;
30924
+ const a = hi >> 8 & 255;
30925
+ const b = hi & 255;
30926
+ const c = lo >> 8 & 255;
30927
+ const d = lo & 255;
30928
+ return isPrivateIPv4(`${a}.${b}.${c}.${d}`);
30929
+ }
30930
+ __name(isPrivateIPv4Words, "isPrivateIPv4Words");
30931
+ function isPrivateIPv6(ip) {
30932
+ let normalized;
30894
30933
  try {
30895
- return readFileSync(htmlUrl, "utf8");
30896
- } catch (error) {
30897
- const message = error instanceof Error ? error.message : String(error);
30898
- throw new Error(`Missing packaged media upload app HTML at ${htmlUrl.pathname}. Rebuild @productmaker/mcp so ${MEDIA_UPLOAD_APP_RESOURCE_URI} is bundled. Original error: ${message}`);
30934
+ normalized = new URL(`http://[${ip.split("%", 1)[0]}]/`).hostname.slice(1, -1).toLowerCase();
30935
+ } catch {
30936
+ return false;
30899
30937
  }
30938
+ if (normalized === "::" || /^::[0-9a-f]+$/.test(normalized)) return true;
30939
+ if (normalized.startsWith("64:ff9b:")) return true;
30940
+ if (/^fe[89a-f][0-9a-f]:/.test(normalized)) return true;
30941
+ if (/^f[cd][0-9a-f]{2}:/.test(normalized)) return true;
30942
+ if (normalized.startsWith("ff")) return true;
30943
+ if (/^2001:0*:/.test(normalized)) return true;
30944
+ if (normalized.startsWith("2001:db8:")) return true;
30945
+ if (normalized.startsWith("2002:") && is6to4PrivateIPv4(normalized)) return true;
30946
+ const embedded = /^::(?:ffff:(?:0:)?)?([0-9a-f]+):([0-9a-f]+)$/.exec(normalized);
30947
+ if (embedded?.[1] && embedded[2]) return isPrivateIPv4Words(embedded[1], embedded[2]);
30948
+ return false;
30900
30949
  }
30901
- __name(readMediaUploadAppHtml, "readMediaUploadAppHtml");
30902
- function renderMediaUploadAppHtml(resolveEndpoint) {
30903
- const encodedEndpoint = JSON.stringify(resolveEndpoint).slice(1, -1).replaceAll("<", "\\u003c");
30904
- return readMediaUploadAppHtml().replace("__PRODUCTMAKER_RESOLVE_ENDPOINT__", encodedEndpoint);
30905
- }
30906
- __name(renderMediaUploadAppHtml, "renderMediaUploadAppHtml");
30907
- function registerMediaUploadAppResource(server, resolveEndpoint) {
30908
- const html = renderMediaUploadAppHtml(resolveEndpoint);
30909
- const csp = buildMediaUploadAppCsp(resolveEndpoint);
30910
- registerAppResource(server, MEDIA_UPLOAD_APP_NAME, MEDIA_UPLOAD_APP_RESOURCE_URI, {
30911
- title: "Upload media",
30912
- mimeType: RESOURCE_MIME_TYPE,
30913
- _meta: {
30914
- ui: {
30915
- csp
30916
- }
30917
- }
30918
- }, async () => ({
30919
- contents: [
30920
- {
30921
- uri: MEDIA_UPLOAD_APP_RESOURCE_URI,
30922
- mimeType: RESOURCE_MIME_TYPE,
30923
- text: html,
30924
- _meta: {
30925
- ui: {
30926
- csp
30927
- }
30928
- }
30929
- }
30930
- ]
30931
- }));
30950
+ __name(isPrivateIPv6, "isPrivateIPv6");
30951
+ function isPrivateIp(ip) {
30952
+ const kind2 = net.isIP(ip);
30953
+ if (kind2 === 4) return isPrivateIPv4(ip);
30954
+ if (kind2 === 6) return isPrivateIPv6(ip);
30955
+ return false;
30932
30956
  }
30933
- __name(registerMediaUploadAppResource, "registerMediaUploadAppResource");
30934
-
30935
- // src/task-results-app.ts
30936
- import { readFileSync as readFileSync2 } from "fs";
30937
- import { registerAppResource as registerAppResource2, RESOURCE_MIME_TYPE as RESOURCE_MIME_TYPE2 } from "@modelcontextprotocol/ext-apps/server";
30938
- var TASK_RESULTS_APP_RESOURCE_URI = "ui://task-results/v1.html";
30939
- var HTML_URL = new URL("./ui/task-results-app.html", import.meta.url);
30940
- function assetOrigin(value) {
30941
- if (!value) return null;
30942
- try {
30943
- const url2 = new URL(value);
30944
- return url2.protocol === "https:" && !url2.username && !url2.password ? url2.origin : null;
30945
- } catch {
30946
- return null;
30957
+ __name(isPrivateIp, "isPrivateIp");
30958
+ function looksLikeNonCanonicalIp(host) {
30959
+ if (net.isIP(host) !== 0) return false;
30960
+ if (/^\d+$/.test(host)) return true;
30961
+ if (/^0x[0-9a-f]+$/i.test(host)) return true;
30962
+ if (host.includes(".")) {
30963
+ const labels = host.split(".");
30964
+ const allNumeric = labels.every((label) => {
30965
+ if (label.length === 0) return false;
30966
+ return /^\d+$/.test(label) || // decimal / octal-with-leading-zero
30967
+ /^0x[0-9a-f]+$/i.test(label);
30968
+ });
30969
+ if (allNumeric) return true;
30947
30970
  }
30971
+ return false;
30948
30972
  }
30949
- __name(assetOrigin, "assetOrigin");
30950
- function buildTaskResultsView(media, proxyBaseUrl) {
30951
- const origin = assetOrigin(proxyBaseUrl);
30952
- const safeUrl = /* @__PURE__ */ __name((value) => {
30953
- if (!origin) return null;
30973
+ __name(looksLikeNonCanonicalIp, "looksLikeNonCanonicalIp");
30974
+
30975
+ // src/public-host.ts
30976
+ function isPrivateHost(value) {
30977
+ let host = value.toLowerCase().replace(/^\[|\]$/g, "").replace(/\.$/, "");
30978
+ if (host === "localhost" || host === "metadata.google.internal") return true;
30979
+ if (host.includes(":")) {
30954
30980
  try {
30955
- const url2 = new URL(value);
30956
- return url2.protocol === "https:" && url2.origin === origin && url2.pathname.startsWith("/assets/") && url2.pathname.length > "/assets/".length && !url2.username && !url2.password ? url2.href : null;
30981
+ host = new URL(`https://[${host}]/`).hostname.slice(1, -1);
30957
30982
  } catch {
30958
- return null;
30983
+ return true;
30959
30984
  }
30960
- }, "safeUrl");
30961
- const images = media.images.flatMap((image2, index) => {
30962
- const url2 = safeUrl(image2.url);
30963
- return url2 ? [
30964
- {
30965
- url: url2,
30966
- alt: image2.alt?.slice(0, 160) ?? `ProductMaker image ${index + 1}`
30967
- }
30968
- ] : [];
30969
- });
30970
- const videos = media.videos.flatMap((video) => {
30971
- const url2 = safeUrl(video.url);
30972
- return url2 ? [
30973
- {
30974
- url: url2,
30975
- ...video.label ? {
30976
- label: video.label.slice(0, 120)
30977
- } : {},
30978
- ...video.scriptRewritten === true ? {
30979
- scriptRewritten: true
30980
- } : {}
30981
- }
30982
- ] : [];
30983
- });
30984
- const links = media.links.flatMap((link) => {
30985
- const url2 = safeUrl(link.url);
30986
- return url2 ? [
30987
- {
30988
- url: url2,
30989
- label: link.label?.slice(0, 120) ?? "Open result"
30990
- }
30991
- ] : [];
30992
- });
30993
- return {
30994
- state: images.length || videos.length || links.length ? "ready" : "empty",
30995
- images,
30996
- videos,
30997
- links
30985
+ }
30986
+ return looksLikeNonCanonicalIp(host) || isPrivateIp(host);
30987
+ }
30988
+ __name(isPrivateHost, "isPrivateHost");
30989
+
30990
+ // src/public-https.ts
30991
+ var PublicHttpsError = class extends Error {
30992
+ static {
30993
+ __name(this, "PublicHttpsError");
30994
+ }
30995
+ code;
30996
+ constructor(code, message) {
30997
+ super(message), this.code = code;
30998
+ this.name = "PublicHttpsError";
30999
+ }
31000
+ };
31001
+ function pinnedLookup(pin) {
31002
+ return (_hostname, options, callback) => {
31003
+ if (options.all) callback(null, [
31004
+ pin
31005
+ ]);
31006
+ else callback(null, pin.address, pin.family);
30998
31007
  };
30999
31008
  }
31000
- __name(buildTaskResultsView, "buildTaskResultsView");
31001
- function taskResultsToolMeta() {
31009
+ __name(pinnedLookup, "pinnedLookup");
31010
+ async function resolvePublicAddress(url2, signal) {
31011
+ const hostname = url2.hostname.replace(/^\[|\]$/g, "");
31012
+ if (isPrivateHost(hostname)) throw new PublicHttpsError("INVALID_INPUT", "media URL host is private");
31013
+ const literalFamily = isIP(hostname);
31014
+ const records = literalFamily ? [
31015
+ {
31016
+ address: hostname,
31017
+ family: literalFamily
31018
+ }
31019
+ ] : await new Promise((resolve4, reject) => {
31020
+ const abort = /* @__PURE__ */ __name(() => reject(signal.reason), "abort");
31021
+ if (signal.aborted) return abort();
31022
+ signal.addEventListener("abort", abort, {
31023
+ once: true
31024
+ });
31025
+ lookup(hostname, {
31026
+ all: true
31027
+ }).then(resolve4, reject).finally(() => {
31028
+ signal.removeEventListener("abort", abort);
31029
+ });
31030
+ });
31031
+ if (!Array.isArray(records) || !records.length || records.some((entry) => !isIP(entry.address) || isIP(entry.address) !== entry.family || isPrivateHost(entry.address))) {
31032
+ throw new PublicHttpsError("INVALID_INPUT", "media URL DNS answers are not exclusively public IP addresses");
31033
+ }
31034
+ const first = records[0];
31035
+ if (!first || first.family !== 4 && first.family !== 6) throw new PublicHttpsError("INVALID_INPUT", "media URL has no usable DNS address");
31002
31036
  return {
31003
- ui: {
31004
- resourceUri: TASK_RESULTS_APP_RESOURCE_URI
31005
- },
31006
- "openai/outputTemplate": TASK_RESULTS_APP_RESOURCE_URI
31037
+ address: first.address,
31038
+ family: first.family
31007
31039
  };
31008
31040
  }
31009
- __name(taskResultsToolMeta, "taskResultsToolMeta");
31010
- function renderTaskResultsAppHtml(proxyBaseUrl) {
31011
- return readFileSync2(HTML_URL, "utf8").replace("__PRODUCTMAKER_ASSET_ORIGIN__", JSON.stringify(assetOrigin(proxyBaseUrl)).replaceAll("<", "\\u003c"));
31041
+ __name(resolvePublicAddress, "resolvePublicAddress");
31042
+ async function withPinnedHttpsResponse(url2, pin, signal, consume, tls = {}) {
31043
+ const agent = new https.Agent({
31044
+ keepAlive: false,
31045
+ lookup: pinnedLookup(pin),
31046
+ ...tls
31047
+ });
31048
+ let body;
31049
+ let incoming;
31050
+ let req;
31051
+ try {
31052
+ const response = await new Promise((resolve4, reject) => {
31053
+ req = https.request(url2, {
31054
+ agent,
31055
+ signal,
31056
+ headers: {
31057
+ "accept-encoding": "gzip, deflate, br"
31058
+ }
31059
+ }, (res) => {
31060
+ try {
31061
+ incoming = res;
31062
+ const encoding = res.headers["content-encoding"]?.toLowerCase();
31063
+ const decoder = encoding === "gzip" ? createGunzip() : encoding === "deflate" ? createInflate() : encoding === "br" ? createBrotliDecompress() : void 0;
31064
+ body = decoder ? res.pipe(decoder) : res;
31065
+ if (decoder) res.on("error", (error) => decoder.destroy(error));
31066
+ const iterator = body[Symbol.asyncIterator]();
31067
+ const stream = new ReadableStream({
31068
+ async pull(controller) {
31069
+ try {
31070
+ const next = await iterator.next();
31071
+ if (next.done) controller.close();
31072
+ else controller.enqueue(new Uint8Array(next.value));
31073
+ } catch (error) {
31074
+ controller.error(error);
31075
+ }
31076
+ },
31077
+ cancel() {
31078
+ body?.destroy();
31079
+ res.destroy();
31080
+ }
31081
+ });
31082
+ const headers = new Headers();
31083
+ for (const [key2, value] of Object.entries(res.headers)) {
31084
+ if (value !== void 0) headers.set(key2, Array.isArray(value) ? value.join(", ") : value);
31085
+ }
31086
+ const status = res.statusCode ?? 502;
31087
+ resolve4(new Response([
31088
+ 204,
31089
+ 205,
31090
+ 304
31091
+ ].includes(status) ? null : stream, {
31092
+ status,
31093
+ headers
31094
+ }));
31095
+ } catch (error) {
31096
+ res.destroy();
31097
+ reject(error);
31098
+ }
31099
+ });
31100
+ req.on("error", reject);
31101
+ req.end();
31102
+ });
31103
+ if (response.status >= 300 && response.status < 400) throw new PublicHttpsError("DOWNLOAD_FAILED", "media URL redirects are not allowed");
31104
+ return await consume(response, url2);
31105
+ } finally {
31106
+ body?.destroy();
31107
+ incoming?.destroy();
31108
+ req?.destroy();
31109
+ agent.destroy();
31110
+ }
31012
31111
  }
31013
- __name(renderTaskResultsAppHtml, "renderTaskResultsAppHtml");
31014
- function registerTaskResultsAppResource(server, proxyBaseUrl) {
31015
- const origin = assetOrigin(proxyBaseUrl);
31016
- const resourceDomains = origin ? [
31017
- origin
31018
- ] : [];
31019
- const html = renderTaskResultsAppHtml(proxyBaseUrl);
31020
- const meta = {
31021
- ui: {
31022
- csp: {
31023
- resourceDomains,
31024
- connectDomains: []
31025
- }
31026
- },
31027
- "openai/widgetCSP": {
31028
- resource_domains: resourceDomains,
31029
- connect_domains: []
31030
- },
31031
- "openai/widgetDescription": "Displays the actual ProductMaker images and videos with playback controls and download links."
31032
- };
31033
- registerAppResource2(server, "ProductMaker task results", TASK_RESULTS_APP_RESOURCE_URI, {
31034
- title: "Your creative",
31035
- mimeType: RESOURCE_MIME_TYPE2,
31036
- _meta: meta
31037
- }, async () => ({
31038
- contents: [
31039
- {
31040
- uri: TASK_RESULTS_APP_RESOURCE_URI,
31041
- mimeType: RESOURCE_MIME_TYPE2,
31042
- text: html,
31043
- _meta: meta
31044
- }
31045
- ]
31046
- }));
31112
+ __name(withPinnedHttpsResponse, "withPinnedHttpsResponse");
31113
+ async function withPublicHttpsResponse(rawUrl, timeoutMs, consume) {
31114
+ let url2;
31115
+ try {
31116
+ url2 = new URL(rawUrl);
31117
+ } catch {
31118
+ throw new PublicHttpsError("INVALID_INPUT", "media URL is not valid");
31119
+ }
31120
+ if (url2.protocol !== "https:" || url2.username || url2.password) throw new PublicHttpsError("INVALID_INPUT", "media URL must be HTTPS without embedded credentials");
31121
+ const controller = new AbortController();
31122
+ const timer = setTimeout(() => controller.abort(new Error("media download timed out")), timeoutMs);
31123
+ timer.unref();
31124
+ try {
31125
+ let pin;
31126
+ try {
31127
+ pin = await resolvePublicAddress(url2, controller.signal);
31128
+ } catch (error) {
31129
+ if (error instanceof PublicHttpsError || controller.signal.aborted) throw error;
31130
+ throw new PublicHttpsError("INVALID_INPUT", "media URL DNS lookup failed");
31131
+ }
31132
+ return await withPinnedHttpsResponse(url2, pin, controller.signal, consume);
31133
+ } finally {
31134
+ clearTimeout(timer);
31135
+ }
31047
31136
  }
31048
- __name(registerTaskResultsAppResource, "registerTaskResultsAppResource");
31049
-
31050
- // src/tools/tasks.ts
31051
- import { z as z137 } from "zod";
31137
+ __name(withPublicHttpsResponse, "withPublicHttpsResponse");
31052
31138
 
31053
31139
  // src/resolve-image.ts
31054
- import { lookup as dnsLookup } from "dns/promises";
31055
31140
  import { promises as fs, createReadStream } from "fs";
31056
31141
  import * as path from "path";
31057
31142
  import * as os from "os";
@@ -31098,53 +31183,6 @@ function sniffMime(buf) {
31098
31183
  return null;
31099
31184
  }
31100
31185
  __name(sniffMime, "sniffMime");
31101
- function isPrivateHost(host) {
31102
- if (host === "localhost" || host === "0.0.0.0") return true;
31103
- if (host === "metadata.google.internal" || host === "169.254.169.254") return true;
31104
- if (/^127\./.test(host)) return true;
31105
- if (/^10\./.test(host)) return true;
31106
- if (/^192\.168\./.test(host)) return true;
31107
- if (/^172\.(1[6-9]|2\d|3[0-1])\./.test(host)) return true;
31108
- if (host.startsWith("169.254.")) return true;
31109
- if (host === "[::1]" || host === "::1") return true;
31110
- if (/^\[?0*:(?:0*:){6}0*1\]?$/i.test(host)) return true;
31111
- const ipv4Mapped = host.match(/^\[?::ffff:([0-9a-f.:]+)\]?$/i);
31112
- if (ipv4Mapped && ipv4Mapped[1]) {
31113
- const inner = ipv4Mapped[1];
31114
- if (inner.includes(".")) return isPrivateHost(inner);
31115
- const parts = inner.split(":").map((h) => parseInt(h, 16));
31116
- if (parts.length === 2 && parts.every((n) => !Number.isNaN(n))) {
31117
- const bytes = [
31118
- parts[0] >> 8,
31119
- parts[0] & 255,
31120
- parts[1] >> 8,
31121
- parts[1] & 255
31122
- ];
31123
- return isPrivateHost(bytes.join("."));
31124
- }
31125
- }
31126
- if (/^\[?f[cd][0-9a-f]{2}:/i.test(host)) return true;
31127
- if (/^\[?fe[89ab][0-9a-f]:/i.test(host)) return true;
31128
- return false;
31129
- }
31130
- __name(isPrivateHost, "isPrivateHost");
31131
- async function assertHostResolvesPublic(hostname) {
31132
- let records;
31133
- try {
31134
- records = await dnsLookup(hostname, {
31135
- all: true
31136
- });
31137
- } catch {
31138
- if (process.env.NODE_ENV === "test" || process.env.VITEST) return;
31139
- throw new ResolveImageError("INVALID_INPUT", `imageUrl DNS lookup failed for ${hostname}`);
31140
- }
31141
- for (const { address } of records) {
31142
- if (isPrivateHost(address)) {
31143
- throw new ResolveImageError("INVALID_INPUT", `imageUrl host resolves to a private address (${address})`);
31144
- }
31145
- }
31146
- }
31147
- __name(assertHostResolvesPublic, "assertHostResolvesPublic");
31148
31186
  function expandUserPath(raw) {
31149
31187
  let s = raw.trim();
31150
31188
  if (s === "~") return os.homedir();
@@ -31218,48 +31256,37 @@ async function resolvePath(rawPath) {
31218
31256
  }
31219
31257
  __name(resolvePath, "resolvePath");
31220
31258
  async function resolveImageUrl(rawUrl, maxBytes) {
31221
- let u;
31222
- try {
31223
- u = new URL(rawUrl);
31224
- } catch {
31225
- throw new ResolveImageError("INVALID_INPUT", "imageUrl is not a valid URL");
31226
- }
31227
- if (u.protocol !== "https:") throw new ResolveImageError("INVALID_INPUT", "imageUrl must be HTTPS");
31228
- if (isPrivateHost(u.hostname)) throw new ResolveImageError("INVALID_INPUT", "imageUrl host not allowed");
31229
- await assertHostResolvesPublic(u.hostname);
31230
- let res;
31231
31259
  try {
31232
- res = await fetch(rawUrl, {
31233
- signal: AbortSignal.timeout(3e4),
31234
- redirect: "error"
31260
+ return await withPublicHttpsResponse(rawUrl, 3e4, async (res) => {
31261
+ if (!res.ok) throw new ResolveImageError("IMAGE_DOWNLOAD_FAILED", `HTTP ${res.status}`);
31262
+ const cl = res.headers.get("content-length");
31263
+ if (cl && Number(cl) > maxBytes) {
31264
+ throw new ResolveImageError("IMAGE_TOO_LARGE", `content-length ${cl}`);
31265
+ }
31266
+ const ct = (res.headers.get("content-type") ?? "").split(";")[0]?.trim() ?? "";
31267
+ if (!ALLOWED_MIMES.has(ct)) throw new ResolveImageError("IMAGE_TYPE_UNSUPPORTED", `content-type ${ct}`);
31268
+ let buf;
31269
+ try {
31270
+ buf = Buffer.from(await readResponseBytes(res, maxBytes));
31271
+ } catch (error) {
31272
+ if (error instanceof ResponseBodyTooLargeError) {
31273
+ throw new ResolveImageError("IMAGE_TOO_LARGE", `image URL exceeds ${maxBytes} bytes`);
31274
+ }
31275
+ throw new ResolveImageError("IMAGE_DOWNLOAD_FAILED", "image response could not be read");
31276
+ }
31277
+ const sniffed = sniffMime(buf);
31278
+ if (!sniffed || sniffed !== ct) {
31279
+ throw new ResolveImageError("IMAGE_TYPE_UNSUPPORTED", `magic bytes (${sniffed ?? "unknown"}) do not match content-type (${ct})`);
31280
+ }
31281
+ return {
31282
+ buffer: buf,
31283
+ mimetype: sniffed
31284
+ };
31235
31285
  });
31236
- } catch (e) {
31237
- throw new ResolveImageError("IMAGE_DOWNLOAD_FAILED", e.message);
31238
- }
31239
- if (!res.ok) throw new ResolveImageError("IMAGE_DOWNLOAD_FAILED", `HTTP ${res.status}`);
31240
- const cl = res.headers.get("content-length");
31241
- if (cl && Number(cl) > maxBytes) {
31242
- throw new ResolveImageError("IMAGE_TOO_LARGE", `content-length ${cl}`);
31243
- }
31244
- const ct = (res.headers.get("content-type") ?? "").split(";")[0]?.trim() ?? "";
31245
- if (!ALLOWED_MIMES.has(ct)) throw new ResolveImageError("IMAGE_TYPE_UNSUPPORTED", `content-type ${ct}`);
31246
- let buf;
31247
- try {
31248
- buf = Buffer.from(await readResponseBytes(res, maxBytes));
31249
31286
  } catch (error) {
31250
- if (error instanceof ResponseBodyTooLargeError) {
31251
- throw new ResolveImageError("IMAGE_TOO_LARGE", `image URL exceeds ${maxBytes} bytes`);
31252
- }
31253
- throw new ResolveImageError("IMAGE_DOWNLOAD_FAILED", "image response could not be read");
31254
- }
31255
- const sniffed = sniffMime(buf);
31256
- if (!sniffed || sniffed !== ct) {
31257
- throw new ResolveImageError("IMAGE_TYPE_UNSUPPORTED", `magic bytes (${sniffed ?? "unknown"}) do not match content-type (${ct})`);
31287
+ if (error instanceof ResolveImageError) throw error;
31288
+ throw new ResolveImageError(error instanceof PublicHttpsError && error.code === "INVALID_INPUT" ? "INVALID_INPUT" : "IMAGE_DOWNLOAD_FAILED", error instanceof Error ? error.message : "image download failed");
31258
31289
  }
31259
- return {
31260
- buffer: buf,
31261
- mimetype: sniffed
31262
- };
31263
31290
  }
31264
31291
  __name(resolveImageUrl, "resolveImageUrl");
31265
31292
  async function resolveImages(images, opts = {
@@ -31290,6 +31317,256 @@ async function resolveOne(ref, transport, maxBytes) {
31290
31317
  }
31291
31318
  __name(resolveOne, "resolveOne");
31292
31319
 
31320
+ // src/validate-api-url.ts
31321
+ function assertSafePublicBaseUrl(envName, url2) {
31322
+ let u;
31323
+ try {
31324
+ u = new URL(url2);
31325
+ } catch {
31326
+ throw new Error(`${envName} is not a valid URL: ${url2}`);
31327
+ }
31328
+ if (u.protocol !== "https:") {
31329
+ throw new Error(`${envName} must use https (got ${u.protocol})`);
31330
+ }
31331
+ if (u.username || u.password || u.search || u.hash) {
31332
+ throw new Error(`${envName} must not contain credentials, query parameters, or fragments`);
31333
+ }
31334
+ if (isPrivateHost(u.hostname)) {
31335
+ throw new Error(`${envName} must not point to a private/loopback host: ${u.hostname}`);
31336
+ }
31337
+ }
31338
+ __name(assertSafePublicBaseUrl, "assertSafePublicBaseUrl");
31339
+
31340
+ // src/meta-upload-app.ts
31341
+ import { registerAppResource, RESOURCE_MIME_TYPE } from "@modelcontextprotocol/ext-apps/server";
31342
+ var MEDIA_UPLOAD_APP_RESOURCE_URI = "ui://media-upload/v1.html";
31343
+ var MEDIA_UPLOAD_APP_NAME = "ProductMaker media upload";
31344
+ var PRODUCTMAKER_APP_ORIGIN = "https://productmaker.app";
31345
+ var MEDIA_UPLOAD_APP_HTML_URL = new URL("./ui/meta-upload-app.html", import.meta.url);
31346
+ var PRODUCTMAKER_UPLOAD_STORAGE_ORIGIN = "https://825805a8113ddb98b68b9b0ddbb5d72a.r2.cloudflarestorage.com";
31347
+ var MEDIA_UPLOAD_CONNECT_DOMAINS = [
31348
+ "https://api.productmaker.app",
31349
+ PRODUCTMAKER_APP_ORIGIN,
31350
+ "https://www.productmaker.app",
31351
+ // The file bridge supplies temporary URLs. Keep this host-owned scope until
31352
+ // a real ChatGPT/library upload establishes every required exact origin.
31353
+ "https://*.oaiusercontent.com"
31354
+ ];
31355
+ var STORAGE_ORIGINS_ERROR = "PM_MEDIA_UPLOAD_STORAGE_ORIGINS must be a JSON array of exact public HTTPS origins";
31356
+ function normalizeStorageOrigins(value) {
31357
+ try {
31358
+ const origins = z132.array(z132.string().trim().min(1)).max(16).parse(value);
31359
+ return [
31360
+ ...new Set(origins.map((origin) => {
31361
+ assertSafePublicBaseUrl("PM_MEDIA_UPLOAD_STORAGE_ORIGINS", origin);
31362
+ const parsed = new URL(origin);
31363
+ if (parsed.pathname !== "/" || parsed.hostname.includes("*")) throw new Error(STORAGE_ORIGINS_ERROR);
31364
+ return parsed.origin;
31365
+ }))
31366
+ ];
31367
+ } catch {
31368
+ throw new Error(STORAGE_ORIGINS_ERROR);
31369
+ }
31370
+ }
31371
+ __name(normalizeStorageOrigins, "normalizeStorageOrigins");
31372
+ function parseMediaUploadStorageOrigins(value) {
31373
+ if (value === void 0) return void 0;
31374
+ try {
31375
+ return normalizeStorageOrigins(JSON.parse(value));
31376
+ } catch {
31377
+ throw new Error(STORAGE_ORIGINS_ERROR);
31378
+ }
31379
+ }
31380
+ __name(parseMediaUploadStorageOrigins, "parseMediaUploadStorageOrigins");
31381
+ function buildMediaUploadAppCsp(resolveEndpoint, storageOrigins) {
31382
+ const resolveOrigin = new URL(resolveEndpoint).origin;
31383
+ const approvedStorage = storageOrigins === void 0 ? resolveOrigin === "https://api.productmaker.app" ? [
31384
+ PRODUCTMAKER_UPLOAD_STORAGE_ORIGIN
31385
+ ] : [] : normalizeStorageOrigins(storageOrigins);
31386
+ return {
31387
+ connectDomains: [
31388
+ .../* @__PURE__ */ new Set([
31389
+ ...MEDIA_UPLOAD_CONNECT_DOMAINS,
31390
+ ...approvedStorage,
31391
+ resolveOrigin
31392
+ ])
31393
+ ],
31394
+ // This standalone HTML has no remote static assets. Local image previews
31395
+ // use File/createImageBitmap and data: URLs; host-library bytes use fetch.
31396
+ resourceDomains: []
31397
+ };
31398
+ }
31399
+ __name(buildMediaUploadAppCsp, "buildMediaUploadAppCsp");
31400
+ var MEDIA_UPLOAD_APP_CSP = buildMediaUploadAppCsp("https://api.productmaker.app/v1/uploaded-assets/sessions/resolve");
31401
+ function readMediaUploadAppHtml(htmlUrl = MEDIA_UPLOAD_APP_HTML_URL) {
31402
+ try {
31403
+ return readFileSync(htmlUrl, "utf8");
31404
+ } catch (error) {
31405
+ const message = error instanceof Error ? error.message : String(error);
31406
+ throw new Error(`Missing packaged media upload app HTML at ${htmlUrl.pathname}. Rebuild @productmaker/mcp so ${MEDIA_UPLOAD_APP_RESOURCE_URI} is bundled. Original error: ${message}`);
31407
+ }
31408
+ }
31409
+ __name(readMediaUploadAppHtml, "readMediaUploadAppHtml");
31410
+ function renderMediaUploadAppHtml(resolveEndpoint) {
31411
+ const encodedEndpoint = JSON.stringify(resolveEndpoint).slice(1, -1).replaceAll("<", "\\u003c");
31412
+ return readMediaUploadAppHtml().replace("__PRODUCTMAKER_RESOLVE_ENDPOINT__", encodedEndpoint);
31413
+ }
31414
+ __name(renderMediaUploadAppHtml, "renderMediaUploadAppHtml");
31415
+ function registerMediaUploadAppResource(server, resolveEndpoint, storageOrigins) {
31416
+ const html = renderMediaUploadAppHtml(resolveEndpoint);
31417
+ const csp = buildMediaUploadAppCsp(resolveEndpoint, storageOrigins);
31418
+ const meta = {
31419
+ ui: {
31420
+ csp
31421
+ },
31422
+ // OpenAI requires a dedicated per-plugin UI origin. Keep its compatibility
31423
+ // alias separate from the host-derived standard domain used by Claude.
31424
+ "openai/widgetDomain": PRODUCTMAKER_APP_ORIGIN,
31425
+ "openai/widgetCSP": {
31426
+ connect_domains: csp.connectDomains,
31427
+ resource_domains: csp.resourceDomains,
31428
+ // Trust only the existing secure-upload fallback for OpenAI external opens.
31429
+ // The current HTML uses an ordinary anchor; no navigation behavior changes.
31430
+ redirect_domains: [
31431
+ PRODUCTMAKER_APP_ORIGIN
31432
+ ]
31433
+ }
31434
+ };
31435
+ registerAppResource(server, MEDIA_UPLOAD_APP_NAME, MEDIA_UPLOAD_APP_RESOURCE_URI, {
31436
+ title: "Upload media",
31437
+ mimeType: RESOURCE_MIME_TYPE,
31438
+ _meta: meta
31439
+ }, async () => ({
31440
+ contents: [
31441
+ {
31442
+ uri: MEDIA_UPLOAD_APP_RESOURCE_URI,
31443
+ mimeType: RESOURCE_MIME_TYPE,
31444
+ text: html,
31445
+ _meta: meta
31446
+ }
31447
+ ]
31448
+ }));
31449
+ }
31450
+ __name(registerMediaUploadAppResource, "registerMediaUploadAppResource");
31451
+
31452
+ // src/task-results-app.ts
31453
+ import { readFileSync as readFileSync2 } from "fs";
31454
+ import { registerAppResource as registerAppResource2, RESOURCE_MIME_TYPE as RESOURCE_MIME_TYPE2 } from "@modelcontextprotocol/ext-apps/server";
31455
+ var TASK_RESULTS_APP_RESOURCE_URI = "ui://task-results/v1.html";
31456
+ var HTML_URL = new URL("./ui/task-results-app.html", import.meta.url);
31457
+ function assetOrigin(value) {
31458
+ if (!value) return null;
31459
+ try {
31460
+ const url2 = new URL(value);
31461
+ return url2.protocol === "https:" && !url2.username && !url2.password ? url2.origin : null;
31462
+ } catch {
31463
+ return null;
31464
+ }
31465
+ }
31466
+ __name(assetOrigin, "assetOrigin");
31467
+ function buildTaskResultsView(media, proxyBaseUrl) {
31468
+ const origin = assetOrigin(proxyBaseUrl);
31469
+ const safeUrl = /* @__PURE__ */ __name((value) => {
31470
+ if (!origin) return null;
31471
+ try {
31472
+ const url2 = new URL(value);
31473
+ return url2.protocol === "https:" && url2.origin === origin && url2.pathname.startsWith("/assets/") && url2.pathname.length > "/assets/".length && !url2.username && !url2.password ? url2.href : null;
31474
+ } catch {
31475
+ return null;
31476
+ }
31477
+ }, "safeUrl");
31478
+ const images = media.images.flatMap((image2, index) => {
31479
+ const url2 = safeUrl(image2.url);
31480
+ return url2 ? [
31481
+ {
31482
+ url: url2,
31483
+ alt: image2.alt?.slice(0, 160) ?? `ProductMaker image ${index + 1}`
31484
+ }
31485
+ ] : [];
31486
+ });
31487
+ const videos = media.videos.flatMap((video) => {
31488
+ const url2 = safeUrl(video.url);
31489
+ return url2 ? [
31490
+ {
31491
+ url: url2,
31492
+ ...video.label ? {
31493
+ label: video.label.slice(0, 120)
31494
+ } : {},
31495
+ ...video.scriptRewritten === true ? {
31496
+ scriptRewritten: true
31497
+ } : {}
31498
+ }
31499
+ ] : [];
31500
+ });
31501
+ const links = media.links.flatMap((link) => {
31502
+ const url2 = safeUrl(link.url);
31503
+ return url2 ? [
31504
+ {
31505
+ url: url2,
31506
+ label: link.label?.slice(0, 120) ?? "Open result"
31507
+ }
31508
+ ] : [];
31509
+ });
31510
+ return {
31511
+ state: images.length || videos.length || links.length ? "ready" : "empty",
31512
+ images,
31513
+ videos,
31514
+ links
31515
+ };
31516
+ }
31517
+ __name(buildTaskResultsView, "buildTaskResultsView");
31518
+ function taskResultsToolMeta() {
31519
+ return {
31520
+ ui: {
31521
+ resourceUri: TASK_RESULTS_APP_RESOURCE_URI
31522
+ },
31523
+ "openai/outputTemplate": TASK_RESULTS_APP_RESOURCE_URI
31524
+ };
31525
+ }
31526
+ __name(taskResultsToolMeta, "taskResultsToolMeta");
31527
+ function renderTaskResultsAppHtml(proxyBaseUrl) {
31528
+ return readFileSync2(HTML_URL, "utf8").replace("__PRODUCTMAKER_ASSET_ORIGIN__", JSON.stringify(assetOrigin(proxyBaseUrl)).replaceAll("<", "\\u003c"));
31529
+ }
31530
+ __name(renderTaskResultsAppHtml, "renderTaskResultsAppHtml");
31531
+ function registerTaskResultsAppResource(server, proxyBaseUrl) {
31532
+ const origin = assetOrigin(proxyBaseUrl);
31533
+ const resourceDomains = origin ? [
31534
+ origin
31535
+ ] : [];
31536
+ const html = renderTaskResultsAppHtml(proxyBaseUrl);
31537
+ const meta = {
31538
+ ui: {
31539
+ csp: {
31540
+ resourceDomains,
31541
+ connectDomains: []
31542
+ }
31543
+ },
31544
+ "openai/widgetCSP": {
31545
+ resource_domains: resourceDomains,
31546
+ connect_domains: []
31547
+ },
31548
+ "openai/widgetDescription": "Displays the actual ProductMaker images and videos with playback controls and download links."
31549
+ };
31550
+ registerAppResource2(server, "ProductMaker task results", TASK_RESULTS_APP_RESOURCE_URI, {
31551
+ title: "Your creative",
31552
+ mimeType: RESOURCE_MIME_TYPE2,
31553
+ _meta: meta
31554
+ }, async () => ({
31555
+ contents: [
31556
+ {
31557
+ uri: TASK_RESULTS_APP_RESOURCE_URI,
31558
+ mimeType: RESOURCE_MIME_TYPE2,
31559
+ text: html,
31560
+ _meta: meta
31561
+ }
31562
+ ]
31563
+ }));
31564
+ }
31565
+ __name(registerTaskResultsAppResource, "registerTaskResultsAppResource");
31566
+
31567
+ // src/tools/tasks.ts
31568
+ import { z as z138 } from "zod";
31569
+
31293
31570
  // src/status-poller.ts
31294
31571
  var TERMINAL = /* @__PURE__ */ new Set([
31295
31572
  "done",
@@ -31366,7 +31643,6 @@ __name(buildPollingContinuation, "buildPollingContinuation");
31366
31643
  import { McpError, ErrorCode } from "@modelcontextprotocol/sdk/types.js";
31367
31644
 
31368
31645
  // src/resolve-media.ts
31369
- import { lookup as dnsLookup2 } from "dns/promises";
31370
31646
  import { promises as fs2, createReadStream as createReadStream2 } from "fs";
31371
31647
  import * as os2 from "os";
31372
31648
  import * as path2 from "path";
@@ -31503,32 +31779,6 @@ async function readFileWithCap2(absPath) {
31503
31779
  return Buffer.concat(chunks, total);
31504
31780
  }
31505
31781
  __name(readFileWithCap2, "readFileWithCap");
31506
- async function assertPublicUrl(rawUrl) {
31507
- let url2;
31508
- try {
31509
- url2 = new URL(rawUrl);
31510
- } catch {
31511
- throw new ResolveMediaError("INVALID_INPUT", "media URL is not valid");
31512
- }
31513
- if (url2.protocol !== "https:") throw new ResolveMediaError("INVALID_INPUT", "media URL must be HTTPS");
31514
- if (url2.username || url2.password) throw new ResolveMediaError("INVALID_INPUT", "media URL must not contain embedded credentials");
31515
- if (isPrivateHost(url2.hostname)) throw new ResolveMediaError("INVALID_INPUT", "media URL host is private");
31516
- try {
31517
- const records = await dnsLookup2(url2.hostname, {
31518
- all: true
31519
- });
31520
- if (records.some((record) => isPrivateHost(record.address))) {
31521
- throw new ResolveMediaError("INVALID_INPUT", "media URL resolves to a private address");
31522
- }
31523
- } catch (error) {
31524
- if (error instanceof ResolveMediaError) throw error;
31525
- if (process.env.NODE_ENV !== "test" && !process.env.VITEST) {
31526
- throw new ResolveMediaError("INVALID_INPUT", "media URL DNS lookup failed");
31527
- }
31528
- }
31529
- return url2;
31530
- }
31531
- __name(assertPublicUrl, "assertPublicUrl");
31532
31782
  async function resolvePath2(rawPath, expectedKind) {
31533
31783
  const absolutePath = path2.resolve(expandUserPath2(rawPath));
31534
31784
  const extension = path2.extname(absolutePath).toLowerCase();
@@ -31572,46 +31822,48 @@ async function resolveMediaRef(ref, opts) {
31572
31822
  return resolvePath2(ref.path, opts.expectedKind);
31573
31823
  }
31574
31824
  if (!ref.url) throw new ResolveMediaError("INVALID_INPUT", "media URL is empty");
31575
- const url2 = await assertPublicUrl(ref.url);
31576
- const extension = path2.extname(url2.pathname).toLowerCase();
31577
- const format = MEDIA_FORMATS.find((entry) => entry.exts.some((candidate) => candidate === extension));
31578
- if (format && opts.expectedKind && format.kind !== opts.expectedKind) {
31579
- throw new ResolveMediaError("MEDIA_TYPE_UNSUPPORTED", "media URL extension does not match the requested type");
31580
- }
31581
- const response = await fetch(url2, {
31582
- redirect: "error",
31583
- signal: AbortSignal.timeout(12e4)
31584
- });
31585
- if (!response.ok) throw new ResolveMediaError("MEDIA_URL_UNAVAILABLE", `media URL returned HTTP ${response.status}`);
31586
- const contentLength = Number(response.headers.get("content-length") ?? 0);
31587
- if (contentLength > MAX_MEDIA_BYTES) {
31588
- void response.body?.cancel().catch(() => void 0);
31589
- throw new ResolveMediaError("MEDIA_TOO_LARGE", `media URL exceeds ${MAX_MEDIA_BYTES} bytes`);
31590
- }
31591
- const buffer = await readResponseWithCap(response);
31592
- const sniffed = sniffMime2(buffer, extension);
31593
- if (!sniffed) throw new ResolveMediaError("MEDIA_TYPE_UNSUPPORTED", "media URL magic bytes are unsupported");
31594
- const kind2 = sniffed.startsWith("video/") ? "video" : "image";
31595
- if (opts.expectedKind && kind2 !== opts.expectedKind) {
31596
- throw new ResolveMediaError("MEDIA_TYPE_UNSUPPORTED", `expected ${opts.expectedKind} but received ${kind2}`);
31597
- }
31598
- if (format && sniffed !== format.mime) {
31599
- throw new ResolveMediaError("MEDIA_TYPE_UNSUPPORTED", `media URL magic bytes do not match ${format.mime}`);
31825
+ try {
31826
+ return await withPublicHttpsResponse(ref.url, 12e4, async (response, url2) => {
31827
+ const extension = path2.extname(url2.pathname).toLowerCase();
31828
+ const format = MEDIA_FORMATS.find((entry) => entry.exts.some((candidate) => candidate === extension));
31829
+ if (format && opts.expectedKind && format.kind !== opts.expectedKind) {
31830
+ throw new ResolveMediaError("MEDIA_TYPE_UNSUPPORTED", "media URL extension does not match the requested type");
31831
+ }
31832
+ if (!response.ok) throw new ResolveMediaError("MEDIA_URL_UNAVAILABLE", `media URL returned HTTP ${response.status}`);
31833
+ const contentLength = Number(response.headers.get("content-length") ?? 0);
31834
+ if (contentLength > MAX_MEDIA_BYTES) {
31835
+ void response.body?.cancel().catch(() => void 0);
31836
+ throw new ResolveMediaError("MEDIA_TOO_LARGE", `media URL exceeds ${MAX_MEDIA_BYTES} bytes`);
31837
+ }
31838
+ const buffer = await readResponseWithCap(response);
31839
+ const sniffed = sniffMime2(buffer, extension);
31840
+ if (!sniffed) throw new ResolveMediaError("MEDIA_TYPE_UNSUPPORTED", "media URL magic bytes are unsupported");
31841
+ const kind2 = sniffed.startsWith("video/") ? "video" : "image";
31842
+ if (opts.expectedKind && kind2 !== opts.expectedKind) {
31843
+ throw new ResolveMediaError("MEDIA_TYPE_UNSUPPORTED", `expected ${opts.expectedKind} but received ${kind2}`);
31844
+ }
31845
+ if (format && sniffed !== format.mime) {
31846
+ throw new ResolveMediaError("MEDIA_TYPE_UNSUPPORTED", `media URL magic bytes do not match ${format.mime}`);
31847
+ }
31848
+ const filename = path2.basename(url2.pathname) || `media.${kind2 === "video" ? "mp4" : "jpg"}`;
31849
+ return {
31850
+ kind: kind2,
31851
+ mimetype: sniffed,
31852
+ filename,
31853
+ buffer
31854
+ };
31855
+ });
31856
+ } catch (error) {
31857
+ if (error instanceof ResolveMediaError) throw error;
31858
+ throw new ResolveMediaError(error instanceof PublicHttpsError && error.code === "INVALID_INPUT" ? "INVALID_INPUT" : "MEDIA_URL_UNAVAILABLE", error instanceof Error ? error.message : "media download failed");
31600
31859
  }
31601
- const filename = path2.basename(url2.pathname) || `media.${kind2 === "video" ? "mp4" : "jpg"}`;
31602
- return {
31603
- kind: kind2,
31604
- mimetype: sniffed,
31605
- filename,
31606
- buffer
31607
- };
31608
31860
  }
31609
31861
  __name(resolveMediaRef, "resolveMediaRef");
31610
31862
 
31611
31863
  // src/tools/shared.ts
31612
- import { z as z132 } from "zod";
31613
- 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);
31614
- var Country = z132.string().min(2).max(3);
31864
+ import { z as z133 } from "zod";
31865
+ var Lang = z133.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);
31866
+ var Country = z133.string().min(2).max(3);
31615
31867
  var LandingStrategyEnum = LandingStrategySchema;
31616
31868
  var LandingImageFormatEnum = LandingImageFormatSchema;
31617
31869
  var VideoPresetEnum = VideoPreset.extract([
@@ -31715,7 +31967,7 @@ function animationStyleApplies(videoPreset, angleNarratives) {
31715
31967
  return ANIMATED_VIDEO_PRESETS.has(videoPreset ?? "") || (angleNarratives ?? []).some((n) => ANIMATED_NARRATIVE_STYLES.has(n));
31716
31968
  }
31717
31969
  __name(animationStyleApplies, "animationStyleApplies");
31718
- var VideoTierEnum = z132.enum([
31970
+ var VideoTierEnum = z133.enum([
31719
31971
  "basic"
31720
31972
  ]);
31721
31973
  function resolveVideoTier(_tier) {
@@ -31773,10 +32025,10 @@ var ImageCreativeStyleEnum = ImageCreativeStyle;
31773
32025
  var ImageCreativeArchetypeEnum = ImageCreativeArchetype;
31774
32026
  var ImageCreativeVisualTreatmentEnum = ImageCreativeVisualTreatment;
31775
32027
  var ImageCreativeAspectRatioEnum = ImageCreativeAspectRatio;
31776
- var ImageRefSchema = z132.object({
31777
- 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.'),
31778
- 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`.'),
31779
- 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.")
32028
+ var ImageRefSchema = z133.object({
32029
+ url: z133.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.'),
32030
+ path: z133.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`.'),
32031
+ assetId: z133.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.")
31780
32032
  }).refine((v) => [
31781
32033
  v.url,
31782
32034
  v.path,
@@ -31784,11 +32036,11 @@ var ImageRefSchema = z132.object({
31784
32036
  ].filter(Boolean).length === 1, {
31785
32037
  message: "each image entry must have exactly one of `url`, `path`, or `assetId`"
31786
32038
  });
31787
- var ImagesInput = z132.array(ImageRefSchema).min(1).max(5).superRefine((images, ctx) => {
32039
+ var ImagesInput = z133.array(ImageRefSchema).min(1).max(5).superRefine((images, ctx) => {
31788
32040
  const assetCount = images.filter((image2) => Boolean(image2.assetId)).length;
31789
32041
  if (assetCount > 0 && assetCount !== images.length) {
31790
32042
  ctx.addIssue({
31791
- code: z132.ZodIssueCode.custom,
32043
+ code: z133.ZodIssueCode.custom,
31792
32044
  message: "all image entries must use assetId, or all must use URL/path sources"
31793
32045
  });
31794
32046
  }
@@ -31869,14 +32121,14 @@ async function submitCreative(api, opts) {
31869
32121
  });
31870
32122
  }
31871
32123
  __name(submitCreative, "submitCreative");
31872
- var ResearchTargetCountryEnum = z132.enum([
32124
+ var ResearchTargetCountryEnum = z133.enum([
31873
32125
  "CO",
31874
32126
  "MX",
31875
32127
  "PE",
31876
32128
  "EC",
31877
32129
  "CL"
31878
32130
  ]);
31879
- var ResearchCategoryEnum = z132.enum([
32131
+ var ResearchCategoryEnum = z133.enum([
31880
32132
  "auto",
31881
32133
  "automotive",
31882
32134
  "kitchen",
@@ -32671,7 +32923,7 @@ function approveRefusalMessage(reason) {
32671
32923
  __name(approveRefusalMessage, "approveRefusalMessage");
32672
32924
 
32673
32925
  // src/tools/task-outputs.ts
32674
- import { z as z133 } from "zod";
32926
+ import { z as z134 } from "zod";
32675
32927
 
32676
32928
  // src/tools/conversational-review-format.ts
32677
32929
  function escapeMarkdownText(value) {
@@ -32802,12 +33054,12 @@ function serviceVideoResultMedia(result) {
32802
33054
  };
32803
33055
  }
32804
33056
  __name(serviceVideoResultMedia, "serviceVideoResultMedia");
32805
- var CreativeVariantIdentity = z133.object({
32806
- variantTaskId: z133.string().uuid(),
32807
- sourceTaskId: z133.string().uuid(),
33057
+ var CreativeVariantIdentity = z134.object({
33058
+ variantTaskId: z134.string().uuid(),
33059
+ sourceTaskId: z134.string().uuid(),
32808
33060
  kind: TaskCreativeVariantKind,
32809
33061
  status: TaskStatusSchema,
32810
- error: z133.string().optional()
33062
+ error: z134.string().optional()
32811
33063
  });
32812
33064
  function proxifyOutputs(outputs, proxyBaseUrl) {
32813
33065
  if (!proxyBaseUrl || !isProxyEnabled()) return outputs;
@@ -33411,29 +33663,29 @@ ${parsed.data.error ? `${escapeMarkdownText(parsed.data.error)}
33411
33663
  __name(renderVideoScriptMarkdown, "renderVideoScriptMarkdown");
33412
33664
 
33413
33665
  // src/tools/media-presentation.ts
33414
- import { z as z134 } from "zod";
33415
- var HttpsPresentationUrlSchema = z134.string().url().max(2048).refine((value) => new URL(value).protocol === "https:");
33666
+ import { z as z135 } from "zod";
33667
+ var HttpsPresentationUrlSchema = z135.string().url().max(2048).refine((value) => new URL(value).protocol === "https:");
33416
33668
  var ProductMakerPreviewUrlSchema = HttpsPresentationUrlSchema.refine((value) => {
33417
33669
  const hostname = new URL(value).hostname.toLowerCase();
33418
33670
  return hostname === "productmaker.app" || hostname.endsWith(".productmaker.app");
33419
33671
  }, "preview URL must be owned by ProductMaker");
33420
- var positiveIntAtMost = /* @__PURE__ */ __name((maximum) => z134.number().int().positive().max(maximum), "positiveIntAtMost");
33421
- var PresentationImageSchema = z134.object({
33422
- kind: z134.literal("image"),
33672
+ var positiveIntAtMost = /* @__PURE__ */ __name((maximum) => z135.number().int().positive().max(maximum), "positiveIntAtMost");
33673
+ var PresentationImageSchema = z135.object({
33674
+ kind: z135.literal("image"),
33423
33675
  url: HttpsPresentationUrlSchema,
33424
- mimeType: z134.enum([
33676
+ mimeType: z135.enum([
33425
33677
  "image/jpeg",
33426
33678
  "image/png"
33427
33679
  ]),
33428
33680
  byteSize: positiveIntAtMost(5 * 1024 * 1024),
33429
33681
  width: positiveIntAtMost(16384),
33430
33682
  height: positiveIntAtMost(16384),
33431
- alt: z134.string().trim().min(1).max(240)
33683
+ alt: z135.string().trim().min(1).max(240)
33432
33684
  }).strict();
33433
33685
  var McpMediaPresentationSchema = PresentationImageSchema;
33434
- var GalleryImageSchema = z134.object({
33686
+ var GalleryImageSchema = z135.object({
33435
33687
  url: ProductMakerPreviewUrlSchema,
33436
- alt: z134.string().trim().min(1).max(240)
33688
+ alt: z135.string().trim().min(1).max(240)
33437
33689
  });
33438
33690
  var MCP_GALLERY_IMAGE_MAX_BYTES = 10 * 1024 * 1024;
33439
33691
  async function imageBlocks(image2, transport) {
@@ -33490,12 +33742,12 @@ async function buildMcpMediaPresentationContent(rawPresentation, transport = "ht
33490
33742
  ];
33491
33743
  }
33492
33744
  __name(buildMcpMediaPresentationContent, "buildMcpMediaPresentationContent");
33493
- var OwnedImagePreviewSchema = z134.object({
33745
+ var OwnedImagePreviewSchema = z135.object({
33494
33746
  url: HttpsPresentationUrlSchema.refine((value) => {
33495
33747
  const url2 = new URL(value);
33496
33748
  return !url2.username && !url2.password && !isPrivateHost(url2.hostname);
33497
33749
  }, "Preview URL must be public HTTPS without credentials"),
33498
- alt: z134.string().trim().min(1).max(240)
33750
+ alt: z135.string().trim().min(1).max(240)
33499
33751
  });
33500
33752
  async function buildMcpImagePreviewContent(rawPreview, transport = "http") {
33501
33753
  const preview2 = OwnedImagePreviewSchema.safeParse(rawPreview);
@@ -33690,7 +33942,7 @@ async function observeMcpMediaDelivery(fingerprint, work) {
33690
33942
  __name(observeMcpMediaDelivery, "observeMcpMediaDelivery");
33691
33943
 
33692
33944
  // src/tools/animated-video.ts
33693
- import { z as z135 } from "zod";
33945
+ import { z as z136 } from "zod";
33694
33946
 
33695
33947
  // src/tools/paid-confirmation.ts
33696
33948
  function describeMarketStudyChoice(enabled) {
@@ -33939,30 +34191,30 @@ function hookVariantsLabel(count) {
33939
34191
  return count === 1 ? "1 versi\xF3n del gancho" : `${count} versiones del gancho`;
33940
34192
  }
33941
34193
  __name(hookVariantsLabel, "hookVariantsLabel");
33942
- var DurationInput = z135.union(MOTION_GRAPHICS_DURATIONS.map((d) => z135.literal(d)));
33943
- var CreateAnimatedVideoInput = z135.object({
33944
- action: z135.enum([
34194
+ var DurationInput = z136.union(MOTION_GRAPHICS_DURATIONS.map((d) => z136.literal(d)));
34195
+ var CreateAnimatedVideoInput = z136.object({
34196
+ action: z136.enum([
33945
34197
  "prepare",
33946
34198
  "status",
33947
34199
  "cancel",
33948
34200
  "generate"
33949
34201
  ]).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."),
33950
- 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."),
33951
- websiteCaptureId: z135.string().uuid().optional().describe("ID de la captura preparada. Obligatorio para status/cancel/generate; no inventarlo."),
33952
- 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."),
33953
- 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."),
34202
+ brandId: z136.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."),
34203
+ websiteCaptureId: z136.string().uuid().optional().describe("ID de la captura preparada. Obligatorio para status/cancel/generate; no inventarlo."),
34204
+ generationRequestId: z136.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."),
34205
+ confirmed: z136.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."),
33954
34206
  // Plain text, not `.url()`: users type «cal.com», and the web accepts it.
33955
34207
  // normalizeWebAddress (shared with the web) adds https:// and refuses
33956
34208
  // anything that is not a public http(s) address.
33957
- 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."),
34209
+ landingUrl: z136.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."),
33958
34210
  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.'),
33959
34211
  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.`),
33960
34212
  language: MotionGraphicsLanguageSchema.optional().describe('Idioma de los textos del video: "es", "en" o "pt".'),
33961
34213
  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.'),
33962
34214
  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".'),
33963
- 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."),
34215
+ notes: z136.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."),
33964
34216
  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.`),
33965
- 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.")
34217
+ music: z136.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.")
33966
34218
  }).strict();
33967
34219
  function captureNextAction(capture) {
33968
34220
  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.";
@@ -34015,33 +34267,33 @@ var FORMAT_LABELS = {
34015
34267
  square: "cuadrado 1:1",
34016
34268
  horizontal: "horizontal 16:9"
34017
34269
  };
34018
- var AnimatedVideoResultSchema = z135.object({
34019
- status: z135.enum([
34270
+ var AnimatedVideoResultSchema = z136.object({
34271
+ status: z136.enum([
34020
34272
  "pending",
34021
34273
  "done",
34022
34274
  "error"
34023
34275
  ]),
34024
- serviceMode: z135.literal("motion_graphics"),
34025
- finalMp4Url: z135.string().optional(),
34026
- adCopy: z135.string().optional(),
34027
- adCopies: z135.array(z135.string()).optional(),
34028
- error: z135.string().optional(),
34029
- motionGraphics: z135.object({
34276
+ serviceMode: z136.literal("motion_graphics"),
34277
+ finalMp4Url: z136.string().optional(),
34278
+ adCopy: z136.string().optional(),
34279
+ adCopies: z136.array(z136.string()).optional(),
34280
+ error: z136.string().optional(),
34281
+ motionGraphics: z136.object({
34030
34282
  stage: MotionGraphicsStageSchema,
34031
34283
  format: MotionGraphicsFormatSchema,
34032
- durationSec: z135.number(),
34033
- width: z135.number().optional(),
34034
- height: z135.number().optional(),
34284
+ durationSec: z136.number(),
34285
+ width: z136.number().optional(),
34286
+ height: z136.number().optional(),
34035
34287
  language: MotionGraphicsLanguageSchema.optional(),
34036
34288
  videoQuality: MotionGraphicsVideoQualitySchema.optional().catch(void 0),
34037
34289
  videoResolution: MotionGraphicsVideoResolutionSchema.optional().catch(void 0),
34038
- creativeBatch: z135.object({
34290
+ creativeBatch: z136.object({
34039
34291
  index: MotionGraphicsHookVariantsSchema,
34040
34292
  count: MotionGraphicsHookVariantsSchema,
34041
- tasks: z135.array(z135.object({
34042
- taskId: z135.string().uuid(),
34293
+ tasks: z136.array(z136.object({
34294
+ taskId: z136.string().uuid(),
34043
34295
  index: MotionGraphicsHookVariantsSchema,
34044
- status: z135.enum([
34296
+ status: z136.enum([
34045
34297
  "queued",
34046
34298
  "processing",
34047
34299
  "done",
@@ -34049,29 +34301,29 @@ var AnimatedVideoResultSchema = z135.object({
34049
34301
  ])
34050
34302
  })).max(MOTION_GRAPHICS_HOOK_VARIANTS_MAX)
34051
34303
  }).optional().catch(void 0),
34052
- queuedAt: z135.string().optional(),
34053
- jobsAhead: z135.number().int().nonnegative().optional(),
34054
- stageStartedAt: z135.string().optional(),
34055
- renderProgress: z135.number().optional(),
34304
+ queuedAt: z136.string().optional(),
34305
+ jobsAhead: z136.number().int().nonnegative().optional(),
34306
+ stageStartedAt: z136.string().optional(),
34307
+ renderProgress: z136.number().optional(),
34056
34308
  // AI clip fields (absent on a video paid before clips, or from an API
34057
34309
  // that predates them). Lenient: an unreadable value is dropped
34058
34310
  // instead of hiding the status.
34059
- clipCount: z135.number().int().nonnegative().optional().catch(void 0),
34060
- clipsUsed: z135.number().int().nonnegative().optional().catch(void 0),
34061
- clipPriceCents: z135.number().finite().nonnegative().optional().catch(void 0),
34062
- clipsRefunded: z135.number().int().nonnegative().optional().catch(void 0),
34311
+ clipCount: z136.number().int().nonnegative().optional().catch(void 0),
34312
+ clipsUsed: z136.number().int().nonnegative().optional().catch(void 0),
34313
+ clipPriceCents: z136.number().finite().nonnegative().optional().catch(void 0),
34314
+ clipsRefunded: z136.number().int().nonnegative().optional().catch(void 0),
34063
34315
  // Hook versions (absent from an API or a video before them): the
34064
34316
  // count ordered and, once done, the signed URLs of the EXTRA versions
34065
34317
  // (hook 2, hook 3); `finalMp4Url` is hook 1.
34066
- hookVariants: z135.number().int().min(1).max(MOTION_GRAPHICS_HOOK_VARIANTS_MAX).optional().catch(void 0),
34067
- cleanVideoUrl: z135.string().url().optional().catch(void 0),
34068
- cleanVariantUrls: z135.array(z135.string().url()).max(MOTION_GRAPHICS_HOOK_VARIANTS_MAX - 1).optional().catch(void 0),
34069
- variantUrls: z135.array(z135.string().min(1)).max(MOTION_GRAPHICS_HOOK_VARIANTS_MAX - 1).optional().catch(void 0),
34318
+ hookVariants: z136.number().int().min(1).max(MOTION_GRAPHICS_HOOK_VARIANTS_MAX).optional().catch(void 0),
34319
+ cleanVideoUrl: z136.string().url().optional().catch(void 0),
34320
+ cleanVariantUrls: z136.array(z136.string().url()).max(MOTION_GRAPHICS_HOOK_VARIANTS_MAX - 1).optional().catch(void 0),
34321
+ variantUrls: z136.array(z136.string().min(1)).max(MOTION_GRAPHICS_HOOK_VARIANTS_MAX - 1).optional().catch(void 0),
34070
34322
  // Extra hook versions given back, as the task recorded it once the
34071
34323
  // refunds landed (absent until then, and from an API before it).
34072
- variantsRefunded: z135.number().int().nonnegative().optional().catch(void 0),
34073
- activity: z135.array(MotionGraphicsActivitySchema).optional().catch(void 0),
34074
- previewUrl: z135.string().optional()
34324
+ variantsRefunded: z136.number().int().nonnegative().optional().catch(void 0),
34325
+ activity: z136.array(MotionGraphicsActivitySchema).optional().catch(void 0),
34326
+ previewUrl: z136.string().optional()
34075
34327
  }).passthrough()
34076
34328
  }).passthrough();
34077
34329
  function parseAnimatedVideoResult(value) {
@@ -34350,7 +34602,7 @@ function registerAnimatedVideoTools(server, api) {
34350
34602
  nowMs: 0
34351
34603
  })
34352
34604
  });
34353
- const parsedTaskIds = z135.array(z135.string().uuid()).min(1).max(MOTION_GRAPHICS_HOOK_VARIANTS_MAX).safeParse(created.taskIds);
34605
+ const parsedTaskIds = z136.array(z136.string().uuid()).min(1).max(MOTION_GRAPHICS_HOOK_VARIANTS_MAX).safeParse(created.taskIds);
34354
34606
  const taskIds = parsedTaskIds.success && parsedTaskIds.data[0] === created.taskId ? parsedTaskIds.data : void 0;
34355
34607
  const receipt = {
34356
34608
  taskId: created.taskId,
@@ -34392,10 +34644,10 @@ function registerAnimatedVideoTools(server, api) {
34392
34644
  __name(registerAnimatedVideoTools, "registerAnimatedVideoTools");
34393
34645
 
34394
34646
  // src/tools/winning-product-image.ts
34395
- import { z as z136 } from "zod";
34396
- var WinnerReferenceInput = z136.object({
34397
- runId: z136.string().uuid(),
34398
- candidateSourceId: z136.string().trim().min(1).max(160)
34647
+ import { z as z137 } from "zod";
34648
+ var WinnerReferenceInput = z137.object({
34649
+ runId: z137.string().uuid(),
34650
+ candidateSourceId: z137.string().trim().min(1).max(160)
34399
34651
  }).strict().describe("Referencia opaca de un producto devuelto por find_winning_products/get_product_research_run.");
34400
34652
  async function resolveWinnerImages(api, winnerReference, images) {
34401
34653
  if (!winnerReference) return images;
@@ -34432,62 +34684,62 @@ var createProductTaskSingleOutputDescription = renderCreateProductTaskAvoidWhenF
34432
34684
  var CREATE_SERVICE_VIDEO_ON_MCP = PRODUCTMAKER_MCP_CAPABILITY_NAMES.includes("create_service_video");
34433
34685
  var listTasksGuidance = getProductMakerCapabilityModelGuidance("list_tasks");
34434
34686
  var editTaskDraftGuidance = getProductMakerCapabilityModelGuidance("edit_task_draft");
34435
- var CreateProductTaskInput = z137.object({
34687
+ var CreateProductTaskInput = z138.object({
34436
34688
  images: ImagesInput.optional().describe("1\u20135 fotos del mismo producto. Omitir \xFAnicamente cuando projectId apunta a un proyecto propio con imagen guardada."),
34437
34689
  winnerReference: WinnerReferenceInput.optional().describe(WINNER_IMAGE_CHOICE_GUIDANCE),
34438
- 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."),
34439
- 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."),
34690
+ projectId: z138.string().uuid().optional().describe("Opcional. Proyecto guiado propio. Permite continuar sin volver a subir la imagen guardada; nunca evita cr\xE9ditos, cuotas ni confirmaciones."),
34691
+ productUrl: z138.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."),
34440
34692
  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.'),
34441
34693
  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.'),
34442
- 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.'),
34443
- 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".'),
34444
- 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.'),
34445
- 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.'),
34446
- 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."),
34447
- 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.'),
34694
+ price: z138.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.'),
34695
+ compareAtPrice: z138.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".'),
34696
+ offer: z138.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.'),
34697
+ paymentMethods: z138.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.'),
34698
+ productContext: z138.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."),
34699
+ productLabel: z138.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.'),
34448
34700
  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.'),
34449
34701
  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.'),
34450
- durationSeconds: z137.union([
34451
- z137.literal(15),
34452
- z137.literal(30)
34702
+ durationSeconds: z138.union([
34703
+ z138.literal(15),
34704
+ z138.literal(30)
34453
34705
  ]).default(15).describe("Opcional. Duraci\xF3n objetivo del video: 15s (single_call, default) o 30s (Multiescena, aproximadamente 30s y facturado por escena)."),
34454
- 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."),
34455
- 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."),
34706
+ pauseForAngleReview: z138.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."),
34707
+ aiDecidesAll: z138.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."),
34456
34708
  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.'),
34457
34709
  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.'),
34458
34710
  videoQuality: VideoQualityEnum.default("economy").describe('Opcional. Calidad: "economy" = Econ\xF3mico, "medium" = Medio o "pro" = Pro. El proveedor se elige internamente. Default: economy.'),
34459
34711
  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).'),
34460
- 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."),
34712
+ videoAngleCount: z138.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."),
34461
34713
  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.'),
34462
- outputs: z137.array(z137.enum([
34714
+ outputs: z138.array(z138.enum([
34463
34715
  "landing",
34464
34716
  "video",
34465
34717
  "image"
34466
34718
  ])).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}`),
34467
- 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."),
34468
- 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."),
34719
+ includeInitialLandingGif: z138.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."),
34720
+ competitorScanEnabled: z138.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."),
34469
34721
  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.'),
34470
- 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."),
34471
- 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."),
34472
- 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."),
34722
+ replicateWinnerWhenFound: z138.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."),
34723
+ brandId: z138.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."),
34724
+ actorId: z138.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."),
34473
34725
  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.'),
34474
- 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.'),
34475
- 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."),
34476
- 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."),
34726
+ creativeDirection: z138.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.'),
34727
+ headline: z138.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."),
34728
+ subheadline: z138.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."),
34477
34729
  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".'),
34478
34730
  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".')
34479
34731
  }).strict();
34480
- var CreateServiceVideoInput = z137.object({
34481
- 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."),
34482
- 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.'),
34732
+ var CreateServiceVideoInput = z138.object({
34733
+ landingUrl: z138.string().url().describe("URL p\xFAblica de la landing del servicio. Es suficiente para generar el video; los medios son opcionales en la web."),
34734
+ additionalContext: z138.string().max(3e3).optional().describe('Instrucci\xF3n natural prioritaria. Ej.: "adapta el ritmo de estos competidores", "enfatiza reservas de demos" o restricciones de claims.'),
34483
34735
  language: Lang.describe("Idioma del video: es, en o pt."),
34484
- 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.'),
34485
- brandId: z137.string().uuid().optional().describe("Marca guardada del usuario. Aplica voz, mensajes, paleta y persona predeterminada."),
34486
- reviewMode: z137.enum([
34736
+ voiceId: z138.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.'),
34737
+ brandId: z138.string().uuid().optional().describe("Marca guardada del usuario. Aplica voz, mensajes, paleta y persona predeterminada."),
34738
+ reviewMode: z138.enum([
34487
34739
  "automatic",
34488
34740
  "guided"
34489
34741
  ]).default("automatic").describe("automatic = la IA elige concepto y guion sin pausas; guided = el usuario aprueba concepto y storyboard."),
34490
- videoPreset: z137.enum([
34742
+ videoPreset: z138.enum([
34491
34743
  "auto",
34492
34744
  "ugc_dynamic",
34493
34745
  "ugc_how_to",
@@ -34497,128 +34749,128 @@ var CreateServiceVideoInput = z137.object({
34497
34749
  videoQuality: VideoQualityEnum.default("economy").describe('Opcional. Calidad: "economy" = Econ\xF3mico, "medium" = Medio o "pro" = Pro. El proveedor se elige internamente. Default: economy.'),
34498
34750
  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.')
34499
34751
  }).strict();
34500
- var GetTaskStatusInput = z137.object({
34501
- taskId: z137.string().min(1),
34502
- waitSeconds: z137.number().int().min(0).max(120).optional()
34752
+ var GetTaskStatusInput = z138.object({
34753
+ taskId: z138.string().min(1),
34754
+ waitSeconds: z138.number().int().min(0).max(120).optional()
34503
34755
  });
34504
- var ListTasksInput = z137.object({
34505
- limit: z137.number().int().min(1).max(50).optional(),
34506
- offset: z137.number().int().min(0).optional(),
34507
- search: z137.string().optional()
34756
+ var ListTasksInput = z138.object({
34757
+ limit: z138.number().int().min(1).max(50).optional(),
34758
+ offset: z138.number().int().min(0).optional(),
34759
+ search: z138.string().optional()
34508
34760
  });
34509
34761
  var MCP_REVIEW_LIST_FIELD_MAX = IMAGE_WIZARD_LIST_FIELD_MAX;
34510
34762
  var MCP_REVIEW_FEATURE_ITEM_MAX = IMAGE_FEATURE_ITEM_MAX;
34511
- var EditTaskDraftInput = z137.object({
34512
- taskId: z137.string().min(1),
34513
- angleIndex: z137.number().int().min(0),
34514
- angle: z137.object({
34515
- angle: z137.string().min(1).max(480),
34516
- buyerPersona: z137.string().min(1).max(360),
34517
- actor: z137.string().min(1).max(360).optional(),
34518
- 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."),
34519
- gender: z137.enum([
34763
+ var EditTaskDraftInput = z138.object({
34764
+ taskId: z138.string().min(1),
34765
+ angleIndex: z138.number().int().min(0),
34766
+ angle: z138.object({
34767
+ angle: z138.string().min(1).max(480),
34768
+ buyerPersona: z138.string().min(1).max(360),
34769
+ actor: z138.string().min(1).max(360).optional(),
34770
+ actorId: z138.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."),
34771
+ gender: z138.enum([
34520
34772
  "female",
34521
34773
  "male"
34522
34774
  ]).optional(),
34523
- scriptNotes: z137.string().max(2e3).optional()
34775
+ scriptNotes: z138.string().max(2e3).optional()
34524
34776
  })
34525
34777
  });
34526
- var PendingActionPayload = z137.discriminatedUnion("kind", [
34527
- z137.object({
34528
- kind: z137.literal("product_reference_set_approval"),
34778
+ var PendingActionPayload = z138.discriminatedUnion("kind", [
34779
+ z138.object({
34780
+ kind: z138.literal("product_reference_set_approval"),
34529
34781
  scope: ProductReferenceScopeEnum.optional(),
34530
- scopeKey: z137.string().min(1).max(120).optional(),
34531
- scopeSnapshotId: z137.string().uuid().optional(),
34532
- expectedScopeHash: z137.string().min(1).max(128).optional(),
34782
+ scopeKey: z138.string().min(1).max(120).optional(),
34783
+ scopeSnapshotId: z138.string().uuid().optional(),
34784
+ expectedScopeHash: z138.string().min(1).max(128).optional(),
34533
34785
  decision: ProductReferenceResolutionDecisionEnum,
34534
34786
  reason: ProductReferenceRejectionReasonEnum.optional(),
34535
- affectedRefKeys: z137.array(z137.string().min(1).max(200)).min(1).optional(),
34536
- affectedPhotoNumbers: z137.array(z137.number().int().positive()).min(1).optional(),
34537
- recoveryAction: z137.enum([
34787
+ affectedRefKeys: z138.array(z138.string().min(1).max(200)).min(1).optional(),
34788
+ affectedPhotoNumbers: z138.array(z138.number().int().positive()).min(1).optional(),
34789
+ recoveryAction: z138.enum([
34538
34790
  "remove",
34539
34791
  "replace",
34540
34792
  "upload",
34541
34793
  "return_to_selection"
34542
34794
  ]).optional()
34543
34795
  }),
34544
- z137.object({
34545
- kind: z137.literal("product_reference_confirmation"),
34546
- 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."),
34796
+ z138.object({
34797
+ kind: z138.literal("product_reference_confirmation"),
34798
+ selectedPhotoNumbers: z138.array(z138.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."),
34547
34799
  userChoice: ProductReferenceUserChoiceEnum.optional(),
34548
- confirmedProductNames: z137.array(z137.string().min(1).max(120)).min(2).max(8).optional(),
34800
+ confirmedProductNames: z138.array(z138.string().min(1).max(120)).min(2).max(8).optional(),
34549
34801
  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."),
34550
34802
  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."),
34551
- useMultiSceneShowcase: z137.boolean().optional().describe("Compatibilidad con confirmaciones antiguas: si es true junto con userChoice=variants, conserva el showcase multiescena.")
34803
+ useMultiSceneShowcase: z138.boolean().optional().describe("Compatibilidad con confirmaciones antiguas: si es true junto con userChoice=variants, conserva el showcase multiescena.")
34552
34804
  }),
34553
- z137.object({
34554
- kind: z137.literal("angle_review_approval"),
34805
+ z138.object({
34806
+ kind: z138.literal("angle_review_approval"),
34555
34807
  // All edits are optional — empty action body just approves the
34556
34808
  // backend-generated draft as-is. Field shapes mirror the angle-review
34557
34809
  // request contracts in @packages/types/wizard.ts; the handler routes to
34558
34810
  // the dedicated video/image/autopilot endpoint based on StatusResponse.kind.
34559
- customAngle: z137.string().min(1).max(480).optional(),
34811
+ customAngle: z138.string().min(1).max(480).optional(),
34560
34812
  // Keep the shared MCP action within the strictest downstream contract:
34561
34813
  // autopilot/image resume endpoints cap these fields at 1500 characters.
34562
- painPoints: z137.string().max(MCP_REVIEW_LIST_FIELD_MAX).optional(),
34563
- benefits: z137.string().max(MCP_REVIEW_LIST_FIELD_MAX).optional(),
34814
+ painPoints: z138.string().max(MCP_REVIEW_LIST_FIELD_MAX).optional(),
34815
+ benefits: z138.string().max(MCP_REVIEW_LIST_FIELD_MAX).optional(),
34564
34816
  // Same policy as the two fields above: the strictest downstream contract
34565
34817
  // wins. The image endpoint caps each feature at 240 while landing/video
34566
34818
  // accept 300, and this single action routes to whichever the task is — so
34567
34819
  // accepting 300 here turns a 250-character feature on an image task into
34568
34820
  // an opaque HTTP 400 after the model already believed it had been saved.
34569
- features: z137.array(z137.string().min(1).max(MCP_REVIEW_FEATURE_ITEM_MAX)).max(10).optional(),
34570
- buyerPersona: z137.string().min(1).max(360).optional(),
34821
+ features: z138.array(z138.string().min(1).max(MCP_REVIEW_FEATURE_ITEM_MAX)).max(10).optional(),
34822
+ buyerPersona: z138.string().min(1).max(360).optional(),
34571
34823
  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.'),
34572
- 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."),
34573
- actorId: z137.string().max(120).nullable().optional(),
34574
- creativeDirection: z137.string().max(2e3).optional(),
34575
- 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."),
34576
- angleEdits: z137.array(z137.object({
34577
- angleNumber: z137.number().int().positive(),
34578
- narrativeSelection: z137.enum([
34824
+ actor: z138.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."),
34825
+ actorId: z138.string().max(120).nullable().optional(),
34826
+ creativeDirection: z138.string().max(2e3).optional(),
34827
+ selectedAngleNumbers: z138.array(z138.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."),
34828
+ angleEdits: z138.array(z138.object({
34829
+ angleNumber: z138.number().int().positive(),
34830
+ narrativeSelection: z138.enum([
34579
34831
  "market",
34580
34832
  "guided"
34581
34833
  ]).optional().describe("Elecci\xF3n expl\xEDcita: market usa el estudio disponible; guided usa la narrativa elegida para este \xE1ngulo."),
34582
- angle: z137.string().min(1).max(480).optional(),
34583
- buyerPersona: z137.string().min(1).max(360).optional(),
34834
+ angle: z138.string().min(1).max(480).optional(),
34835
+ buyerPersona: z138.string().min(1).max(360).optional(),
34584
34836
  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.'),
34585
- actor: z137.string().min(1).max(360).optional(),
34586
- actorId: z137.string().max(120).nullable().optional(),
34587
- creativeDirection: z137.string().max(2e3).optional()
34837
+ actor: z138.string().min(1).max(360).optional(),
34838
+ actorId: z138.string().max(120).nullable().optional(),
34839
+ creativeDirection: z138.string().max(2e3).optional()
34588
34840
  }).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)."),
34589
- gapDurationSeconds: z137.union([
34590
- z137.literal(15),
34591
- z137.literal(30)
34841
+ gapDurationSeconds: z138.union([
34842
+ z138.literal(15),
34843
+ z138.literal(30)
34592
34844
  ]).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.")
34593
34845
  }),
34594
- z137.object({
34595
- kind: z137.literal("service_angle_approval"),
34596
- angleId: z137.string().uuid().optional(),
34597
- angleNumber: z137.number().int().positive().optional(),
34598
- content: z137.string().min(1).max(480).optional(),
34599
- creativeDirection: z137.string().min(1).max(1500).optional(),
34600
- actor: z137.string().min(1).max(360).optional()
34846
+ z138.object({
34847
+ kind: z138.literal("service_angle_approval"),
34848
+ angleId: z138.string().uuid().optional(),
34849
+ angleNumber: z138.number().int().positive().optional(),
34850
+ content: z138.string().min(1).max(480).optional(),
34851
+ creativeDirection: z138.string().min(1).max(1500).optional(),
34852
+ actor: z138.string().min(1).max(360).optional()
34601
34853
  }),
34602
- z137.object({
34603
- kind: z137.literal("service_script_approval"),
34604
- voiceovers: z137.record(z137.string(), z137.string().min(1).max(600)).optional(),
34605
- visualDirections: z137.record(z137.string(), z137.string().min(1).max(1200)).optional(),
34606
- voiceoversByScene: z137.record(z137.string(), z137.string().min(1).max(600)).optional(),
34607
- visualDirectionsByScene: z137.record(z137.string(), z137.string().min(1).max(1200)).optional()
34854
+ z138.object({
34855
+ kind: z138.literal("service_script_approval"),
34856
+ voiceovers: z138.record(z138.string(), z138.string().min(1).max(600)).optional(),
34857
+ visualDirections: z138.record(z138.string(), z138.string().min(1).max(1200)).optional(),
34858
+ voiceoversByScene: z138.record(z138.string(), z138.string().min(1).max(600)).optional(),
34859
+ visualDirectionsByScene: z138.record(z138.string(), z138.string().min(1).max(1200)).optional()
34608
34860
  })
34609
34861
  ]);
34610
- var ResolvePendingActionInput = z137.object({
34611
- taskId: z137.string().min(1),
34862
+ var ResolvePendingActionInput = z138.object({
34863
+ taskId: z138.string().min(1),
34612
34864
  action: PendingActionPayload
34613
34865
  }).strict();
34614
34866
  function resolveVariantConfirmVideoTier(action) {
34615
34867
  return action.useMultiSceneShowcase && action.userChoice === "variants" ? "standard" : void 0;
34616
34868
  }
34617
34869
  __name(resolveVariantConfirmVideoTier, "resolveVariantConfirmVideoTier");
34618
- var StatusForResolveSchema = z137.object({
34870
+ var StatusForResolveSchema = z138.object({
34619
34871
  // Metadata used to select the backend resume contract. Older API responses
34620
34872
  // may omit these fields, so the resolver keeps the generic fallback.
34621
- kind: z137.enum([
34873
+ kind: z138.enum([
34622
34874
  "autopilot",
34623
34875
  "video",
34624
34876
  "image",
@@ -34626,18 +34878,18 @@ var StatusForResolveSchema = z137.object({
34626
34878
  "mix",
34627
34879
  "service"
34628
34880
  ]).optional(),
34629
- selectedOutputs: z137.array(z137.enum([
34881
+ selectedOutputs: z138.array(z138.enum([
34630
34882
  "landing",
34631
34883
  "video",
34632
34884
  "image"
34633
34885
  ])).optional(),
34634
- videoAngleCount: z137.number().int().min(1).max(3).optional(),
34635
- ugcFormat: z137.enum([
34886
+ videoAngleCount: z138.number().int().min(1).max(3).optional(),
34887
+ ugcFormat: z138.enum([
34636
34888
  "single_8s",
34637
34889
  "single_15s",
34638
34890
  "double_30s"
34639
34891
  ]).optional(),
34640
- videoStyle: z137.enum([
34892
+ videoStyle: z138.enum([
34641
34893
  "ugc",
34642
34894
  "cartoon_3d",
34643
34895
  "product_only",
@@ -34647,13 +34899,13 @@ var StatusForResolveSchema = z137.object({
34647
34899
  videoQuality: VideoQualityEnum.optional(),
34648
34900
  videoRouteId: VideoRouteIdEnum.optional(),
34649
34901
  videoModel: TaskVideoModelEnum.optional(),
34650
- videoTier: z137.enum([
34902
+ videoTier: z138.enum([
34651
34903
  "legacy",
34652
34904
  "standard",
34653
34905
  "pro",
34654
34906
  "single_call"
34655
34907
  ]).optional(),
34656
- videoResolution: z137.enum([
34908
+ videoResolution: z138.enum([
34657
34909
  "480p",
34658
34910
  "720p"
34659
34911
  ]).optional(),
@@ -34661,60 +34913,60 @@ var StatusForResolveSchema = z137.object({
34661
34913
  // A task created before a narrative was blocked/hidden must still parse,
34662
34914
  // or resolve_pending_action could never approve it.
34663
34915
  narrativeStyle: NarrativeStyle.optional(),
34664
- pauseForVideoScriptReview: z137.boolean().optional(),
34665
- pendingUserAction: z137.object({
34666
- kind: z137.string(),
34667
- candidateGrouping: z137.string().optional(),
34668
- selectedImageUrls: z137.array(z137.string()).optional(),
34669
- proposedProductNames: z137.array(z137.string()).optional(),
34916
+ pauseForVideoScriptReview: z138.boolean().optional(),
34917
+ pendingUserAction: z138.object({
34918
+ kind: z138.string(),
34919
+ candidateGrouping: z138.string().optional(),
34920
+ selectedImageUrls: z138.array(z138.string()).optional(),
34921
+ proposedProductNames: z138.array(z138.string()).optional(),
34670
34922
  // Adaptive reference questions are server-owned. Echo only the evidence
34671
34923
  // identity and the choices shown by the current snapshot; never accept a
34672
34924
  // client-supplied reference set as authorization.
34673
- options: z137.array(ProductReferenceUserChoiceEnum).max(4).optional(),
34674
- mode: z137.enum([
34925
+ options: z138.array(ProductReferenceUserChoiceEnum).max(4).optional(),
34926
+ mode: z138.enum([
34675
34927
  "question",
34676
34928
  "visual_recovery"
34677
34929
  ]).optional(),
34678
34930
  /** Hand-mirrored from packages/types result.ts — see CLAUDE.md § MCP Server Sync. */
34679
- canKeepSelection: z137.boolean().optional(),
34680
- decisionKey: z137.string().optional(),
34681
- questionOrdinal: z137.number().int().min(1).max(5).optional(),
34682
- questionCeiling: z137.union([
34683
- z137.literal(3),
34684
- z137.literal(5)
34931
+ canKeepSelection: z138.boolean().optional(),
34932
+ decisionKey: z138.string().optional(),
34933
+ questionOrdinal: z138.number().int().min(1).max(5).optional(),
34934
+ questionCeiling: z138.union([
34935
+ z138.literal(3),
34936
+ z138.literal(5)
34685
34937
  ]).optional(),
34686
- referenceEvidenceVersion: z137.number().int().positive().optional(),
34687
- expectedEvidenceHash: z137.string().optional(),
34688
- question: z137.object({
34689
- prompt: z137.string().min(1).max(240),
34690
- helpText: z137.string().min(1).max(300).optional(),
34691
- options: z137.array(z137.object({
34938
+ referenceEvidenceVersion: z138.number().int().positive().optional(),
34939
+ expectedEvidenceHash: z138.string().optional(),
34940
+ question: z138.object({
34941
+ prompt: z138.string().min(1).max(240),
34942
+ helpText: z138.string().min(1).max(300).optional(),
34943
+ options: z138.array(z138.object({
34692
34944
  value: ProductReferenceUserChoiceEnum,
34693
- label: z137.string().min(1).max(120),
34694
- description: z137.string().min(1).max(240).optional()
34945
+ label: z138.string().min(1).max(120),
34946
+ description: z138.string().min(1).max(240).optional()
34695
34947
  })).min(2).max(4)
34696
34948
  }).optional(),
34697
34949
  scope: ProductReferenceScopeEnum.optional(),
34698
- scopeKey: z137.string().optional(),
34699
- scopeSnapshotId: z137.string().uuid().optional(),
34700
- scopeHash: z137.string().optional(),
34701
- baseReferenceSetHash: z137.string().optional(),
34702
- phase: z137.enum([
34950
+ scopeKey: z138.string().optional(),
34951
+ scopeSnapshotId: z138.string().uuid().optional(),
34952
+ scopeHash: z138.string().optional(),
34953
+ baseReferenceSetHash: z138.string().optional(),
34954
+ phase: z138.enum([
34703
34955
  "preparation",
34704
34956
  "final"
34705
34957
  ]).optional(),
34706
- selectedRefKeys: z137.array(z137.string().min(1).max(200)).optional(),
34707
- allowedDecisions: z137.array(ProductReferenceResolutionDecisionEnum).optional(),
34708
- previews: z137.array(z137.object({
34709
- refKey: z137.string().min(1).max(200)
34958
+ selectedRefKeys: z138.array(z138.string().min(1).max(200)).optional(),
34959
+ allowedDecisions: z138.array(ProductReferenceResolutionDecisionEnum).optional(),
34960
+ previews: z138.array(z138.object({
34961
+ refKey: z138.string().min(1).max(200)
34710
34962
  }).passthrough()).optional(),
34711
- customAngleDraft: z137.string().optional(),
34712
- painPointsDraft: z137.string().optional(),
34713
- benefitsDraft: z137.string().optional(),
34714
- featuresDraft: z137.array(z137.string()).optional(),
34715
- angleOptions: z137.array(z137.object({
34716
- index: z137.number().int().nonnegative(),
34717
- angle: z137.string().min(1)
34963
+ customAngleDraft: z138.string().optional(),
34964
+ painPointsDraft: z138.string().optional(),
34965
+ benefitsDraft: z138.string().optional(),
34966
+ featuresDraft: z138.array(z138.string()).optional(),
34967
+ angleOptions: z138.array(z138.object({
34968
+ index: z138.number().int().nonnegative(),
34969
+ angle: z138.string().min(1)
34718
34970
  }).passthrough()).min(1).max(5).optional(),
34719
34971
  // Recommendation consumed only when the caller explicitly sends
34720
34972
  // gapDurationSeconds: 30 in the angle approval action.
@@ -34724,17 +34976,17 @@ var StatusForResolveSchema = z137.object({
34724
34976
  // keys. `actorId` (the saved custom actor the picker stored) was silently
34725
34977
  // dropped here, so the approval handler below could not echo it back on the
34726
34978
  // PATCH — the selection looked accepted and reached no render.
34727
- angles: z137.array(z137.object({
34728
- id: z137.string().uuid(),
34729
- content: z137.string(),
34730
- creativeDirection: z137.string(),
34731
- actor: z137.string(),
34732
- actorId: z137.string().nullable().optional()
34979
+ angles: z138.array(z138.object({
34980
+ id: z138.string().uuid(),
34981
+ content: z138.string(),
34982
+ creativeDirection: z138.string(),
34983
+ actor: z138.string(),
34984
+ actorId: z138.string().nullable().optional()
34733
34985
  }).passthrough()).optional(),
34734
- panels: z137.array(z137.object({
34735
- beatId: z137.string(),
34736
- voiceover: z137.string(),
34737
- visualDirection: z137.string()
34986
+ panels: z138.array(z138.object({
34987
+ beatId: z138.string(),
34988
+ voiceover: z138.string(),
34989
+ visualDirection: z138.string()
34738
34990
  }).passthrough()).optional()
34739
34991
  }).optional()
34740
34992
  }).passthrough();
@@ -35087,8 +35339,8 @@ ANTES de invocar, resume outputs, cantidad de videos y datos comerciales, explic
35087
35339
  server.registerTool("verify_landing_commerce", {
35088
35340
  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.",
35089
35341
  inputSchema: {
35090
- taskId: z137.string().uuid(),
35091
- editId: z137.string().uuid()
35342
+ taskId: z138.string().uuid(),
35343
+ editId: z138.string().uuid()
35092
35344
  },
35093
35345
  annotations: OVERWRITES_EXTERNAL
35094
35346
  }, async (input) => {
@@ -35359,16 +35611,16 @@ ANTES de invocar, resume outputs, cantidad de videos y datos comerciales, explic
35359
35611
  if (input.search) qs.set("search", input.search);
35360
35612
  const path3 = `/v1/tasks${qs.toString() ? "?" + qs.toString() : ""}`;
35361
35613
  const r = await api.getJson(path3);
35362
- const page = z137.object({
35363
- items: z137.array(z137.unknown())
35614
+ const page = z138.object({
35615
+ items: z138.array(z138.unknown())
35364
35616
  }).passthrough().safeParse(r);
35365
35617
  if (!page.success) return asText(r);
35366
35618
  return asText({
35367
35619
  ...page.data,
35368
35620
  items: page.data.items.map((item) => {
35369
- const servicePage = z137.object({
35370
- taskId: z137.string().uuid(),
35371
- resourceType: z137.literal("service_page")
35621
+ const servicePage = z138.object({
35622
+ taskId: z138.string().uuid(),
35623
+ resourceType: z138.literal("service_page")
35372
35624
  }).passthrough().safeParse(item);
35373
35625
  if (!servicePage.success) return item;
35374
35626
  return {
@@ -35858,28 +36110,28 @@ ANTES de invocar, resume outputs, cantidad de videos y datos comerciales, explic
35858
36110
  __name(registerTaskTools, "registerTaskTools");
35859
36111
 
35860
36112
  // src/tools/brands.ts
35861
- import { z as z138 } from "zod";
36113
+ import { z as z139 } from "zod";
35862
36114
  var guidance2 = getProductMakerCapabilityModelGuidance("create_brand");
35863
36115
  var BRAND_START_REPLAY_WINDOW_MS = 15 * 60 * 1e3;
35864
36116
  var BRAND_NOTES_MAX_CHARS = 2e4;
35865
36117
  var BRAND_AMBIGUOUS_REQUEST_RETENTION_MS = 24 * 60 * 60 * 1e3;
35866
36118
  var BRAND_CREATION_REQUEST_LIMIT = 500;
35867
36119
  var brandCreationRequests = /* @__PURE__ */ new Map();
35868
- var CreateBrandInput = z138.object({
35869
- action: z138.enum([
36120
+ var CreateBrandInput = z139.object({
36121
+ action: z139.enum([
35870
36122
  "start",
35871
36123
  "status",
35872
36124
  "list"
35873
36125
  ]).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."),
35874
- 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."),
35875
- 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."),
35876
- language: z138.enum([
36126
+ sourceUrl: z139.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."),
36127
+ businessNotes: z139.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."),
36128
+ language: z139.enum([
35877
36129
  "es",
35878
36130
  "en",
35879
36131
  "pt"
35880
36132
  ]).optional().describe("Idioma de la conversaci\xF3n. Solo se usa si la p\xE1gina del negocio no deja claro su idioma."),
35881
- 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."),
35882
- brandId: z138.string().uuid().optional().describe("ID de la marca. Obligatorio para status; no lo inventes.")
36133
+ confirmed: z139.boolean().optional().describe("Solo true despu\xE9s de mostrar el precio y que el usuario acepte. Sin confirmed, start solo cotiza y no cobra."),
36134
+ brandId: z139.string().uuid().optional().describe("ID de la marca. Obligatorio para status; no lo inventes.")
35883
36135
  }).strict();
35884
36136
  function normalizeBrandSourceUrl(raw) {
35885
36137
  const value = raw.trim();
@@ -36163,24 +36415,24 @@ __name(registerBrandTools, "registerBrandTools");
36163
36415
 
36164
36416
  // src/tools/service-landing.ts
36165
36417
  import { createHash as createHash4, randomUUID } from "crypto";
36166
- import { z as z139 } from "zod";
36418
+ import { z as z140 } from "zod";
36167
36419
  var guidance3 = getProductMakerCapabilityModelGuidance("create_service_landing");
36168
36420
  var SERVICE_LANDING_KEY_NAMESPACE = "6f1d8c2a-3b4e-5f60-8a7b-9c0d1e2f3a4b";
36169
36421
  var SERVICE_LANDING_INFO_MAX_CHARS = 2e4;
36170
- var CreateServiceLandingInput = z139.object({
36171
- action: z139.enum([
36422
+ var CreateServiceLandingInput = z140.object({
36423
+ action: z140.enum([
36172
36424
  "prepare",
36173
36425
  "build",
36174
36426
  "status",
36175
36427
  "publish"
36176
36428
  ]).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."),
36177
- brandId: z139.string().uuid().optional().describe("ID de la marca (de create_brand). Obligatorio para prepare. Nunca un taskId de producto."),
36178
- servicePageId: z139.string().uuid().optional().describe("ID de la landing que devolvi\xF3 prepare. Obligatorio para build, status y publish."),
36179
- 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."),
36180
- confirmed: z139.boolean().optional().describe("Solo true despu\xE9s de mostrar el precio total y que el usuario acepte. Obligatorio para build."),
36181
- 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."),
36182
- operationId: z139.string().uuid().optional().describe("Opcional en status: la operaci\xF3n que devolvi\xF3 build."),
36183
- 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.")
36429
+ brandId: z140.string().uuid().optional().describe("ID de la marca (de create_brand). Obligatorio para prepare. Nunca un taskId de producto."),
36430
+ servicePageId: z140.string().uuid().optional().describe("ID de la landing que devolvi\xF3 prepare. Obligatorio para build, status y publish."),
36431
+ businessInfo: z140.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."),
36432
+ confirmed: z140.boolean().optional().describe("Solo true despu\xE9s de mostrar el precio total y que el usuario acepte. Obligatorio para build."),
36433
+ maxCredits: z140.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."),
36434
+ operationId: z140.string().uuid().optional().describe("Opcional en status: la operaci\xF3n que devolvi\xF3 build."),
36435
+ slug: z140.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.")
36184
36436
  }).strict();
36185
36437
  function serviceKey(...parts) {
36186
36438
  return deriveUuidV5(parts.join(":"), SERVICE_LANDING_KEY_NAMESPACE);
@@ -36377,7 +36629,11 @@ function registerServiceLandingTools(server, api) {
36377
36629
  server.registerTool("create_service_landing", {
36378
36630
  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.",
36379
36631
  inputSchema: CreateServiceLandingInput.shape,
36380
- annotations: CREATES_PAID
36632
+ // action=publish exposes the page to the public internet.
36633
+ annotations: {
36634
+ ...CREATES_PAID,
36635
+ openWorldHint: true
36636
+ }
36381
36637
  }, async (rawInput) => {
36382
36638
  try {
36383
36639
  const input = CreateServiceLandingInput.parse(rawInput);
@@ -36555,7 +36811,7 @@ function registerServiceLandingTools(server, api) {
36555
36811
  __name(registerServiceLandingTools, "registerServiceLandingTools");
36556
36812
 
36557
36813
  // src/tools/service-images.ts
36558
- import { z as z140 } from "zod";
36814
+ import { z as z141 } from "zod";
36559
36815
  var guidance4 = getProductMakerCapabilityModelGuidance("create_service_images");
36560
36816
  var SERVICE_IMAGE_FORMATS = [
36561
36817
  "4:5",
@@ -36568,23 +36824,23 @@ var LANGUAGE_TAGS = {
36568
36824
  en: "en-US",
36569
36825
  pt: "pt-BR"
36570
36826
  };
36571
- var CreateServiceImagesInput = z140.object({
36572
- 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."),
36573
- 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."),
36574
- 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."),
36575
- format: z140.enum(SERVICE_IMAGE_FORMATS).optional().describe('Formato: "4:5" (feed, por defecto), "9:16" (historias/reels) o "1:1" (cuadrado).'),
36576
- count: z140.number().int().min(1).max(SERVICE_IMAGE_MAX_COUNT).optional().describe("Cu\xE1ntas im\xE1genes, de 1 a 3 (por defecto 1)."),
36577
- quality: z140.enum([
36827
+ var CreateServiceImagesInput = z141.object({
36828
+ brandId: z141.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."),
36829
+ instruction: z141.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."),
36830
+ delegateToAi: z141.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."),
36831
+ format: z141.enum(SERVICE_IMAGE_FORMATS).optional().describe('Formato: "4:5" (feed, por defecto), "9:16" (historias/reels) o "1:1" (cuadrado).'),
36832
+ count: z141.number().int().min(1).max(SERVICE_IMAGE_MAX_COUNT).optional().describe("Cu\xE1ntas im\xE1genes, de 1 a 3 (por defecto 1)."),
36833
+ quality: z141.enum([
36578
36834
  "standard",
36579
36835
  "high"
36580
36836
  ]).optional().describe('Calidad: "standard" (por defecto) o "high" (m\xE1s cara; no disponible en 4:5).'),
36581
- language: z140.enum([
36837
+ language: z141.enum([
36582
36838
  "es",
36583
36839
  "en",
36584
36840
  "pt"
36585
36841
  ]).optional().describe("Idioma de los textos de la imagen (por defecto es)."),
36586
- adCopy: z140.boolean().optional().describe("Compatibilidad con clientes anteriores: el copy para publicar siempre est\xE1 incluido en cada imagen de servicios."),
36587
- 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.")
36842
+ adCopy: z141.boolean().optional().describe("Compatibilidad con clientes anteriores: el copy para publicar siempre est\xE1 incluido en cada imagen de servicios."),
36843
+ confirmed: z141.boolean().optional().describe("Solo true despu\xE9s de mostrar el precio y que el usuario acepte. Sin confirmed, solo cotiza y no cobra.")
36588
36844
  }).strict();
36589
36845
  function serviceImageModel(quality) {
36590
36846
  return quality === "high" ? AD_CREATIVE_MODELS.PREMIUM : AD_CREATIVE_MODELS.STANDARD;
@@ -36701,7 +36957,7 @@ function registerServiceImageTools(server, api, options = {}) {
36701
36957
  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."
36702
36958
  });
36703
36959
  } catch (error) {
36704
- if (error instanceof z140.ZodError) throw invalidInputFromZod(error);
36960
+ if (error instanceof z141.ZodError) throw invalidInputFromZod(error);
36705
36961
  throw translateError(error);
36706
36962
  }
36707
36963
  });
@@ -36709,7 +36965,7 @@ function registerServiceImageTools(server, api, options = {}) {
36709
36965
  __name(registerServiceImageTools, "registerServiceImageTools");
36710
36966
 
36711
36967
  // src/tools/creatives.ts
36712
- import { z as z141 } from "zod";
36968
+ import { z as z142 } from "zod";
36713
36969
  var generateFreeVideoGuidance = getProductMakerCapabilityModelGuidance("generate_free_video");
36714
36970
  var generateVideoCreativeGuidance = getProductMakerCapabilityModelGuidance("generate_video_creative");
36715
36971
  var generateImageCreativesGuidance = getProductMakerCapabilityModelGuidance("generate_image_creatives");
@@ -36720,72 +36976,72 @@ var CreativeRefs = {
36720
36976
  images: ImagesInput,
36721
36977
  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.'),
36722
36978
  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.'),
36723
- 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.'),
36724
- 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.'),
36725
- 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".'),
36726
- 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."),
36727
- 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.'),
36728
- 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.'),
36729
- isCombo: z141.boolean().optional().describe("Opcional. Si true, el creativo presenta el producto como combo/pack. Default false."),
36730
- 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."),
36731
- 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.")
36979
+ price: z142.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.'),
36980
+ offer: z142.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.'),
36981
+ compareAtPrice: z142.string().min(1).max(120).optional().describe('Opcional. Precio anterior o de referencia para mostrar descuento. Ej. "$129.000" cuando price="$89.900".'),
36982
+ productContext: z142.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."),
36983
+ productLabel: z142.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.'),
36984
+ buyerPersona: z142.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.'),
36985
+ isCombo: z142.boolean().optional().describe("Opcional. Si true, el creativo presenta el producto como combo/pack. Default false."),
36986
+ competitorScanEnabled: z142.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."),
36987
+ brandId: z142.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.")
36732
36988
  };
36733
- var VideoPresenter = z141.discriminatedUnion("mode", [
36734
- z141.object({
36735
- mode: z141.literal("none")
36989
+ var VideoPresenter = z142.discriminatedUnion("mode", [
36990
+ z142.object({
36991
+ mode: z142.literal("none")
36736
36992
  }).strict(),
36737
- z141.object({
36738
- mode: z141.literal("saved_actor"),
36739
- actorId: z141.string().trim().min(1).max(120).describe("ID devuelto por list_actors para un actor guardado del usuario.")
36993
+ z142.object({
36994
+ mode: z142.literal("saved_actor"),
36995
+ actorId: z142.string().trim().min(1).max(120).describe("ID devuelto por list_actors para un actor guardado del usuario.")
36740
36996
  }).strict(),
36741
- z141.object({
36742
- mode: z141.literal("described"),
36743
- description: z141.string().trim().min(1).max(500).describe("Descripci\xF3n concreta de la persona que presenta el producto.")
36997
+ z142.object({
36998
+ mode: z142.literal("described"),
36999
+ description: z142.string().trim().min(1).max(500).describe("Descripci\xF3n concreta de la persona que presenta el producto.")
36744
37000
  }).strict()
36745
37001
  ]);
36746
- var VideoCreativeSetup = z141.discriminatedUnion("mode", [
36747
- z141.object({
36748
- mode: z141.literal("automatic").describe('S\xF3lo cuando el usuario pidi\xF3 expl\xEDcitamente "decide todo".')
37002
+ var VideoCreativeSetup = z142.discriminatedUnion("mode", [
37003
+ z142.object({
37004
+ mode: z142.literal("automatic").describe('S\xF3lo cuando el usuario pidi\xF3 expl\xEDcitamente "decide todo".')
36749
37005
  }).strict(),
36750
- z141.object({
36751
- mode: z141.literal("guided"),
37006
+ z142.object({
37007
+ mode: z142.literal("guided"),
36752
37008
  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.'),
36753
- buyerPersona: z141.string().trim().min(1).max(360),
37009
+ buyerPersona: z142.string().trim().min(1).max(360),
36754
37010
  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.'),
36755
37011
  presenter: VideoPresenter,
36756
- 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.")
37012
+ creativeDirection: z142.string().trim().min(1).max(500).nullable().describe("Direcci\xF3n visual elegida. null significa que el usuario deleg\xF3 expl\xEDcitamente esta decisi\xF3n.")
36757
37013
  }).strict()
36758
37014
  ]);
36759
- var VideoInput = z141.object({
37015
+ var VideoInput = z142.object({
36760
37016
  images: CreativeRefs.images.optional(),
36761
37017
  winnerReference: WinnerReferenceInput.optional().describe(WINNER_IMAGE_CHOICE_GUIDANCE),
36762
- title: z141.string().min(1).describe('T\xEDtulo corto del producto. Aparece en la primera vista del video. Ej. "Crema Anti-Edad Premium".'),
36763
- 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".'),
37018
+ title: z142.string().min(1).describe('T\xEDtulo corto del producto. Aparece en la primera vista del video. Ej. "Crema Anti-Edad Premium".'),
37019
+ primaryAngle: z142.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".'),
36764
37020
  language: CreativeRefs.language,
36765
37021
  country: CreativeRefs.country,
36766
- aspectRatio: z141.enum([
37022
+ aspectRatio: z142.enum([
36767
37023
  "9:16",
36768
37024
  "16:9",
36769
37025
  "Auto"
36770
37026
  ]).describe('Relaci\xF3n de aspecto. "9:16" = vertical (TikTok/Reels \u2014 default recomendado para ads). "16:9" = horizontal (YouTube). "Auto" = decide la IA.'),
36771
37027
  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.'),
36772
- 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."),
36773
- 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."),
37028
+ durationSeconds: z142.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."),
37029
+ videoAngleCount: z142.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."),
36774
37030
  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.'),
36775
37031
  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."),
36776
37032
  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.'),
36777
37033
  videoQuality: VideoQualityEnum.default("economy").describe('Opcional. Calidad: "economy" = Econ\xF3mico, "medium" = Medio o "pro" = Pro. El sistema elige el proveedor internamente. Default: economy.'),
36778
37034
  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.'),
36779
- 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"].'),
37035
+ features: z142.array(z142.string()).optional().describe('Opcional. Lista de features/beneficios clave a mencionar. Ej. ["resistente al agua", "bater\xEDa 24h", "carga inal\xE1mbrica"].'),
36780
37036
  price: CreativeRefs.price,
36781
37037
  offer: CreativeRefs.offer,
36782
37038
  productContext: CreativeRefs.productContext,
36783
37039
  productLabel: CreativeRefs.productLabel,
36784
- 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.'),
36785
- 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.'),
37040
+ cta: z142.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.'),
37041
+ scriptNotes: z142.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.'),
36786
37042
  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.'),
36787
37043
  competitorScanEnabled: CreativeRefs.competitorScanEnabled,
36788
- 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."),
37044
+ replicateWinnerWhenFound: z142.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."),
36789
37045
  brandId: CreativeRefs.brandId
36790
37046
  }).strict();
36791
37047
  var FreeVideoQualityEnum = VideoQualityEnum;
@@ -36803,7 +37059,7 @@ var FREE_VIDEO_DURATIONS_BY_QUALITY = {
36803
37059
  length: 29
36804
37060
  }, (_, index) => index + 2)
36805
37061
  };
36806
- var FreeVideoModelIdEnum = z141.enum([
37062
+ var FreeVideoModelIdEnum = z142.enum([
36807
37063
  "grok-imagine-video-1-5-preview",
36808
37064
  "minimax-h3/reference-to-video",
36809
37065
  "wan/3-0-video",
@@ -36814,66 +37070,66 @@ var FREE_SELECTABLE_MODEL_IDS = [
36814
37070
  "minimax-h3/reference-to-video",
36815
37071
  "wan/3-0-video"
36816
37072
  ];
36817
- var FreeVideoInput = z141.object({
37073
+ var FreeVideoInput = z142.object({
36818
37074
  images: ImagesInput.optional().describe("Opcional. 1-5 fotos del producto. Om\xEDtelo para generar desde texto puro."),
36819
- 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."),
37075
+ rawPrompt: z142.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."),
36820
37076
  language: CreativeRefs.language,
36821
37077
  quality: FreeVideoQualityEnum.describe('Calidad del video: "economy" = Econ\xF3mico, "medium" = Medio o "pro" = Pro (Wan).'),
36822
37078
  // Retired in MCP 7.0.0 (Task 11): kept in the schema, accepted, and
36823
37079
  // parsed ONLY so the handler can reject it with a message naming its
36824
37080
  // replacement — dropping it silently would turn a caller's explicit
36825
37081
  // Pro++ pick into a quiet fallback to Wan.
36826
- freeProModel: z141.enum([
37082
+ freeProModel: z142.enum([
36827
37083
  "wan",
36828
37084
  "seedance"
36829
37085
  ]).optional().describe("ELIMINADO en MCP 7.0.0 \u2014 usa `modelId`. Enviar este campo produce un error."),
36830
37086
  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.'),
36831
- 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."),
37087
+ durationSeconds: z142.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."),
36832
37088
  videoResolution: VideoResolutionEnum.describe("Resoluci\xF3n del video: 480p o 720p."),
36833
- 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.'),
37089
+ generateAiMusic: z142.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.'),
36834
37090
  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.'),
36835
- addSubtitles: z141.boolean().default(false).describe("Opcional. Quema subt\xEDtulos usando el rawPrompt exacto. Default false."),
36836
- 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.")
37091
+ addSubtitles: z142.boolean().default(false).describe("Opcional. Quema subt\xEDtulos usando el rawPrompt exacto. Default false."),
37092
+ idempotencyKey: z142.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.")
36837
37093
  }).strict();
36838
37094
  var imageStyleList = ImageCreativeStyleEnum.options.join(" | ");
36839
- var ImageCreativeSetup = z141.discriminatedUnion("mode", [
36840
- z141.object({
36841
- mode: z141.literal("automatic")
37095
+ var ImageCreativeSetup = z142.discriminatedUnion("mode", [
37096
+ z142.object({
37097
+ mode: z142.literal("automatic")
36842
37098
  }).strict(),
36843
- z141.object({
36844
- mode: z141.literal("guided"),
36845
- creativeArchetype: z141.union([
37099
+ z142.object({
37100
+ mode: z142.literal("guided"),
37101
+ creativeArchetype: z142.union([
36846
37102
  ImageCreativeArchetypeEnum,
36847
- z141.literal("ai_decides")
37103
+ z142.literal("ai_decides")
36848
37104
  ]),
36849
- visualTreatment: z141.union([
37105
+ visualTreatment: z142.union([
36850
37106
  ImageCreativeVisualTreatmentEnum,
36851
- z141.literal("ai_decides")
37107
+ z142.literal("ai_decides")
36852
37108
  ]),
36853
37109
  aspectRatio: ImageCreativeAspectRatioEnum,
36854
- nVariants: z141.union([
36855
- z141.literal(1),
36856
- z141.literal(2),
36857
- z141.literal(3),
36858
- z141.literal(4),
36859
- z141.literal(6)
37110
+ nVariants: z142.union([
37111
+ z142.literal(1),
37112
+ z142.literal(2),
37113
+ z142.literal(3),
37114
+ z142.literal(4),
37115
+ z142.literal(6)
36860
37116
  ]),
36861
- 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.'),
36862
- 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."),
36863
- generateAdCopy: z141.boolean(),
36864
- competitorScanEnabled: z141.boolean(),
36865
- productContext: z141.string().trim().min(1).max(3e3).nullable(),
36866
- creativeDirection: z141.string().trim().min(1).max(500).nullable(),
36867
- 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.'),
36868
- headline: z141.string().trim().min(1).max(120).nullable(),
36869
- subheadline: z141.string().trim().min(1).max(160).nullable()
37117
+ imageModel: z142.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.'),
37118
+ styleMix: z142.boolean().nullable().describe("Decisi\xF3n s\xF3lo para 2\u20134 variantes. Usa null para 1 o 6, cuando la UI no hace esta pregunta."),
37119
+ generateAdCopy: z142.boolean(),
37120
+ competitorScanEnabled: z142.boolean(),
37121
+ productContext: z142.string().trim().min(1).max(3e3).nullable(),
37122
+ creativeDirection: z142.string().trim().min(1).max(500).nullable(),
37123
+ brandColors: z142.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.'),
37124
+ headline: z142.string().trim().min(1).max(120).nullable(),
37125
+ subheadline: z142.string().trim().min(1).max(160).nullable()
36870
37126
  }).strict()
36871
37127
  ]).superRefine((setup, context) => {
36872
37128
  if (setup.mode !== "guided") return;
36873
37129
  const needsStyleMix = setup.nVariants >= 2 && setup.nVariants <= 4;
36874
37130
  if (needsStyleMix && setup.styleMix === null) {
36875
37131
  context.addIssue({
36876
- code: z141.ZodIssueCode.custom,
37132
+ code: z142.ZodIssueCode.custom,
36877
37133
  path: [
36878
37134
  "styleMix"
36879
37135
  ],
@@ -36882,7 +37138,7 @@ var ImageCreativeSetup = z141.discriminatedUnion("mode", [
36882
37138
  }
36883
37139
  if (!needsStyleMix && setup.styleMix !== null) {
36884
37140
  context.addIssue({
36885
- code: z141.ZodIssueCode.custom,
37141
+ code: z142.ZodIssueCode.custom,
36886
37142
  path: [
36887
37143
  "styleMix"
36888
37144
  ],
@@ -36891,7 +37147,7 @@ var ImageCreativeSetup = z141.discriminatedUnion("mode", [
36891
37147
  }
36892
37148
  if (imageResolutionOf(setup.imageModel) === "2k" && setup.aspectRatio === "4:5") {
36893
37149
  context.addIssue({
36894
- code: z141.ZodIssueCode.custom,
37150
+ code: z142.ZodIssueCode.custom,
36895
37151
  path: [
36896
37152
  "aspectRatio"
36897
37153
  ],
@@ -36899,55 +37155,55 @@ var ImageCreativeSetup = z141.discriminatedUnion("mode", [
36899
37155
  });
36900
37156
  }
36901
37157
  });
36902
- var LandingCreativeSetup = z141.discriminatedUnion("mode", [
36903
- z141.object({
36904
- mode: z141.literal("automatic")
37158
+ var LandingCreativeSetup = z142.discriminatedUnion("mode", [
37159
+ z142.object({
37160
+ mode: z142.literal("automatic")
36905
37161
  }).strict(),
36906
- z141.object({
36907
- mode: z141.literal("guided"),
37162
+ z142.object({
37163
+ mode: z142.literal("guided"),
36908
37164
  landingStrategy: LandingStrategyEnum,
36909
37165
  landingImageFormat: LandingImageFormatEnum,
36910
- competitorScanEnabled: z141.boolean(),
36911
- paymentMethods: z141.string().trim().min(1).max(240).nullable(),
36912
- productContext: z141.string().trim().min(1).max(3e3).nullable(),
36913
- creativeDirection: z141.string().trim().min(1).max(500).nullable(),
36914
- headline: z141.string().trim().min(1).max(120).nullable(),
36915
- subheadline: z141.string().trim().min(1).max(160).nullable()
37166
+ competitorScanEnabled: z142.boolean(),
37167
+ paymentMethods: z142.string().trim().min(1).max(240).nullable(),
37168
+ productContext: z142.string().trim().min(1).max(3e3).nullable(),
37169
+ creativeDirection: z142.string().trim().min(1).max(500).nullable(),
37170
+ headline: z142.string().trim().min(1).max(120).nullable(),
37171
+ subheadline: z142.string().trim().min(1).max(160).nullable()
36916
37172
  }).strict()
36917
37173
  ]);
36918
- var ImageInput = z141.object({
37174
+ var ImageInput = z142.object({
36919
37175
  images: CreativeRefs.images.optional(),
36920
37176
  winnerReference: WinnerReferenceInput.optional().describe(WINNER_IMAGE_CHOICE_GUIDANCE),
36921
- productTitle: z141.string().min(1).describe('Nombre del producto. Ej. "Crema Anti-Edad Premium".'),
36922
- 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.'),
37177
+ productTitle: z142.string().min(1).describe('Nombre del producto. Ej. "Crema Anti-Edad Premium".'),
37178
+ primaryAngle: z142.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.'),
36923
37179
  buyerPersona: CreativeRefs.buyerPersona,
36924
37180
  language: CreativeRefs.language,
36925
37181
  country: CreativeRefs.country,
36926
37182
  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."),
36927
37183
  imageStyle: ImageCreativeStyleEnum.optional().describe(`Opcional. Estilo del shot del producto. Valores: ${imageStyleList}. Si se omite, la IA escoge seg\xFAn el archetype.`),
36928
- features: z141.array(z141.string()).optional().describe('Opcional. Features/beneficios a destacar visualmente. Ej. ["resistente al agua", "ultra liviano"].'),
37184
+ features: z142.array(z142.string()).optional().describe('Opcional. Features/beneficios a destacar visualmente. Ej. ["resistente al agua", "ultra liviano"].'),
36929
37185
  price: CreativeRefs.price,
36930
- 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."),
37186
+ omitPrice: z142.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."),
36931
37187
  compareAtPrice: CreativeRefs.compareAtPrice,
36932
37188
  offer: CreativeRefs.offer,
36933
37189
  productLabel: CreativeRefs.productLabel,
36934
37190
  isCombo: CreativeRefs.isCombo,
36935
37191
  brandId: CreativeRefs.brandId
36936
37192
  });
36937
- var EditImageCreativeInput = z141.object({
36938
- taskId: z141.string().uuid().describe("ID de la tarea de imagen ya finalizada."),
36939
- variantIndex: z141.number().int().min(0).max(10).describe("\xCDndice de la variante a corregir (0 = primera imagen)."),
36940
- 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."),
36941
- 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".')
37193
+ var EditImageCreativeInput = z142.object({
37194
+ taskId: z142.string().uuid().describe("ID de la tarea de imagen ya finalizada."),
37195
+ variantIndex: z142.number().int().min(0).max(10).describe("\xCDndice de la variante a corregir (0 = primera imagen)."),
37196
+ clientRequestId: z142.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."),
37197
+ instruction: z142.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".')
36942
37198
  });
36943
- var LandingInput = z141.object({
36944
- 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."),
37199
+ var LandingInput = z142.object({
37200
+ includeInitialLandingGif: z142.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."),
36945
37201
  images: CreativeRefs.images.optional(),
36946
37202
  winnerReference: WinnerReferenceInput.optional().describe(WINNER_IMAGE_CHOICE_GUIDANCE),
36947
37203
  language: CreativeRefs.language,
36948
37204
  country: CreativeRefs.country,
36949
37205
  creativeSetup: LandingCreativeSetup.describe("Configuraci\xF3n obligatoria de landing cl\xE1sica equivalente al wizard web. No expone landing editable/code mientras siga siendo admin-only."),
36950
- 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.'),
37206
+ salesAngle: z142.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.'),
36951
37207
  buyerPersona: CreativeRefs.buyerPersona,
36952
37208
  price: CreativeRefs.price,
36953
37209
  compareAtPrice: CreativeRefs.compareAtPrice,
@@ -37017,6 +37273,7 @@ function resolveLandingSetup(setup) {
37017
37273
  __name(resolveLandingSetup, "resolveLandingSetup");
37018
37274
  function registerCreativeTools(server, api, { transport = "http" } = {}) {
37019
37275
  server.registerTool("generate_free_video", {
37276
+ title: "Custom-script video",
37020
37277
  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.',
37021
37278
  inputSchema: FreeVideoInput.shape,
37022
37279
  annotations: CREATES_PAID_OPEN_WORLD
@@ -37229,8 +37486,8 @@ function registerCreativeTools(server, api, { transport = "http" } = {}) {
37229
37486
  server.registerTool("get_image_edit_status", {
37230
37487
  description: imageEditStatusGuidance.summary,
37231
37488
  inputSchema: {
37232
- taskId: z141.string().uuid(),
37233
- operationId: z141.string().uuid()
37489
+ taskId: z142.string().uuid(),
37490
+ operationId: z142.string().uuid()
37234
37491
  },
37235
37492
  annotations: READS_INTERNAL
37236
37493
  }, async (input) => {
@@ -37287,10 +37544,10 @@ function registerCreativeTools(server, api, { transport = "http" } = {}) {
37287
37544
  __name(registerCreativeTools, "registerCreativeTools");
37288
37545
 
37289
37546
  // src/tools/creative-variants.ts
37290
- import { z as z142 } from "zod";
37547
+ import { z as z143 } from "zod";
37291
37548
  var estimateCreativeVariantGuidance = getProductMakerCapabilityModelGuidance("estimate_creative_variant");
37292
37549
  var createCreativeVariantGuidance = getProductMakerCapabilityModelGuidance("create_creative_variant");
37293
- var LandingSectionKeyEnum = z142.enum([
37550
+ var LandingSectionKeyEnum = z143.enum([
37294
37551
  "problem_hook",
37295
37552
  "problem_cost",
37296
37553
  "solution_mechanism",
@@ -37330,24 +37587,24 @@ var LandingSectionKeyEnum = z142.enum([
37330
37587
  "lifestyle_fit",
37331
37588
  "join_lifestyle"
37332
37589
  ]);
37333
- var CreativeVariantKindEnum = z142.enum([
37590
+ var CreativeVariantKindEnum = z143.enum([
37334
37591
  "video",
37335
37592
  "image",
37336
37593
  "landing"
37337
37594
  ]);
37338
- var EstimateCreativeVariantInputBase = z142.object({
37339
- sourceTaskId: z142.string().uuid().describe("ID de la tarea original completada."),
37595
+ var EstimateCreativeVariantInputBase = z143.object({
37596
+ sourceTaskId: z143.string().uuid().describe("ID de la tarea original completada."),
37340
37597
  kind: CreativeVariantKindEnum,
37341
37598
  videoPreset: TaskCreativeVariantVideoPresetEnum.optional().describe("Opcional para video. Solo cambia el preset p\xFAblico; el servidor conserva el modelo/tier del task origen."),
37342
37599
  videoResolution: VideoResolutionEnum.optional().describe("Opcional para video. Resoluci\xF3n del nuevo render."),
37343
- nVariants: z142.number().int().min(1).max(3).optional().describe("Opcional para imagen. N\xFAmero de im\xE1genes adicionales (1-3)."),
37600
+ nVariants: z143.number().int().min(1).max(3).optional().describe("Opcional para imagen. N\xFAmero de im\xE1genes adicionales (1-3)."),
37344
37601
  sectionKey: LandingSectionKeyEnum.optional().describe("Opcional para landing. Si ya conoces la secci\xF3n, evita cotizar la clasificaci\xF3n con IA."),
37345
- 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.")
37602
+ instructions: z143.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.")
37346
37603
  }).strict();
37347
37604
  var EstimateCreativeVariantInput = EstimateCreativeVariantInputBase.superRefine((input, ctx) => {
37348
37605
  if (input.kind === "video" && input.nVariants !== void 0) {
37349
37606
  ctx.addIssue({
37350
- code: z142.ZodIssueCode.custom,
37607
+ code: z143.ZodIssueCode.custom,
37351
37608
  path: [
37352
37609
  "nVariants"
37353
37610
  ],
@@ -37357,7 +37614,7 @@ var EstimateCreativeVariantInput = EstimateCreativeVariantInputBase.superRefine(
37357
37614
  if (input.kind !== "video") {
37358
37615
  if (input.videoPreset !== void 0) {
37359
37616
  ctx.addIssue({
37360
- code: z142.ZodIssueCode.custom,
37617
+ code: z143.ZodIssueCode.custom,
37361
37618
  path: [
37362
37619
  "videoPreset"
37363
37620
  ],
@@ -37366,7 +37623,7 @@ var EstimateCreativeVariantInput = EstimateCreativeVariantInputBase.superRefine(
37366
37623
  }
37367
37624
  if (input.videoResolution !== void 0) {
37368
37625
  ctx.addIssue({
37369
- code: z142.ZodIssueCode.custom,
37626
+ code: z143.ZodIssueCode.custom,
37370
37627
  path: [
37371
37628
  "videoResolution"
37372
37629
  ],
@@ -37376,7 +37633,7 @@ var EstimateCreativeVariantInput = EstimateCreativeVariantInputBase.superRefine(
37376
37633
  }
37377
37634
  if (input.kind !== "image" && input.nVariants !== void 0) {
37378
37635
  ctx.addIssue({
37379
- code: z142.ZodIssueCode.custom,
37636
+ code: z143.ZodIssueCode.custom,
37380
37637
  path: [
37381
37638
  "nVariants"
37382
37639
  ],
@@ -37386,7 +37643,7 @@ var EstimateCreativeVariantInput = EstimateCreativeVariantInputBase.superRefine(
37386
37643
  if (input.kind !== "landing") {
37387
37644
  if (input.sectionKey !== void 0) {
37388
37645
  ctx.addIssue({
37389
- code: z142.ZodIssueCode.custom,
37646
+ code: z143.ZodIssueCode.custom,
37390
37647
  path: [
37391
37648
  "sectionKey"
37392
37649
  ],
@@ -37395,7 +37652,7 @@ var EstimateCreativeVariantInput = EstimateCreativeVariantInputBase.superRefine(
37395
37652
  }
37396
37653
  if (input.instructions !== void 0) {
37397
37654
  ctx.addIssue({
37398
- code: z142.ZodIssueCode.custom,
37655
+ code: z143.ZodIssueCode.custom,
37399
37656
  path: [
37400
37657
  "instructions"
37401
37658
  ],
@@ -37404,31 +37661,31 @@ var EstimateCreativeVariantInput = EstimateCreativeVariantInputBase.superRefine(
37404
37661
  }
37405
37662
  }
37406
37663
  });
37407
- var CreateCreativeVariantInputBase = z142.object({
37408
- sourceTaskId: z142.string().uuid().describe("ID de la tarea original completada."),
37664
+ var CreateCreativeVariantInputBase = z143.object({
37665
+ sourceTaskId: z143.string().uuid().describe("ID de la tarea original completada."),
37409
37666
  kind: CreativeVariantKindEnum,
37410
- confirm: z142.literal(true).describe("Confirmaci\xF3n expl\xEDcita del usuario despu\xE9s de mostrarle el estimado de cr\xE9ditos."),
37411
- 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."),
37412
- angle: z142.string().trim().min(1).max(500).optional().describe("\xC1ngulo nuevo para video o imagen."),
37413
- buyerPersona: z142.string().trim().min(1).max(360).optional().describe("Buyer persona coherente con el \xE1ngulo."),
37414
- scriptNotes: z142.string().trim().min(1).max(2e3).optional().describe("Direcci\xF3n breve del guion para una variante de video."),
37415
- creativeDirection: z142.string().trim().min(1).max(2e3).optional().describe("Direcci\xF3n creativa para una variante de imagen."),
37667
+ confirm: z143.literal(true).describe("Confirmaci\xF3n expl\xEDcita del usuario despu\xE9s de mostrarle el estimado de cr\xE9ditos."),
37668
+ clientRequestId: z143.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."),
37669
+ angle: z143.string().trim().min(1).max(500).optional().describe("\xC1ngulo nuevo para video o imagen."),
37670
+ buyerPersona: z143.string().trim().min(1).max(360).optional().describe("Buyer persona coherente con el \xE1ngulo."),
37671
+ scriptNotes: z143.string().trim().min(1).max(2e3).optional().describe("Direcci\xF3n breve del guion para una variante de video."),
37672
+ creativeDirection: z143.string().trim().min(1).max(2e3).optional().describe("Direcci\xF3n creativa para una variante de imagen."),
37416
37673
  videoPreset: TaskCreativeVariantVideoPresetEnum.optional().describe("Opcional para video. Preset p\xFAblico; el servidor resuelve modelo/tier desde el task origen."),
37417
37674
  videoResolution: VideoResolutionEnum.optional().describe("Opcional para video."),
37418
- generateAiMusic: z142.boolean().optional().describe("Opcional para video."),
37419
- nVariants: z142.number().int().min(1).max(3).optional().describe("Opcional para imagen. N\xFAmero de im\xE1genes adicionales (1-3)."),
37675
+ generateAiMusic: z143.boolean().optional().describe("Opcional para video."),
37676
+ nVariants: z143.number().int().min(1).max(3).optional().describe("Opcional para imagen. N\xFAmero de im\xE1genes adicionales (1-3)."),
37420
37677
  imageStyle: ImageCreativeStyleEnum.optional().describe("Opcional para imagen."),
37421
37678
  creativeArchetype: ImageCreativeArchetypeEnum.optional().describe("Opcional para imagen."),
37422
37679
  visualTreatment: ImageCreativeVisualTreatmentEnum.optional().describe("Opcional para imagen."),
37423
37680
  aspectRatio: ImageCreativeAspectRatioEnum.optional().describe("Opcional para imagen."),
37424
- styleMix: z142.boolean().optional().describe("Opcional para imagen."),
37681
+ styleMix: z143.boolean().optional().describe("Opcional para imagen."),
37425
37682
  sectionKey: LandingSectionKeyEnum.optional().describe("Opcional para landing. Si se omite, se clasifica con IA a partir de instructions."),
37426
- 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.")
37683
+ instructions: z143.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.")
37427
37684
  }).strict();
37428
37685
  var CreateCreativeVariantInput = CreateCreativeVariantInputBase.superRefine((input, ctx) => {
37429
37686
  if ((input.kind === "video" || input.kind === "image") && !input.angle) {
37430
37687
  ctx.addIssue({
37431
- code: z142.ZodIssueCode.custom,
37688
+ code: z143.ZodIssueCode.custom,
37432
37689
  path: [
37433
37690
  "angle"
37434
37691
  ],
@@ -37437,7 +37694,7 @@ var CreateCreativeVariantInput = CreateCreativeVariantInputBase.superRefine((inp
37437
37694
  }
37438
37695
  if (input.kind === "landing" && !input.sectionKey && !input.instructions) {
37439
37696
  ctx.addIssue({
37440
- code: z142.ZodIssueCode.custom,
37697
+ code: z143.ZodIssueCode.custom,
37441
37698
  path: [
37442
37699
  "instructions"
37443
37700
  ],
@@ -37483,7 +37740,7 @@ var CreateCreativeVariantInput = CreateCreativeVariantInputBase.superRefine((inp
37483
37740
  for (const field of fieldsByKind[input.kind]) {
37484
37741
  if (input[field] !== void 0) {
37485
37742
  ctx.addIssue({
37486
- code: z142.ZodIssueCode.custom,
37743
+ code: z143.ZodIssueCode.custom,
37487
37744
  path: [
37488
37745
  field
37489
37746
  ],
@@ -37641,13 +37898,13 @@ function registerCreativeVariantTools(server, api) {
37641
37898
  __name(registerCreativeVariantTools, "registerCreativeVariantTools");
37642
37899
 
37643
37900
  // src/tools/script-review.ts
37644
- import { z as z143 } from "zod";
37901
+ import { z as z144 } from "zod";
37645
37902
  var getVideoScriptGuidance = getProductMakerCapabilityModelGuidance("get_video_script");
37646
37903
  var regenerateVideoScriptGuidance = getProductMakerCapabilityModelGuidance("regenerate_video_script");
37647
37904
  var approveVideoScriptGuidance = getProductMakerCapabilityModelGuidance("approve_video_script");
37648
- var GetVideoScriptInput = z143.object({
37649
- taskId: z143.string().min(1).describe("ID de la tarea (la misma que devolvi\xF3 create_product_task / generate_video_creative)."),
37650
- 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.')
37905
+ var GetVideoScriptInput = z144.object({
37906
+ taskId: z144.string().min(1).describe("ID de la tarea (la misma que devolvi\xF3 create_product_task / generate_video_creative)."),
37907
+ angleKey: z144.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.')
37651
37908
  }).strict();
37652
37909
  var ApproveVideoScriptInput = ApproveVideoScriptCapabilityRequest;
37653
37910
  var RegenerateVideoScriptInput = RegenerateVideoScriptCapabilityRequest.extend({
@@ -37753,7 +38010,7 @@ Consulta get_task_status con esta tarea antes de reintentar. Si la tarea termin\
37753
38010
  __name(registerScriptReviewTools, "registerScriptReviewTools");
37754
38011
 
37755
38012
  // src/tools/connections.ts
37756
- import { z as z144 } from "zod";
38013
+ import { z as z145 } from "zod";
37757
38014
  var listShopifyShopsGuidance = getProductMakerCapabilityModelGuidance("list_shopify_shops");
37758
38015
  var listShopifyProductsGuidance = getProductMakerCapabilityModelGuidance("list_shopify_products");
37759
38016
  var listMetaAdAccountsGuidance = getProductMakerCapabilityModelGuidance("list_meta_ad_accounts");
@@ -37762,7 +38019,7 @@ var listTikTokAdvertisersGuidance = getProductMakerCapabilityModelGuidance("list
37762
38019
  function registerConnectionTools(server, api) {
37763
38020
  server.registerTool("list_shopify_shops", {
37764
38021
  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.",
37765
- inputSchema: z144.object({}).shape,
38022
+ inputSchema: z145.object({}).shape,
37766
38023
  annotations: READS_INTERNAL
37767
38024
  }, async () => {
37768
38025
  try {
@@ -37774,10 +38031,10 @@ function registerConnectionTools(server, api) {
37774
38031
  });
37775
38032
  server.registerTool("list_shopify_products", {
37776
38033
  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.',
37777
- inputSchema: z144.object({
37778
- shopDomain: z144.string().min(3).max(120),
37779
- query: z144.string().trim().max(120).optional(),
37780
- taskId: z144.string().uuid().optional()
38034
+ inputSchema: z145.object({
38035
+ shopDomain: z145.string().min(3).max(120),
38036
+ query: z145.string().trim().max(120).optional(),
38037
+ taskId: z145.string().uuid().optional()
37781
38038
  }).shape,
37782
38039
  annotations: READS_EXTERNAL
37783
38040
  }, async ({ shopDomain, query, taskId }) => {
@@ -37795,7 +38052,7 @@ function registerConnectionTools(server, api) {
37795
38052
  });
37796
38053
  server.registerTool("list_meta_ad_accounts", {
37797
38054
  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.",
37798
- inputSchema: z144.object({}).shape,
38055
+ inputSchema: z145.object({}).shape,
37799
38056
  annotations: READS_EXTERNAL
37800
38057
  }, async () => {
37801
38058
  try {
@@ -37807,7 +38064,7 @@ function registerConnectionTools(server, api) {
37807
38064
  });
37808
38065
  server.registerTool("list_meta_destinations", {
37809
38066
  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.",
37810
- inputSchema: z144.object({}).shape,
38067
+ inputSchema: z145.object({}).shape,
37811
38068
  annotations: READS_EXTERNAL,
37812
38069
  _meta: {
37813
38070
  "openai/widgetAccessible": true
@@ -37822,7 +38079,7 @@ function registerConnectionTools(server, api) {
37822
38079
  });
37823
38080
  server.registerTool("list_tiktok_advertisers", {
37824
38081
  description: listTikTokAdvertisersGuidance.summary + " Si est\xE1 vac\xEDa, ind\xEDcale al usuario conectar TikTok en productmaker.app/integrations.",
37825
- inputSchema: z144.object({}).shape,
38082
+ inputSchema: z145.object({}).shape,
37826
38083
  annotations: READS_EXTERNAL
37827
38084
  }, async () => {
37828
38085
  try {
@@ -37836,7 +38093,7 @@ function registerConnectionTools(server, api) {
37836
38093
  __name(registerConnectionTools, "registerConnectionTools");
37837
38094
 
37838
38095
  // src/tools/actors.ts
37839
- import { z as z145 } from "zod";
38096
+ import { z as z146 } from "zod";
37840
38097
  var listActorsGuidance = getProductMakerCapabilityModelGuidance("list_actors");
37841
38098
  var actorVoiceGuidance = {
37842
38099
  capabilities: getProductMakerCapabilityModelGuidance("get_actor_voice_capabilities"),
@@ -37876,7 +38133,7 @@ function sanitizeVoiceResult(value) {
37876
38133
  __name(sanitizeVoiceResult, "sanitizeVoiceResult");
37877
38134
  function registerActorTools(server, api) {
37878
38135
  const actor = {
37879
- actorId: z145.string().min(1).max(64)
38136
+ actorId: z146.string().min(1).max(64)
37880
38137
  };
37881
38138
  const safe = /* @__PURE__ */ __name(async (request) => {
37882
38139
  try {
@@ -37905,8 +38162,8 @@ function registerActorTools(server, api) {
37905
38162
  inputSchema: {
37906
38163
  ...actor,
37907
38164
  draft: ActorVoiceDraftSchema,
37908
- requestKey: z145.string().trim().min(1).max(128),
37909
- acceptedQuoteId: z145.string().trim().min(1).max(128)
38165
+ requestKey: z146.string().trim().min(1).max(128),
38166
+ acceptedQuoteId: z146.string().trim().min(1).max(128)
37910
38167
  },
37911
38168
  annotations: CREATES_PAID
37912
38169
  }, async ({ actorId, ...body }) => safe(api.postJson(`/v1/actors/${encodeURIComponent(actorId)}/voice/previews`, body)));
@@ -37914,7 +38171,7 @@ function registerActorTools(server, api) {
37914
38171
  description: actorVoiceGuidance.get.summary,
37915
38172
  inputSchema: {
37916
38173
  ...actor,
37917
- candidateId: z145.string().min(1).max(64)
38174
+ candidateId: z146.string().min(1).max(64)
37918
38175
  },
37919
38176
  // Polling can resume the first paid submission of an accepted quote.
37920
38177
  // The immutable candidate/operation key keeps recovery idempotent.
@@ -37927,16 +38184,16 @@ function registerActorTools(server, api) {
37927
38184
  description: actorVoiceGuidance.approve.summary,
37928
38185
  inputSchema: {
37929
38186
  ...actor,
37930
- candidateId: z145.string().min(1).max(64),
37931
- expectedRevision: z145.number().int().positive(),
37932
- expectedApprovedRevision: z145.number().int().positive().nullable().optional()
38187
+ candidateId: z146.string().min(1).max(64),
38188
+ expectedRevision: z146.number().int().positive(),
38189
+ expectedApprovedRevision: z146.number().int().positive().nullable().optional()
37933
38190
  },
37934
38191
  // Replaces the selected voice and withdraws the old catalog grants.
37935
38192
  annotations: OVERWRITES_INTERNAL
37936
38193
  }, async ({ actorId, ...body }) => safe(api.postJson(`/v1/actors/${encodeURIComponent(actorId)}/voice/approve`, body)));
37937
38194
  server.registerTool("list_actors", {
37938
38195
  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.",
37939
- inputSchema: z145.object({}).shape,
38196
+ inputSchema: z146.object({}).shape,
37940
38197
  annotations: READS_INTERNAL
37941
38198
  }, async () => {
37942
38199
  try {
@@ -37969,7 +38226,7 @@ function registerActorTools(server, api) {
37969
38226
  __name(registerActorTools, "registerActorTools");
37970
38227
 
37971
38228
  // src/tools/publish.ts
37972
- import { z as z146 } from "zod";
38229
+ import { z as z147 } from "zod";
37973
38230
 
37974
38231
  // src/tools/meta-publish-attempt-id.ts
37975
38232
  var META_PUBLISH_NAMESPACE = "7b19c9ac-b4eb-4db1-970e-c2f24ce30b98";
@@ -38008,40 +38265,40 @@ var publishToShopifyGuidance = getProductMakerCapabilityModelGuidance("publish_t
38008
38265
  var publishToMetaGuidance = getProductMakerCapabilityModelGuidance("publish_to_meta");
38009
38266
  var publishCreativesToMetaGuidance = getProductMakerCapabilityModelGuidance("publish_creatives_to_meta");
38010
38267
  var publishToTikTokGuidance = getProductMakerCapabilityModelGuidance("publish_to_tiktok");
38011
- var ShopifyInput = z146.object({
38012
- taskId: z146.string().min(1),
38013
- shopDomain: z146.string().min(1),
38268
+ var ShopifyInput = z147.object({
38269
+ taskId: z147.string().min(1),
38270
+ shopDomain: z147.string().min(1),
38014
38271
  /** Override: number of landing images to include. Default = ALL images the
38015
38272
  * task generated (the MCP fetches /v1/results first to count). Bounded
38016
38273
  * generously here; the backend validates each index against
38017
38274
  * landing.images.length and rejects out-of-range. */
38018
- numLandingImages: z146.number().int().min(1).max(20).optional(),
38019
- variantIndex: z146.number().int().min(0).optional(),
38275
+ numLandingImages: z147.number().int().min(1).max(20).optional(),
38276
+ variantIndex: z147.number().int().min(0).optional(),
38020
38277
  /** ASK THE USER before setting: "¿Tu producto tiene variantes (talla, color, etc.)?".
38021
38278
  * When true, the landing renders variant-picker pills; when false, it ships
38022
38279
  * a single-option product page. Defaults to false. */
38023
- showVariantPicker: z146.boolean().optional(),
38280
+ showVariantPicker: z147.boolean().optional(),
38024
38281
  /** Default `auto` updates the product previously published for this task and
38025
38282
  * shop. Use `existing` with targetProductId to choose another product, or
38026
38283
  * `new` only when the user explicitly wants a duplicate/new listing. */
38027
- publicationMode: z146.enum([
38284
+ publicationMode: z147.enum([
38028
38285
  "auto",
38029
38286
  "existing",
38030
38287
  "new"
38031
38288
  ]).optional(),
38032
38289
  /** Shopify product GID, numeric id, or admin product URL. Required when
38033
38290
  * publicationMode is `existing`. */
38034
- targetProductId: z146.string().trim().min(1).max(300).optional(),
38291
+ targetProductId: z147.string().trim().min(1).max(300).optional(),
38035
38292
  /** Add an announcement bar text above add-to-cart (optional). */
38036
- announcementBarEnabled: z146.boolean().optional(),
38037
- announcementBarText: z146.string().trim().min(1).max(SHOPIFY_ANNOUNCEMENT_BAR_MAX_LENGTH).optional(),
38293
+ announcementBarEnabled: z147.boolean().optional(),
38294
+ announcementBarText: z147.string().trim().min(1).max(SHOPIFY_ANNOUNCEMENT_BAR_MAX_LENGTH).optional(),
38038
38295
  commerceCorrection: PublishCommerceCorrectionSchema.optional().describe("Complete seller-confirmed commerce values, with the revision and fingerprint returned in recovery. Never invent missing prices or convert currency."),
38039
- confirmed: z146.boolean().optional().describe("Required as true only when submitting commerceCorrection, after showing its values and obtaining explicit seller confirmation.")
38296
+ confirmed: z147.boolean().optional().describe("Required as true only when submitting commerceCorrection, after showing its values and obtaining explicit seller confirmation.")
38040
38297
  });
38041
38298
  var ShopifyInputWithAnnouncementBarValidation = ShopifyInput.superRefine((value, ctx) => {
38042
38299
  if (value.commerceCorrection && value.confirmed !== true) {
38043
38300
  ctx.addIssue({
38044
- code: z146.ZodIssueCode.custom,
38301
+ code: z147.ZodIssueCode.custom,
38045
38302
  path: [
38046
38303
  "confirmed"
38047
38304
  ],
@@ -38050,7 +38307,7 @@ var ShopifyInputWithAnnouncementBarValidation = ShopifyInput.superRefine((value,
38050
38307
  }
38051
38308
  if (value.announcementBarEnabled && !value.announcementBarText) {
38052
38309
  ctx.addIssue({
38053
- code: z146.ZodIssueCode.custom,
38310
+ code: z147.ZodIssueCode.custom,
38054
38311
  path: [
38055
38312
  "announcementBarText"
38056
38313
  ],
@@ -38059,7 +38316,7 @@ var ShopifyInputWithAnnouncementBarValidation = ShopifyInput.superRefine((value,
38059
38316
  }
38060
38317
  if (!value.announcementBarEnabled && value.announcementBarText) {
38061
38318
  ctx.addIssue({
38062
- code: z146.ZodIssueCode.custom,
38319
+ code: z147.ZodIssueCode.custom,
38063
38320
  path: [
38064
38321
  "announcementBarText"
38065
38322
  ],
@@ -38073,90 +38330,90 @@ function parseShopifyInput(rawInput) {
38073
38330
  return parsed.data;
38074
38331
  }
38075
38332
  __name(parseShopifyInput, "parseShopifyInput");
38076
- var McpCampaignConfig = z146.object({
38333
+ var McpCampaignConfig = z147.object({
38077
38334
  /** Legacy USD amount. Meta retains its existing conversion; the TikTok
38078
38335
  * schema below restricts this input to USD advertiser accounts. */
38079
- dailyBudgetUsd: z146.number().positive().optional(),
38336
+ dailyBudgetUsd: z147.number().positive().optional(),
38080
38337
  /** ISO-3166-1 alpha-2 country codes or resolvable names (CountryIso2Schema
38081
38338
  * normalizes at validation). Meta accepts max 1 (single-country ad set).
38082
38339
  * TikTok takes only the first entry. */
38083
- targetCountries: z146.array(CountryIso2Schema).max(1).optional(),
38084
- campaignName: z146.string().trim().min(1).max(100).optional()
38340
+ targetCountries: z147.array(CountryIso2Schema).max(1).optional(),
38341
+ campaignName: z147.string().trim().min(1).max(100).optional()
38085
38342
  });
38086
38343
  var McpAdAccountId = MetaAdAccountId.describe('Meta ad account id. Accepts "act_1234567890" or the bare numeric id "1234567890".');
38087
- var MetaInput = z146.object({
38088
- taskId: z146.string().min(1),
38344
+ var MetaInput = z147.object({
38345
+ taskId: z147.string().min(1),
38089
38346
  /** Exact Meta Business portfolio selected from `list_meta_destinations`. */
38090
- portfolioId: z146.string().trim().min(1),
38347
+ portfolioId: z147.string().trim().min(1),
38091
38348
  /** Exact Facebook Page selected from `list_meta_destinations`. */
38092
- pageId: z146.string().trim().min(1),
38349
+ pageId: z147.string().trim().min(1),
38093
38350
  adAccountId: McpAdAccountId,
38094
- pixelId: z146.string().min(1).optional(),
38095
- landingUrl: z146.string().url().optional(),
38351
+ pixelId: z147.string().min(1).optional(),
38352
+ landingUrl: z147.string().url().optional(),
38096
38353
  /** Select which angles to publish by 0-based index. Omit to publish all
38097
38354
  * successful angles. Example: a task with 3 angles → `[0,2]` publishes #0 + #2. */
38098
- angleIndexes: z146.array(z146.number().int().nonnegative()).optional(),
38355
+ angleIndexes: z147.array(z147.number().int().nonnegative()).optional(),
38099
38356
  campaignConfig: McpCampaignConfig.optional(),
38100
38357
  /** Override the ad's primary text (body copy). If omitted, ProductMaker
38101
38358
  * uses the auto-generated copy attached to the task. Capped at 500 chars
38102
38359
  * (Meta's primary-text limit). */
38103
- primaryText: z146.string().trim().min(1).max(500).optional(),
38360
+ primaryText: z147.string().trim().min(1).max(500).optional(),
38104
38361
  /** Optional WhatsApp number. When set, the ad button opens a WhatsApp chat
38105
38362
  * (wa.me) instead of a landing page. */
38106
- phoneNumber: z146.string().min(8).max(32).optional()
38363
+ phoneNumber: z147.string().min(8).max(32).optional()
38107
38364
  });
38108
- var GroupedTaskSource = z146.object({
38109
- type: z146.literal("task"),
38110
- taskId: z146.string().min(1),
38111
- angleIndex: z146.number().int().min(0).optional(),
38112
- videoVariant: z146.enum([
38365
+ var GroupedTaskSource = z147.object({
38366
+ type: z147.literal("task"),
38367
+ taskId: z147.string().min(1),
38368
+ angleIndex: z147.number().int().min(0).optional(),
38369
+ videoVariant: z147.enum([
38113
38370
  "with_subtitles",
38114
38371
  "without_subtitles"
38115
38372
  ]).optional(),
38116
- imageVariantIndex: z146.number().int().min(0).optional()
38117
- });
38118
- var GroupedMediaSource = z146.object({
38119
- type: z146.literal("media"),
38120
- 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."),
38121
- 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".'),
38122
- thumbnailUrl: z146.string().url().optional(),
38123
- thumbnailPath: z146.string().min(1).optional()
38373
+ imageVariantIndex: z147.number().int().min(0).optional()
38374
+ });
38375
+ var GroupedMediaSource = z147.object({
38376
+ type: z147.literal("media"),
38377
+ url: z147.string().url().optional().describe("Public HTTPS media URL. For a file on the user laptop, use request_media_upload on the hosted MCP instead."),
38378
+ path: z147.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".'),
38379
+ thumbnailUrl: z147.string().url().optional(),
38380
+ thumbnailPath: z147.string().min(1).optional()
38124
38381
  }).refine((value) => Boolean(value.url) !== Boolean(value.path), {
38125
38382
  message: "media source needs exactly one of `url` or `path`"
38126
38383
  }).refine((value) => !(value.thumbnailUrl && value.thumbnailPath), {
38127
38384
  message: "media source needs at most one of `thumbnailUrl` or `thumbnailPath`"
38128
38385
  });
38129
- var GroupedUploadedAssetSource = z146.object({
38130
- type: z146.literal("asset"),
38386
+ var GroupedUploadedAssetSource = z147.object({
38387
+ type: z147.literal("asset"),
38131
38388
  /** Returned by check_media_upload. ProductMaker resolves ownership server-side. */
38132
- assetId: z146.string().uuid(),
38133
- thumbnailAssetId: z146.string().uuid().optional()
38389
+ assetId: z147.string().uuid(),
38390
+ thumbnailAssetId: z147.string().uuid().optional()
38134
38391
  });
38135
- var GroupedCreative = z146.object({
38136
- type: z146.enum([
38392
+ var GroupedCreative = z147.object({
38393
+ type: z147.enum([
38137
38394
  "image",
38138
38395
  "video"
38139
38396
  ]),
38140
- source: z146.union([
38397
+ source: z147.union([
38141
38398
  GroupedTaskSource,
38142
38399
  GroupedMediaSource,
38143
38400
  GroupedUploadedAssetSource
38144
38401
  ]),
38145
- title: z146.string().max(120).optional(),
38146
- description: z146.string().max(500).optional(),
38402
+ title: z147.string().max(120).optional(),
38403
+ description: z147.string().max(500).optional(),
38147
38404
  cta: MetaCtaType.optional(),
38148
- creativeId: z146.string().min(1).optional()
38405
+ creativeId: z147.string().min(1).optional()
38149
38406
  });
38150
- var GroupedCampaignConfigBase = z146.object({
38407
+ var GroupedCampaignConfigBase = z147.object({
38151
38408
  campaignType: MetaCampaignType.default("sales"),
38152
38409
  optimizationMode: MetaOptimizationMode.default("advantage_plus"),
38153
38410
  budgetMode: MetaBudgetMode.optional(),
38154
- campaignDailyBudget: z146.number().int().positive().optional(),
38155
- adSets: z146.array(z146.object({
38156
- tempId: z146.string().min(1).max(80),
38157
- name: z146.string().min(1).max(100),
38158
- dailyBudget: z146.number().int().positive().optional(),
38159
- creativeIds: z146.array(z146.string().min(1)).min(1)
38411
+ campaignDailyBudget: z147.number().int().positive().optional(),
38412
+ adSets: z147.array(z147.object({
38413
+ tempId: z147.string().min(1).max(80),
38414
+ name: z147.string().min(1).max(100),
38415
+ dailyBudget: z147.number().int().positive().optional(),
38416
+ creativeIds: z147.array(z147.string().min(1)).min(1)
38160
38417
  })).min(1).max(CAMPAIGN_LIMITS.MAX_AD_SETS),
38161
38418
  /**
38162
38419
  * Valid values are 'landing' and 'whatsapp' — there is no 'website'. This
@@ -38164,17 +38421,17 @@ var GroupedCampaignConfigBase = z146.object({
38164
38421
  * opaque `invalid_literal` and made the WhatsApp destination — supported by
38165
38422
  * the API since #849 — unreachable from this tool.
38166
38423
  */
38167
- destination: z146.discriminatedUnion("type", [
38168
- z146.object({
38169
- type: z146.literal("landing"),
38170
- landingUrl: z146.string().url()
38424
+ destination: z147.discriminatedUnion("type", [
38425
+ z147.object({
38426
+ type: z147.literal("landing"),
38427
+ landingUrl: z147.string().url()
38171
38428
  }),
38172
- z146.object({
38173
- type: z146.literal("whatsapp"),
38174
- phoneNumber: z146.string().min(8).max(32)
38429
+ z147.object({
38430
+ type: z147.literal("whatsapp"),
38431
+ phoneNumber: z147.string().min(8).max(32)
38175
38432
  })
38176
38433
  ]),
38177
- saveAsDefault: z146.boolean().default(false),
38434
+ saveAsDefault: z147.boolean().default(false),
38178
38435
  budgetPolicy: MetaBudgetPolicy.optional()
38179
38436
  });
38180
38437
  function resolveBudgetMode(value) {
@@ -38188,7 +38445,7 @@ var GroupedCampaignConfig = GroupedCampaignConfigBase.superRefine((value, ctx) =
38188
38445
  const budgetMode = resolveBudgetMode(value);
38189
38446
  if (budgetMode === "campaign" && value.campaignDailyBudget === void 0) {
38190
38447
  ctx.addIssue({
38191
- code: z146.ZodIssueCode.custom,
38448
+ code: z147.ZodIssueCode.custom,
38192
38449
  path: [
38193
38450
  "campaignDailyBudget"
38194
38451
  ],
@@ -38199,7 +38456,7 @@ var GroupedCampaignConfig = GroupedCampaignConfigBase.superRefine((value, ctx) =
38199
38456
  value.adSets.forEach((adSet, index) => {
38200
38457
  if (adSet.dailyBudget === void 0) {
38201
38458
  ctx.addIssue({
38202
- code: z146.ZodIssueCode.custom,
38459
+ code: z147.ZodIssueCode.custom,
38203
38460
  path: [
38204
38461
  "adSets",
38205
38462
  index,
@@ -38214,25 +38471,25 @@ var GroupedCampaignConfig = GroupedCampaignConfigBase.superRefine((value, ctx) =
38214
38471
  ...value,
38215
38472
  budgetMode: resolveBudgetMode(value)
38216
38473
  }));
38217
- var PublishCreativesToMetaInputBase = z146.object({
38218
- creatives: z146.array(GroupedCreative).min(1).max(CAMPAIGN_LIMITS.MAX_CREATIVES),
38474
+ var PublishCreativesToMetaInputBase = z147.object({
38475
+ creatives: z147.array(GroupedCreative).min(1).max(CAMPAIGN_LIMITS.MAX_CREATIVES),
38219
38476
  /** Exact Meta Business portfolio selected from `list_meta_destinations`. */
38220
- portfolioId: z146.string().trim().min(1),
38477
+ portfolioId: z147.string().trim().min(1),
38221
38478
  /** Exact Facebook Page selected from `list_meta_destinations`. */
38222
- pageId: z146.string().trim().min(1),
38479
+ pageId: z147.string().trim().min(1),
38223
38480
  adAccountId: McpAdAccountId,
38224
- pixelId: z146.string().min(1).optional(),
38481
+ pixelId: z147.string().min(1).optional(),
38225
38482
  /** Required unless `campaignConfig.destination` is whatsapp or a creative
38226
38483
  * comes from a task (the API then lands on the published service landing
38227
38484
  * of its service tasks) — see the superRefine below. Mirrors the API's
38228
38485
  * `FacebookCreateCampaignFromTasksBodySchema`. */
38229
- landingUrl: z146.string().url().optional(),
38486
+ landingUrl: z147.string().url().optional(),
38230
38487
  /** ISO-2 code or resolvable country name — mirrors the API contract
38231
38488
  * (CountryIso2Schema), which normalizes names to alpha-2 at validation.
38232
38489
  * Omitted: the brand country of its service tasks (the API answers
38233
38490
  * META_NO_TARGET_COUNTRY when it has none); "US" for products. */
38234
38491
  targetCountry: CountryIso2Schema.optional(),
38235
- campaignName: z146.string().trim().min(1).max(100),
38492
+ campaignName: z147.string().trim().min(1).max(100),
38236
38493
  campaignConfig: GroupedCampaignConfig.optional()
38237
38494
  });
38238
38495
  var PublishCreativesToMetaInput = PublishCreativesToMetaInputBase.superRefine((value, ctx) => {
@@ -38240,7 +38497,7 @@ var PublishCreativesToMetaInput = PublishCreativesToMetaInputBase.superRefine((v
38240
38497
  const hasTaskCreative = value.creatives.some((creative) => creative.source.type === "task");
38241
38498
  if (destination?.type !== "whatsapp" && destination?.type !== "landing" && !value.landingUrl && !hasTaskCreative) {
38242
38499
  ctx.addIssue({
38243
- code: z146.ZodIssueCode.custom,
38500
+ code: z147.ZodIssueCode.custom,
38244
38501
  path: [
38245
38502
  "landingUrl"
38246
38503
  ],
@@ -38249,7 +38506,7 @@ var PublishCreativesToMetaInput = PublishCreativesToMetaInputBase.superRefine((v
38249
38506
  }
38250
38507
  if (destination?.type === "landing" && value.landingUrl && destination.landingUrl !== value.landingUrl) {
38251
38508
  ctx.addIssue({
38252
- code: z146.ZodIssueCode.custom,
38509
+ code: z147.ZodIssueCode.custom,
38253
38510
  path: [
38254
38511
  "campaignConfig",
38255
38512
  "destination",
@@ -38268,16 +38525,16 @@ var PublishCreativesToMetaInput = PublishCreativesToMetaInputBase.superRefine((v
38268
38525
  }
38269
38526
  return value;
38270
38527
  });
38271
- var PresignUploadResponseSchema = z146.object({
38272
- uploadUrl: z146.string().url().refine((value) => {
38528
+ var PresignUploadResponseSchema = z147.object({
38529
+ uploadUrl: z147.string().url().refine((value) => {
38273
38530
  const url2 = new URL(value);
38274
38531
  return !url2.username && !url2.password && url2.protocol === "https:";
38275
38532
  }, "presigned upload URL must be HTTPS without embedded credentials"),
38276
- publicUrl: z146.string().url().refine((value) => {
38533
+ publicUrl: z147.string().url().refine((value) => {
38277
38534
  const url2 = new URL(value);
38278
38535
  return !url2.username && !url2.password;
38279
38536
  }, "public media URL must not contain embedded credentials"),
38280
- stagingAssetId: z146.string().uuid()
38537
+ stagingAssetId: z147.string().uuid()
38281
38538
  });
38282
38539
  async function uploadResolvedMedia(api, media, assetRole) {
38283
38540
  if (!media.buffer) throw new ResolveMediaError("INVALID_INPUT", "resolved media has no bytes");
@@ -38303,14 +38560,14 @@ async function resolveAndUploadMedia(api, ref, expectedKind, assetRole, transpor
38303
38560
  }
38304
38561
  __name(resolveAndUploadMedia, "resolveAndUploadMedia");
38305
38562
  var TikTokMcpCampaignConfig = McpCampaignConfig.extend({
38306
- dailyBudgetUsd: z146.number().finite().positive().optional().describe("Legacy USD amount, supported only for USD advertiser accounts. No FX conversion."),
38307
- dailyBudget: z146.number().finite().positive().optional().describe("Daily budget in the selected advertiser currency major units; pass budgetCurrency together."),
38563
+ dailyBudgetUsd: z147.number().finite().positive().optional().describe("Legacy USD amount, supported only for USD advertiser accounts. No FX conversion."),
38564
+ dailyBudget: z147.number().finite().positive().optional().describe("Daily budget in the selected advertiser currency major units; pass budgetCurrency together."),
38308
38565
  budgetCurrency: TikTokBudgetCurrencySchema.optional(),
38309
38566
  destination: TikTokCampaignDestinationSchema.optional()
38310
38567
  }).superRefine((value, ctx) => {
38311
38568
  if (value.dailyBudgetUsd !== void 0 && (value.dailyBudget !== void 0 || value.budgetCurrency !== void 0)) {
38312
38569
  ctx.addIssue({
38313
- code: z146.ZodIssueCode.custom,
38570
+ code: z147.ZodIssueCode.custom,
38314
38571
  path: [
38315
38572
  "dailyBudgetUsd"
38316
38573
  ],
@@ -38319,7 +38576,7 @@ var TikTokMcpCampaignConfig = McpCampaignConfig.extend({
38319
38576
  }
38320
38577
  if (value.dailyBudget !== void 0 !== (value.budgetCurrency !== void 0)) {
38321
38578
  ctx.addIssue({
38322
- code: z146.ZodIssueCode.custom,
38579
+ code: z147.ZodIssueCode.custom,
38323
38580
  path: [
38324
38581
  "budgetCurrency"
38325
38582
  ],
@@ -38327,23 +38584,27 @@ var TikTokMcpCampaignConfig = McpCampaignConfig.extend({
38327
38584
  });
38328
38585
  }
38329
38586
  });
38330
- var TikTokInput = z146.object({
38331
- taskId: z146.string().min(1),
38332
- advertiserId: z146.string().min(1),
38333
- pixelId: z146.string().min(1).optional(),
38334
- landingUrl: z146.string().url().optional(),
38335
- angleIndexes: z146.array(z146.number().int().nonnegative()).optional(),
38587
+ var TikTokInput = z147.object({
38588
+ taskId: z147.string().min(1),
38589
+ advertiserId: z147.string().min(1),
38590
+ pixelId: z147.string().min(1).optional(),
38591
+ landingUrl: z147.string().url().optional(),
38592
+ angleIndexes: z147.array(z147.number().int().nonnegative()).optional(),
38336
38593
  campaignConfig: TikTokMcpCampaignConfig.optional(),
38337
38594
  /** Override the ad's description (TikTok's body copy). If omitted,
38338
38595
  * ProductMaker uses the auto-generated copy attached to the task. */
38339
- primaryText: z146.string().trim().min(1).max(500).optional()
38596
+ primaryText: z147.string().trim().min(1).max(500).optional()
38340
38597
  });
38341
38598
  function registerPublishTools(server, api, opts = {}) {
38342
38599
  const transport = opts.transport ?? "http";
38343
38600
  server.registerTool("publish_to_shopify", {
38344
38601
  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.",
38345
38602
  inputSchema: ShopifyInput.shape,
38346
- annotations: OVERWRITES_EXTERNAL
38603
+ // Publication reaches the public storefront, beyond private account data.
38604
+ annotations: {
38605
+ ...OVERWRITES_EXTERNAL,
38606
+ openWorldHint: true
38607
+ }
38347
38608
  }, async (input, extra) => {
38348
38609
  try {
38349
38610
  const parsed = parseShopifyInput(input);
@@ -38460,7 +38721,13 @@ function registerPublishTools(server, api, opts = {}) {
38460
38721
  server.registerTool("publish_creatives_to_meta", {
38461
38722
  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.',
38462
38723
  inputSchema: PublishCreativesToMetaInputBase.shape,
38463
- annotations: CREATES_EXTERNAL
38724
+ // May fetch arbitrary public media URLs and consumes owned upload assets
38725
+ // after successful publication, even though the campaign starts paused.
38726
+ annotations: {
38727
+ ...CREATES_EXTERNAL,
38728
+ openWorldHint: true,
38729
+ destructiveHint: true
38730
+ }
38464
38731
  }, async (rawInput, extra) => {
38465
38732
  try {
38466
38733
  const parsed = PublishCreativesToMetaInput.safeParse(rawInput);
@@ -38696,39 +38963,40 @@ __name(toTikTokCampaignConfig, "toTikTokCampaignConfig");
38696
38963
 
38697
38964
  // src/tools/meta-upload.ts
38698
38965
  import { registerAppTool } from "@modelcontextprotocol/ext-apps/server";
38699
- import { z as z147 } from "zod";
38966
+ import { z as z148 } from "zod";
38700
38967
  var requestMediaUploadGuidance = getProductMakerCapabilityModelGuidance("request_media_upload");
38701
- var UploadSessionInputBase = z147.object({
38702
- mediaKind: z147.enum([
38968
+ var checkMediaUploadGuidance = getProductMakerCapabilityModelGuidance("check_media_upload");
38969
+ var UploadSessionInputBase = z148.object({
38970
+ mediaKind: z148.enum([
38703
38971
  "image",
38704
38972
  "video",
38705
38973
  "mixed"
38706
38974
  ]).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."),
38707
- assetRole: z147.enum([
38975
+ assetRole: z148.enum([
38708
38976
  "media",
38709
38977
  "thumbnail"
38710
38978
  ]).default("media").describe("Use media for the creative itself; thumbnail only for an image thumbnail."),
38711
- uploadPurpose: z147.enum([
38979
+ uploadPurpose: z148.enum([
38712
38980
  "product_reference",
38713
38981
  "creative",
38714
38982
  "generic"
38715
38983
  ]).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."),
38716
- maxFiles: z147.number().int().min(1).max(10).optional().describe("Optional batch limit: image accepts 1-5, video exactly 1, and mixed 1-10.")
38984
+ maxFiles: z148.number().int().min(1).max(10).optional().describe("Optional batch limit: image accepts 1-5, video exactly 1, and mixed 1-10.")
38717
38985
  });
38718
- var OpenAiFileReference = z147.object({
38719
- 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."),
38720
- file_id: z147.string().min(1),
38721
- mime_type: z147.string().min(1).optional(),
38722
- file_name: z147.string().min(1).optional()
38986
+ var OpenAiFileReference = z148.object({
38987
+ download_url: z148.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."),
38988
+ file_id: z148.string().min(1),
38989
+ mime_type: z148.string().min(1).optional(),
38990
+ file_name: z148.string().min(1).optional()
38723
38991
  });
38724
38992
  var UploadSessionToolInputShape = {
38725
38993
  ...UploadSessionInputBase.shape,
38726
- 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.")
38994
+ files: z148.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.")
38727
38995
  };
38728
- var UploadSessionToolInput = z147.object(UploadSessionToolInputShape).superRefine((value, ctx) => {
38996
+ var UploadSessionToolInput = z148.object(UploadSessionToolInputShape).superRefine((value, ctx) => {
38729
38997
  if (value.assetRole === "thumbnail" && value.mediaKind !== "image") {
38730
38998
  ctx.addIssue({
38731
- code: z147.ZodIssueCode.custom,
38999
+ code: z148.ZodIssueCode.custom,
38732
39000
  path: [
38733
39001
  "mediaKind"
38734
39002
  ],
@@ -38737,7 +39005,7 @@ var UploadSessionToolInput = z147.object(UploadSessionToolInputShape).superRefin
38737
39005
  }
38738
39006
  if (value.mediaKind === "video" && value.maxFiles !== void 0 && value.maxFiles !== 1) {
38739
39007
  ctx.addIssue({
38740
- code: z147.ZodIssueCode.custom,
39008
+ code: z148.ZodIssueCode.custom,
38741
39009
  path: [
38742
39010
  "maxFiles"
38743
39011
  ],
@@ -38746,7 +39014,7 @@ var UploadSessionToolInput = z147.object(UploadSessionToolInputShape).superRefin
38746
39014
  }
38747
39015
  if (value.mediaKind === "image" && (value.maxFiles ?? 1) > 5) {
38748
39016
  ctx.addIssue({
38749
- code: z147.ZodIssueCode.custom,
39017
+ code: z148.ZodIssueCode.custom,
38750
39018
  path: [
38751
39019
  "maxFiles"
38752
39020
  ],
@@ -38754,38 +39022,38 @@ var UploadSessionToolInput = z147.object(UploadSessionToolInputShape).superRefin
38754
39022
  });
38755
39023
  }
38756
39024
  });
38757
- var UploadSessionResponse = z147.object({
38758
- sessionId: z147.string().uuid(),
38759
- uploadPageUrl: z147.string().url(),
38760
- mediaKind: z147.enum([
39025
+ var UploadSessionResponse = z148.object({
39026
+ sessionId: z148.string().uuid(),
39027
+ uploadPageUrl: z148.string().url(),
39028
+ mediaKind: z148.enum([
38761
39029
  "image",
38762
39030
  "video",
38763
39031
  "mixed"
38764
39032
  ]),
38765
- assetRole: z147.enum([
39033
+ assetRole: z148.enum([
38766
39034
  "media",
38767
39035
  "thumbnail"
38768
39036
  ]),
38769
- maxFiles: z147.number().int().positive(),
38770
- expiresAt: z147.string().datetime()
39037
+ maxFiles: z148.number().int().positive(),
39038
+ expiresAt: z148.string().datetime()
38771
39039
  });
38772
- var UploadStatusInput = z147.object({
38773
- sessionId: z147.string().uuid().describe("The sessionId returned by request_media_upload.")
39040
+ var UploadStatusInput = z148.object({
39041
+ sessionId: z148.string().uuid().describe("The sessionId returned by request_media_upload.")
38774
39042
  });
38775
- var UniversalUploadStatusResponse = z147.discriminatedUnion("status", [
38776
- z147.object({
38777
- status: z147.literal("pending"),
38778
- assets: z147.array(z147.never()).length(0)
39043
+ var UniversalUploadStatusResponse = z148.discriminatedUnion("status", [
39044
+ z148.object({
39045
+ status: z148.literal("pending"),
39046
+ assets: z148.array(z148.never()).length(0)
38779
39047
  }),
38780
- z147.object({
38781
- status: z147.literal("uploaded"),
38782
- assets: z147.array(z147.object({
38783
- assetId: z147.string().uuid(),
38784
- mediaKind: z147.enum([
39048
+ z148.object({
39049
+ status: z148.literal("uploaded"),
39050
+ assets: z148.array(z148.object({
39051
+ assetId: z148.string().uuid(),
39052
+ mediaKind: z148.enum([
38785
39053
  "image",
38786
39054
  "video"
38787
39055
  ]),
38788
- assetRole: z147.enum([
39056
+ assetRole: z148.enum([
38789
39057
  "media",
38790
39058
  "thumbnail"
38791
39059
  ])
@@ -38793,13 +39061,13 @@ var UniversalUploadStatusResponse = z147.discriminatedUnion("status", [
38793
39061
  })
38794
39062
  ]);
38795
39063
  var UniversalUploadStructuredResponse = UploadSessionResponse.extend({
38796
- uploadPurpose: z147.enum([
39064
+ uploadPurpose: z148.enum([
38797
39065
  "product_reference",
38798
39066
  "creative",
38799
39067
  "generic"
38800
39068
  ]),
38801
- checkToolName: z147.literal("check_media_upload"),
38802
- resolveEndpoint: z147.string().url()
39069
+ checkToolName: z148.literal("check_media_upload"),
39070
+ resolveEndpoint: z148.string().url()
38803
39071
  });
38804
39072
  function contextualizeUploadPageUrl(uploadPageUrl, uploadPurpose) {
38805
39073
  const url2 = new URL(uploadPageUrl);
@@ -38831,7 +39099,7 @@ __name(resolveEndpointForSession, "resolveEndpointForSession");
38831
39099
  function registerMediaUploadTools(server, api) {
38832
39100
  registerAppTool(server, "request_media_upload", {
38833
39101
  title: "Subir archivos",
38834
- 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.",
39102
+ 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.",
38835
39103
  inputSchema: UploadSessionToolInputShape,
38836
39104
  annotations: CREATES_INTERNAL,
38837
39105
  outputSchema: UniversalUploadStructuredResponse.shape,
@@ -38872,7 +39140,7 @@ function registerMediaUploadTools(server, api) {
38872
39140
  }
38873
39141
  });
38874
39142
  server.registerTool("check_media_upload", {
38875
- 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.",
39143
+ 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.",
38876
39144
  inputSchema: UploadStatusInput.shape,
38877
39145
  annotations: READS_INTERNAL,
38878
39146
  _meta: {
@@ -38890,15 +39158,15 @@ function registerMediaUploadTools(server, api) {
38890
39158
  __name(registerMediaUploadTools, "registerMediaUploadTools");
38891
39159
 
38892
39160
  // src/tools/campaigns.ts
38893
- import { z as z148 } from "zod";
39161
+ import { z as z149 } from "zod";
38894
39162
  var getMetaCampaignStatusGuidance = getProductMakerCapabilityModelGuidance("get_meta_campaign_status");
38895
39163
  var getTikTokCampaignStatusGuidance = getProductMakerCapabilityModelGuidance("get_tiktok_campaign_status");
38896
- var MetaStatusInput = z148.object({
38897
- campaignId: z148.string().min(1).describe("Meta campaign ID returned by publish_to_meta.")
39164
+ var MetaStatusInput = z149.object({
39165
+ campaignId: z149.string().min(1).describe("Meta campaign ID returned by publish_to_meta.")
38898
39166
  });
38899
- var TikTokStatusInput = z148.object({
38900
- campaignId: z148.string().min(1).describe("TikTok campaign ID returned by publish_to_tiktok."),
38901
- advertiserId: z148.string().min(1).describe("The same advertiserId used to publish the campaign.")
39167
+ var TikTokStatusInput = z149.object({
39168
+ campaignId: z149.string().min(1).describe("TikTok campaign ID returned by publish_to_tiktok."),
39169
+ advertiserId: z149.string().min(1).describe("The same advertiserId used to publish the campaign.")
38902
39170
  });
38903
39171
  function registerCampaignTools(server, api) {
38904
39172
  server.registerTool("get_meta_campaign_status", {
@@ -39003,10 +39271,10 @@ function renderTikTokStatusMarkdown(r) {
39003
39271
  __name(renderTikTokStatusMarkdown, "renderTikTokStatusMarkdown");
39004
39272
 
39005
39273
  // ../../packages/product-economics/src/product-costing.ts
39006
- import { z as z150 } from "zod";
39274
+ import { z as z151 } from "zod";
39007
39275
 
39008
39276
  // ../../packages/product-economics/src/product-costing-countries.ts
39009
- import { z as z149 } from "zod";
39277
+ import { z as z150 } from "zod";
39010
39278
 
39011
39279
  // ../../packages/product-economics/src/product-costing-shipping-references.json
39012
39280
  var product_costing_shipping_references_default = {
@@ -39070,21 +39338,21 @@ var product_costing_shipping_references_default = {
39070
39338
  };
39071
39339
 
39072
39340
  // ../../packages/product-economics/src/product-costing-countries.ts
39073
- var ShippingReferenceSchema = z149.object({
39074
- reference: z149.number().positive(),
39075
- min: z149.number().nonnegative(),
39076
- max: z149.number().positive(),
39077
- updatedAt: z149.string().date(),
39078
- confidence: z149.enum([
39341
+ var ShippingReferenceSchema = z150.object({
39342
+ reference: z150.number().positive(),
39343
+ min: z150.number().nonnegative(),
39344
+ max: z150.number().positive(),
39345
+ updatedAt: z150.string().date(),
39346
+ confidence: z150.enum([
39079
39347
  "high",
39080
39348
  "medium",
39081
39349
  "medium-low"
39082
39350
  ]),
39083
- sources: z149.array(z149.string().url().startsWith("https://")).min(1).max(3)
39351
+ sources: z150.array(z150.string().url().startsWith("https://")).min(1).max(3)
39084
39352
  }).superRefine(({ reference, min, max }, context) => {
39085
39353
  if (min > reference || reference > max) {
39086
39354
  context.addIssue({
39087
- code: z149.ZodIssueCode.custom,
39355
+ code: z150.ZodIssueCode.custom,
39088
39356
  message: "shipping_reference_outside_range",
39089
39357
  path: [
39090
39358
  "reference"
@@ -39092,7 +39360,7 @@ var ShippingReferenceSchema = z149.object({
39092
39360
  });
39093
39361
  }
39094
39362
  });
39095
- var SHIPPING_REFERENCES = z149.record(ShippingReferenceSchema).parse(product_costing_shipping_references_default);
39363
+ var SHIPPING_REFERENCES = z150.record(ShippingReferenceSchema).parse(product_costing_shipping_references_default);
39096
39364
  var PRODUCT_COSTING_COUNTRY_CODES = Object.freeze(Object.keys(SHIPPING_REFERENCES));
39097
39365
  var SPANISH_LOCALE_COUNTRIES = /* @__PURE__ */ new Set([
39098
39366
  "MX",
@@ -39274,18 +39542,18 @@ function getProductCostingCountryName(country) {
39274
39542
  __name(getProductCostingCountryName, "getProductCostingCountryName");
39275
39543
 
39276
39544
  // ../../packages/product-economics/src/product-costing.ts
39277
- var ProductCostingInputSchema = z150.object({
39278
- country: z150.string().refine(isProductCostingCountryCode, "unsupported_country"),
39279
- supplierCost: z150.number().finite().max(Number.MAX_SAFE_INTEGER, "amount_too_large").positive("supplier_cost_positive"),
39280
- shippingCost: z150.number().finite().max(Number.MAX_SAFE_INTEGER, "amount_too_large").nonnegative("shipping_cost_nonnegative"),
39281
- returnRate: z150.number().finite().min(0, "rate_nonnegative").lt(1, "rate_below_one"),
39282
- cancellationRate: z150.number().finite().min(0, "rate_nonnegative").lt(1, "rate_below_one"),
39283
- adsManagerCpa: z150.number().finite().max(Number.MAX_SAFE_INTEGER, "amount_too_large").nonnegative("cpa_nonnegative"),
39284
- desiredMargin: z150.number().finite().min(0, "margin_nonnegative").lt(1, "margin_below_one")
39545
+ var ProductCostingInputSchema = z151.object({
39546
+ country: z151.string().refine(isProductCostingCountryCode, "unsupported_country"),
39547
+ supplierCost: z151.number().finite().max(Number.MAX_SAFE_INTEGER, "amount_too_large").positive("supplier_cost_positive"),
39548
+ shippingCost: z151.number().finite().max(Number.MAX_SAFE_INTEGER, "amount_too_large").nonnegative("shipping_cost_nonnegative"),
39549
+ returnRate: z151.number().finite().min(0, "rate_nonnegative").lt(1, "rate_below_one"),
39550
+ cancellationRate: z151.number().finite().min(0, "rate_nonnegative").lt(1, "rate_below_one"),
39551
+ adsManagerCpa: z151.number().finite().max(Number.MAX_SAFE_INTEGER, "amount_too_large").nonnegative("cpa_nonnegative"),
39552
+ desiredMargin: z151.number().finite().min(0, "margin_nonnegative").lt(1, "margin_below_one")
39285
39553
  }).superRefine(({ returnRate, cancellationRate }, context) => {
39286
39554
  if (returnRate + cancellationRate >= 1) {
39287
39555
  context.addIssue({
39288
- code: z150.ZodIssueCode.custom,
39556
+ code: z151.ZodIssueCode.custom,
39289
39557
  message: "delivery_rate_positive",
39290
39558
  path: [
39291
39559
  "cancellationRate"
@@ -39382,15 +39650,15 @@ function calculateProductCosting(rawInput) {
39382
39650
  __name(calculateProductCosting, "calculateProductCosting");
39383
39651
 
39384
39652
  // src/tools/product-costing.ts
39385
- import { z as z151 } from "zod";
39653
+ import { z as z152 } from "zod";
39386
39654
  var costProductGuidance = getProductMakerCapabilityModelGuidance("cost_product");
39387
- var optionalAmount = /* @__PURE__ */ __name((description) => z151.number().finite().max(Number.MAX_SAFE_INTEGER).nonnegative().optional().describe(description), "optionalAmount");
39388
- var optionalPercent = /* @__PURE__ */ __name((description) => z151.number().finite().min(0).lt(100).optional().describe(description), "optionalPercent");
39389
- var ProductCostingToolInput = z151.object({
39390
- country: z151.string().trim().toUpperCase().refine(isProductCostingCountryCode, {
39655
+ var optionalAmount = /* @__PURE__ */ __name((description) => z152.number().finite().max(Number.MAX_SAFE_INTEGER).nonnegative().optional().describe(description), "optionalAmount");
39656
+ var optionalPercent = /* @__PURE__ */ __name((description) => z152.number().finite().min(0).lt(100).optional().describe(description), "optionalPercent");
39657
+ var ProductCostingToolInput = z152.object({
39658
+ country: z152.string().trim().toUpperCase().refine(isProductCostingCountryCode, {
39391
39659
  message: "Pa\xEDs no soportado por ProductMaker."
39392
39660
  }).optional().describe("C\xF3digo ISO-2 de cualquiera de los pa\xEDses disponibles en ProductMaker. Omite para Colombia."),
39393
- 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."),
39661
+ supplierCost: z152.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."),
39394
39662
  supplierCostCop: optionalAmount("Alias anterior para costo del proveedor en Colombia. Prefiere supplierCost."),
39395
39663
  shippingCost: optionalAmount("Costo de un env\xEDo en la moneda del pa\xEDs. Omite para usar el valor gu\xEDa vigente."),
39396
39664
  shippingCostCop: optionalAmount("Alias anterior para flete en Colombia. Prefiere shippingCost."),
@@ -39553,7 +39821,7 @@ function formatPercent(value) {
39553
39821
  __name(formatPercent, "formatPercent");
39554
39822
 
39555
39823
  // src/tools/product-research.ts
39556
- import { z as z152 } from "zod";
39824
+ import { z as z153 } from "zod";
39557
39825
 
39558
39826
  // src/tools/product-research-idempotency.ts
39559
39827
  var RESEARCH_IDEMPOTENCY_WINDOW_MS = 2 * 60 * 60 * 1e3;
@@ -39924,17 +40192,17 @@ var TERMINAL2 = /* @__PURE__ */ new Set([
39924
40192
  ]);
39925
40193
  var POLL_INTERVAL_MS = 5e3;
39926
40194
  var MAX_WAIT_SECONDS = 120;
39927
- var FindInput = z152.object({
40195
+ var FindInput = z153.object({
39928
40196
  targetCountry: ResearchTargetCountryEnum.describe("Pa\xEDs objetivo. S\xF3lo CO, MX, PE, EC y CL tienen cobertura de investigaci\xF3n."),
39929
40197
  categoryKey: ResearchCategoryEnum.default("auto").describe('Categor\xEDa a investigar. Usa "auto" para que la IA elija seg\xFAn el mercado.'),
39930
- 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.`),
39931
- 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.")
40198
+ confirmed: z153.boolean().describe(`Ponlo en true SOLO despu\xE9s de que el usuario haya aceptado el cargo de ${PRICE_CREDITS} cr\xE9ditos. No lo asumas.`),
40199
+ waitSeconds: z153.number().int().min(0).max(MAX_WAIT_SECONDS).optional().describe("Segundos a esperar el reporte antes de devolver el runId. M\xE1ximo 120.")
39932
40200
  });
39933
- var GetInput = z152.object({
39934
- runId: z152.string().uuid().describe("ID del run devuelto por find_winning_products.")
40201
+ var GetInput = z153.object({
40202
+ runId: z153.string().uuid().describe("ID del run devuelto por find_winning_products.")
39935
40203
  });
39936
- var ListInput = z152.object({
39937
- limit: z152.number().int().min(1).max(20).default(10)
40204
+ var ListInput = z153.object({
40205
+ limit: z153.number().int().min(1).max(20).default(10)
39938
40206
  });
39939
40207
  function textResult(text4) {
39940
40208
  return {
@@ -40073,17 +40341,17 @@ _Esta llamada no cobr\xF3 cr\xE9ditos: se reutiliz\xF3 una investigaci\xF3n reci
40073
40341
  __name(registerProductResearchTools, "registerProductResearchTools");
40074
40342
 
40075
40343
  // src/tools/dropshipping-projects.ts
40076
- import { z as z153 } from "zod";
40344
+ import { z as z154 } from "zod";
40077
40345
  var startDropshippingProjectGuidance = getProductMakerCapabilityModelGuidance("start_dropshipping_project");
40078
40346
  var costDropshippingProjectGuidance = getProductMakerCapabilityModelGuidance("cost_dropshipping_project");
40079
40347
  var guideDropshippingLaunchGuidance = getProductMakerCapabilityModelGuidance("guide_dropshipping_launch");
40080
- var ProjectId = z153.string().uuid();
40081
- var DropshippingProjectStatus = z153.enum([
40348
+ var ProjectId = z154.string().uuid();
40349
+ var DropshippingProjectStatus = z154.enum([
40082
40350
  "active",
40083
40351
  "completed",
40084
40352
  "archived"
40085
40353
  ]);
40086
- var DropshippingProjectPhase = z153.enum([
40354
+ var DropshippingProjectPhase = z154.enum([
40087
40355
  "research",
40088
40356
  "costing",
40089
40357
  "media",
@@ -40092,21 +40360,21 @@ var DropshippingProjectPhase = z153.enum([
40092
40360
  "advertising",
40093
40361
  "complete"
40094
40362
  ]);
40095
- var DropshippingRewardMilestone = z153.enum([
40363
+ var DropshippingRewardMilestone = z154.enum([
40096
40364
  "product_selected",
40097
40365
  "costing_saved",
40098
40366
  "product_media_ready",
40099
40367
  "creatives_completed",
40100
40368
  "campaign_created"
40101
40369
  ]);
40102
- var TargetCountry = z153.enum([
40370
+ var TargetCountry = z154.enum([
40103
40371
  "CO",
40104
40372
  "MX",
40105
40373
  "PE",
40106
40374
  "EC",
40107
40375
  "CL"
40108
40376
  ]);
40109
- var ResearchCategory = z153.enum([
40377
+ var ResearchCategory = z154.enum([
40110
40378
  "auto",
40111
40379
  "automotive",
40112
40380
  "kitchen",
@@ -40121,7 +40389,7 @@ var ResearchCategory = z153.enum([
40121
40389
  "garden",
40122
40390
  "cleaning"
40123
40391
  ]);
40124
- var HttpUrl3 = z153.string().url().max(2e3).refine((value) => {
40392
+ var HttpUrl3 = z154.string().url().max(2e3).refine((value) => {
40125
40393
  try {
40126
40394
  return [
40127
40395
  "http:",
@@ -40131,45 +40399,45 @@ var HttpUrl3 = z153.string().url().max(2e3).refine((value) => {
40131
40399
  return false;
40132
40400
  }
40133
40401
  }, "URL must use HTTP(S)");
40134
- var ResearchCandidateOrigin = z153.object({
40135
- type: z153.literal("research_candidate"),
40402
+ var ResearchCandidateOrigin = z154.object({
40403
+ type: z154.literal("research_candidate"),
40136
40404
  runId: ProjectId,
40137
- candidateSourceId: z153.string().min(1).max(160)
40405
+ candidateSourceId: z154.string().min(1).max(160)
40138
40406
  });
40139
- var TaskOrigin = z153.object({
40140
- type: z153.literal("task"),
40407
+ var TaskOrigin = z154.object({
40408
+ type: z154.literal("task"),
40141
40409
  taskId: ProjectId
40142
40410
  });
40143
- var ManualOrigin = z153.object({
40144
- type: z153.literal("manual"),
40411
+ var ManualOrigin = z154.object({
40412
+ type: z154.literal("manual"),
40145
40413
  clientRequestId: ProjectId,
40146
- country: z153.string().min(2).max(3).optional()
40414
+ country: z154.string().min(2).max(3).optional()
40147
40415
  });
40148
- var StartProjectInput = z153.object({
40149
- origin: z153.discriminatedUnion("type", [
40416
+ var StartProjectInput = z154.object({
40417
+ origin: z154.discriminatedUnion("type", [
40150
40418
  ResearchCandidateOrigin,
40151
40419
  TaskOrigin,
40152
40420
  ManualOrigin
40153
40421
  ]),
40154
- productName: z153.string().trim().min(1).max(240).optional(),
40422
+ productName: z154.string().trim().min(1).max(240).optional(),
40155
40423
  productUrl: HttpUrl3.optional(),
40156
- country: z153.string().min(2).max(3).optional(),
40157
- languageCode: z153.string().min(2).max(16).optional()
40424
+ country: z154.string().min(2).max(3).optional(),
40425
+ languageCode: z154.string().min(2).max(16).optional()
40158
40426
  }).strict();
40159
- var CostProjectInput = z153.object({
40427
+ var CostProjectInput = z154.object({
40160
40428
  projectId: ProjectId,
40161
- version: z153.number().int().positive().describe("Version le\xEDda del proyecto. Copia la \xFAltima que viste; no se la pidas al usuario."),
40162
- country: z153.string().trim().toUpperCase().refine(isProductCostingCountryCode, {
40429
+ version: z154.number().int().positive().describe("Version le\xEDda del proyecto. Copia la \xFAltima que viste; no se la pidas al usuario."),
40430
+ country: z154.string().trim().toUpperCase().refine(isProductCostingCountryCode, {
40163
40431
  message: "Pa\xEDs no soportado por ProductMaker."
40164
40432
  }).describe("C\xF3digo ISO-2 del pa\xEDs del proyecto. Usa el `targetCountry` que devuelve el proyecto."),
40165
- 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."),
40166
- 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."),
40167
- 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."),
40168
- 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."),
40169
- adsManagerCpa: z153.number().finite().nonnegative().optional().describe("Costo por compra de Ads Manager. Omite para usar el valor gu\xEDa del pa\xEDs."),
40170
- 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.")
40171
- });
40172
- var GuideInput = z153.object({
40433
+ supplierCost: z154.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."),
40434
+ shippingCost: z154.number().finite().nonnegative().optional().describe("Costo de un env\xEDo. Omite para usar el valor gu\xEDa del pa\xEDs en vez de inventarlo."),
40435
+ returnRate: z154.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."),
40436
+ cancellationRate: z154.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."),
40437
+ adsManagerCpa: z154.number().finite().nonnegative().optional().describe("Costo por compra de Ads Manager. Omite para usar el valor gu\xEDa del pa\xEDs."),
40438
+ desiredMargin: z154.number().finite().min(0).lt(1).optional().describe("Margen deseado (0.1 = 10%). Omite para usar el valor gu\xEDa del pa\xEDs.")
40439
+ });
40440
+ var GuideInput = z154.object({
40173
40441
  projectId: ProjectId.optional()
40174
40442
  }).strict();
40175
40443
  var NEXT_ACTION_KINDS = [
@@ -40187,29 +40455,29 @@ var NEXT_ACTION_KINDS = [
40187
40455
  "repair_meta_campaign",
40188
40456
  "review_paused_campaign"
40189
40457
  ];
40190
- var DropshippingProjectNextAction = z153.discriminatedUnion("kind", NEXT_ACTION_KINDS.map((kind2) => z153.object({
40191
- kind: z153.literal(kind2),
40192
- href: z153.string().min(1)
40458
+ var DropshippingProjectNextAction = z154.discriminatedUnion("kind", NEXT_ACTION_KINDS.map((kind2) => z154.object({
40459
+ kind: z154.literal(kind2),
40460
+ href: z154.string().min(1)
40193
40461
  })));
40194
- var CostingInputSchema = z153.object({
40195
- country: z153.string().min(2).max(3),
40196
- supplierCost: z153.number().finite().positive(),
40197
- shippingCost: z153.number().finite().nonnegative(),
40198
- returnRate: z153.number().finite().min(0).lt(1),
40199
- cancellationRate: z153.number().finite().min(0).lt(1),
40200
- adsManagerCpa: z153.number().finite().nonnegative(),
40201
- desiredMargin: z153.number().finite().min(0).lt(1)
40462
+ var CostingInputSchema = z154.object({
40463
+ country: z154.string().min(2).max(3),
40464
+ supplierCost: z154.number().finite().positive(),
40465
+ shippingCost: z154.number().finite().nonnegative(),
40466
+ returnRate: z154.number().finite().min(0).lt(1),
40467
+ cancellationRate: z154.number().finite().min(0).lt(1),
40468
+ adsManagerCpa: z154.number().finite().nonnegative(),
40469
+ desiredMargin: z154.number().finite().min(0).lt(1)
40202
40470
  }).superRefine(({ returnRate, cancellationRate }, context) => {
40203
40471
  if (returnRate + cancellationRate >= 1) context.addIssue({
40204
- code: z153.ZodIssueCode.custom,
40472
+ code: z154.ZodIssueCode.custom,
40205
40473
  path: [
40206
40474
  "cancellationRate"
40207
40475
  ]
40208
40476
  });
40209
40477
  });
40210
- var FiniteCostingNumber = z153.number().finite();
40211
- var CostingResultSchema = z153.object({
40212
- country: z153.string().min(2).max(3),
40478
+ var FiniteCostingNumber = z154.number().finite();
40479
+ var CostingResultSchema = z154.object({
40480
+ country: z154.string().min(2).max(3),
40213
40481
  deliveryRate: FiniteCostingNumber,
40214
40482
  adjustedShippingCost: FiniteCostingNumber,
40215
40483
  adjustedCpa: FiniteCostingNumber,
@@ -40222,45 +40490,45 @@ var CostingResultSchema = z153.object({
40222
40490
  estimatedProfit: FiniteCostingNumber,
40223
40491
  actualMargin: FiniteCostingNumber,
40224
40492
  breakEvenAdsManagerCpa: FiniteCostingNumber,
40225
- wasPriceAutoAdjusted: z153.boolean(),
40226
- profitabilityStatus: z153.literal("healthy")
40227
- });
40228
- var RewardProgressSchema = z153.object({
40229
- campaignKey: z153.literal("dropshipping-launch-v1"),
40230
- totalCredits: z153.number().int().positive(),
40231
- earnedCredits: z153.number().int().nonnegative(),
40232
- milestones: z153.array(z153.object({
40493
+ wasPriceAutoAdjusted: z154.boolean(),
40494
+ profitabilityStatus: z154.literal("healthy")
40495
+ });
40496
+ var RewardProgressSchema = z154.object({
40497
+ campaignKey: z154.literal("dropshipping-launch-v1"),
40498
+ totalCredits: z154.number().int().positive(),
40499
+ earnedCredits: z154.number().int().nonnegative(),
40500
+ milestones: z154.array(z154.object({
40233
40501
  milestone: DropshippingRewardMilestone,
40234
- credits: z153.number().int().positive(),
40235
- available: z153.boolean().optional(),
40236
- earned: z153.boolean(),
40502
+ credits: z154.number().int().positive(),
40503
+ available: z154.boolean().optional(),
40504
+ earned: z154.boolean(),
40237
40505
  grantId: ProjectId.nullable(),
40238
- grantedAt: z153.string().datetime().nullable()
40506
+ grantedAt: z154.string().datetime().nullable()
40239
40507
  })).length(DropshippingRewardMilestone.options.length)
40240
40508
  });
40241
- var ProjectResponseSchema = z153.object({
40509
+ var ProjectResponseSchema = z154.object({
40242
40510
  id: ProjectId,
40243
40511
  taskId: ProjectId.nullable(),
40244
40512
  status: DropshippingProjectStatus,
40245
- originKey: z153.string().min(1),
40513
+ originKey: z154.string().min(1),
40246
40514
  researchRunId: ProjectId.nullable(),
40247
- candidateSourceId: z153.string().nullable(),
40248
- productName: z153.string().nullable(),
40515
+ candidateSourceId: z154.string().nullable(),
40516
+ productName: z154.string().nullable(),
40249
40517
  productUrl: HttpUrl3.nullable(),
40250
40518
  referenceImageUrl: HttpUrl3.nullable(),
40251
- targetCountry: z153.string().nullable(),
40252
- languageCode: z153.string().nullable(),
40253
- hasReferenceImage: z153.boolean(),
40254
- hasWinnerReferenceImage: z153.boolean(),
40519
+ targetCountry: z154.string().nullable(),
40520
+ languageCode: z154.string().nullable(),
40521
+ hasReferenceImage: z154.boolean(),
40522
+ hasWinnerReferenceImage: z154.boolean(),
40255
40523
  costingInput: CostingInputSchema.nullable(),
40256
40524
  costingResult: CostingResultSchema.nullable(),
40257
- costingVersion: z153.number().int().positive().nullable(),
40258
- version: z153.number().int().positive(),
40525
+ costingVersion: z154.number().int().positive().nullable(),
40526
+ version: z154.number().int().positive(),
40259
40527
  phase: DropshippingProjectPhase,
40260
40528
  nextAction: DropshippingProjectNextAction.nullable(),
40261
40529
  rewards: RewardProgressSchema,
40262
- createdAt: z153.string().datetime(),
40263
- updatedAt: z153.string().datetime()
40530
+ createdAt: z154.string().datetime(),
40531
+ updatedAt: z154.string().datetime()
40264
40532
  }).passthrough();
40265
40533
  var NEXT_ACTION_GUIDANCE = {
40266
40534
  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.",
@@ -40380,58 +40648,58 @@ function registerDropshippingProjectTools(server, api) {
40380
40648
  __name(registerDropshippingProjectTools, "registerDropshippingProjectTools");
40381
40649
 
40382
40650
  // src/tools/daily-control.ts
40383
- import { z as z154 } from "zod";
40651
+ import { z as z155 } from "zod";
40384
40652
  var getDailyControlGuidance = getProductMakerCapabilityModelGuidance("get_daily_control");
40385
40653
  var linkCampaignToProjectGuidance = getProductMakerCapabilityModelGuidance("link_campaign_to_project");
40386
40654
  var reportDailyReturnsGuidance = getProductMakerCapabilityModelGuidance("report_daily_returns");
40387
- var ProviderCampaignIdentifier = z154.string().min(1).max(64).regex(/^[A-Za-z0-9_-]+$/);
40388
- var DateOnly = z154.string().regex(/^\d{4}-\d{2}-\d{2}$/);
40389
- var DailyControlInput = z154.object({
40655
+ var ProviderCampaignIdentifier = z155.string().min(1).max(64).regex(/^[A-Za-z0-9_-]+$/);
40656
+ var DateOnly = z155.string().regex(/^\d{4}-\d{2}-\d{2}$/);
40657
+ var DailyControlInput = z155.object({
40390
40658
  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."),
40391
- days: z154.number().int().min(1).max(14).default(3).describe("Tama\xF1o de la ventana, 1 a 14 d\xEDas."),
40392
- projectId: z154.string().uuid().optional().describe("Opcional: limita el reporte a un solo proyecto en vez de todos los productos activos.")
40659
+ days: z155.number().int().min(1).max(14).default(3).describe("Tama\xF1o de la ventana, 1 a 14 d\xEDas."),
40660
+ projectId: z155.string().uuid().optional().describe("Opcional: limita el reporte a un solo proyecto en vez de todos los productos activos.")
40393
40661
  }).strict();
40394
- var LinkCampaignInput = z154.object({
40395
- projectId: z154.string().uuid(),
40396
- provider: z154.enum([
40662
+ var LinkCampaignInput = z155.object({
40663
+ projectId: z155.string().uuid(),
40664
+ provider: z155.enum([
40397
40665
  "meta",
40398
40666
  "tiktok"
40399
40667
  ]),
40400
40668
  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."),
40401
40669
  campaignId: ProviderCampaignIdentifier
40402
40670
  }).strict();
40403
- var ReportReturnsInput = z154.object({
40404
- projectId: z154.string().uuid(),
40671
+ var ReportReturnsInput = z155.object({
40672
+ projectId: z155.string().uuid(),
40405
40673
  date: DateOnly,
40406
- returnedOrders: z154.number().int().min(0).max(1e5),
40407
- note: z154.string().trim().max(280).optional()
40408
- }).strict();
40409
- var DailyControlStatusMirror = DailyControlStatusEnum.or(z154.string());
40410
- var DailyControlLightMirror = DailyControlLightEnum.or(z154.string());
40411
- var DailyControlSourceMirror = DailyControlSourceEnum.or(z154.string());
40412
- var DailyControlVerdictMirror = DailyControlVerdictEnum.or(z154.string());
40413
- var DailyControlDayMirror = z154.object({
40414
- date: z154.string(),
40415
- returnedOrders: z154.number(),
40674
+ returnedOrders: z155.number().int().min(0).max(1e5),
40675
+ note: z155.string().trim().max(280).optional()
40676
+ }).strict();
40677
+ var DailyControlStatusMirror = DailyControlStatusEnum.or(z155.string());
40678
+ var DailyControlLightMirror = DailyControlLightEnum.or(z155.string());
40679
+ var DailyControlSourceMirror = DailyControlSourceEnum.or(z155.string());
40680
+ var DailyControlVerdictMirror = DailyControlVerdictEnum.or(z155.string());
40681
+ var DailyControlDayMirror = z155.object({
40682
+ date: z155.string(),
40683
+ returnedOrders: z155.number(),
40416
40684
  returnedSource: DailyControlSourceMirror,
40417
40685
  cancelledSource: DailyControlSourceMirror,
40418
- hasEstimates: z154.boolean(),
40686
+ hasEstimates: z155.boolean(),
40419
40687
  light: DailyControlLightMirror
40420
40688
  }).passthrough();
40421
- var DailyControlProductMirror = z154.object({
40422
- projectId: z154.string().uuid(),
40423
- productName: z154.string().nullable(),
40689
+ var DailyControlProductMirror = z155.object({
40690
+ projectId: z155.string().uuid(),
40691
+ productName: z155.string().nullable(),
40424
40692
  status: DailyControlStatusMirror,
40425
- statusMessage: z154.string().nullable(),
40426
- days: z154.array(DailyControlDayMirror),
40693
+ statusMessage: z155.string().nullable(),
40694
+ days: z155.array(DailyControlDayMirror),
40427
40695
  verdict: DailyControlVerdictMirror.nullable(),
40428
- rationale: z154.string().nullable()
40696
+ rationale: z155.string().nullable()
40429
40697
  }).passthrough();
40430
- var DailyControlResponseMirror = z154.object({
40431
- since: z154.string(),
40432
- until: z154.string(),
40433
- products: z154.array(DailyControlProductMirror),
40434
- archivedProjectSpend: z154.array(z154.unknown())
40698
+ var DailyControlResponseMirror = z155.object({
40699
+ since: z155.string(),
40700
+ until: z155.string(),
40701
+ products: z155.array(DailyControlProductMirror),
40702
+ archivedProjectSpend: z155.array(z155.unknown())
40435
40703
  }).passthrough();
40436
40704
  var MCP_WARNINGS_KEY = "_mcpDailyControlWarnings";
40437
40705
  function withWarnings(response) {
@@ -40507,7 +40775,7 @@ __name(registerDailyControlTools, "registerDailyControlTools");
40507
40775
  import { createHash as createHash5 } from "crypto";
40508
40776
  import { readFile, stat } from "fs/promises";
40509
40777
  import { basename as basename2, isAbsolute, resolve as resolve3 } from "path";
40510
- import { z as z155 } from "zod";
40778
+ import { z as z156 } from "zod";
40511
40779
  var getProductControlGuidance = getProductMakerCapabilityModelGuidance("get_product_control");
40512
40780
  var listImportableProductsGuidance = getProductMakerCapabilityModelGuidance("list_importable_products");
40513
40781
  var importProductControlsGuidance = getProductMakerCapabilityModelGuidance("import_product_controls");
@@ -40529,42 +40797,42 @@ var listLinkableCampaignsGuidance = getProductMakerCapabilityModelGuidance("list
40529
40797
  var linkProductCampaignGuidance = getProductMakerCapabilityModelGuidance("link_product_campaign");
40530
40798
  var deleteMonthlyExpenseGuidance = getProductMakerCapabilityModelGuidance("delete_monthly_expense");
40531
40799
  var removeProductControlGuidance = getProductMakerCapabilityModelGuidance("remove_product_control");
40532
- var DateOnly2 = z155.string().regex(/^\d{4}-\d{2}-\d{2}$/);
40800
+ var DateOnly2 = z156.string().regex(/^\d{4}-\d{2}-\d{2}$/);
40533
40801
  var IMPORT_BATCH_MAX2 = 50;
40534
- var ProductControlListInput = z155.object({
40535
- days: z155.number().int().min(1).max(14).default(3).describe("Tama\xF1o de la ventana, 1 a 14 d\xEDas."),
40802
+ var ProductControlListInput = z156.object({
40803
+ days: z156.number().int().min(1).max(14).default(3).describe("Tama\xF1o de la ventana, 1 a 14 d\xEDas."),
40536
40804
  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."),
40537
- shopDomain: z155.string().min(1).optional().describe("Tienda, si tiene m\xE1s de una conectada.")
40805
+ shopDomain: z156.string().min(1).optional().describe("Tienda, si tiene m\xE1s de una conectada.")
40538
40806
  }).strict();
40539
- var ImportableProductsInput = z155.object({
40540
- q: z155.string().trim().max(120).optional().describe("B\xFAsqueda por t\xEDtulo. Sin esto un cat\xE1logo grande es inrecorrible."),
40541
- after: z155.string().max(512).optional().describe("Cursor de la p\xE1gina anterior, tal como vino en la respuesta."),
40542
- shopDomain: z155.string().min(1).optional()
40807
+ var ImportableProductsInput = z156.object({
40808
+ q: z156.string().trim().max(120).optional().describe("B\xFAsqueda por t\xEDtulo. Sin esto un cat\xE1logo grande es inrecorrible."),
40809
+ after: z156.string().max(512).optional().describe("Cursor de la p\xE1gina anterior, tal como vino en la respuesta."),
40810
+ shopDomain: z156.string().min(1).optional()
40543
40811
  }).strict();
40544
- var ImportProductControlsInput = z155.object({
40545
- 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."),
40812
+ var ImportProductControlsInput = z156.object({
40813
+ shopifyProductIds: z156.array(z156.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."),
40546
40814
  // Obligatorio, como en el endpoint: con dos tiendas conectadas, importar
40547
40815
  // sin decir a cuál archivaría el producto bajo la tienda equivocada.
40548
- shopDomain: z155.string().min(1)
40816
+ shopDomain: z156.string().min(1)
40549
40817
  }).strict();
40550
- var SetProductCostingInput = z155.object({
40818
+ var SetProductCostingInput = z156.object({
40551
40819
  /* La identidad, de UNA forma: la del producto nuestro, o la pareja
40552
40820
  tienda + id de Shopify. El esquema canónico también las admite juntas,
40553
40821
  pero pedirlas por separado evita que el modelo emita una mezcla que
40554
40822
  diga dos productos a la vez. */
40555
- 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."),
40556
- 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."),
40557
- shopDomain: z155.string().min(1).optional(),
40558
- shopifyProductId: z155.string().min(1).max(64).optional(),
40559
- targetCountry: z155.string().length(2).describe("Pa\xEDs de venta en ISO-2, que fija moneda y referencia de flete."),
40560
- supplierCost: z155.number().finite().positive().describe("Lo que le cuesta el producto al proveedor. Nunca cero."),
40561
- shippingCost: z155.number().finite().nonnegative(),
40562
- returnRate: z155.number().finite().min(0).lt(1).describe("Proporci\xF3n 0\u20131, no porcentaje: 0.15 es quince por ciento."),
40563
- cancellationRate: z155.number().finite().min(0).lt(1)
40564
- }).strict();
40565
- var Count = z155.number().int().min(0).max(1e5).nullable().optional();
40566
- var ReportProductCountsInput = z155.object({
40567
- productControlId: z155.string().uuid().describe("El identificador que devuelve get_product_control."),
40823
+ productControlId: z156.string().uuid().optional().describe("El id nuestro (lo devuelven get_product_control y create_manual_product). Obligatorio para productos manuales o importados sin tienda."),
40824
+ manualSellingPrice: z156.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."),
40825
+ shopDomain: z156.string().min(1).optional(),
40826
+ shopifyProductId: z156.string().min(1).max(64).optional(),
40827
+ targetCountry: z156.string().length(2).describe("Pa\xEDs de venta en ISO-2, que fija moneda y referencia de flete."),
40828
+ supplierCost: z156.number().finite().positive().describe("Lo que le cuesta el producto al proveedor. Nunca cero."),
40829
+ shippingCost: z156.number().finite().nonnegative(),
40830
+ returnRate: z156.number().finite().min(0).lt(1).describe("Proporci\xF3n 0\u20131, no porcentaje: 0.15 es quince por ciento."),
40831
+ cancellationRate: z156.number().finite().min(0).lt(1)
40832
+ }).strict();
40833
+ var Count = z156.number().int().min(0).max(1e5).nullable().optional();
40834
+ var ReportProductCountsInput = z156.object({
40835
+ productControlId: z156.string().uuid().describe("El identificador que devuelve get_product_control."),
40568
40836
  date: DateOnly2,
40569
40837
  orders: Count.describe("Pedidos que entraron ese d\xEDa."),
40570
40838
  cancelledOrders: Count,
@@ -40574,64 +40842,64 @@ var ReportProductCountsInput = z155.object({
40574
40842
  }).strict();
40575
40843
  var MAX_ORDERS_FILE_BYTES = 8 * 1024 * 1024;
40576
40844
  var SyncShopifyOrdersInput = SyncShopifyOrdersSchema;
40577
- var ImportOrdersFileInput = z155.object({
40845
+ var ImportOrdersFileInput = z156.object({
40578
40846
  /** Ruta ABSOLUTA en la máquina de quien usa el MCP. */
40579
- path: z155.string().min(1),
40580
- operationId: z155.string().uuid().optional(),
40847
+ path: z156.string().min(1),
40848
+ operationId: z156.string().uuid().optional(),
40581
40849
  /** Solo mirar: devuelve el resumen y no escribe nada. */
40582
- dryRun: z155.boolean().optional()
40850
+ dryRun: z156.boolean().optional()
40583
40851
  }).strict();
40584
- var CreateOperationInput = z155.object({
40585
- countryCode: z155.string().length(2),
40586
- label: z155.string().trim().min(1).max(60).optional()
40852
+ var CreateOperationInput = z156.object({
40853
+ countryCode: z156.string().length(2),
40854
+ label: z156.string().trim().min(1).max(60).optional()
40587
40855
  }).strict();
40588
- var MergeProductsInput = z155.object({
40589
- sourceId: z155.string().uuid(),
40590
- targetId: z155.string().uuid()
40856
+ var MergeProductsInput = z156.object({
40857
+ sourceId: z156.string().uuid(),
40858
+ targetId: z156.string().uuid()
40591
40859
  }).strict();
40592
- var SetMonthlyExpenseInput = z155.object({
40593
- month: z155.string().regex(/^\d{4}-(0[1-9]|1[0-2])$/).optional(),
40860
+ var SetMonthlyExpenseInput = z156.object({
40861
+ month: z156.string().regex(/^\d{4}-(0[1-9]|1[0-2])$/).optional(),
40594
40862
  label: SaveMonthlyExpenseSchema.shape.label,
40595
- amount: z155.number().finite().min(0),
40596
- operationId: z155.string().uuid().optional()
40863
+ amount: z156.number().finite().min(0),
40864
+ operationId: z156.string().uuid().optional()
40597
40865
  }).strict();
40598
- var SetAdSpendInput = z155.object({
40599
- month: z155.string().regex(/^\d{4}-(0[1-9]|1[0-2])$/).optional(),
40600
- operationId: z155.string().uuid().optional(),
40866
+ var SetAdSpendInput = z156.object({
40867
+ month: z156.string().regex(/^\d{4}-(0[1-9]|1[0-2])$/).optional(),
40868
+ operationId: z156.string().uuid().optional(),
40601
40869
  /** null RETIRA la corrección; cero afirma que no pautó. */
40602
- amount: z155.number().finite().min(0).nullable()
40870
+ amount: z156.number().finite().min(0).nullable()
40603
40871
  }).strict();
40604
- var GetFinancesInput = z155.object({
40605
- 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."),
40606
- operationId: z155.string().uuid().optional().describe("El negocio, si tiene mas de uno. Con uno solo no hace falta decirlo.")
40872
+ var GetFinancesInput = z156.object({
40873
+ month: z156.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."),
40874
+ operationId: z156.string().uuid().optional().describe("El negocio, si tiene mas de uno. Con uno solo no hace falta decirlo.")
40607
40875
  }).strict();
40608
40876
  var GetDropiStatusInput = DropiCountryQuerySchema.extend({
40609
40877
  countryCode: DropiCountrySchema.optional().describe("Pa\xEDs de la cuenta (ISO-2). Si se omite, el servidor responde por Colombia (CO)."),
40610
- connectionId: z155.string().uuid().optional().describe("Una cuenta concreta cuando el pa\xEDs tiene varias. Sale de la lista de conexiones; nunca se inventa.")
40878
+ connectionId: z156.string().uuid().optional().describe("Una cuenta concreta cuando el pa\xEDs tiene varias. Sale de la lista de conexiones; nunca se inventa.")
40611
40879
  });
40612
40880
  var SyncDropiOrdersInput = SyncDropiFinancialSchema;
40613
40881
  var CreateManualProductInput = CreateManualProductControlSchema.innerType().omit({
40614
40882
  requestId: true
40615
40883
  });
40616
- var ListUnmatchedProductsInput = z155.object({
40617
- operationId: z155.string().uuid().optional().describe("El negocio, si tiene mas de uno. Con uno solo no hace falta decirlo.")
40884
+ var ListUnmatchedProductsInput = z156.object({
40885
+ operationId: z156.string().uuid().optional().describe("El negocio, si tiene mas de uno. Con uno solo no hace falta decirlo.")
40618
40886
  }).strict();
40619
40887
  var ResolveUnmatchedProductInput = ResolveUnmatchedProductSchema;
40620
- var ListLinkableCampaignsInput = z155.object({}).strict();
40621
- var LinkProductCampaignInput = z155.object({
40622
- provider: z155.enum([
40888
+ var ListLinkableCampaignsInput = z156.object({}).strict();
40889
+ var LinkProductCampaignInput = z156.object({
40890
+ provider: z156.enum([
40623
40891
  "meta",
40624
40892
  "tiktok"
40625
40893
  ]).describe("La red publicitaria de la campa\xF1a."),
40626
- accountId: z155.string().min(1).max(64).regex(/^[A-Za-z0-9_-]+$/).describe("La cuenta publicitaria. Sale de list_linkable_campaigns; nunca se inventa."),
40627
- campaignId: z155.string().min(1).max(64).regex(/^[A-Za-z0-9_-]+$/).describe("La campa\xF1a. Sale de list_linkable_campaigns; nunca se inventa."),
40628
- productControlId: z155.string().uuid().optional().describe("El id del producto en su control financiero."),
40629
- shopDomain: z155.string().min(1).optional().describe("La tienda del producto; con shopifyProductId es la otra forma de decir cu\xE1l es."),
40630
- shopifyProductId: z155.string().min(1).optional().describe("El id del producto en su tienda. Va junto a shopDomain.")
40894
+ accountId: z156.string().min(1).max(64).regex(/^[A-Za-z0-9_-]+$/).describe("La cuenta publicitaria. Sale de list_linkable_campaigns; nunca se inventa."),
40895
+ campaignId: z156.string().min(1).max(64).regex(/^[A-Za-z0-9_-]+$/).describe("La campa\xF1a. Sale de list_linkable_campaigns; nunca se inventa."),
40896
+ productControlId: z156.string().uuid().optional().describe("El id del producto en su control financiero."),
40897
+ shopDomain: z156.string().min(1).optional().describe("La tienda del producto; con shopifyProductId es la otra forma de decir cu\xE1l es."),
40898
+ shopifyProductId: z156.string().min(1).optional().describe("El id del producto en su tienda. Va junto a shopDomain.")
40631
40899
  }).strict();
40632
40900
  var DeleteMonthlyExpenseInput = DeleteMonthlyExpenseSchema;
40633
- var RemoveProductControlInput = z155.object({
40634
- productControlId: z155.string().uuid().describe("El id del producto en su control. Sale del listado; nunca se inventa.")
40901
+ var RemoveProductControlInput = z156.object({
40902
+ productControlId: z156.string().uuid().describe("El id del producto en su control. Sale del listado; nunca se inventa.")
40635
40903
  }).strict();
40636
40904
  function manualProductRequestId(input) {
40637
40905
  const digest3 = createHash5("sha256").update(JSON.stringify(input, [
@@ -40972,28 +41240,28 @@ function registerProductControlTools(server, api, { transport = "http" } = {}) {
40972
41240
  __name(registerProductControlTools, "registerProductControlTools");
40973
41241
 
40974
41242
  // src/tools/market-study-estimate.ts
40975
- import { z as z156 } from "zod";
41243
+ import { z as z157 } from "zod";
40976
41244
  var estimateMarketStudyGuidance = getProductMakerCapabilityModelGuidance("estimate_market_study_cost");
40977
- var ProductTaskOutputEnum = z156.enum([
41245
+ var ProductTaskOutputEnum = z157.enum([
40978
41246
  "landing",
40979
41247
  "video",
40980
41248
  "image"
40981
41249
  ]);
40982
- var EstimateMarketStudyInput = z156.object({
40983
- kind: z156.enum([
41250
+ var EstimateMarketStudyInput = z157.object({
41251
+ kind: z157.enum([
40984
41252
  "video",
40985
41253
  "product_task"
40986
41254
  ]).describe('"video" para generate_video_creative; "product_task" para create_product_task.'),
40987
- videoAngleCount: z156.number().int().min(1).max(3).describe("Los mismos 1, 2 o 3 videos que vas a pedir al crear."),
40988
- durationSeconds: z156.number().int().min(4).max(30).default(15).describe("La misma duraci\xF3n que enviar\xE1s al crear."),
41255
+ videoAngleCount: z157.number().int().min(1).max(3).describe("Los mismos 1, 2 o 3 videos que vas a pedir al crear."),
41256
+ durationSeconds: z157.number().int().min(4).max(30).default(15).describe("La misma duraci\xF3n que enviar\xE1s al crear."),
40989
41257
  videoStructure: VideoStructureEnum.default("single_take"),
40990
41258
  videoQuality: VideoQualityEnum.default("economy"),
40991
41259
  videoResolution: VideoResolutionEnum.default("480p"),
40992
- productImageCount: z156.number().int().min(1).max(5).optional().describe("Cantidad de fotos del producto que adjuntar\xE1s (1-5)."),
41260
+ productImageCount: z157.number().int().min(1).max(5).optional().describe("Cantidad de fotos del producto que adjuntar\xE1s (1-5)."),
40993
41261
  outputs: ProductTaskOutputEnum.array().min(1).max(3).optional().describe('Solo para kind="product_task": los mismos outputs de create_product_task. Default: todos.'),
40994
- generateAiMusic: z156.boolean().optional(),
41262
+ generateAiMusic: z157.boolean().optional(),
40995
41263
  generateAiEdit: AiEditRequestSchema.optional(),
40996
- 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.")
41264
+ replicateWinnerWhenFound: z157.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.")
40997
41265
  }).strict();
40998
41266
  function buildMarketStudyEstimateRequests(input) {
40999
41267
  const quotesVideo = input.kind === "video" || (input.outputs ?? [
@@ -41131,7 +41399,7 @@ function createServer(opts) {
41131
41399
  signal: opts.abortSignal
41132
41400
  });
41133
41401
  registerProductMakerPrompts(server);
41134
- registerMediaUploadAppResource(server, api.publicUrl("/v1/uploaded-assets/sessions/resolve"));
41402
+ registerMediaUploadAppResource(server, api.publicUrl("/v1/uploaded-assets/sessions/resolve"), opts.mediaUploadStorageOrigins);
41135
41403
  registerTaskResultsAppResource(server, proxyBaseUrl);
41136
41404
  registerProductCostingTools(server);
41137
41405
  registerProductResearchTools(server, api);
@@ -41165,26 +41433,6 @@ function createServer(opts) {
41165
41433
  }
41166
41434
  __name(createServer, "createServer");
41167
41435
 
41168
- // src/validate-api-url.ts
41169
- function assertSafePublicBaseUrl(envName, url2) {
41170
- let u;
41171
- try {
41172
- u = new URL(url2);
41173
- } catch {
41174
- throw new Error(`${envName} is not a valid URL: ${url2}`);
41175
- }
41176
- if (u.protocol !== "https:") {
41177
- throw new Error(`${envName} must use https (got ${u.protocol})`);
41178
- }
41179
- if (u.username || u.password || u.search || u.hash) {
41180
- throw new Error(`${envName} must not contain credentials, query parameters, or fragments`);
41181
- }
41182
- if (isPrivateHost(u.hostname)) {
41183
- throw new Error(`${envName} must not point to a private/loopback host: ${u.hostname}`);
41184
- }
41185
- }
41186
- __name(assertSafePublicBaseUrl, "assertSafePublicBaseUrl");
41187
-
41188
41436
  // src/http.ts
41189
41437
  var ALLOWED_ORIGINS = /* @__PURE__ */ new Set([
41190
41438
  "https://chat.openai.com",
@@ -41391,6 +41639,7 @@ function createAuthenticatedMcpHandler(opts, oauthAnalytics) {
41391
41639
  credential,
41392
41640
  apiBaseUrl: opts.apiBaseUrl,
41393
41641
  proxyBaseUrl: opts.proxyBaseUrl,
41642
+ mediaUploadStorageOrigins: opts.mediaUploadStorageOrigins,
41394
41643
  transport: "http",
41395
41644
  instructions: buildServerInstructions(legacySignal),
41396
41645
  abortSignal: abortController.signal
@@ -41540,6 +41789,7 @@ async function startHttpServer(env = process.env) {
41540
41789
  const proxyBaseUrl = env.MCP_PROXY_BASE_URL ?? "https://mcp.productmaker.app";
41541
41790
  assertSafePublicBaseUrl("PM_API_URL", apiBaseUrl);
41542
41791
  assertSafePublicBaseUrl("MCP_PROXY_BASE_URL", proxyBaseUrl);
41792
+ const mediaUploadStorageOrigins = parseMediaUploadStorageOrigins(env.PM_MEDIA_UPLOAD_STORAGE_ORIGINS);
41543
41793
  const oauthConfig = loadMcpOAuthConfig(env);
41544
41794
  const auth0Metadata = await loadAuth0Metadata(oauthConfig).catch((error) => {
41545
41795
  oauthAnalytics.capture(MCP_OAUTH_ANALYTICS_EVENTS.FAILED, null, {
@@ -41552,6 +41802,7 @@ async function startHttpServer(env = process.env) {
41552
41802
  const app = createApp({
41553
41803
  apiBaseUrl,
41554
41804
  proxyBaseUrl,
41805
+ mediaUploadStorageOrigins,
41555
41806
  oauth,
41556
41807
  appsChallengeToken: env.OPENAI_APPS_CHALLENGE_TOKEN ?? null
41557
41808
  });