@productmaker/mcp 12.2.6 → 12.2.8

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
@@ -3021,7 +3021,7 @@ var LlmPricingSnapshotSchema = z3.discriminatedUnion("source", [
3021
3021
  });
3022
3022
  function nativeOutputTokenCeiling(target) {
3023
3023
  if (target.nativeOwnedOutputPolicy) return target.nativeOwnedOutputPolicy.verifiedMaxOutputTokens;
3024
- return target.transport === "google" || target.id === "gemini-3.1-pro-openrouter" ? 65536 : 1e5;
3024
+ return target.transport === "google" || target.id === "gemini-flash-runware" || target.id === "gemini-3.1-pro-openrouter" ? 65536 : 1e5;
3025
3025
  }
3026
3026
  __name(nativeOutputTokenCeiling, "nativeOutputTokenCeiling");
3027
3027
  var LlmRouteTargetSchema = z3.object({
@@ -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")
@@ -8276,6 +8288,12 @@ var CodeLandingAuthorEnvelopeSchema = z23.object({
8276
8288
  */
8277
8289
  variantFacts: z23.array(LandingVariantFactSchema).max(16).optional()
8278
8290
  });
8291
+ var CODE_LANDING_DEFERRED_AUTHOR_PROVIDER_RESULT_KIND = "code_landing_deferred_author_provider_v1";
8292
+ var CodeLandingDeferredAuthorProviderResultSchema = z23.object({
8293
+ kind: z23.literal(CODE_LANDING_DEFERRED_AUTHOR_PROVIDER_RESULT_KIND),
8294
+ stepKey: z23.string().min(1),
8295
+ providerResult: z23.record(z23.string(), z23.unknown())
8296
+ });
8279
8297
  var CodeLandingCommerceChangeSchema = z23.object({
8280
8298
  price: z23.string().trim().min(1).max(40).optional(),
8281
8299
  compareAtPrice: z23.string().trim().min(1).max(40).nullable().optional(),
@@ -10806,6 +10824,8 @@ var role = z38.object({
10806
10824
  provider: id2,
10807
10825
  model: id2,
10808
10826
  bindingId: id2,
10827
+ /** Immutable shared-router chain for fresh recipes; absent preserves legacy custody. */
10828
+ routeSnapshot: LlmRouteSnapshotSchema.optional(),
10809
10829
  /** null explicitly omits effort on OpenRouter; undefined is a historical recipe. */
10810
10830
  reasoning: CreativeEngineRoleReasoningSchema.nullable().optional(),
10811
10831
  /** Server-owned output ceiling; omitted historical recipes keep their old stage cap. */
@@ -10825,7 +10845,13 @@ var CreativeEngineRecipeSchema = z38.object({
10825
10845
  roles: z38.object({
10826
10846
  director: role,
10827
10847
  reviewer: role,
10828
- executor: role.nullable()
10848
+ executor: role.nullable(),
10849
+ /**
10850
+ * Independent reviewer of the assembled final video only (owner
10851
+ * decision 2026-10-26). Absent on historical recipes: their final
10852
+ * review keeps the frozen `reviewer`, unchanged hash and identity.
10853
+ */
10854
+ finalReviewer: role.optional()
10829
10855
  }).strict(),
10830
10856
  capabilities: z38.object({
10831
10857
  videoModels: z38.array(id2).min(1),
@@ -10844,9 +10870,14 @@ var CreativeEngineRecipeSchema = z38.object({
10844
10870
  /** New recipes reserve an explicit native failover; historical calls stay single-route. */
10845
10871
  modelFallbackPolicy: z38.literal("anthropic_conclusive_v1").optional(),
10846
10872
  /** New reviewer/executor calls use the shared same-model native provider router. */
10847
- centralModelRouting: z38.literal("native_owned_same_model_v1").optional(),
10873
+ centralModelRouting: z38.enum([
10874
+ "native_owned_same_model_v1",
10875
+ "shared_role_chain_v1"
10876
+ ]).optional(),
10848
10877
  /** Skip only a redundant global review of a deterministically frozen scene repair. */
10849
10878
  repairPlanReviewPolicy: z38.literal("frozen_contract_v1").optional(),
10879
+ /** Root-only visual patch wire contract; absent preserves historic full-plan recovery. */
10880
+ repairAuthoring: z38.literal("scene_patch_v1").optional(),
10850
10881
  /** New requests maximize output inside model and measured input headroom. */
10851
10882
  outputTokenPolicy: z38.literal("model_max_v1").optional(),
10852
10883
  /**
@@ -11307,6 +11338,7 @@ var CreativeEngineCandidateSchema = z38.object({
11307
11338
  qualityScore: z38.number().min(0).max(100)
11308
11339
  }).strict();
11309
11340
  var CreativeEngineCostBasisSchema = z38.enum([
11341
+ "reused",
11310
11342
  "provider_reported",
11311
11343
  "provider_credits_conversion",
11312
11344
  "catalogue_estimate"
@@ -11607,6 +11639,15 @@ var CreativeEngineEventSchema = z38.discriminatedUnion("type", [
11607
11639
  }).strict(),
11608
11640
  plan: CreativeEnginePlanSchema
11609
11641
  }).strict(),
11642
+ z38.object({
11643
+ type: z38.literal("accepted_author_output_revalidated"),
11644
+ operationId: id2,
11645
+ acceptedId: id2,
11646
+ context: CreativeEngineContextBindingSchema,
11647
+ outputSha256: hash,
11648
+ validationRevision: z38.string().regex(/^[a-z][a-z0-9_]{0,63}$/),
11649
+ plan: CreativeEnginePlanSchema
11650
+ }).strict(),
11610
11651
  z38.object({
11611
11652
  type: z38.literal("plan_recorded"),
11612
11653
  plan: CreativeEnginePlanSchema
@@ -13098,7 +13139,9 @@ var SERVICE_MODES = [
13098
13139
  ];
13099
13140
  var ServiceModeSchema = z46.enum(SERVICE_MODES);
13100
13141
  var AppFeaturesSchema = z46.object({
13101
- serviceMotionGraphics: z46.boolean()
13142
+ serviceMotionGraphics: z46.boolean(),
13143
+ /** GIF authoring controls; absent on older APIs means hidden. */
13144
+ landingGif: z46.boolean().default(false)
13102
13145
  });
13103
13146
  var MOTION_GRAPHICS_FORMATS = [
13104
13147
  "vertical",
@@ -20905,7 +20948,7 @@ var CAPABILITY_MODEL_GUIDANCE = {
20905
20948
  ]
20906
20949
  },
20907
20950
  generate_free_video: {
20908
- summary: "Genera UN video libre con prompt/guion exacto, opcionalmente con imagen de referencia.",
20951
+ summary: "Creates custom-script video using paid credits; legacy 'free' means free-form.",
20909
20952
  useWhen: [
20910
20953
  "genera un video libre creativo",
20911
20954
  "la persona pide un video libre con un prompt o guion espec\xEDfico"
@@ -30375,11 +30418,11 @@ var OAuthOboCredential = class {
30375
30418
  // src/guidance/productmaker-guidance.ts
30376
30419
  var START_DROPSHIPPING_PROMPT_NAME = "start_dropshipping";
30377
30420
  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.",
30421
+ "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
30422
  "For requests that do not need a new media upload, understand the user's intent before choosing a ProductMaker tool.",
30380
30423
  "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.",
30424
+ "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.",
30425
+ "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
30426
  "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
30427
  "Use the guided journey only for an explicit end-to-end launch.",
30385
30428
  "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 +30904,271 @@ var ApiClient = class {
30861
30904
  };
30862
30905
 
30863
30906
  // src/index.ts
30864
- var MCP_VERSION = "12.2.6";
30907
+ var MCP_VERSION = "12.2.8";
30865
30908
 
30866
30909
  // src/meta-upload-app.ts
30867
30910
  import { readFileSync } from "fs";
30911
+ import { z as z132 } from "zod";
30868
30912
  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) {
30913
+
30914
+ // src/public-https.ts
30915
+ import { lookup } from "dns/promises";
30916
+ import https from "https";
30917
+ import { isIP } from "net";
30918
+ import { createGunzip, createInflate, createBrotliDecompress } from "zlib";
30919
+
30920
+ // ../../packages/utils/src/ip-classification.ts
30921
+ import net from "net";
30922
+ function parseIPv4(ip) {
30923
+ const parts = ip.split(".");
30924
+ if (parts.length !== 4) return null;
30925
+ const nums = [
30926
+ Number(parts[0]),
30927
+ Number(parts[1]),
30928
+ Number(parts[2]),
30929
+ Number(parts[3])
30930
+ ];
30931
+ if (nums.some((n) => !Number.isInteger(n) || n < 0 || n > 255)) return null;
30932
+ return nums;
30933
+ }
30934
+ __name(parseIPv4, "parseIPv4");
30935
+ function classifyPrivateIPv4(ip, includeAdditionalRanges) {
30936
+ const p = parseIPv4(ip);
30937
+ if (!p) return false;
30938
+ const [a, b] = p;
30939
+ 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);
30940
+ }
30941
+ __name(classifyPrivateIPv4, "classifyPrivateIPv4");
30942
+ function isPrivateIPv4(ip) {
30943
+ return classifyPrivateIPv4(ip, true);
30944
+ }
30945
+ __name(isPrivateIPv4, "isPrivateIPv4");
30946
+ function is6to4PrivateIPv4(ipv6) {
30947
+ const groups = ipv6.split(":");
30948
+ if (groups.length < 3) return false;
30949
+ return isPrivateIPv4Words(groups[1] || "0", groups[2] || "0");
30950
+ }
30951
+ __name(is6to4PrivateIPv4, "is6to4PrivateIPv4");
30952
+ function isPrivateIPv4Words(high, low) {
30953
+ const hi = parseInt(high, 16);
30954
+ const lo = parseInt(low, 16);
30955
+ if (Number.isNaN(hi) || Number.isNaN(lo)) return false;
30956
+ const a = hi >> 8 & 255;
30957
+ const b = hi & 255;
30958
+ const c = lo >> 8 & 255;
30959
+ const d = lo & 255;
30960
+ return isPrivateIPv4(`${a}.${b}.${c}.${d}`);
30961
+ }
30962
+ __name(isPrivateIPv4Words, "isPrivateIPv4Words");
30963
+ function isPrivateIPv6(ip) {
30964
+ let normalized;
30894
30965
  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}`);
30966
+ normalized = new URL(`http://[${ip.split("%", 1)[0]}]/`).hostname.slice(1, -1).toLowerCase();
30967
+ } catch {
30968
+ return false;
30899
30969
  }
30970
+ if (normalized === "::" || /^::[0-9a-f]+$/.test(normalized)) return true;
30971
+ if (normalized.startsWith("64:ff9b:")) return true;
30972
+ if (/^fe[89a-f][0-9a-f]:/.test(normalized)) return true;
30973
+ if (/^f[cd][0-9a-f]{2}:/.test(normalized)) return true;
30974
+ if (normalized.startsWith("ff")) return true;
30975
+ if (/^2001:0*:/.test(normalized)) return true;
30976
+ if (normalized.startsWith("2001:db8:")) return true;
30977
+ if (normalized.startsWith("2002:") && is6to4PrivateIPv4(normalized)) return true;
30978
+ const embedded = /^::(?:ffff:(?:0:)?)?([0-9a-f]+):([0-9a-f]+)$/.exec(normalized);
30979
+ if (embedded?.[1] && embedded[2]) return isPrivateIPv4Words(embedded[1], embedded[2]);
30980
+ return false;
30900
30981
  }
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
- }));
30982
+ __name(isPrivateIPv6, "isPrivateIPv6");
30983
+ function isPrivateIp(ip) {
30984
+ const kind2 = net.isIP(ip);
30985
+ if (kind2 === 4) return isPrivateIPv4(ip);
30986
+ if (kind2 === 6) return isPrivateIPv6(ip);
30987
+ return false;
30932
30988
  }
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;
30989
+ __name(isPrivateIp, "isPrivateIp");
30990
+ function looksLikeNonCanonicalIp(host) {
30991
+ if (net.isIP(host) !== 0) return false;
30992
+ if (/^\d+$/.test(host)) return true;
30993
+ if (/^0x[0-9a-f]+$/i.test(host)) return true;
30994
+ if (host.includes(".")) {
30995
+ const labels = host.split(".");
30996
+ const allNumeric = labels.every((label) => {
30997
+ if (label.length === 0) return false;
30998
+ return /^\d+$/.test(label) || // decimal / octal-with-leading-zero
30999
+ /^0x[0-9a-f]+$/i.test(label);
31000
+ });
31001
+ if (allNumeric) return true;
30947
31002
  }
31003
+ return false;
30948
31004
  }
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;
31005
+ __name(looksLikeNonCanonicalIp, "looksLikeNonCanonicalIp");
31006
+
31007
+ // src/public-host.ts
31008
+ function isPrivateHost(value) {
31009
+ let host = value.toLowerCase().replace(/^\[|\]$/g, "").replace(/\.$/, "");
31010
+ if (host === "localhost" || host === "metadata.google.internal") return true;
31011
+ if (host.includes(":")) {
30954
31012
  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;
31013
+ host = new URL(`https://[${host}]/`).hostname.slice(1, -1);
30957
31014
  } catch {
30958
- return null;
31015
+ return true;
30959
31016
  }
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
31017
+ }
31018
+ return looksLikeNonCanonicalIp(host) || isPrivateIp(host);
31019
+ }
31020
+ __name(isPrivateHost, "isPrivateHost");
31021
+
31022
+ // src/public-https.ts
31023
+ var PublicHttpsError = class extends Error {
31024
+ static {
31025
+ __name(this, "PublicHttpsError");
31026
+ }
31027
+ code;
31028
+ constructor(code, message) {
31029
+ super(message), this.code = code;
31030
+ this.name = "PublicHttpsError";
31031
+ }
31032
+ };
31033
+ function pinnedLookup(pin) {
31034
+ return (_hostname, options, callback) => {
31035
+ if (options.all) callback(null, [
31036
+ pin
31037
+ ]);
31038
+ else callback(null, pin.address, pin.family);
30998
31039
  };
30999
31040
  }
31000
- __name(buildTaskResultsView, "buildTaskResultsView");
31001
- function taskResultsToolMeta() {
31041
+ __name(pinnedLookup, "pinnedLookup");
31042
+ async function resolvePublicAddress(url2, signal) {
31043
+ const hostname = url2.hostname.replace(/^\[|\]$/g, "");
31044
+ if (isPrivateHost(hostname)) throw new PublicHttpsError("INVALID_INPUT", "media URL host is private");
31045
+ const literalFamily = isIP(hostname);
31046
+ const records = literalFamily ? [
31047
+ {
31048
+ address: hostname,
31049
+ family: literalFamily
31050
+ }
31051
+ ] : await new Promise((resolve4, reject) => {
31052
+ const abort = /* @__PURE__ */ __name(() => reject(signal.reason), "abort");
31053
+ if (signal.aborted) return abort();
31054
+ signal.addEventListener("abort", abort, {
31055
+ once: true
31056
+ });
31057
+ lookup(hostname, {
31058
+ all: true
31059
+ }).then(resolve4, reject).finally(() => {
31060
+ signal.removeEventListener("abort", abort);
31061
+ });
31062
+ });
31063
+ if (!Array.isArray(records) || !records.length || records.some((entry) => !isIP(entry.address) || isIP(entry.address) !== entry.family || isPrivateHost(entry.address))) {
31064
+ throw new PublicHttpsError("INVALID_INPUT", "media URL DNS answers are not exclusively public IP addresses");
31065
+ }
31066
+ const first = records[0];
31067
+ if (!first || first.family !== 4 && first.family !== 6) throw new PublicHttpsError("INVALID_INPUT", "media URL has no usable DNS address");
31002
31068
  return {
31003
- ui: {
31004
- resourceUri: TASK_RESULTS_APP_RESOURCE_URI
31005
- },
31006
- "openai/outputTemplate": TASK_RESULTS_APP_RESOURCE_URI
31069
+ address: first.address,
31070
+ family: first.family
31007
31071
  };
31008
31072
  }
31009
- __name(taskResultsToolMeta, "taskResultsToolMeta");
31010
- function renderTaskResultsAppHtml(proxyBaseUrl) {
31011
- return readFileSync2(HTML_URL, "utf8").replace("__PRODUCTMAKER_ASSET_ORIGIN__", JSON.stringify(assetOrigin(proxyBaseUrl)).replaceAll("<", "\\u003c"));
31073
+ __name(resolvePublicAddress, "resolvePublicAddress");
31074
+ async function withPinnedHttpsResponse(url2, pin, signal, consume, tls = {}) {
31075
+ const agent = new https.Agent({
31076
+ keepAlive: false,
31077
+ lookup: pinnedLookup(pin),
31078
+ ...tls
31079
+ });
31080
+ let body;
31081
+ let incoming;
31082
+ let req;
31083
+ try {
31084
+ const response = await new Promise((resolve4, reject) => {
31085
+ req = https.request(url2, {
31086
+ agent,
31087
+ signal,
31088
+ headers: {
31089
+ "accept-encoding": "gzip, deflate, br"
31090
+ }
31091
+ }, (res) => {
31092
+ try {
31093
+ incoming = res;
31094
+ const encoding = res.headers["content-encoding"]?.toLowerCase();
31095
+ const decoder = encoding === "gzip" ? createGunzip() : encoding === "deflate" ? createInflate() : encoding === "br" ? createBrotliDecompress() : void 0;
31096
+ body = decoder ? res.pipe(decoder) : res;
31097
+ if (decoder) res.on("error", (error) => decoder.destroy(error));
31098
+ const iterator = body[Symbol.asyncIterator]();
31099
+ const stream = new ReadableStream({
31100
+ async pull(controller) {
31101
+ try {
31102
+ const next = await iterator.next();
31103
+ if (next.done) controller.close();
31104
+ else controller.enqueue(new Uint8Array(next.value));
31105
+ } catch (error) {
31106
+ controller.error(error);
31107
+ }
31108
+ },
31109
+ cancel() {
31110
+ body?.destroy();
31111
+ res.destroy();
31112
+ }
31113
+ });
31114
+ const headers = new Headers();
31115
+ for (const [key2, value] of Object.entries(res.headers)) {
31116
+ if (value !== void 0) headers.set(key2, Array.isArray(value) ? value.join(", ") : value);
31117
+ }
31118
+ const status = res.statusCode ?? 502;
31119
+ resolve4(new Response([
31120
+ 204,
31121
+ 205,
31122
+ 304
31123
+ ].includes(status) ? null : stream, {
31124
+ status,
31125
+ headers
31126
+ }));
31127
+ } catch (error) {
31128
+ res.destroy();
31129
+ reject(error);
31130
+ }
31131
+ });
31132
+ req.on("error", reject);
31133
+ req.end();
31134
+ });
31135
+ if (response.status >= 300 && response.status < 400) throw new PublicHttpsError("DOWNLOAD_FAILED", "media URL redirects are not allowed");
31136
+ return await consume(response, url2);
31137
+ } finally {
31138
+ body?.destroy();
31139
+ incoming?.destroy();
31140
+ req?.destroy();
31141
+ agent.destroy();
31142
+ }
31012
31143
  }
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
- }));
31144
+ __name(withPinnedHttpsResponse, "withPinnedHttpsResponse");
31145
+ async function withPublicHttpsResponse(rawUrl, timeoutMs, consume) {
31146
+ let url2;
31147
+ try {
31148
+ url2 = new URL(rawUrl);
31149
+ } catch {
31150
+ throw new PublicHttpsError("INVALID_INPUT", "media URL is not valid");
31151
+ }
31152
+ if (url2.protocol !== "https:" || url2.username || url2.password) throw new PublicHttpsError("INVALID_INPUT", "media URL must be HTTPS without embedded credentials");
31153
+ const controller = new AbortController();
31154
+ const timer = setTimeout(() => controller.abort(new Error("media download timed out")), timeoutMs);
31155
+ timer.unref();
31156
+ try {
31157
+ let pin;
31158
+ try {
31159
+ pin = await resolvePublicAddress(url2, controller.signal);
31160
+ } catch (error) {
31161
+ if (error instanceof PublicHttpsError || controller.signal.aborted) throw error;
31162
+ throw new PublicHttpsError("INVALID_INPUT", "media URL DNS lookup failed");
31163
+ }
31164
+ return await withPinnedHttpsResponse(url2, pin, controller.signal, consume);
31165
+ } finally {
31166
+ clearTimeout(timer);
31167
+ }
31047
31168
  }
31048
- __name(registerTaskResultsAppResource, "registerTaskResultsAppResource");
31049
-
31050
- // src/tools/tasks.ts
31051
- import { z as z137 } from "zod";
31169
+ __name(withPublicHttpsResponse, "withPublicHttpsResponse");
31052
31170
 
31053
31171
  // src/resolve-image.ts
31054
- import { lookup as dnsLookup } from "dns/promises";
31055
31172
  import { promises as fs, createReadStream } from "fs";
31056
31173
  import * as path from "path";
31057
31174
  import * as os from "os";
@@ -31098,53 +31215,6 @@ function sniffMime(buf) {
31098
31215
  return null;
31099
31216
  }
31100
31217
  __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
31218
  function expandUserPath(raw) {
31149
31219
  let s = raw.trim();
31150
31220
  if (s === "~") return os.homedir();
@@ -31218,48 +31288,37 @@ async function resolvePath(rawPath) {
31218
31288
  }
31219
31289
  __name(resolvePath, "resolvePath");
31220
31290
  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
31291
  try {
31232
- res = await fetch(rawUrl, {
31233
- signal: AbortSignal.timeout(3e4),
31234
- redirect: "error"
31292
+ return await withPublicHttpsResponse(rawUrl, 3e4, async (res) => {
31293
+ if (!res.ok) throw new ResolveImageError("IMAGE_DOWNLOAD_FAILED", `HTTP ${res.status}`);
31294
+ const cl = res.headers.get("content-length");
31295
+ if (cl && Number(cl) > maxBytes) {
31296
+ throw new ResolveImageError("IMAGE_TOO_LARGE", `content-length ${cl}`);
31297
+ }
31298
+ const ct = (res.headers.get("content-type") ?? "").split(";")[0]?.trim() ?? "";
31299
+ if (!ALLOWED_MIMES.has(ct)) throw new ResolveImageError("IMAGE_TYPE_UNSUPPORTED", `content-type ${ct}`);
31300
+ let buf;
31301
+ try {
31302
+ buf = Buffer.from(await readResponseBytes(res, maxBytes));
31303
+ } catch (error) {
31304
+ if (error instanceof ResponseBodyTooLargeError) {
31305
+ throw new ResolveImageError("IMAGE_TOO_LARGE", `image URL exceeds ${maxBytes} bytes`);
31306
+ }
31307
+ throw new ResolveImageError("IMAGE_DOWNLOAD_FAILED", "image response could not be read");
31308
+ }
31309
+ const sniffed = sniffMime(buf);
31310
+ if (!sniffed || sniffed !== ct) {
31311
+ throw new ResolveImageError("IMAGE_TYPE_UNSUPPORTED", `magic bytes (${sniffed ?? "unknown"}) do not match content-type (${ct})`);
31312
+ }
31313
+ return {
31314
+ buffer: buf,
31315
+ mimetype: sniffed
31316
+ };
31235
31317
  });
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
31318
  } 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})`);
31319
+ if (error instanceof ResolveImageError) throw error;
31320
+ throw new ResolveImageError(error instanceof PublicHttpsError && error.code === "INVALID_INPUT" ? "INVALID_INPUT" : "IMAGE_DOWNLOAD_FAILED", error instanceof Error ? error.message : "image download failed");
31258
31321
  }
31259
- return {
31260
- buffer: buf,
31261
- mimetype: sniffed
31262
- };
31263
31322
  }
31264
31323
  __name(resolveImageUrl, "resolveImageUrl");
31265
31324
  async function resolveImages(images, opts = {
@@ -31290,6 +31349,258 @@ async function resolveOne(ref, transport, maxBytes) {
31290
31349
  }
31291
31350
  __name(resolveOne, "resolveOne");
31292
31351
 
31352
+ // src/validate-api-url.ts
31353
+ function assertSafePublicBaseUrl(envName, url2) {
31354
+ let u;
31355
+ try {
31356
+ u = new URL(url2);
31357
+ } catch {
31358
+ throw new Error(`${envName} is not a valid URL: ${url2}`);
31359
+ }
31360
+ if (u.protocol !== "https:") {
31361
+ throw new Error(`${envName} must use https (got ${u.protocol})`);
31362
+ }
31363
+ if (u.username || u.password || u.search || u.hash) {
31364
+ throw new Error(`${envName} must not contain credentials, query parameters, or fragments`);
31365
+ }
31366
+ if (isPrivateHost(u.hostname)) {
31367
+ throw new Error(`${envName} must not point to a private/loopback host: ${u.hostname}`);
31368
+ }
31369
+ }
31370
+ __name(assertSafePublicBaseUrl, "assertSafePublicBaseUrl");
31371
+
31372
+ // src/app-origin.ts
31373
+ var PRODUCTMAKER_APP_ORIGIN = "https://productmaker.app";
31374
+
31375
+ // src/meta-upload-app.ts
31376
+ var MEDIA_UPLOAD_APP_RESOURCE_URI = "ui://media-upload/v1.html";
31377
+ var MEDIA_UPLOAD_APP_NAME = "ProductMaker media upload";
31378
+ var MEDIA_UPLOAD_APP_HTML_URL = new URL("./ui/meta-upload-app.html", import.meta.url);
31379
+ var PRODUCTMAKER_UPLOAD_STORAGE_ORIGIN = "https://825805a8113ddb98b68b9b0ddbb5d72a.r2.cloudflarestorage.com";
31380
+ var MEDIA_UPLOAD_CONNECT_DOMAINS = [
31381
+ "https://api.productmaker.app",
31382
+ PRODUCTMAKER_APP_ORIGIN,
31383
+ "https://www.productmaker.app",
31384
+ // The file bridge supplies temporary URLs. Keep this host-owned scope until
31385
+ // a real ChatGPT/library upload establishes every required exact origin.
31386
+ "https://*.oaiusercontent.com"
31387
+ ];
31388
+ var STORAGE_ORIGINS_ERROR = "PM_MEDIA_UPLOAD_STORAGE_ORIGINS must be a JSON array of exact public HTTPS origins";
31389
+ function normalizeStorageOrigins(value) {
31390
+ try {
31391
+ const origins = z132.array(z132.string().trim().min(1)).max(16).parse(value);
31392
+ return [
31393
+ ...new Set(origins.map((origin) => {
31394
+ assertSafePublicBaseUrl("PM_MEDIA_UPLOAD_STORAGE_ORIGINS", origin);
31395
+ const parsed = new URL(origin);
31396
+ if (parsed.pathname !== "/" || parsed.hostname.includes("*")) throw new Error(STORAGE_ORIGINS_ERROR);
31397
+ return parsed.origin;
31398
+ }))
31399
+ ];
31400
+ } catch {
31401
+ throw new Error(STORAGE_ORIGINS_ERROR);
31402
+ }
31403
+ }
31404
+ __name(normalizeStorageOrigins, "normalizeStorageOrigins");
31405
+ function parseMediaUploadStorageOrigins(value) {
31406
+ if (value === void 0) return void 0;
31407
+ try {
31408
+ return normalizeStorageOrigins(JSON.parse(value));
31409
+ } catch {
31410
+ throw new Error(STORAGE_ORIGINS_ERROR);
31411
+ }
31412
+ }
31413
+ __name(parseMediaUploadStorageOrigins, "parseMediaUploadStorageOrigins");
31414
+ function buildMediaUploadAppCsp(resolveEndpoint, storageOrigins) {
31415
+ const resolveOrigin = new URL(resolveEndpoint).origin;
31416
+ const approvedStorage = storageOrigins === void 0 ? resolveOrigin === "https://api.productmaker.app" ? [
31417
+ PRODUCTMAKER_UPLOAD_STORAGE_ORIGIN
31418
+ ] : [] : normalizeStorageOrigins(storageOrigins);
31419
+ return {
31420
+ connectDomains: [
31421
+ .../* @__PURE__ */ new Set([
31422
+ ...MEDIA_UPLOAD_CONNECT_DOMAINS,
31423
+ ...approvedStorage,
31424
+ resolveOrigin
31425
+ ])
31426
+ ],
31427
+ // This standalone HTML has no remote static assets. Local image previews
31428
+ // use File/createImageBitmap and data: URLs; host-library bytes use fetch.
31429
+ resourceDomains: []
31430
+ };
31431
+ }
31432
+ __name(buildMediaUploadAppCsp, "buildMediaUploadAppCsp");
31433
+ var MEDIA_UPLOAD_APP_CSP = buildMediaUploadAppCsp("https://api.productmaker.app/v1/uploaded-assets/sessions/resolve");
31434
+ function readMediaUploadAppHtml(htmlUrl = MEDIA_UPLOAD_APP_HTML_URL) {
31435
+ try {
31436
+ return readFileSync(htmlUrl, "utf8");
31437
+ } catch (error) {
31438
+ const message = error instanceof Error ? error.message : String(error);
31439
+ 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}`);
31440
+ }
31441
+ }
31442
+ __name(readMediaUploadAppHtml, "readMediaUploadAppHtml");
31443
+ function renderMediaUploadAppHtml(resolveEndpoint) {
31444
+ const encodedEndpoint = JSON.stringify(resolveEndpoint).slice(1, -1).replaceAll("<", "\\u003c");
31445
+ return readMediaUploadAppHtml().replace("__PRODUCTMAKER_RESOLVE_ENDPOINT__", encodedEndpoint);
31446
+ }
31447
+ __name(renderMediaUploadAppHtml, "renderMediaUploadAppHtml");
31448
+ function registerMediaUploadAppResource(server, resolveEndpoint, storageOrigins) {
31449
+ const html = renderMediaUploadAppHtml(resolveEndpoint);
31450
+ const csp = buildMediaUploadAppCsp(resolveEndpoint, storageOrigins);
31451
+ const meta = {
31452
+ ui: {
31453
+ csp
31454
+ },
31455
+ // OpenAI requires a dedicated per-plugin UI origin. Keep its compatibility
31456
+ // alias separate from the host-derived standard domain used by Claude.
31457
+ "openai/widgetDomain": PRODUCTMAKER_APP_ORIGIN,
31458
+ "openai/widgetCSP": {
31459
+ connect_domains: csp.connectDomains,
31460
+ resource_domains: csp.resourceDomains,
31461
+ // Trust only the existing secure-upload fallback for OpenAI external opens.
31462
+ // The current HTML uses an ordinary anchor; no navigation behavior changes.
31463
+ redirect_domains: [
31464
+ PRODUCTMAKER_APP_ORIGIN
31465
+ ]
31466
+ }
31467
+ };
31468
+ registerAppResource(server, MEDIA_UPLOAD_APP_NAME, MEDIA_UPLOAD_APP_RESOURCE_URI, {
31469
+ title: "Upload media",
31470
+ mimeType: RESOURCE_MIME_TYPE,
31471
+ _meta: meta
31472
+ }, async () => ({
31473
+ contents: [
31474
+ {
31475
+ uri: MEDIA_UPLOAD_APP_RESOURCE_URI,
31476
+ mimeType: RESOURCE_MIME_TYPE,
31477
+ text: html,
31478
+ _meta: meta
31479
+ }
31480
+ ]
31481
+ }));
31482
+ }
31483
+ __name(registerMediaUploadAppResource, "registerMediaUploadAppResource");
31484
+
31485
+ // src/task-results-app.ts
31486
+ import { readFileSync as readFileSync2 } from "fs";
31487
+ import { registerAppResource as registerAppResource2, RESOURCE_MIME_TYPE as RESOURCE_MIME_TYPE2 } from "@modelcontextprotocol/ext-apps/server";
31488
+ var TASK_RESULTS_APP_RESOURCE_URI = "ui://task-results/v1.html";
31489
+ var HTML_URL = new URL("./ui/task-results-app.html", import.meta.url);
31490
+ function assetOrigin(value) {
31491
+ if (!value) return null;
31492
+ try {
31493
+ const url2 = new URL(value);
31494
+ return url2.protocol === "https:" && !url2.username && !url2.password ? url2.origin : null;
31495
+ } catch {
31496
+ return null;
31497
+ }
31498
+ }
31499
+ __name(assetOrigin, "assetOrigin");
31500
+ function buildTaskResultsView(media, proxyBaseUrl) {
31501
+ const origin = assetOrigin(proxyBaseUrl);
31502
+ const safeUrl = /* @__PURE__ */ __name((value) => {
31503
+ if (!origin) return null;
31504
+ try {
31505
+ const url2 = new URL(value);
31506
+ return url2.protocol === "https:" && url2.origin === origin && url2.pathname.startsWith("/assets/") && url2.pathname.length > "/assets/".length && !url2.username && !url2.password ? url2.href : null;
31507
+ } catch {
31508
+ return null;
31509
+ }
31510
+ }, "safeUrl");
31511
+ const images = media.images.flatMap((image2, index) => {
31512
+ const url2 = safeUrl(image2.url);
31513
+ return url2 ? [
31514
+ {
31515
+ url: url2,
31516
+ alt: image2.alt?.slice(0, 160) ?? `ProductMaker image ${index + 1}`
31517
+ }
31518
+ ] : [];
31519
+ });
31520
+ const videos = media.videos.flatMap((video) => {
31521
+ const url2 = safeUrl(video.url);
31522
+ return url2 ? [
31523
+ {
31524
+ url: url2,
31525
+ ...video.label ? {
31526
+ label: video.label.slice(0, 120)
31527
+ } : {},
31528
+ ...video.scriptRewritten === true ? {
31529
+ scriptRewritten: true
31530
+ } : {}
31531
+ }
31532
+ ] : [];
31533
+ });
31534
+ const links = media.links.flatMap((link) => {
31535
+ const url2 = safeUrl(link.url);
31536
+ return url2 ? [
31537
+ {
31538
+ url: url2,
31539
+ label: link.label?.slice(0, 120) ?? "Open result"
31540
+ }
31541
+ ] : [];
31542
+ });
31543
+ return {
31544
+ state: images.length || videos.length || links.length ? "ready" : "empty",
31545
+ images,
31546
+ videos,
31547
+ links
31548
+ };
31549
+ }
31550
+ __name(buildTaskResultsView, "buildTaskResultsView");
31551
+ function taskResultsToolMeta() {
31552
+ return {
31553
+ ui: {
31554
+ resourceUri: TASK_RESULTS_APP_RESOURCE_URI
31555
+ },
31556
+ "openai/outputTemplate": TASK_RESULTS_APP_RESOURCE_URI
31557
+ };
31558
+ }
31559
+ __name(taskResultsToolMeta, "taskResultsToolMeta");
31560
+ function renderTaskResultsAppHtml(proxyBaseUrl) {
31561
+ return readFileSync2(HTML_URL, "utf8").replace("__PRODUCTMAKER_ASSET_ORIGIN__", JSON.stringify(assetOrigin(proxyBaseUrl)).replaceAll("<", "\\u003c"));
31562
+ }
31563
+ __name(renderTaskResultsAppHtml, "renderTaskResultsAppHtml");
31564
+ function registerTaskResultsAppResource(server, proxyBaseUrl) {
31565
+ const origin = assetOrigin(proxyBaseUrl);
31566
+ const resourceDomains = origin ? [
31567
+ origin
31568
+ ] : [];
31569
+ const html = renderTaskResultsAppHtml(proxyBaseUrl);
31570
+ const meta = {
31571
+ ui: {
31572
+ csp: {
31573
+ resourceDomains,
31574
+ connectDomains: []
31575
+ }
31576
+ },
31577
+ "openai/widgetDomain": PRODUCTMAKER_APP_ORIGIN,
31578
+ "openai/widgetCSP": {
31579
+ resource_domains: resourceDomains,
31580
+ connect_domains: []
31581
+ },
31582
+ "openai/widgetDescription": "Displays the actual ProductMaker images and videos with playback controls and download links."
31583
+ };
31584
+ registerAppResource2(server, "ProductMaker task results", TASK_RESULTS_APP_RESOURCE_URI, {
31585
+ title: "Your creative",
31586
+ mimeType: RESOURCE_MIME_TYPE2,
31587
+ _meta: meta
31588
+ }, async () => ({
31589
+ contents: [
31590
+ {
31591
+ uri: TASK_RESULTS_APP_RESOURCE_URI,
31592
+ mimeType: RESOURCE_MIME_TYPE2,
31593
+ text: html,
31594
+ _meta: meta
31595
+ }
31596
+ ]
31597
+ }));
31598
+ }
31599
+ __name(registerTaskResultsAppResource, "registerTaskResultsAppResource");
31600
+
31601
+ // src/tools/tasks.ts
31602
+ import { z as z138 } from "zod";
31603
+
31293
31604
  // src/status-poller.ts
31294
31605
  var TERMINAL = /* @__PURE__ */ new Set([
31295
31606
  "done",
@@ -31366,7 +31677,6 @@ __name(buildPollingContinuation, "buildPollingContinuation");
31366
31677
  import { McpError, ErrorCode } from "@modelcontextprotocol/sdk/types.js";
31367
31678
 
31368
31679
  // src/resolve-media.ts
31369
- import { lookup as dnsLookup2 } from "dns/promises";
31370
31680
  import { promises as fs2, createReadStream as createReadStream2 } from "fs";
31371
31681
  import * as os2 from "os";
31372
31682
  import * as path2 from "path";
@@ -31503,32 +31813,6 @@ async function readFileWithCap2(absPath) {
31503
31813
  return Buffer.concat(chunks, total);
31504
31814
  }
31505
31815
  __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
31816
  async function resolvePath2(rawPath, expectedKind) {
31533
31817
  const absolutePath = path2.resolve(expandUserPath2(rawPath));
31534
31818
  const extension = path2.extname(absolutePath).toLowerCase();
@@ -31572,46 +31856,48 @@ async function resolveMediaRef(ref, opts) {
31572
31856
  return resolvePath2(ref.path, opts.expectedKind);
31573
31857
  }
31574
31858
  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}`);
31859
+ try {
31860
+ return await withPublicHttpsResponse(ref.url, 12e4, async (response, url2) => {
31861
+ const extension = path2.extname(url2.pathname).toLowerCase();
31862
+ const format = MEDIA_FORMATS.find((entry) => entry.exts.some((candidate) => candidate === extension));
31863
+ if (format && opts.expectedKind && format.kind !== opts.expectedKind) {
31864
+ throw new ResolveMediaError("MEDIA_TYPE_UNSUPPORTED", "media URL extension does not match the requested type");
31865
+ }
31866
+ if (!response.ok) throw new ResolveMediaError("MEDIA_URL_UNAVAILABLE", `media URL returned HTTP ${response.status}`);
31867
+ const contentLength = Number(response.headers.get("content-length") ?? 0);
31868
+ if (contentLength > MAX_MEDIA_BYTES) {
31869
+ void response.body?.cancel().catch(() => void 0);
31870
+ throw new ResolveMediaError("MEDIA_TOO_LARGE", `media URL exceeds ${MAX_MEDIA_BYTES} bytes`);
31871
+ }
31872
+ const buffer = await readResponseWithCap(response);
31873
+ const sniffed = sniffMime2(buffer, extension);
31874
+ if (!sniffed) throw new ResolveMediaError("MEDIA_TYPE_UNSUPPORTED", "media URL magic bytes are unsupported");
31875
+ const kind2 = sniffed.startsWith("video/") ? "video" : "image";
31876
+ if (opts.expectedKind && kind2 !== opts.expectedKind) {
31877
+ throw new ResolveMediaError("MEDIA_TYPE_UNSUPPORTED", `expected ${opts.expectedKind} but received ${kind2}`);
31878
+ }
31879
+ if (format && sniffed !== format.mime) {
31880
+ throw new ResolveMediaError("MEDIA_TYPE_UNSUPPORTED", `media URL magic bytes do not match ${format.mime}`);
31881
+ }
31882
+ const filename = path2.basename(url2.pathname) || `media.${kind2 === "video" ? "mp4" : "jpg"}`;
31883
+ return {
31884
+ kind: kind2,
31885
+ mimetype: sniffed,
31886
+ filename,
31887
+ buffer
31888
+ };
31889
+ });
31890
+ } catch (error) {
31891
+ if (error instanceof ResolveMediaError) throw error;
31892
+ throw new ResolveMediaError(error instanceof PublicHttpsError && error.code === "INVALID_INPUT" ? "INVALID_INPUT" : "MEDIA_URL_UNAVAILABLE", error instanceof Error ? error.message : "media download failed");
31600
31893
  }
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
31894
  }
31609
31895
  __name(resolveMediaRef, "resolveMediaRef");
31610
31896
 
31611
31897
  // 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);
31898
+ import { z as z133 } from "zod";
31899
+ 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);
31900
+ var Country = z133.string().min(2).max(3);
31615
31901
  var LandingStrategyEnum = LandingStrategySchema;
31616
31902
  var LandingImageFormatEnum = LandingImageFormatSchema;
31617
31903
  var VideoPresetEnum = VideoPreset.extract([
@@ -31715,7 +32001,7 @@ function animationStyleApplies(videoPreset, angleNarratives) {
31715
32001
  return ANIMATED_VIDEO_PRESETS.has(videoPreset ?? "") || (angleNarratives ?? []).some((n) => ANIMATED_NARRATIVE_STYLES.has(n));
31716
32002
  }
31717
32003
  __name(animationStyleApplies, "animationStyleApplies");
31718
- var VideoTierEnum = z132.enum([
32004
+ var VideoTierEnum = z133.enum([
31719
32005
  "basic"
31720
32006
  ]);
31721
32007
  function resolveVideoTier(_tier) {
@@ -31773,10 +32059,10 @@ var ImageCreativeStyleEnum = ImageCreativeStyle;
31773
32059
  var ImageCreativeArchetypeEnum = ImageCreativeArchetype;
31774
32060
  var ImageCreativeVisualTreatmentEnum = ImageCreativeVisualTreatment;
31775
32061
  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.")
32062
+ var ImageRefSchema = z133.object({
32063
+ 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.'),
32064
+ 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`.'),
32065
+ 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
32066
  }).refine((v) => [
31781
32067
  v.url,
31782
32068
  v.path,
@@ -31784,11 +32070,11 @@ var ImageRefSchema = z132.object({
31784
32070
  ].filter(Boolean).length === 1, {
31785
32071
  message: "each image entry must have exactly one of `url`, `path`, or `assetId`"
31786
32072
  });
31787
- var ImagesInput = z132.array(ImageRefSchema).min(1).max(5).superRefine((images, ctx) => {
32073
+ var ImagesInput = z133.array(ImageRefSchema).min(1).max(5).superRefine((images, ctx) => {
31788
32074
  const assetCount = images.filter((image2) => Boolean(image2.assetId)).length;
31789
32075
  if (assetCount > 0 && assetCount !== images.length) {
31790
32076
  ctx.addIssue({
31791
- code: z132.ZodIssueCode.custom,
32077
+ code: z133.ZodIssueCode.custom,
31792
32078
  message: "all image entries must use assetId, or all must use URL/path sources"
31793
32079
  });
31794
32080
  }
@@ -31869,14 +32155,14 @@ async function submitCreative(api, opts) {
31869
32155
  });
31870
32156
  }
31871
32157
  __name(submitCreative, "submitCreative");
31872
- var ResearchTargetCountryEnum = z132.enum([
32158
+ var ResearchTargetCountryEnum = z133.enum([
31873
32159
  "CO",
31874
32160
  "MX",
31875
32161
  "PE",
31876
32162
  "EC",
31877
32163
  "CL"
31878
32164
  ]);
31879
- var ResearchCategoryEnum = z132.enum([
32165
+ var ResearchCategoryEnum = z133.enum([
31880
32166
  "auto",
31881
32167
  "automotive",
31882
32168
  "kitchen",
@@ -32671,7 +32957,7 @@ function approveRefusalMessage(reason) {
32671
32957
  __name(approveRefusalMessage, "approveRefusalMessage");
32672
32958
 
32673
32959
  // src/tools/task-outputs.ts
32674
- import { z as z133 } from "zod";
32960
+ import { z as z134 } from "zod";
32675
32961
 
32676
32962
  // src/tools/conversational-review-format.ts
32677
32963
  function escapeMarkdownText(value) {
@@ -32802,12 +33088,12 @@ function serviceVideoResultMedia(result) {
32802
33088
  };
32803
33089
  }
32804
33090
  __name(serviceVideoResultMedia, "serviceVideoResultMedia");
32805
- var CreativeVariantIdentity = z133.object({
32806
- variantTaskId: z133.string().uuid(),
32807
- sourceTaskId: z133.string().uuid(),
33091
+ var CreativeVariantIdentity = z134.object({
33092
+ variantTaskId: z134.string().uuid(),
33093
+ sourceTaskId: z134.string().uuid(),
32808
33094
  kind: TaskCreativeVariantKind,
32809
33095
  status: TaskStatusSchema,
32810
- error: z133.string().optional()
33096
+ error: z134.string().optional()
32811
33097
  });
32812
33098
  function proxifyOutputs(outputs, proxyBaseUrl) {
32813
33099
  if (!proxyBaseUrl || !isProxyEnabled()) return outputs;
@@ -33411,29 +33697,29 @@ ${parsed.data.error ? `${escapeMarkdownText(parsed.data.error)}
33411
33697
  __name(renderVideoScriptMarkdown, "renderVideoScriptMarkdown");
33412
33698
 
33413
33699
  // 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:");
33700
+ import { z as z135 } from "zod";
33701
+ var HttpsPresentationUrlSchema = z135.string().url().max(2048).refine((value) => new URL(value).protocol === "https:");
33416
33702
  var ProductMakerPreviewUrlSchema = HttpsPresentationUrlSchema.refine((value) => {
33417
33703
  const hostname = new URL(value).hostname.toLowerCase();
33418
33704
  return hostname === "productmaker.app" || hostname.endsWith(".productmaker.app");
33419
33705
  }, "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"),
33706
+ var positiveIntAtMost = /* @__PURE__ */ __name((maximum) => z135.number().int().positive().max(maximum), "positiveIntAtMost");
33707
+ var PresentationImageSchema = z135.object({
33708
+ kind: z135.literal("image"),
33423
33709
  url: HttpsPresentationUrlSchema,
33424
- mimeType: z134.enum([
33710
+ mimeType: z135.enum([
33425
33711
  "image/jpeg",
33426
33712
  "image/png"
33427
33713
  ]),
33428
33714
  byteSize: positiveIntAtMost(5 * 1024 * 1024),
33429
33715
  width: positiveIntAtMost(16384),
33430
33716
  height: positiveIntAtMost(16384),
33431
- alt: z134.string().trim().min(1).max(240)
33717
+ alt: z135.string().trim().min(1).max(240)
33432
33718
  }).strict();
33433
33719
  var McpMediaPresentationSchema = PresentationImageSchema;
33434
- var GalleryImageSchema = z134.object({
33720
+ var GalleryImageSchema = z135.object({
33435
33721
  url: ProductMakerPreviewUrlSchema,
33436
- alt: z134.string().trim().min(1).max(240)
33722
+ alt: z135.string().trim().min(1).max(240)
33437
33723
  });
33438
33724
  var MCP_GALLERY_IMAGE_MAX_BYTES = 10 * 1024 * 1024;
33439
33725
  async function imageBlocks(image2, transport) {
@@ -33490,12 +33776,12 @@ async function buildMcpMediaPresentationContent(rawPresentation, transport = "ht
33490
33776
  ];
33491
33777
  }
33492
33778
  __name(buildMcpMediaPresentationContent, "buildMcpMediaPresentationContent");
33493
- var OwnedImagePreviewSchema = z134.object({
33779
+ var OwnedImagePreviewSchema = z135.object({
33494
33780
  url: HttpsPresentationUrlSchema.refine((value) => {
33495
33781
  const url2 = new URL(value);
33496
33782
  return !url2.username && !url2.password && !isPrivateHost(url2.hostname);
33497
33783
  }, "Preview URL must be public HTTPS without credentials"),
33498
- alt: z134.string().trim().min(1).max(240)
33784
+ alt: z135.string().trim().min(1).max(240)
33499
33785
  });
33500
33786
  async function buildMcpImagePreviewContent(rawPreview, transport = "http") {
33501
33787
  const preview2 = OwnedImagePreviewSchema.safeParse(rawPreview);
@@ -33690,7 +33976,7 @@ async function observeMcpMediaDelivery(fingerprint, work) {
33690
33976
  __name(observeMcpMediaDelivery, "observeMcpMediaDelivery");
33691
33977
 
33692
33978
  // src/tools/animated-video.ts
33693
- import { z as z135 } from "zod";
33979
+ import { z as z136 } from "zod";
33694
33980
 
33695
33981
  // src/tools/paid-confirmation.ts
33696
33982
  function describeMarketStudyChoice(enabled) {
@@ -33939,30 +34225,30 @@ function hookVariantsLabel(count) {
33939
34225
  return count === 1 ? "1 versi\xF3n del gancho" : `${count} versiones del gancho`;
33940
34226
  }
33941
34227
  __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([
34228
+ var DurationInput = z136.union(MOTION_GRAPHICS_DURATIONS.map((d) => z136.literal(d)));
34229
+ var CreateAnimatedVideoInput = z136.object({
34230
+ action: z136.enum([
33945
34231
  "prepare",
33946
34232
  "status",
33947
34233
  "cancel",
33948
34234
  "generate"
33949
34235
  ]).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."),
34236
+ 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."),
34237
+ websiteCaptureId: z136.string().uuid().optional().describe("ID de la captura preparada. Obligatorio para status/cancel/generate; no inventarlo."),
34238
+ 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."),
34239
+ 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
34240
  // Plain text, not `.url()`: users type «cal.com», and the web accepts it.
33955
34241
  // normalizeWebAddress (shared with the web) adds https:// and refuses
33956
34242
  // 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."),
34243
+ 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
34244
  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
34245
  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
34246
  language: MotionGraphicsLanguageSchema.optional().describe('Idioma de los textos del video: "es", "en" o "pt".'),
33961
34247
  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
34248
  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."),
34249
+ 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
34250
  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.")
34251
+ 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
34252
  }).strict();
33967
34253
  function captureNextAction(capture) {
33968
34254
  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 +34301,33 @@ var FORMAT_LABELS = {
34015
34301
  square: "cuadrado 1:1",
34016
34302
  horizontal: "horizontal 16:9"
34017
34303
  };
34018
- var AnimatedVideoResultSchema = z135.object({
34019
- status: z135.enum([
34304
+ var AnimatedVideoResultSchema = z136.object({
34305
+ status: z136.enum([
34020
34306
  "pending",
34021
34307
  "done",
34022
34308
  "error"
34023
34309
  ]),
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({
34310
+ serviceMode: z136.literal("motion_graphics"),
34311
+ finalMp4Url: z136.string().optional(),
34312
+ adCopy: z136.string().optional(),
34313
+ adCopies: z136.array(z136.string()).optional(),
34314
+ error: z136.string().optional(),
34315
+ motionGraphics: z136.object({
34030
34316
  stage: MotionGraphicsStageSchema,
34031
34317
  format: MotionGraphicsFormatSchema,
34032
- durationSec: z135.number(),
34033
- width: z135.number().optional(),
34034
- height: z135.number().optional(),
34318
+ durationSec: z136.number(),
34319
+ width: z136.number().optional(),
34320
+ height: z136.number().optional(),
34035
34321
  language: MotionGraphicsLanguageSchema.optional(),
34036
34322
  videoQuality: MotionGraphicsVideoQualitySchema.optional().catch(void 0),
34037
34323
  videoResolution: MotionGraphicsVideoResolutionSchema.optional().catch(void 0),
34038
- creativeBatch: z135.object({
34324
+ creativeBatch: z136.object({
34039
34325
  index: MotionGraphicsHookVariantsSchema,
34040
34326
  count: MotionGraphicsHookVariantsSchema,
34041
- tasks: z135.array(z135.object({
34042
- taskId: z135.string().uuid(),
34327
+ tasks: z136.array(z136.object({
34328
+ taskId: z136.string().uuid(),
34043
34329
  index: MotionGraphicsHookVariantsSchema,
34044
- status: z135.enum([
34330
+ status: z136.enum([
34045
34331
  "queued",
34046
34332
  "processing",
34047
34333
  "done",
@@ -34049,29 +34335,29 @@ var AnimatedVideoResultSchema = z135.object({
34049
34335
  ])
34050
34336
  })).max(MOTION_GRAPHICS_HOOK_VARIANTS_MAX)
34051
34337
  }).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(),
34338
+ queuedAt: z136.string().optional(),
34339
+ jobsAhead: z136.number().int().nonnegative().optional(),
34340
+ stageStartedAt: z136.string().optional(),
34341
+ renderProgress: z136.number().optional(),
34056
34342
  // AI clip fields (absent on a video paid before clips, or from an API
34057
34343
  // that predates them). Lenient: an unreadable value is dropped
34058
34344
  // 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),
34345
+ clipCount: z136.number().int().nonnegative().optional().catch(void 0),
34346
+ clipsUsed: z136.number().int().nonnegative().optional().catch(void 0),
34347
+ clipPriceCents: z136.number().finite().nonnegative().optional().catch(void 0),
34348
+ clipsRefunded: z136.number().int().nonnegative().optional().catch(void 0),
34063
34349
  // Hook versions (absent from an API or a video before them): the
34064
34350
  // count ordered and, once done, the signed URLs of the EXTRA versions
34065
34351
  // (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),
34352
+ hookVariants: z136.number().int().min(1).max(MOTION_GRAPHICS_HOOK_VARIANTS_MAX).optional().catch(void 0),
34353
+ cleanVideoUrl: z136.string().url().optional().catch(void 0),
34354
+ cleanVariantUrls: z136.array(z136.string().url()).max(MOTION_GRAPHICS_HOOK_VARIANTS_MAX - 1).optional().catch(void 0),
34355
+ variantUrls: z136.array(z136.string().min(1)).max(MOTION_GRAPHICS_HOOK_VARIANTS_MAX - 1).optional().catch(void 0),
34070
34356
  // Extra hook versions given back, as the task recorded it once the
34071
34357
  // 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()
34358
+ variantsRefunded: z136.number().int().nonnegative().optional().catch(void 0),
34359
+ activity: z136.array(MotionGraphicsActivitySchema).optional().catch(void 0),
34360
+ previewUrl: z136.string().optional()
34075
34361
  }).passthrough()
34076
34362
  }).passthrough();
34077
34363
  function parseAnimatedVideoResult(value) {
@@ -34350,7 +34636,7 @@ function registerAnimatedVideoTools(server, api) {
34350
34636
  nowMs: 0
34351
34637
  })
34352
34638
  });
34353
- const parsedTaskIds = z135.array(z135.string().uuid()).min(1).max(MOTION_GRAPHICS_HOOK_VARIANTS_MAX).safeParse(created.taskIds);
34639
+ const parsedTaskIds = z136.array(z136.string().uuid()).min(1).max(MOTION_GRAPHICS_HOOK_VARIANTS_MAX).safeParse(created.taskIds);
34354
34640
  const taskIds = parsedTaskIds.success && parsedTaskIds.data[0] === created.taskId ? parsedTaskIds.data : void 0;
34355
34641
  const receipt = {
34356
34642
  taskId: created.taskId,
@@ -34392,10 +34678,10 @@ function registerAnimatedVideoTools(server, api) {
34392
34678
  __name(registerAnimatedVideoTools, "registerAnimatedVideoTools");
34393
34679
 
34394
34680
  // 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)
34681
+ import { z as z137 } from "zod";
34682
+ var WinnerReferenceInput = z137.object({
34683
+ runId: z137.string().uuid(),
34684
+ candidateSourceId: z137.string().trim().min(1).max(160)
34399
34685
  }).strict().describe("Referencia opaca de un producto devuelto por find_winning_products/get_product_research_run.");
34400
34686
  async function resolveWinnerImages(api, winnerReference, images) {
34401
34687
  if (!winnerReference) return images;
@@ -34432,62 +34718,62 @@ var createProductTaskSingleOutputDescription = renderCreateProductTaskAvoidWhenF
34432
34718
  var CREATE_SERVICE_VIDEO_ON_MCP = PRODUCTMAKER_MCP_CAPABILITY_NAMES.includes("create_service_video");
34433
34719
  var listTasksGuidance = getProductMakerCapabilityModelGuidance("list_tasks");
34434
34720
  var editTaskDraftGuidance = getProductMakerCapabilityModelGuidance("edit_task_draft");
34435
- var CreateProductTaskInput = z137.object({
34721
+ var CreateProductTaskInput = z138.object({
34436
34722
  images: ImagesInput.optional().describe("1\u20135 fotos del mismo producto. Omitir \xFAnicamente cuando projectId apunta a un proyecto propio con imagen guardada."),
34437
34723
  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."),
34724
+ 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."),
34725
+ 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
34726
  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
34727
  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.'),
34728
+ 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.'),
34729
+ 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".'),
34730
+ 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.'),
34731
+ 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.'),
34732
+ 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."),
34733
+ 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
34734
  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
34735
  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)
34736
+ durationSeconds: z138.union([
34737
+ z138.literal(15),
34738
+ z138.literal(30)
34453
34739
  ]).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."),
34740
+ 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."),
34741
+ 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
34742
  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
34743
  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
34744
  videoQuality: VideoQualityEnum.default("economy").describe('Opcional. Calidad: "economy" = Econ\xF3mico, "medium" = Medio o "pro" = Pro. El proveedor se elige internamente. Default: economy.'),
34459
34745
  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."),
34746
+ 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
34747
  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([
34748
+ outputs: z138.array(z138.enum([
34463
34749
  "landing",
34464
34750
  "video",
34465
34751
  "image"
34466
34752
  ])).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."),
34753
+ 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."),
34754
+ 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
34755
  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."),
34756
+ 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."),
34757
+ 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."),
34758
+ 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
34759
  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."),
34760
+ 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.'),
34761
+ 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."),
34762
+ 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
34763
  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
34764
  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
34765
  }).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.'),
34766
+ var CreateServiceVideoInput = z138.object({
34767
+ 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."),
34768
+ 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
34769
  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([
34770
+ 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.'),
34771
+ brandId: z138.string().uuid().optional().describe("Marca guardada del usuario. Aplica voz, mensajes, paleta y persona predeterminada."),
34772
+ reviewMode: z138.enum([
34487
34773
  "automatic",
34488
34774
  "guided"
34489
34775
  ]).default("automatic").describe("automatic = la IA elige concepto y guion sin pausas; guided = el usuario aprueba concepto y storyboard."),
34490
- videoPreset: z137.enum([
34776
+ videoPreset: z138.enum([
34491
34777
  "auto",
34492
34778
  "ugc_dynamic",
34493
34779
  "ugc_how_to",
@@ -34497,128 +34783,128 @@ var CreateServiceVideoInput = z137.object({
34497
34783
  videoQuality: VideoQualityEnum.default("economy").describe('Opcional. Calidad: "economy" = Econ\xF3mico, "medium" = Medio o "pro" = Pro. El proveedor se elige internamente. Default: economy.'),
34498
34784
  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
34785
  }).strict();
34500
- var GetTaskStatusInput = z137.object({
34501
- taskId: z137.string().min(1),
34502
- waitSeconds: z137.number().int().min(0).max(120).optional()
34786
+ var GetTaskStatusInput = z138.object({
34787
+ taskId: z138.string().min(1),
34788
+ waitSeconds: z138.number().int().min(0).max(120).optional()
34503
34789
  });
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()
34790
+ var ListTasksInput = z138.object({
34791
+ limit: z138.number().int().min(1).max(50).optional(),
34792
+ offset: z138.number().int().min(0).optional(),
34793
+ search: z138.string().optional()
34508
34794
  });
34509
34795
  var MCP_REVIEW_LIST_FIELD_MAX = IMAGE_WIZARD_LIST_FIELD_MAX;
34510
34796
  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([
34797
+ var EditTaskDraftInput = z138.object({
34798
+ taskId: z138.string().min(1),
34799
+ angleIndex: z138.number().int().min(0),
34800
+ angle: z138.object({
34801
+ angle: z138.string().min(1).max(480),
34802
+ buyerPersona: z138.string().min(1).max(360),
34803
+ actor: z138.string().min(1).max(360).optional(),
34804
+ 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."),
34805
+ gender: z138.enum([
34520
34806
  "female",
34521
34807
  "male"
34522
34808
  ]).optional(),
34523
- scriptNotes: z137.string().max(2e3).optional()
34809
+ scriptNotes: z138.string().max(2e3).optional()
34524
34810
  })
34525
34811
  });
34526
- var PendingActionPayload = z137.discriminatedUnion("kind", [
34527
- z137.object({
34528
- kind: z137.literal("product_reference_set_approval"),
34812
+ var PendingActionPayload = z138.discriminatedUnion("kind", [
34813
+ z138.object({
34814
+ kind: z138.literal("product_reference_set_approval"),
34529
34815
  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(),
34816
+ scopeKey: z138.string().min(1).max(120).optional(),
34817
+ scopeSnapshotId: z138.string().uuid().optional(),
34818
+ expectedScopeHash: z138.string().min(1).max(128).optional(),
34533
34819
  decision: ProductReferenceResolutionDecisionEnum,
34534
34820
  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([
34821
+ affectedRefKeys: z138.array(z138.string().min(1).max(200)).min(1).optional(),
34822
+ affectedPhotoNumbers: z138.array(z138.number().int().positive()).min(1).optional(),
34823
+ recoveryAction: z138.enum([
34538
34824
  "remove",
34539
34825
  "replace",
34540
34826
  "upload",
34541
34827
  "return_to_selection"
34542
34828
  ]).optional()
34543
34829
  }),
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."),
34830
+ z138.object({
34831
+ kind: z138.literal("product_reference_confirmation"),
34832
+ 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
34833
  userChoice: ProductReferenceUserChoiceEnum.optional(),
34548
- confirmedProductNames: z137.array(z137.string().min(1).max(120)).min(2).max(8).optional(),
34834
+ confirmedProductNames: z138.array(z138.string().min(1).max(120)).min(2).max(8).optional(),
34549
34835
  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
34836
  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.")
34837
+ useMultiSceneShowcase: z138.boolean().optional().describe("Compatibilidad con confirmaciones antiguas: si es true junto con userChoice=variants, conserva el showcase multiescena.")
34552
34838
  }),
34553
- z137.object({
34554
- kind: z137.literal("angle_review_approval"),
34839
+ z138.object({
34840
+ kind: z138.literal("angle_review_approval"),
34555
34841
  // All edits are optional — empty action body just approves the
34556
34842
  // backend-generated draft as-is. Field shapes mirror the angle-review
34557
34843
  // request contracts in @packages/types/wizard.ts; the handler routes to
34558
34844
  // the dedicated video/image/autopilot endpoint based on StatusResponse.kind.
34559
- customAngle: z137.string().min(1).max(480).optional(),
34845
+ customAngle: z138.string().min(1).max(480).optional(),
34560
34846
  // Keep the shared MCP action within the strictest downstream contract:
34561
34847
  // 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(),
34848
+ painPoints: z138.string().max(MCP_REVIEW_LIST_FIELD_MAX).optional(),
34849
+ benefits: z138.string().max(MCP_REVIEW_LIST_FIELD_MAX).optional(),
34564
34850
  // Same policy as the two fields above: the strictest downstream contract
34565
34851
  // wins. The image endpoint caps each feature at 240 while landing/video
34566
34852
  // accept 300, and this single action routes to whichever the task is — so
34567
34853
  // accepting 300 here turns a 250-character feature on an image task into
34568
34854
  // 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(),
34855
+ features: z138.array(z138.string().min(1).max(MCP_REVIEW_FEATURE_ITEM_MAX)).max(10).optional(),
34856
+ buyerPersona: z138.string().min(1).max(360).optional(),
34571
34857
  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([
34858
+ 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."),
34859
+ actorId: z138.string().max(120).nullable().optional(),
34860
+ creativeDirection: z138.string().max(2e3).optional(),
34861
+ 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."),
34862
+ angleEdits: z138.array(z138.object({
34863
+ angleNumber: z138.number().int().positive(),
34864
+ narrativeSelection: z138.enum([
34579
34865
  "market",
34580
34866
  "guided"
34581
34867
  ]).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(),
34868
+ angle: z138.string().min(1).max(480).optional(),
34869
+ buyerPersona: z138.string().min(1).max(360).optional(),
34584
34870
  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()
34871
+ actor: z138.string().min(1).max(360).optional(),
34872
+ actorId: z138.string().max(120).nullable().optional(),
34873
+ creativeDirection: z138.string().max(2e3).optional()
34588
34874
  }).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)
34875
+ gapDurationSeconds: z138.union([
34876
+ z138.literal(15),
34877
+ z138.literal(30)
34592
34878
  ]).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
34879
  }),
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()
34880
+ z138.object({
34881
+ kind: z138.literal("service_angle_approval"),
34882
+ angleId: z138.string().uuid().optional(),
34883
+ angleNumber: z138.number().int().positive().optional(),
34884
+ content: z138.string().min(1).max(480).optional(),
34885
+ creativeDirection: z138.string().min(1).max(1500).optional(),
34886
+ actor: z138.string().min(1).max(360).optional()
34601
34887
  }),
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()
34888
+ z138.object({
34889
+ kind: z138.literal("service_script_approval"),
34890
+ voiceovers: z138.record(z138.string(), z138.string().min(1).max(600)).optional(),
34891
+ visualDirections: z138.record(z138.string(), z138.string().min(1).max(1200)).optional(),
34892
+ voiceoversByScene: z138.record(z138.string(), z138.string().min(1).max(600)).optional(),
34893
+ visualDirectionsByScene: z138.record(z138.string(), z138.string().min(1).max(1200)).optional()
34608
34894
  })
34609
34895
  ]);
34610
- var ResolvePendingActionInput = z137.object({
34611
- taskId: z137.string().min(1),
34896
+ var ResolvePendingActionInput = z138.object({
34897
+ taskId: z138.string().min(1),
34612
34898
  action: PendingActionPayload
34613
34899
  }).strict();
34614
34900
  function resolveVariantConfirmVideoTier(action) {
34615
34901
  return action.useMultiSceneShowcase && action.userChoice === "variants" ? "standard" : void 0;
34616
34902
  }
34617
34903
  __name(resolveVariantConfirmVideoTier, "resolveVariantConfirmVideoTier");
34618
- var StatusForResolveSchema = z137.object({
34904
+ var StatusForResolveSchema = z138.object({
34619
34905
  // Metadata used to select the backend resume contract. Older API responses
34620
34906
  // may omit these fields, so the resolver keeps the generic fallback.
34621
- kind: z137.enum([
34907
+ kind: z138.enum([
34622
34908
  "autopilot",
34623
34909
  "video",
34624
34910
  "image",
@@ -34626,18 +34912,18 @@ var StatusForResolveSchema = z137.object({
34626
34912
  "mix",
34627
34913
  "service"
34628
34914
  ]).optional(),
34629
- selectedOutputs: z137.array(z137.enum([
34915
+ selectedOutputs: z138.array(z138.enum([
34630
34916
  "landing",
34631
34917
  "video",
34632
34918
  "image"
34633
34919
  ])).optional(),
34634
- videoAngleCount: z137.number().int().min(1).max(3).optional(),
34635
- ugcFormat: z137.enum([
34920
+ videoAngleCount: z138.number().int().min(1).max(3).optional(),
34921
+ ugcFormat: z138.enum([
34636
34922
  "single_8s",
34637
34923
  "single_15s",
34638
34924
  "double_30s"
34639
34925
  ]).optional(),
34640
- videoStyle: z137.enum([
34926
+ videoStyle: z138.enum([
34641
34927
  "ugc",
34642
34928
  "cartoon_3d",
34643
34929
  "product_only",
@@ -34647,13 +34933,13 @@ var StatusForResolveSchema = z137.object({
34647
34933
  videoQuality: VideoQualityEnum.optional(),
34648
34934
  videoRouteId: VideoRouteIdEnum.optional(),
34649
34935
  videoModel: TaskVideoModelEnum.optional(),
34650
- videoTier: z137.enum([
34936
+ videoTier: z138.enum([
34651
34937
  "legacy",
34652
34938
  "standard",
34653
34939
  "pro",
34654
34940
  "single_call"
34655
34941
  ]).optional(),
34656
- videoResolution: z137.enum([
34942
+ videoResolution: z138.enum([
34657
34943
  "480p",
34658
34944
  "720p"
34659
34945
  ]).optional(),
@@ -34661,60 +34947,60 @@ var StatusForResolveSchema = z137.object({
34661
34947
  // A task created before a narrative was blocked/hidden must still parse,
34662
34948
  // or resolve_pending_action could never approve it.
34663
34949
  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(),
34950
+ pauseForVideoScriptReview: z138.boolean().optional(),
34951
+ pendingUserAction: z138.object({
34952
+ kind: z138.string(),
34953
+ candidateGrouping: z138.string().optional(),
34954
+ selectedImageUrls: z138.array(z138.string()).optional(),
34955
+ proposedProductNames: z138.array(z138.string()).optional(),
34670
34956
  // Adaptive reference questions are server-owned. Echo only the evidence
34671
34957
  // identity and the choices shown by the current snapshot; never accept a
34672
34958
  // client-supplied reference set as authorization.
34673
- options: z137.array(ProductReferenceUserChoiceEnum).max(4).optional(),
34674
- mode: z137.enum([
34959
+ options: z138.array(ProductReferenceUserChoiceEnum).max(4).optional(),
34960
+ mode: z138.enum([
34675
34961
  "question",
34676
34962
  "visual_recovery"
34677
34963
  ]).optional(),
34678
34964
  /** 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)
34965
+ canKeepSelection: z138.boolean().optional(),
34966
+ decisionKey: z138.string().optional(),
34967
+ questionOrdinal: z138.number().int().min(1).max(5).optional(),
34968
+ questionCeiling: z138.union([
34969
+ z138.literal(3),
34970
+ z138.literal(5)
34685
34971
  ]).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({
34972
+ referenceEvidenceVersion: z138.number().int().positive().optional(),
34973
+ expectedEvidenceHash: z138.string().optional(),
34974
+ question: z138.object({
34975
+ prompt: z138.string().min(1).max(240),
34976
+ helpText: z138.string().min(1).max(300).optional(),
34977
+ options: z138.array(z138.object({
34692
34978
  value: ProductReferenceUserChoiceEnum,
34693
- label: z137.string().min(1).max(120),
34694
- description: z137.string().min(1).max(240).optional()
34979
+ label: z138.string().min(1).max(120),
34980
+ description: z138.string().min(1).max(240).optional()
34695
34981
  })).min(2).max(4)
34696
34982
  }).optional(),
34697
34983
  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([
34984
+ scopeKey: z138.string().optional(),
34985
+ scopeSnapshotId: z138.string().uuid().optional(),
34986
+ scopeHash: z138.string().optional(),
34987
+ baseReferenceSetHash: z138.string().optional(),
34988
+ phase: z138.enum([
34703
34989
  "preparation",
34704
34990
  "final"
34705
34991
  ]).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)
34992
+ selectedRefKeys: z138.array(z138.string().min(1).max(200)).optional(),
34993
+ allowedDecisions: z138.array(ProductReferenceResolutionDecisionEnum).optional(),
34994
+ previews: z138.array(z138.object({
34995
+ refKey: z138.string().min(1).max(200)
34710
34996
  }).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)
34997
+ customAngleDraft: z138.string().optional(),
34998
+ painPointsDraft: z138.string().optional(),
34999
+ benefitsDraft: z138.string().optional(),
35000
+ featuresDraft: z138.array(z138.string()).optional(),
35001
+ angleOptions: z138.array(z138.object({
35002
+ index: z138.number().int().nonnegative(),
35003
+ angle: z138.string().min(1)
34718
35004
  }).passthrough()).min(1).max(5).optional(),
34719
35005
  // Recommendation consumed only when the caller explicitly sends
34720
35006
  // gapDurationSeconds: 30 in the angle approval action.
@@ -34724,17 +35010,17 @@ var StatusForResolveSchema = z137.object({
34724
35010
  // keys. `actorId` (the saved custom actor the picker stored) was silently
34725
35011
  // dropped here, so the approval handler below could not echo it back on the
34726
35012
  // 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()
35013
+ angles: z138.array(z138.object({
35014
+ id: z138.string().uuid(),
35015
+ content: z138.string(),
35016
+ creativeDirection: z138.string(),
35017
+ actor: z138.string(),
35018
+ actorId: z138.string().nullable().optional()
34733
35019
  }).passthrough()).optional(),
34734
- panels: z137.array(z137.object({
34735
- beatId: z137.string(),
34736
- voiceover: z137.string(),
34737
- visualDirection: z137.string()
35020
+ panels: z138.array(z138.object({
35021
+ beatId: z138.string(),
35022
+ voiceover: z138.string(),
35023
+ visualDirection: z138.string()
34738
35024
  }).passthrough()).optional()
34739
35025
  }).optional()
34740
35026
  }).passthrough();
@@ -35087,8 +35373,8 @@ ANTES de invocar, resume outputs, cantidad de videos y datos comerciales, explic
35087
35373
  server.registerTool("verify_landing_commerce", {
35088
35374
  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
35375
  inputSchema: {
35090
- taskId: z137.string().uuid(),
35091
- editId: z137.string().uuid()
35376
+ taskId: z138.string().uuid(),
35377
+ editId: z138.string().uuid()
35092
35378
  },
35093
35379
  annotations: OVERWRITES_EXTERNAL
35094
35380
  }, async (input) => {
@@ -35359,16 +35645,16 @@ ANTES de invocar, resume outputs, cantidad de videos y datos comerciales, explic
35359
35645
  if (input.search) qs.set("search", input.search);
35360
35646
  const path3 = `/v1/tasks${qs.toString() ? "?" + qs.toString() : ""}`;
35361
35647
  const r = await api.getJson(path3);
35362
- const page = z137.object({
35363
- items: z137.array(z137.unknown())
35648
+ const page = z138.object({
35649
+ items: z138.array(z138.unknown())
35364
35650
  }).passthrough().safeParse(r);
35365
35651
  if (!page.success) return asText(r);
35366
35652
  return asText({
35367
35653
  ...page.data,
35368
35654
  items: page.data.items.map((item) => {
35369
- const servicePage = z137.object({
35370
- taskId: z137.string().uuid(),
35371
- resourceType: z137.literal("service_page")
35655
+ const servicePage = z138.object({
35656
+ taskId: z138.string().uuid(),
35657
+ resourceType: z138.literal("service_page")
35372
35658
  }).passthrough().safeParse(item);
35373
35659
  if (!servicePage.success) return item;
35374
35660
  return {
@@ -35858,28 +36144,28 @@ ANTES de invocar, resume outputs, cantidad de videos y datos comerciales, explic
35858
36144
  __name(registerTaskTools, "registerTaskTools");
35859
36145
 
35860
36146
  // src/tools/brands.ts
35861
- import { z as z138 } from "zod";
36147
+ import { z as z139 } from "zod";
35862
36148
  var guidance2 = getProductMakerCapabilityModelGuidance("create_brand");
35863
36149
  var BRAND_START_REPLAY_WINDOW_MS = 15 * 60 * 1e3;
35864
36150
  var BRAND_NOTES_MAX_CHARS = 2e4;
35865
36151
  var BRAND_AMBIGUOUS_REQUEST_RETENTION_MS = 24 * 60 * 60 * 1e3;
35866
36152
  var BRAND_CREATION_REQUEST_LIMIT = 500;
35867
36153
  var brandCreationRequests = /* @__PURE__ */ new Map();
35868
- var CreateBrandInput = z138.object({
35869
- action: z138.enum([
36154
+ var CreateBrandInput = z139.object({
36155
+ action: z139.enum([
35870
36156
  "start",
35871
36157
  "status",
35872
36158
  "list"
35873
36159
  ]).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([
36160
+ 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."),
36161
+ 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."),
36162
+ language: z139.enum([
35877
36163
  "es",
35878
36164
  "en",
35879
36165
  "pt"
35880
36166
  ]).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.")
36167
+ 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."),
36168
+ brandId: z139.string().uuid().optional().describe("ID de la marca. Obligatorio para status; no lo inventes.")
35883
36169
  }).strict();
35884
36170
  function normalizeBrandSourceUrl(raw) {
35885
36171
  const value = raw.trim();
@@ -36163,24 +36449,24 @@ __name(registerBrandTools, "registerBrandTools");
36163
36449
 
36164
36450
  // src/tools/service-landing.ts
36165
36451
  import { createHash as createHash4, randomUUID } from "crypto";
36166
- import { z as z139 } from "zod";
36452
+ import { z as z140 } from "zod";
36167
36453
  var guidance3 = getProductMakerCapabilityModelGuidance("create_service_landing");
36168
36454
  var SERVICE_LANDING_KEY_NAMESPACE = "6f1d8c2a-3b4e-5f60-8a7b-9c0d1e2f3a4b";
36169
36455
  var SERVICE_LANDING_INFO_MAX_CHARS = 2e4;
36170
- var CreateServiceLandingInput = z139.object({
36171
- action: z139.enum([
36456
+ var CreateServiceLandingInput = z140.object({
36457
+ action: z140.enum([
36172
36458
  "prepare",
36173
36459
  "build",
36174
36460
  "status",
36175
36461
  "publish"
36176
36462
  ]).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.")
36463
+ brandId: z140.string().uuid().optional().describe("ID de la marca (de create_brand). Obligatorio para prepare. Nunca un taskId de producto."),
36464
+ servicePageId: z140.string().uuid().optional().describe("ID de la landing que devolvi\xF3 prepare. Obligatorio para build, status y publish."),
36465
+ 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."),
36466
+ confirmed: z140.boolean().optional().describe("Solo true despu\xE9s de mostrar el precio total y que el usuario acepte. Obligatorio para build."),
36467
+ 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."),
36468
+ operationId: z140.string().uuid().optional().describe("Opcional en status: la operaci\xF3n que devolvi\xF3 build."),
36469
+ 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
36470
  }).strict();
36185
36471
  function serviceKey(...parts) {
36186
36472
  return deriveUuidV5(parts.join(":"), SERVICE_LANDING_KEY_NAMESPACE);
@@ -36377,7 +36663,11 @@ function registerServiceLandingTools(server, api) {
36377
36663
  server.registerTool("create_service_landing", {
36378
36664
  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
36665
  inputSchema: CreateServiceLandingInput.shape,
36380
- annotations: CREATES_PAID
36666
+ // action=publish exposes the page to the public internet.
36667
+ annotations: {
36668
+ ...CREATES_PAID,
36669
+ openWorldHint: true
36670
+ }
36381
36671
  }, async (rawInput) => {
36382
36672
  try {
36383
36673
  const input = CreateServiceLandingInput.parse(rawInput);
@@ -36555,7 +36845,7 @@ function registerServiceLandingTools(server, api) {
36555
36845
  __name(registerServiceLandingTools, "registerServiceLandingTools");
36556
36846
 
36557
36847
  // src/tools/service-images.ts
36558
- import { z as z140 } from "zod";
36848
+ import { z as z141 } from "zod";
36559
36849
  var guidance4 = getProductMakerCapabilityModelGuidance("create_service_images");
36560
36850
  var SERVICE_IMAGE_FORMATS = [
36561
36851
  "4:5",
@@ -36568,23 +36858,23 @@ var LANGUAGE_TAGS = {
36568
36858
  en: "en-US",
36569
36859
  pt: "pt-BR"
36570
36860
  };
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([
36861
+ var CreateServiceImagesInput = z141.object({
36862
+ 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."),
36863
+ 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."),
36864
+ 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."),
36865
+ format: z141.enum(SERVICE_IMAGE_FORMATS).optional().describe('Formato: "4:5" (feed, por defecto), "9:16" (historias/reels) o "1:1" (cuadrado).'),
36866
+ count: z141.number().int().min(1).max(SERVICE_IMAGE_MAX_COUNT).optional().describe("Cu\xE1ntas im\xE1genes, de 1 a 3 (por defecto 1)."),
36867
+ quality: z141.enum([
36578
36868
  "standard",
36579
36869
  "high"
36580
36870
  ]).optional().describe('Calidad: "standard" (por defecto) o "high" (m\xE1s cara; no disponible en 4:5).'),
36581
- language: z140.enum([
36871
+ language: z141.enum([
36582
36872
  "es",
36583
36873
  "en",
36584
36874
  "pt"
36585
36875
  ]).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.")
36876
+ adCopy: z141.boolean().optional().describe("Compatibilidad con clientes anteriores: el copy para publicar siempre est\xE1 incluido en cada imagen de servicios."),
36877
+ 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
36878
  }).strict();
36589
36879
  function serviceImageModel(quality) {
36590
36880
  return quality === "high" ? AD_CREATIVE_MODELS.PREMIUM : AD_CREATIVE_MODELS.STANDARD;
@@ -36701,7 +36991,7 @@ function registerServiceImageTools(server, api, options = {}) {
36701
36991
  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
36992
  });
36703
36993
  } catch (error) {
36704
- if (error instanceof z140.ZodError) throw invalidInputFromZod(error);
36994
+ if (error instanceof z141.ZodError) throw invalidInputFromZod(error);
36705
36995
  throw translateError(error);
36706
36996
  }
36707
36997
  });
@@ -36709,7 +36999,7 @@ function registerServiceImageTools(server, api, options = {}) {
36709
36999
  __name(registerServiceImageTools, "registerServiceImageTools");
36710
37000
 
36711
37001
  // src/tools/creatives.ts
36712
- import { z as z141 } from "zod";
37002
+ import { z as z142 } from "zod";
36713
37003
  var generateFreeVideoGuidance = getProductMakerCapabilityModelGuidance("generate_free_video");
36714
37004
  var generateVideoCreativeGuidance = getProductMakerCapabilityModelGuidance("generate_video_creative");
36715
37005
  var generateImageCreativesGuidance = getProductMakerCapabilityModelGuidance("generate_image_creatives");
@@ -36720,72 +37010,72 @@ var CreativeRefs = {
36720
37010
  images: ImagesInput,
36721
37011
  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
37012
  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.")
37013
+ 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.'),
37014
+ 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.'),
37015
+ 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".'),
37016
+ 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."),
37017
+ 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.'),
37018
+ 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.'),
37019
+ isCombo: z142.boolean().optional().describe("Opcional. Si true, el creativo presenta el producto como combo/pack. Default false."),
37020
+ 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."),
37021
+ 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
37022
  };
36733
- var VideoPresenter = z141.discriminatedUnion("mode", [
36734
- z141.object({
36735
- mode: z141.literal("none")
37023
+ var VideoPresenter = z142.discriminatedUnion("mode", [
37024
+ z142.object({
37025
+ mode: z142.literal("none")
36736
37026
  }).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.")
37027
+ z142.object({
37028
+ mode: z142.literal("saved_actor"),
37029
+ actorId: z142.string().trim().min(1).max(120).describe("ID devuelto por list_actors para un actor guardado del usuario.")
36740
37030
  }).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.")
37031
+ z142.object({
37032
+ mode: z142.literal("described"),
37033
+ description: z142.string().trim().min(1).max(500).describe("Descripci\xF3n concreta de la persona que presenta el producto.")
36744
37034
  }).strict()
36745
37035
  ]);
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".')
37036
+ var VideoCreativeSetup = z142.discriminatedUnion("mode", [
37037
+ z142.object({
37038
+ mode: z142.literal("automatic").describe('S\xF3lo cuando el usuario pidi\xF3 expl\xEDcitamente "decide todo".')
36749
37039
  }).strict(),
36750
- z141.object({
36751
- mode: z141.literal("guided"),
37040
+ z142.object({
37041
+ mode: z142.literal("guided"),
36752
37042
  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),
37043
+ buyerPersona: z142.string().trim().min(1).max(360),
36754
37044
  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
37045
  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.")
37046
+ 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
37047
  }).strict()
36758
37048
  ]);
36759
- var VideoInput = z141.object({
37049
+ var VideoInput = z142.object({
36760
37050
  images: CreativeRefs.images.optional(),
36761
37051
  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".'),
37052
+ title: z142.string().min(1).describe('T\xEDtulo corto del producto. Aparece en la primera vista del video. Ej. "Crema Anti-Edad Premium".'),
37053
+ 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
37054
  language: CreativeRefs.language,
36765
37055
  country: CreativeRefs.country,
36766
- aspectRatio: z141.enum([
37056
+ aspectRatio: z142.enum([
36767
37057
  "9:16",
36768
37058
  "16:9",
36769
37059
  "Auto"
36770
37060
  ]).describe('Relaci\xF3n de aspecto. "9:16" = vertical (TikTok/Reels \u2014 default recomendado para ads). "16:9" = horizontal (YouTube). "Auto" = decide la IA.'),
36771
37061
  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."),
37062
+ 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."),
37063
+ 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
37064
  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
37065
  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
37066
  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
37067
  videoQuality: VideoQualityEnum.default("economy").describe('Opcional. Calidad: "economy" = Econ\xF3mico, "medium" = Medio o "pro" = Pro. El sistema elige el proveedor internamente. Default: economy.'),
36778
37068
  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"].'),
37069
+ 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
37070
  price: CreativeRefs.price,
36781
37071
  offer: CreativeRefs.offer,
36782
37072
  productContext: CreativeRefs.productContext,
36783
37073
  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.'),
37074
+ 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.'),
37075
+ 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
37076
  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
37077
  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."),
37078
+ 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
37079
  brandId: CreativeRefs.brandId
36790
37080
  }).strict();
36791
37081
  var FreeVideoQualityEnum = VideoQualityEnum;
@@ -36803,7 +37093,7 @@ var FREE_VIDEO_DURATIONS_BY_QUALITY = {
36803
37093
  length: 29
36804
37094
  }, (_, index) => index + 2)
36805
37095
  };
36806
- var FreeVideoModelIdEnum = z141.enum([
37096
+ var FreeVideoModelIdEnum = z142.enum([
36807
37097
  "grok-imagine-video-1-5-preview",
36808
37098
  "minimax-h3/reference-to-video",
36809
37099
  "wan/3-0-video",
@@ -36814,66 +37104,66 @@ var FREE_SELECTABLE_MODEL_IDS = [
36814
37104
  "minimax-h3/reference-to-video",
36815
37105
  "wan/3-0-video"
36816
37106
  ];
36817
- var FreeVideoInput = z141.object({
37107
+ var FreeVideoInput = z142.object({
36818
37108
  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."),
37109
+ 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
37110
  language: CreativeRefs.language,
36821
37111
  quality: FreeVideoQualityEnum.describe('Calidad del video: "economy" = Econ\xF3mico, "medium" = Medio o "pro" = Pro (Wan).'),
36822
37112
  // Retired in MCP 7.0.0 (Task 11): kept in the schema, accepted, and
36823
37113
  // parsed ONLY so the handler can reject it with a message naming its
36824
37114
  // replacement — dropping it silently would turn a caller's explicit
36825
37115
  // Pro++ pick into a quiet fallback to Wan.
36826
- freeProModel: z141.enum([
37116
+ freeProModel: z142.enum([
36827
37117
  "wan",
36828
37118
  "seedance"
36829
37119
  ]).optional().describe("ELIMINADO en MCP 7.0.0 \u2014 usa `modelId`. Enviar este campo produce un error."),
36830
37120
  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."),
37121
+ 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
37122
  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.'),
37123
+ 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
37124
  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.")
37125
+ addSubtitles: z142.boolean().default(false).describe("Opcional. Quema subt\xEDtulos usando el rawPrompt exacto. Default false."),
37126
+ 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
37127
  }).strict();
36838
37128
  var imageStyleList = ImageCreativeStyleEnum.options.join(" | ");
36839
- var ImageCreativeSetup = z141.discriminatedUnion("mode", [
36840
- z141.object({
36841
- mode: z141.literal("automatic")
37129
+ var ImageCreativeSetup = z142.discriminatedUnion("mode", [
37130
+ z142.object({
37131
+ mode: z142.literal("automatic")
36842
37132
  }).strict(),
36843
- z141.object({
36844
- mode: z141.literal("guided"),
36845
- creativeArchetype: z141.union([
37133
+ z142.object({
37134
+ mode: z142.literal("guided"),
37135
+ creativeArchetype: z142.union([
36846
37136
  ImageCreativeArchetypeEnum,
36847
- z141.literal("ai_decides")
37137
+ z142.literal("ai_decides")
36848
37138
  ]),
36849
- visualTreatment: z141.union([
37139
+ visualTreatment: z142.union([
36850
37140
  ImageCreativeVisualTreatmentEnum,
36851
- z141.literal("ai_decides")
37141
+ z142.literal("ai_decides")
36852
37142
  ]),
36853
37143
  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)
37144
+ nVariants: z142.union([
37145
+ z142.literal(1),
37146
+ z142.literal(2),
37147
+ z142.literal(3),
37148
+ z142.literal(4),
37149
+ z142.literal(6)
36860
37150
  ]),
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()
37151
+ 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.'),
37152
+ 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."),
37153
+ generateAdCopy: z142.boolean(),
37154
+ competitorScanEnabled: z142.boolean(),
37155
+ productContext: z142.string().trim().min(1).max(3e3).nullable(),
37156
+ creativeDirection: z142.string().trim().min(1).max(500).nullable(),
37157
+ 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.'),
37158
+ headline: z142.string().trim().min(1).max(120).nullable(),
37159
+ subheadline: z142.string().trim().min(1).max(160).nullable()
36870
37160
  }).strict()
36871
37161
  ]).superRefine((setup, context) => {
36872
37162
  if (setup.mode !== "guided") return;
36873
37163
  const needsStyleMix = setup.nVariants >= 2 && setup.nVariants <= 4;
36874
37164
  if (needsStyleMix && setup.styleMix === null) {
36875
37165
  context.addIssue({
36876
- code: z141.ZodIssueCode.custom,
37166
+ code: z142.ZodIssueCode.custom,
36877
37167
  path: [
36878
37168
  "styleMix"
36879
37169
  ],
@@ -36882,7 +37172,7 @@ var ImageCreativeSetup = z141.discriminatedUnion("mode", [
36882
37172
  }
36883
37173
  if (!needsStyleMix && setup.styleMix !== null) {
36884
37174
  context.addIssue({
36885
- code: z141.ZodIssueCode.custom,
37175
+ code: z142.ZodIssueCode.custom,
36886
37176
  path: [
36887
37177
  "styleMix"
36888
37178
  ],
@@ -36891,7 +37181,7 @@ var ImageCreativeSetup = z141.discriminatedUnion("mode", [
36891
37181
  }
36892
37182
  if (imageResolutionOf(setup.imageModel) === "2k" && setup.aspectRatio === "4:5") {
36893
37183
  context.addIssue({
36894
- code: z141.ZodIssueCode.custom,
37184
+ code: z142.ZodIssueCode.custom,
36895
37185
  path: [
36896
37186
  "aspectRatio"
36897
37187
  ],
@@ -36899,55 +37189,55 @@ var ImageCreativeSetup = z141.discriminatedUnion("mode", [
36899
37189
  });
36900
37190
  }
36901
37191
  });
36902
- var LandingCreativeSetup = z141.discriminatedUnion("mode", [
36903
- z141.object({
36904
- mode: z141.literal("automatic")
37192
+ var LandingCreativeSetup = z142.discriminatedUnion("mode", [
37193
+ z142.object({
37194
+ mode: z142.literal("automatic")
36905
37195
  }).strict(),
36906
- z141.object({
36907
- mode: z141.literal("guided"),
37196
+ z142.object({
37197
+ mode: z142.literal("guided"),
36908
37198
  landingStrategy: LandingStrategyEnum,
36909
37199
  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()
37200
+ competitorScanEnabled: z142.boolean(),
37201
+ paymentMethods: z142.string().trim().min(1).max(240).nullable(),
37202
+ productContext: z142.string().trim().min(1).max(3e3).nullable(),
37203
+ creativeDirection: z142.string().trim().min(1).max(500).nullable(),
37204
+ headline: z142.string().trim().min(1).max(120).nullable(),
37205
+ subheadline: z142.string().trim().min(1).max(160).nullable()
36916
37206
  }).strict()
36917
37207
  ]);
36918
- var ImageInput = z141.object({
37208
+ var ImageInput = z142.object({
36919
37209
  images: CreativeRefs.images.optional(),
36920
37210
  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.'),
37211
+ productTitle: z142.string().min(1).describe('Nombre del producto. Ej. "Crema Anti-Edad Premium".'),
37212
+ 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
37213
  buyerPersona: CreativeRefs.buyerPersona,
36924
37214
  language: CreativeRefs.language,
36925
37215
  country: CreativeRefs.country,
36926
37216
  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
37217
  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"].'),
37218
+ features: z142.array(z142.string()).optional().describe('Opcional. Features/beneficios a destacar visualmente. Ej. ["resistente al agua", "ultra liviano"].'),
36929
37219
  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."),
37220
+ 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
37221
  compareAtPrice: CreativeRefs.compareAtPrice,
36932
37222
  offer: CreativeRefs.offer,
36933
37223
  productLabel: CreativeRefs.productLabel,
36934
37224
  isCombo: CreativeRefs.isCombo,
36935
37225
  brandId: CreativeRefs.brandId
36936
37226
  });
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".')
37227
+ var EditImageCreativeInput = z142.object({
37228
+ taskId: z142.string().uuid().describe("ID de la tarea de imagen ya finalizada."),
37229
+ variantIndex: z142.number().int().min(0).max(10).describe("\xCDndice de la variante a corregir (0 = primera imagen)."),
37230
+ 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."),
37231
+ 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
37232
  });
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."),
37233
+ var LandingInput = z142.object({
37234
+ 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
37235
  images: CreativeRefs.images.optional(),
36946
37236
  winnerReference: WinnerReferenceInput.optional().describe(WINNER_IMAGE_CHOICE_GUIDANCE),
36947
37237
  language: CreativeRefs.language,
36948
37238
  country: CreativeRefs.country,
36949
37239
  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.'),
37240
+ 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
37241
  buyerPersona: CreativeRefs.buyerPersona,
36952
37242
  price: CreativeRefs.price,
36953
37243
  compareAtPrice: CreativeRefs.compareAtPrice,
@@ -37015,8 +37305,9 @@ function resolveLandingSetup(setup) {
37015
37305
  };
37016
37306
  }
37017
37307
  __name(resolveLandingSetup, "resolveLandingSetup");
37018
- function registerCreativeTools(server, api, { transport = "http" } = {}) {
37308
+ function registerCreativeTools(server, api, { transport = "http", proxyBaseUrl = null } = {}) {
37019
37309
  server.registerTool("generate_free_video", {
37310
+ title: "Custom-script video",
37020
37311
  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
37312
  inputSchema: FreeVideoInput.shape,
37022
37313
  annotations: CREATES_PAID_OPEN_WORLD
@@ -37229,13 +37520,46 @@ function registerCreativeTools(server, api, { transport = "http" } = {}) {
37229
37520
  server.registerTool("get_image_edit_status", {
37230
37521
  description: imageEditStatusGuidance.summary,
37231
37522
  inputSchema: {
37232
- taskId: z141.string().uuid(),
37233
- operationId: z141.string().uuid()
37523
+ taskId: z142.string().uuid(),
37524
+ operationId: z142.string().uuid()
37234
37525
  },
37235
- annotations: READS_INTERNAL
37526
+ annotations: READS_INTERNAL,
37527
+ _meta: taskResultsToolMeta()
37236
37528
  }, async (input) => {
37237
37529
  try {
37238
- return asText(await api.getJson(`/v1/creatives/images/${encodeURIComponent(input.taskId)}/operations/${encodeURIComponent(input.operationId)}`));
37530
+ const { assetUrl, assetKey: _assetKey, ...operation } = ImageCreativeEditStatusResponse.parse(await api.getJson(`/v1/creatives/images/${encodeURIComponent(input.taskId)}/operations/${encodeURIComponent(input.operationId)}`));
37531
+ const imageExtension = assetUrl ? new URL(assetUrl).pathname.match(/\.(png|jpe?g|webp)$/i)?.[0]?.toLowerCase() : void 0;
37532
+ const proxyUrl = operation.status === "published" && assetUrl ? buildProxyUrl({
37533
+ r2Url: assetUrl,
37534
+ filename: `image-edit-${operation.variantIndex + 1}${imageExtension ?? ".bin"}`,
37535
+ proxyBaseUrl
37536
+ }) : null;
37537
+ const view = buildTaskResultsView({
37538
+ images: proxyUrl ? [
37539
+ {
37540
+ url: proxyUrl,
37541
+ alt: `Corrected image ${operation.variantIndex + 1}`
37542
+ }
37543
+ ] : [],
37544
+ videos: [],
37545
+ links: []
37546
+ }, proxyBaseUrl);
37547
+ if (operation.status === "failed") view.state = "error";
37548
+ else if (operation.status !== "published") view.state = "pending";
37549
+ const deliveredUrl = view.images[0]?.url;
37550
+ const response = {
37551
+ ...operation,
37552
+ ...deliveredUrl ? {
37553
+ assetUrl: deliveredUrl
37554
+ } : {}
37555
+ };
37556
+ return {
37557
+ ...asText(response),
37558
+ structuredContent: response,
37559
+ _meta: {
37560
+ taskResults: view
37561
+ }
37562
+ };
37239
37563
  } catch (error) {
37240
37564
  throw translateError(error);
37241
37565
  }
@@ -37287,10 +37611,10 @@ function registerCreativeTools(server, api, { transport = "http" } = {}) {
37287
37611
  __name(registerCreativeTools, "registerCreativeTools");
37288
37612
 
37289
37613
  // src/tools/creative-variants.ts
37290
- import { z as z142 } from "zod";
37614
+ import { z as z143 } from "zod";
37291
37615
  var estimateCreativeVariantGuidance = getProductMakerCapabilityModelGuidance("estimate_creative_variant");
37292
37616
  var createCreativeVariantGuidance = getProductMakerCapabilityModelGuidance("create_creative_variant");
37293
- var LandingSectionKeyEnum = z142.enum([
37617
+ var LandingSectionKeyEnum = z143.enum([
37294
37618
  "problem_hook",
37295
37619
  "problem_cost",
37296
37620
  "solution_mechanism",
@@ -37330,24 +37654,24 @@ var LandingSectionKeyEnum = z142.enum([
37330
37654
  "lifestyle_fit",
37331
37655
  "join_lifestyle"
37332
37656
  ]);
37333
- var CreativeVariantKindEnum = z142.enum([
37657
+ var CreativeVariantKindEnum = z143.enum([
37334
37658
  "video",
37335
37659
  "image",
37336
37660
  "landing"
37337
37661
  ]);
37338
- var EstimateCreativeVariantInputBase = z142.object({
37339
- sourceTaskId: z142.string().uuid().describe("ID de la tarea original completada."),
37662
+ var EstimateCreativeVariantInputBase = z143.object({
37663
+ sourceTaskId: z143.string().uuid().describe("ID de la tarea original completada."),
37340
37664
  kind: CreativeVariantKindEnum,
37341
37665
  videoPreset: TaskCreativeVariantVideoPresetEnum.optional().describe("Opcional para video. Solo cambia el preset p\xFAblico; el servidor conserva el modelo/tier del task origen."),
37342
37666
  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)."),
37667
+ nVariants: z143.number().int().min(1).max(3).optional().describe("Opcional para imagen. N\xFAmero de im\xE1genes adicionales (1-3)."),
37344
37668
  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.")
37669
+ 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
37670
  }).strict();
37347
37671
  var EstimateCreativeVariantInput = EstimateCreativeVariantInputBase.superRefine((input, ctx) => {
37348
37672
  if (input.kind === "video" && input.nVariants !== void 0) {
37349
37673
  ctx.addIssue({
37350
- code: z142.ZodIssueCode.custom,
37674
+ code: z143.ZodIssueCode.custom,
37351
37675
  path: [
37352
37676
  "nVariants"
37353
37677
  ],
@@ -37357,7 +37681,7 @@ var EstimateCreativeVariantInput = EstimateCreativeVariantInputBase.superRefine(
37357
37681
  if (input.kind !== "video") {
37358
37682
  if (input.videoPreset !== void 0) {
37359
37683
  ctx.addIssue({
37360
- code: z142.ZodIssueCode.custom,
37684
+ code: z143.ZodIssueCode.custom,
37361
37685
  path: [
37362
37686
  "videoPreset"
37363
37687
  ],
@@ -37366,7 +37690,7 @@ var EstimateCreativeVariantInput = EstimateCreativeVariantInputBase.superRefine(
37366
37690
  }
37367
37691
  if (input.videoResolution !== void 0) {
37368
37692
  ctx.addIssue({
37369
- code: z142.ZodIssueCode.custom,
37693
+ code: z143.ZodIssueCode.custom,
37370
37694
  path: [
37371
37695
  "videoResolution"
37372
37696
  ],
@@ -37376,7 +37700,7 @@ var EstimateCreativeVariantInput = EstimateCreativeVariantInputBase.superRefine(
37376
37700
  }
37377
37701
  if (input.kind !== "image" && input.nVariants !== void 0) {
37378
37702
  ctx.addIssue({
37379
- code: z142.ZodIssueCode.custom,
37703
+ code: z143.ZodIssueCode.custom,
37380
37704
  path: [
37381
37705
  "nVariants"
37382
37706
  ],
@@ -37386,7 +37710,7 @@ var EstimateCreativeVariantInput = EstimateCreativeVariantInputBase.superRefine(
37386
37710
  if (input.kind !== "landing") {
37387
37711
  if (input.sectionKey !== void 0) {
37388
37712
  ctx.addIssue({
37389
- code: z142.ZodIssueCode.custom,
37713
+ code: z143.ZodIssueCode.custom,
37390
37714
  path: [
37391
37715
  "sectionKey"
37392
37716
  ],
@@ -37395,7 +37719,7 @@ var EstimateCreativeVariantInput = EstimateCreativeVariantInputBase.superRefine(
37395
37719
  }
37396
37720
  if (input.instructions !== void 0) {
37397
37721
  ctx.addIssue({
37398
- code: z142.ZodIssueCode.custom,
37722
+ code: z143.ZodIssueCode.custom,
37399
37723
  path: [
37400
37724
  "instructions"
37401
37725
  ],
@@ -37404,31 +37728,31 @@ var EstimateCreativeVariantInput = EstimateCreativeVariantInputBase.superRefine(
37404
37728
  }
37405
37729
  }
37406
37730
  });
37407
- var CreateCreativeVariantInputBase = z142.object({
37408
- sourceTaskId: z142.string().uuid().describe("ID de la tarea original completada."),
37731
+ var CreateCreativeVariantInputBase = z143.object({
37732
+ sourceTaskId: z143.string().uuid().describe("ID de la tarea original completada."),
37409
37733
  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."),
37734
+ confirm: z143.literal(true).describe("Confirmaci\xF3n expl\xEDcita del usuario despu\xE9s de mostrarle el estimado de cr\xE9ditos."),
37735
+ 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."),
37736
+ angle: z143.string().trim().min(1).max(500).optional().describe("\xC1ngulo nuevo para video o imagen."),
37737
+ buyerPersona: z143.string().trim().min(1).max(360).optional().describe("Buyer persona coherente con el \xE1ngulo."),
37738
+ scriptNotes: z143.string().trim().min(1).max(2e3).optional().describe("Direcci\xF3n breve del guion para una variante de video."),
37739
+ creativeDirection: z143.string().trim().min(1).max(2e3).optional().describe("Direcci\xF3n creativa para una variante de imagen."),
37416
37740
  videoPreset: TaskCreativeVariantVideoPresetEnum.optional().describe("Opcional para video. Preset p\xFAblico; el servidor resuelve modelo/tier desde el task origen."),
37417
37741
  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)."),
37742
+ generateAiMusic: z143.boolean().optional().describe("Opcional para video."),
37743
+ nVariants: z143.number().int().min(1).max(3).optional().describe("Opcional para imagen. N\xFAmero de im\xE1genes adicionales (1-3)."),
37420
37744
  imageStyle: ImageCreativeStyleEnum.optional().describe("Opcional para imagen."),
37421
37745
  creativeArchetype: ImageCreativeArchetypeEnum.optional().describe("Opcional para imagen."),
37422
37746
  visualTreatment: ImageCreativeVisualTreatmentEnum.optional().describe("Opcional para imagen."),
37423
37747
  aspectRatio: ImageCreativeAspectRatioEnum.optional().describe("Opcional para imagen."),
37424
- styleMix: z142.boolean().optional().describe("Opcional para imagen."),
37748
+ styleMix: z143.boolean().optional().describe("Opcional para imagen."),
37425
37749
  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.")
37750
+ 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
37751
  }).strict();
37428
37752
  var CreateCreativeVariantInput = CreateCreativeVariantInputBase.superRefine((input, ctx) => {
37429
37753
  if ((input.kind === "video" || input.kind === "image") && !input.angle) {
37430
37754
  ctx.addIssue({
37431
- code: z142.ZodIssueCode.custom,
37755
+ code: z143.ZodIssueCode.custom,
37432
37756
  path: [
37433
37757
  "angle"
37434
37758
  ],
@@ -37437,7 +37761,7 @@ var CreateCreativeVariantInput = CreateCreativeVariantInputBase.superRefine((inp
37437
37761
  }
37438
37762
  if (input.kind === "landing" && !input.sectionKey && !input.instructions) {
37439
37763
  ctx.addIssue({
37440
- code: z142.ZodIssueCode.custom,
37764
+ code: z143.ZodIssueCode.custom,
37441
37765
  path: [
37442
37766
  "instructions"
37443
37767
  ],
@@ -37483,7 +37807,7 @@ var CreateCreativeVariantInput = CreateCreativeVariantInputBase.superRefine((inp
37483
37807
  for (const field of fieldsByKind[input.kind]) {
37484
37808
  if (input[field] !== void 0) {
37485
37809
  ctx.addIssue({
37486
- code: z142.ZodIssueCode.custom,
37810
+ code: z143.ZodIssueCode.custom,
37487
37811
  path: [
37488
37812
  field
37489
37813
  ],
@@ -37641,13 +37965,13 @@ function registerCreativeVariantTools(server, api) {
37641
37965
  __name(registerCreativeVariantTools, "registerCreativeVariantTools");
37642
37966
 
37643
37967
  // src/tools/script-review.ts
37644
- import { z as z143 } from "zod";
37968
+ import { z as z144 } from "zod";
37645
37969
  var getVideoScriptGuidance = getProductMakerCapabilityModelGuidance("get_video_script");
37646
37970
  var regenerateVideoScriptGuidance = getProductMakerCapabilityModelGuidance("regenerate_video_script");
37647
37971
  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.')
37972
+ var GetVideoScriptInput = z144.object({
37973
+ taskId: z144.string().min(1).describe("ID de la tarea (la misma que devolvi\xF3 create_product_task / generate_video_creative)."),
37974
+ 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
37975
  }).strict();
37652
37976
  var ApproveVideoScriptInput = ApproveVideoScriptCapabilityRequest;
37653
37977
  var RegenerateVideoScriptInput = RegenerateVideoScriptCapabilityRequest.extend({
@@ -37753,7 +38077,7 @@ Consulta get_task_status con esta tarea antes de reintentar. Si la tarea termin\
37753
38077
  __name(registerScriptReviewTools, "registerScriptReviewTools");
37754
38078
 
37755
38079
  // src/tools/connections.ts
37756
- import { z as z144 } from "zod";
38080
+ import { z as z145 } from "zod";
37757
38081
  var listShopifyShopsGuidance = getProductMakerCapabilityModelGuidance("list_shopify_shops");
37758
38082
  var listShopifyProductsGuidance = getProductMakerCapabilityModelGuidance("list_shopify_products");
37759
38083
  var listMetaAdAccountsGuidance = getProductMakerCapabilityModelGuidance("list_meta_ad_accounts");
@@ -37762,7 +38086,7 @@ var listTikTokAdvertisersGuidance = getProductMakerCapabilityModelGuidance("list
37762
38086
  function registerConnectionTools(server, api) {
37763
38087
  server.registerTool("list_shopify_shops", {
37764
38088
  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,
38089
+ inputSchema: z145.object({}).shape,
37766
38090
  annotations: READS_INTERNAL
37767
38091
  }, async () => {
37768
38092
  try {
@@ -37774,10 +38098,10 @@ function registerConnectionTools(server, api) {
37774
38098
  });
37775
38099
  server.registerTool("list_shopify_products", {
37776
38100
  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()
38101
+ inputSchema: z145.object({
38102
+ shopDomain: z145.string().min(3).max(120),
38103
+ query: z145.string().trim().max(120).optional(),
38104
+ taskId: z145.string().uuid().optional()
37781
38105
  }).shape,
37782
38106
  annotations: READS_EXTERNAL
37783
38107
  }, async ({ shopDomain, query, taskId }) => {
@@ -37795,7 +38119,7 @@ function registerConnectionTools(server, api) {
37795
38119
  });
37796
38120
  server.registerTool("list_meta_ad_accounts", {
37797
38121
  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,
38122
+ inputSchema: z145.object({}).shape,
37799
38123
  annotations: READS_EXTERNAL
37800
38124
  }, async () => {
37801
38125
  try {
@@ -37807,7 +38131,7 @@ function registerConnectionTools(server, api) {
37807
38131
  });
37808
38132
  server.registerTool("list_meta_destinations", {
37809
38133
  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,
38134
+ inputSchema: z145.object({}).shape,
37811
38135
  annotations: READS_EXTERNAL,
37812
38136
  _meta: {
37813
38137
  "openai/widgetAccessible": true
@@ -37822,7 +38146,7 @@ function registerConnectionTools(server, api) {
37822
38146
  });
37823
38147
  server.registerTool("list_tiktok_advertisers", {
37824
38148
  description: listTikTokAdvertisersGuidance.summary + " Si est\xE1 vac\xEDa, ind\xEDcale al usuario conectar TikTok en productmaker.app/integrations.",
37825
- inputSchema: z144.object({}).shape,
38149
+ inputSchema: z145.object({}).shape,
37826
38150
  annotations: READS_EXTERNAL
37827
38151
  }, async () => {
37828
38152
  try {
@@ -37836,7 +38160,7 @@ function registerConnectionTools(server, api) {
37836
38160
  __name(registerConnectionTools, "registerConnectionTools");
37837
38161
 
37838
38162
  // src/tools/actors.ts
37839
- import { z as z145 } from "zod";
38163
+ import { z as z146 } from "zod";
37840
38164
  var listActorsGuidance = getProductMakerCapabilityModelGuidance("list_actors");
37841
38165
  var actorVoiceGuidance = {
37842
38166
  capabilities: getProductMakerCapabilityModelGuidance("get_actor_voice_capabilities"),
@@ -37876,7 +38200,7 @@ function sanitizeVoiceResult(value) {
37876
38200
  __name(sanitizeVoiceResult, "sanitizeVoiceResult");
37877
38201
  function registerActorTools(server, api) {
37878
38202
  const actor = {
37879
- actorId: z145.string().min(1).max(64)
38203
+ actorId: z146.string().min(1).max(64)
37880
38204
  };
37881
38205
  const safe = /* @__PURE__ */ __name(async (request) => {
37882
38206
  try {
@@ -37905,8 +38229,8 @@ function registerActorTools(server, api) {
37905
38229
  inputSchema: {
37906
38230
  ...actor,
37907
38231
  draft: ActorVoiceDraftSchema,
37908
- requestKey: z145.string().trim().min(1).max(128),
37909
- acceptedQuoteId: z145.string().trim().min(1).max(128)
38232
+ requestKey: z146.string().trim().min(1).max(128),
38233
+ acceptedQuoteId: z146.string().trim().min(1).max(128)
37910
38234
  },
37911
38235
  annotations: CREATES_PAID
37912
38236
  }, async ({ actorId, ...body }) => safe(api.postJson(`/v1/actors/${encodeURIComponent(actorId)}/voice/previews`, body)));
@@ -37914,7 +38238,7 @@ function registerActorTools(server, api) {
37914
38238
  description: actorVoiceGuidance.get.summary,
37915
38239
  inputSchema: {
37916
38240
  ...actor,
37917
- candidateId: z145.string().min(1).max(64)
38241
+ candidateId: z146.string().min(1).max(64)
37918
38242
  },
37919
38243
  // Polling can resume the first paid submission of an accepted quote.
37920
38244
  // The immutable candidate/operation key keeps recovery idempotent.
@@ -37927,16 +38251,16 @@ function registerActorTools(server, api) {
37927
38251
  description: actorVoiceGuidance.approve.summary,
37928
38252
  inputSchema: {
37929
38253
  ...actor,
37930
- candidateId: z145.string().min(1).max(64),
37931
- expectedRevision: z145.number().int().positive(),
37932
- expectedApprovedRevision: z145.number().int().positive().nullable().optional()
38254
+ candidateId: z146.string().min(1).max(64),
38255
+ expectedRevision: z146.number().int().positive(),
38256
+ expectedApprovedRevision: z146.number().int().positive().nullable().optional()
37933
38257
  },
37934
38258
  // Replaces the selected voice and withdraws the old catalog grants.
37935
38259
  annotations: OVERWRITES_INTERNAL
37936
38260
  }, async ({ actorId, ...body }) => safe(api.postJson(`/v1/actors/${encodeURIComponent(actorId)}/voice/approve`, body)));
37937
38261
  server.registerTool("list_actors", {
37938
38262
  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,
38263
+ inputSchema: z146.object({}).shape,
37940
38264
  annotations: READS_INTERNAL
37941
38265
  }, async () => {
37942
38266
  try {
@@ -37969,7 +38293,7 @@ function registerActorTools(server, api) {
37969
38293
  __name(registerActorTools, "registerActorTools");
37970
38294
 
37971
38295
  // src/tools/publish.ts
37972
- import { z as z146 } from "zod";
38296
+ import { z as z147 } from "zod";
37973
38297
 
37974
38298
  // src/tools/meta-publish-attempt-id.ts
37975
38299
  var META_PUBLISH_NAMESPACE = "7b19c9ac-b4eb-4db1-970e-c2f24ce30b98";
@@ -38008,40 +38332,40 @@ var publishToShopifyGuidance = getProductMakerCapabilityModelGuidance("publish_t
38008
38332
  var publishToMetaGuidance = getProductMakerCapabilityModelGuidance("publish_to_meta");
38009
38333
  var publishCreativesToMetaGuidance = getProductMakerCapabilityModelGuidance("publish_creatives_to_meta");
38010
38334
  var publishToTikTokGuidance = getProductMakerCapabilityModelGuidance("publish_to_tiktok");
38011
- var ShopifyInput = z146.object({
38012
- taskId: z146.string().min(1),
38013
- shopDomain: z146.string().min(1),
38335
+ var ShopifyInput = z147.object({
38336
+ taskId: z147.string().min(1),
38337
+ shopDomain: z147.string().min(1),
38014
38338
  /** Override: number of landing images to include. Default = ALL images the
38015
38339
  * task generated (the MCP fetches /v1/results first to count). Bounded
38016
38340
  * generously here; the backend validates each index against
38017
38341
  * 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(),
38342
+ numLandingImages: z147.number().int().min(1).max(20).optional(),
38343
+ variantIndex: z147.number().int().min(0).optional(),
38020
38344
  /** ASK THE USER before setting: "¿Tu producto tiene variantes (talla, color, etc.)?".
38021
38345
  * When true, the landing renders variant-picker pills; when false, it ships
38022
38346
  * a single-option product page. Defaults to false. */
38023
- showVariantPicker: z146.boolean().optional(),
38347
+ showVariantPicker: z147.boolean().optional(),
38024
38348
  /** Default `auto` updates the product previously published for this task and
38025
38349
  * shop. Use `existing` with targetProductId to choose another product, or
38026
38350
  * `new` only when the user explicitly wants a duplicate/new listing. */
38027
- publicationMode: z146.enum([
38351
+ publicationMode: z147.enum([
38028
38352
  "auto",
38029
38353
  "existing",
38030
38354
  "new"
38031
38355
  ]).optional(),
38032
38356
  /** Shopify product GID, numeric id, or admin product URL. Required when
38033
38357
  * publicationMode is `existing`. */
38034
- targetProductId: z146.string().trim().min(1).max(300).optional(),
38358
+ targetProductId: z147.string().trim().min(1).max(300).optional(),
38035
38359
  /** 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(),
38360
+ announcementBarEnabled: z147.boolean().optional(),
38361
+ announcementBarText: z147.string().trim().min(1).max(SHOPIFY_ANNOUNCEMENT_BAR_MAX_LENGTH).optional(),
38038
38362
  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.")
38363
+ confirmed: z147.boolean().optional().describe("Required as true only when submitting commerceCorrection, after showing its values and obtaining explicit seller confirmation.")
38040
38364
  });
38041
38365
  var ShopifyInputWithAnnouncementBarValidation = ShopifyInput.superRefine((value, ctx) => {
38042
38366
  if (value.commerceCorrection && value.confirmed !== true) {
38043
38367
  ctx.addIssue({
38044
- code: z146.ZodIssueCode.custom,
38368
+ code: z147.ZodIssueCode.custom,
38045
38369
  path: [
38046
38370
  "confirmed"
38047
38371
  ],
@@ -38050,7 +38374,7 @@ var ShopifyInputWithAnnouncementBarValidation = ShopifyInput.superRefine((value,
38050
38374
  }
38051
38375
  if (value.announcementBarEnabled && !value.announcementBarText) {
38052
38376
  ctx.addIssue({
38053
- code: z146.ZodIssueCode.custom,
38377
+ code: z147.ZodIssueCode.custom,
38054
38378
  path: [
38055
38379
  "announcementBarText"
38056
38380
  ],
@@ -38059,7 +38383,7 @@ var ShopifyInputWithAnnouncementBarValidation = ShopifyInput.superRefine((value,
38059
38383
  }
38060
38384
  if (!value.announcementBarEnabled && value.announcementBarText) {
38061
38385
  ctx.addIssue({
38062
- code: z146.ZodIssueCode.custom,
38386
+ code: z147.ZodIssueCode.custom,
38063
38387
  path: [
38064
38388
  "announcementBarText"
38065
38389
  ],
@@ -38073,90 +38397,90 @@ function parseShopifyInput(rawInput) {
38073
38397
  return parsed.data;
38074
38398
  }
38075
38399
  __name(parseShopifyInput, "parseShopifyInput");
38076
- var McpCampaignConfig = z146.object({
38400
+ var McpCampaignConfig = z147.object({
38077
38401
  /** Legacy USD amount. Meta retains its existing conversion; the TikTok
38078
38402
  * schema below restricts this input to USD advertiser accounts. */
38079
- dailyBudgetUsd: z146.number().positive().optional(),
38403
+ dailyBudgetUsd: z147.number().positive().optional(),
38080
38404
  /** ISO-3166-1 alpha-2 country codes or resolvable names (CountryIso2Schema
38081
38405
  * normalizes at validation). Meta accepts max 1 (single-country ad set).
38082
38406
  * TikTok takes only the first entry. */
38083
- targetCountries: z146.array(CountryIso2Schema).max(1).optional(),
38084
- campaignName: z146.string().trim().min(1).max(100).optional()
38407
+ targetCountries: z147.array(CountryIso2Schema).max(1).optional(),
38408
+ campaignName: z147.string().trim().min(1).max(100).optional()
38085
38409
  });
38086
38410
  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),
38411
+ var MetaInput = z147.object({
38412
+ taskId: z147.string().min(1),
38089
38413
  /** Exact Meta Business portfolio selected from `list_meta_destinations`. */
38090
- portfolioId: z146.string().trim().min(1),
38414
+ portfolioId: z147.string().trim().min(1),
38091
38415
  /** Exact Facebook Page selected from `list_meta_destinations`. */
38092
- pageId: z146.string().trim().min(1),
38416
+ pageId: z147.string().trim().min(1),
38093
38417
  adAccountId: McpAdAccountId,
38094
- pixelId: z146.string().min(1).optional(),
38095
- landingUrl: z146.string().url().optional(),
38418
+ pixelId: z147.string().min(1).optional(),
38419
+ landingUrl: z147.string().url().optional(),
38096
38420
  /** Select which angles to publish by 0-based index. Omit to publish all
38097
38421
  * successful angles. Example: a task with 3 angles → `[0,2]` publishes #0 + #2. */
38098
- angleIndexes: z146.array(z146.number().int().nonnegative()).optional(),
38422
+ angleIndexes: z147.array(z147.number().int().nonnegative()).optional(),
38099
38423
  campaignConfig: McpCampaignConfig.optional(),
38100
38424
  /** Override the ad's primary text (body copy). If omitted, ProductMaker
38101
38425
  * uses the auto-generated copy attached to the task. Capped at 500 chars
38102
38426
  * (Meta's primary-text limit). */
38103
- primaryText: z146.string().trim().min(1).max(500).optional(),
38427
+ primaryText: z147.string().trim().min(1).max(500).optional(),
38104
38428
  /** Optional WhatsApp number. When set, the ad button opens a WhatsApp chat
38105
38429
  * (wa.me) instead of a landing page. */
38106
- phoneNumber: z146.string().min(8).max(32).optional()
38430
+ phoneNumber: z147.string().min(8).max(32).optional()
38107
38431
  });
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([
38432
+ var GroupedTaskSource = z147.object({
38433
+ type: z147.literal("task"),
38434
+ taskId: z147.string().min(1),
38435
+ angleIndex: z147.number().int().min(0).optional(),
38436
+ videoVariant: z147.enum([
38113
38437
  "with_subtitles",
38114
38438
  "without_subtitles"
38115
38439
  ]).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()
38440
+ imageVariantIndex: z147.number().int().min(0).optional()
38441
+ });
38442
+ var GroupedMediaSource = z147.object({
38443
+ type: z147.literal("media"),
38444
+ 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."),
38445
+ 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".'),
38446
+ thumbnailUrl: z147.string().url().optional(),
38447
+ thumbnailPath: z147.string().min(1).optional()
38124
38448
  }).refine((value) => Boolean(value.url) !== Boolean(value.path), {
38125
38449
  message: "media source needs exactly one of `url` or `path`"
38126
38450
  }).refine((value) => !(value.thumbnailUrl && value.thumbnailPath), {
38127
38451
  message: "media source needs at most one of `thumbnailUrl` or `thumbnailPath`"
38128
38452
  });
38129
- var GroupedUploadedAssetSource = z146.object({
38130
- type: z146.literal("asset"),
38453
+ var GroupedUploadedAssetSource = z147.object({
38454
+ type: z147.literal("asset"),
38131
38455
  /** Returned by check_media_upload. ProductMaker resolves ownership server-side. */
38132
- assetId: z146.string().uuid(),
38133
- thumbnailAssetId: z146.string().uuid().optional()
38456
+ assetId: z147.string().uuid(),
38457
+ thumbnailAssetId: z147.string().uuid().optional()
38134
38458
  });
38135
- var GroupedCreative = z146.object({
38136
- type: z146.enum([
38459
+ var GroupedCreative = z147.object({
38460
+ type: z147.enum([
38137
38461
  "image",
38138
38462
  "video"
38139
38463
  ]),
38140
- source: z146.union([
38464
+ source: z147.union([
38141
38465
  GroupedTaskSource,
38142
38466
  GroupedMediaSource,
38143
38467
  GroupedUploadedAssetSource
38144
38468
  ]),
38145
- title: z146.string().max(120).optional(),
38146
- description: z146.string().max(500).optional(),
38469
+ title: z147.string().max(120).optional(),
38470
+ description: z147.string().max(500).optional(),
38147
38471
  cta: MetaCtaType.optional(),
38148
- creativeId: z146.string().min(1).optional()
38472
+ creativeId: z147.string().min(1).optional()
38149
38473
  });
38150
- var GroupedCampaignConfigBase = z146.object({
38474
+ var GroupedCampaignConfigBase = z147.object({
38151
38475
  campaignType: MetaCampaignType.default("sales"),
38152
38476
  optimizationMode: MetaOptimizationMode.default("advantage_plus"),
38153
38477
  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)
38478
+ campaignDailyBudget: z147.number().int().positive().optional(),
38479
+ adSets: z147.array(z147.object({
38480
+ tempId: z147.string().min(1).max(80),
38481
+ name: z147.string().min(1).max(100),
38482
+ dailyBudget: z147.number().int().positive().optional(),
38483
+ creativeIds: z147.array(z147.string().min(1)).min(1)
38160
38484
  })).min(1).max(CAMPAIGN_LIMITS.MAX_AD_SETS),
38161
38485
  /**
38162
38486
  * Valid values are 'landing' and 'whatsapp' — there is no 'website'. This
@@ -38164,17 +38488,17 @@ var GroupedCampaignConfigBase = z146.object({
38164
38488
  * opaque `invalid_literal` and made the WhatsApp destination — supported by
38165
38489
  * the API since #849 — unreachable from this tool.
38166
38490
  */
38167
- destination: z146.discriminatedUnion("type", [
38168
- z146.object({
38169
- type: z146.literal("landing"),
38170
- landingUrl: z146.string().url()
38491
+ destination: z147.discriminatedUnion("type", [
38492
+ z147.object({
38493
+ type: z147.literal("landing"),
38494
+ landingUrl: z147.string().url()
38171
38495
  }),
38172
- z146.object({
38173
- type: z146.literal("whatsapp"),
38174
- phoneNumber: z146.string().min(8).max(32)
38496
+ z147.object({
38497
+ type: z147.literal("whatsapp"),
38498
+ phoneNumber: z147.string().min(8).max(32)
38175
38499
  })
38176
38500
  ]),
38177
- saveAsDefault: z146.boolean().default(false),
38501
+ saveAsDefault: z147.boolean().default(false),
38178
38502
  budgetPolicy: MetaBudgetPolicy.optional()
38179
38503
  });
38180
38504
  function resolveBudgetMode(value) {
@@ -38188,7 +38512,7 @@ var GroupedCampaignConfig = GroupedCampaignConfigBase.superRefine((value, ctx) =
38188
38512
  const budgetMode = resolveBudgetMode(value);
38189
38513
  if (budgetMode === "campaign" && value.campaignDailyBudget === void 0) {
38190
38514
  ctx.addIssue({
38191
- code: z146.ZodIssueCode.custom,
38515
+ code: z147.ZodIssueCode.custom,
38192
38516
  path: [
38193
38517
  "campaignDailyBudget"
38194
38518
  ],
@@ -38199,7 +38523,7 @@ var GroupedCampaignConfig = GroupedCampaignConfigBase.superRefine((value, ctx) =
38199
38523
  value.adSets.forEach((adSet, index) => {
38200
38524
  if (adSet.dailyBudget === void 0) {
38201
38525
  ctx.addIssue({
38202
- code: z146.ZodIssueCode.custom,
38526
+ code: z147.ZodIssueCode.custom,
38203
38527
  path: [
38204
38528
  "adSets",
38205
38529
  index,
@@ -38214,25 +38538,25 @@ var GroupedCampaignConfig = GroupedCampaignConfigBase.superRefine((value, ctx) =
38214
38538
  ...value,
38215
38539
  budgetMode: resolveBudgetMode(value)
38216
38540
  }));
38217
- var PublishCreativesToMetaInputBase = z146.object({
38218
- creatives: z146.array(GroupedCreative).min(1).max(CAMPAIGN_LIMITS.MAX_CREATIVES),
38541
+ var PublishCreativesToMetaInputBase = z147.object({
38542
+ creatives: z147.array(GroupedCreative).min(1).max(CAMPAIGN_LIMITS.MAX_CREATIVES),
38219
38543
  /** Exact Meta Business portfolio selected from `list_meta_destinations`. */
38220
- portfolioId: z146.string().trim().min(1),
38544
+ portfolioId: z147.string().trim().min(1),
38221
38545
  /** Exact Facebook Page selected from `list_meta_destinations`. */
38222
- pageId: z146.string().trim().min(1),
38546
+ pageId: z147.string().trim().min(1),
38223
38547
  adAccountId: McpAdAccountId,
38224
- pixelId: z146.string().min(1).optional(),
38548
+ pixelId: z147.string().min(1).optional(),
38225
38549
  /** Required unless `campaignConfig.destination` is whatsapp or a creative
38226
38550
  * comes from a task (the API then lands on the published service landing
38227
38551
  * of its service tasks) — see the superRefine below. Mirrors the API's
38228
38552
  * `FacebookCreateCampaignFromTasksBodySchema`. */
38229
- landingUrl: z146.string().url().optional(),
38553
+ landingUrl: z147.string().url().optional(),
38230
38554
  /** ISO-2 code or resolvable country name — mirrors the API contract
38231
38555
  * (CountryIso2Schema), which normalizes names to alpha-2 at validation.
38232
38556
  * Omitted: the brand country of its service tasks (the API answers
38233
38557
  * META_NO_TARGET_COUNTRY when it has none); "US" for products. */
38234
38558
  targetCountry: CountryIso2Schema.optional(),
38235
- campaignName: z146.string().trim().min(1).max(100),
38559
+ campaignName: z147.string().trim().min(1).max(100),
38236
38560
  campaignConfig: GroupedCampaignConfig.optional()
38237
38561
  });
38238
38562
  var PublishCreativesToMetaInput = PublishCreativesToMetaInputBase.superRefine((value, ctx) => {
@@ -38240,7 +38564,7 @@ var PublishCreativesToMetaInput = PublishCreativesToMetaInputBase.superRefine((v
38240
38564
  const hasTaskCreative = value.creatives.some((creative) => creative.source.type === "task");
38241
38565
  if (destination?.type !== "whatsapp" && destination?.type !== "landing" && !value.landingUrl && !hasTaskCreative) {
38242
38566
  ctx.addIssue({
38243
- code: z146.ZodIssueCode.custom,
38567
+ code: z147.ZodIssueCode.custom,
38244
38568
  path: [
38245
38569
  "landingUrl"
38246
38570
  ],
@@ -38249,7 +38573,7 @@ var PublishCreativesToMetaInput = PublishCreativesToMetaInputBase.superRefine((v
38249
38573
  }
38250
38574
  if (destination?.type === "landing" && value.landingUrl && destination.landingUrl !== value.landingUrl) {
38251
38575
  ctx.addIssue({
38252
- code: z146.ZodIssueCode.custom,
38576
+ code: z147.ZodIssueCode.custom,
38253
38577
  path: [
38254
38578
  "campaignConfig",
38255
38579
  "destination",
@@ -38268,16 +38592,16 @@ var PublishCreativesToMetaInput = PublishCreativesToMetaInputBase.superRefine((v
38268
38592
  }
38269
38593
  return value;
38270
38594
  });
38271
- var PresignUploadResponseSchema = z146.object({
38272
- uploadUrl: z146.string().url().refine((value) => {
38595
+ var PresignUploadResponseSchema = z147.object({
38596
+ uploadUrl: z147.string().url().refine((value) => {
38273
38597
  const url2 = new URL(value);
38274
38598
  return !url2.username && !url2.password && url2.protocol === "https:";
38275
38599
  }, "presigned upload URL must be HTTPS without embedded credentials"),
38276
- publicUrl: z146.string().url().refine((value) => {
38600
+ publicUrl: z147.string().url().refine((value) => {
38277
38601
  const url2 = new URL(value);
38278
38602
  return !url2.username && !url2.password;
38279
38603
  }, "public media URL must not contain embedded credentials"),
38280
- stagingAssetId: z146.string().uuid()
38604
+ stagingAssetId: z147.string().uuid()
38281
38605
  });
38282
38606
  async function uploadResolvedMedia(api, media, assetRole) {
38283
38607
  if (!media.buffer) throw new ResolveMediaError("INVALID_INPUT", "resolved media has no bytes");
@@ -38303,14 +38627,14 @@ async function resolveAndUploadMedia(api, ref, expectedKind, assetRole, transpor
38303
38627
  }
38304
38628
  __name(resolveAndUploadMedia, "resolveAndUploadMedia");
38305
38629
  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."),
38630
+ dailyBudgetUsd: z147.number().finite().positive().optional().describe("Legacy USD amount, supported only for USD advertiser accounts. No FX conversion."),
38631
+ dailyBudget: z147.number().finite().positive().optional().describe("Daily budget in the selected advertiser currency major units; pass budgetCurrency together."),
38308
38632
  budgetCurrency: TikTokBudgetCurrencySchema.optional(),
38309
38633
  destination: TikTokCampaignDestinationSchema.optional()
38310
38634
  }).superRefine((value, ctx) => {
38311
38635
  if (value.dailyBudgetUsd !== void 0 && (value.dailyBudget !== void 0 || value.budgetCurrency !== void 0)) {
38312
38636
  ctx.addIssue({
38313
- code: z146.ZodIssueCode.custom,
38637
+ code: z147.ZodIssueCode.custom,
38314
38638
  path: [
38315
38639
  "dailyBudgetUsd"
38316
38640
  ],
@@ -38319,7 +38643,7 @@ var TikTokMcpCampaignConfig = McpCampaignConfig.extend({
38319
38643
  }
38320
38644
  if (value.dailyBudget !== void 0 !== (value.budgetCurrency !== void 0)) {
38321
38645
  ctx.addIssue({
38322
- code: z146.ZodIssueCode.custom,
38646
+ code: z147.ZodIssueCode.custom,
38323
38647
  path: [
38324
38648
  "budgetCurrency"
38325
38649
  ],
@@ -38327,23 +38651,27 @@ var TikTokMcpCampaignConfig = McpCampaignConfig.extend({
38327
38651
  });
38328
38652
  }
38329
38653
  });
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(),
38654
+ var TikTokInput = z147.object({
38655
+ taskId: z147.string().min(1),
38656
+ advertiserId: z147.string().min(1),
38657
+ pixelId: z147.string().min(1).optional(),
38658
+ landingUrl: z147.string().url().optional(),
38659
+ angleIndexes: z147.array(z147.number().int().nonnegative()).optional(),
38336
38660
  campaignConfig: TikTokMcpCampaignConfig.optional(),
38337
38661
  /** Override the ad's description (TikTok's body copy). If omitted,
38338
38662
  * ProductMaker uses the auto-generated copy attached to the task. */
38339
- primaryText: z146.string().trim().min(1).max(500).optional()
38663
+ primaryText: z147.string().trim().min(1).max(500).optional()
38340
38664
  });
38341
38665
  function registerPublishTools(server, api, opts = {}) {
38342
38666
  const transport = opts.transport ?? "http";
38343
38667
  server.registerTool("publish_to_shopify", {
38344
38668
  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
38669
  inputSchema: ShopifyInput.shape,
38346
- annotations: OVERWRITES_EXTERNAL
38670
+ // Publication reaches the public storefront, beyond private account data.
38671
+ annotations: {
38672
+ ...OVERWRITES_EXTERNAL,
38673
+ openWorldHint: true
38674
+ }
38347
38675
  }, async (input, extra) => {
38348
38676
  try {
38349
38677
  const parsed = parseShopifyInput(input);
@@ -38460,7 +38788,13 @@ function registerPublishTools(server, api, opts = {}) {
38460
38788
  server.registerTool("publish_creatives_to_meta", {
38461
38789
  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
38790
  inputSchema: PublishCreativesToMetaInputBase.shape,
38463
- annotations: CREATES_EXTERNAL
38791
+ // May fetch arbitrary public media URLs and consumes owned upload assets
38792
+ // after successful publication, even though the campaign starts paused.
38793
+ annotations: {
38794
+ ...CREATES_EXTERNAL,
38795
+ openWorldHint: true,
38796
+ destructiveHint: true
38797
+ }
38464
38798
  }, async (rawInput, extra) => {
38465
38799
  try {
38466
38800
  const parsed = PublishCreativesToMetaInput.safeParse(rawInput);
@@ -38696,39 +39030,40 @@ __name(toTikTokCampaignConfig, "toTikTokCampaignConfig");
38696
39030
 
38697
39031
  // src/tools/meta-upload.ts
38698
39032
  import { registerAppTool } from "@modelcontextprotocol/ext-apps/server";
38699
- import { z as z147 } from "zod";
39033
+ import { z as z148 } from "zod";
38700
39034
  var requestMediaUploadGuidance = getProductMakerCapabilityModelGuidance("request_media_upload");
38701
- var UploadSessionInputBase = z147.object({
38702
- mediaKind: z147.enum([
39035
+ var checkMediaUploadGuidance = getProductMakerCapabilityModelGuidance("check_media_upload");
39036
+ var UploadSessionInputBase = z148.object({
39037
+ mediaKind: z148.enum([
38703
39038
  "image",
38704
39039
  "video",
38705
39040
  "mixed"
38706
39041
  ]).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([
39042
+ assetRole: z148.enum([
38708
39043
  "media",
38709
39044
  "thumbnail"
38710
39045
  ]).default("media").describe("Use media for the creative itself; thumbnail only for an image thumbnail."),
38711
- uploadPurpose: z147.enum([
39046
+ uploadPurpose: z148.enum([
38712
39047
  "product_reference",
38713
39048
  "creative",
38714
39049
  "generic"
38715
39050
  ]).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.")
39051
+ 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
39052
  });
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()
39053
+ var OpenAiFileReference = z148.object({
39054
+ 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."),
39055
+ file_id: z148.string().min(1),
39056
+ mime_type: z148.string().min(1).optional(),
39057
+ file_name: z148.string().min(1).optional()
38723
39058
  });
38724
39059
  var UploadSessionToolInputShape = {
38725
39060
  ...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.")
39061
+ 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
39062
  };
38728
- var UploadSessionToolInput = z147.object(UploadSessionToolInputShape).superRefine((value, ctx) => {
39063
+ var UploadSessionToolInput = z148.object(UploadSessionToolInputShape).superRefine((value, ctx) => {
38729
39064
  if (value.assetRole === "thumbnail" && value.mediaKind !== "image") {
38730
39065
  ctx.addIssue({
38731
- code: z147.ZodIssueCode.custom,
39066
+ code: z148.ZodIssueCode.custom,
38732
39067
  path: [
38733
39068
  "mediaKind"
38734
39069
  ],
@@ -38737,7 +39072,7 @@ var UploadSessionToolInput = z147.object(UploadSessionToolInputShape).superRefin
38737
39072
  }
38738
39073
  if (value.mediaKind === "video" && value.maxFiles !== void 0 && value.maxFiles !== 1) {
38739
39074
  ctx.addIssue({
38740
- code: z147.ZodIssueCode.custom,
39075
+ code: z148.ZodIssueCode.custom,
38741
39076
  path: [
38742
39077
  "maxFiles"
38743
39078
  ],
@@ -38746,7 +39081,7 @@ var UploadSessionToolInput = z147.object(UploadSessionToolInputShape).superRefin
38746
39081
  }
38747
39082
  if (value.mediaKind === "image" && (value.maxFiles ?? 1) > 5) {
38748
39083
  ctx.addIssue({
38749
- code: z147.ZodIssueCode.custom,
39084
+ code: z148.ZodIssueCode.custom,
38750
39085
  path: [
38751
39086
  "maxFiles"
38752
39087
  ],
@@ -38754,38 +39089,38 @@ var UploadSessionToolInput = z147.object(UploadSessionToolInputShape).superRefin
38754
39089
  });
38755
39090
  }
38756
39091
  });
38757
- var UploadSessionResponse = z147.object({
38758
- sessionId: z147.string().uuid(),
38759
- uploadPageUrl: z147.string().url(),
38760
- mediaKind: z147.enum([
39092
+ var UploadSessionResponse = z148.object({
39093
+ sessionId: z148.string().uuid(),
39094
+ uploadPageUrl: z148.string().url(),
39095
+ mediaKind: z148.enum([
38761
39096
  "image",
38762
39097
  "video",
38763
39098
  "mixed"
38764
39099
  ]),
38765
- assetRole: z147.enum([
39100
+ assetRole: z148.enum([
38766
39101
  "media",
38767
39102
  "thumbnail"
38768
39103
  ]),
38769
- maxFiles: z147.number().int().positive(),
38770
- expiresAt: z147.string().datetime()
39104
+ maxFiles: z148.number().int().positive(),
39105
+ expiresAt: z148.string().datetime()
38771
39106
  });
38772
- var UploadStatusInput = z147.object({
38773
- sessionId: z147.string().uuid().describe("The sessionId returned by request_media_upload.")
39107
+ var UploadStatusInput = z148.object({
39108
+ sessionId: z148.string().uuid().describe("The sessionId returned by request_media_upload.")
38774
39109
  });
38775
- var UniversalUploadStatusResponse = z147.discriminatedUnion("status", [
38776
- z147.object({
38777
- status: z147.literal("pending"),
38778
- assets: z147.array(z147.never()).length(0)
39110
+ var UniversalUploadStatusResponse = z148.discriminatedUnion("status", [
39111
+ z148.object({
39112
+ status: z148.literal("pending"),
39113
+ assets: z148.array(z148.never()).length(0)
38779
39114
  }),
38780
- z147.object({
38781
- status: z147.literal("uploaded"),
38782
- assets: z147.array(z147.object({
38783
- assetId: z147.string().uuid(),
38784
- mediaKind: z147.enum([
39115
+ z148.object({
39116
+ status: z148.literal("uploaded"),
39117
+ assets: z148.array(z148.object({
39118
+ assetId: z148.string().uuid(),
39119
+ mediaKind: z148.enum([
38785
39120
  "image",
38786
39121
  "video"
38787
39122
  ]),
38788
- assetRole: z147.enum([
39123
+ assetRole: z148.enum([
38789
39124
  "media",
38790
39125
  "thumbnail"
38791
39126
  ])
@@ -38793,13 +39128,13 @@ var UniversalUploadStatusResponse = z147.discriminatedUnion("status", [
38793
39128
  })
38794
39129
  ]);
38795
39130
  var UniversalUploadStructuredResponse = UploadSessionResponse.extend({
38796
- uploadPurpose: z147.enum([
39131
+ uploadPurpose: z148.enum([
38797
39132
  "product_reference",
38798
39133
  "creative",
38799
39134
  "generic"
38800
39135
  ]),
38801
- checkToolName: z147.literal("check_media_upload"),
38802
- resolveEndpoint: z147.string().url()
39136
+ checkToolName: z148.literal("check_media_upload"),
39137
+ resolveEndpoint: z148.string().url()
38803
39138
  });
38804
39139
  function contextualizeUploadPageUrl(uploadPageUrl, uploadPurpose) {
38805
39140
  const url2 = new URL(uploadPageUrl);
@@ -38831,7 +39166,7 @@ __name(resolveEndpointForSession, "resolveEndpointForSession");
38831
39166
  function registerMediaUploadTools(server, api) {
38832
39167
  registerAppTool(server, "request_media_upload", {
38833
39168
  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.",
39169
+ 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
39170
  inputSchema: UploadSessionToolInputShape,
38836
39171
  annotations: CREATES_INTERNAL,
38837
39172
  outputSchema: UniversalUploadStructuredResponse.shape,
@@ -38872,7 +39207,7 @@ function registerMediaUploadTools(server, api) {
38872
39207
  }
38873
39208
  });
38874
39209
  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.",
39210
+ 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
39211
  inputSchema: UploadStatusInput.shape,
38877
39212
  annotations: READS_INTERNAL,
38878
39213
  _meta: {
@@ -38890,15 +39225,15 @@ function registerMediaUploadTools(server, api) {
38890
39225
  __name(registerMediaUploadTools, "registerMediaUploadTools");
38891
39226
 
38892
39227
  // src/tools/campaigns.ts
38893
- import { z as z148 } from "zod";
39228
+ import { z as z149 } from "zod";
38894
39229
  var getMetaCampaignStatusGuidance = getProductMakerCapabilityModelGuidance("get_meta_campaign_status");
38895
39230
  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.")
39231
+ var MetaStatusInput = z149.object({
39232
+ campaignId: z149.string().min(1).describe("Meta campaign ID returned by publish_to_meta.")
38898
39233
  });
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.")
39234
+ var TikTokStatusInput = z149.object({
39235
+ campaignId: z149.string().min(1).describe("TikTok campaign ID returned by publish_to_tiktok."),
39236
+ advertiserId: z149.string().min(1).describe("The same advertiserId used to publish the campaign.")
38902
39237
  });
38903
39238
  function registerCampaignTools(server, api) {
38904
39239
  server.registerTool("get_meta_campaign_status", {
@@ -39003,10 +39338,10 @@ function renderTikTokStatusMarkdown(r) {
39003
39338
  __name(renderTikTokStatusMarkdown, "renderTikTokStatusMarkdown");
39004
39339
 
39005
39340
  // ../../packages/product-economics/src/product-costing.ts
39006
- import { z as z150 } from "zod";
39341
+ import { z as z151 } from "zod";
39007
39342
 
39008
39343
  // ../../packages/product-economics/src/product-costing-countries.ts
39009
- import { z as z149 } from "zod";
39344
+ import { z as z150 } from "zod";
39010
39345
 
39011
39346
  // ../../packages/product-economics/src/product-costing-shipping-references.json
39012
39347
  var product_costing_shipping_references_default = {
@@ -39070,21 +39405,21 @@ var product_costing_shipping_references_default = {
39070
39405
  };
39071
39406
 
39072
39407
  // ../../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([
39408
+ var ShippingReferenceSchema = z150.object({
39409
+ reference: z150.number().positive(),
39410
+ min: z150.number().nonnegative(),
39411
+ max: z150.number().positive(),
39412
+ updatedAt: z150.string().date(),
39413
+ confidence: z150.enum([
39079
39414
  "high",
39080
39415
  "medium",
39081
39416
  "medium-low"
39082
39417
  ]),
39083
- sources: z149.array(z149.string().url().startsWith("https://")).min(1).max(3)
39418
+ sources: z150.array(z150.string().url().startsWith("https://")).min(1).max(3)
39084
39419
  }).superRefine(({ reference, min, max }, context) => {
39085
39420
  if (min > reference || reference > max) {
39086
39421
  context.addIssue({
39087
- code: z149.ZodIssueCode.custom,
39422
+ code: z150.ZodIssueCode.custom,
39088
39423
  message: "shipping_reference_outside_range",
39089
39424
  path: [
39090
39425
  "reference"
@@ -39092,7 +39427,7 @@ var ShippingReferenceSchema = z149.object({
39092
39427
  });
39093
39428
  }
39094
39429
  });
39095
- var SHIPPING_REFERENCES = z149.record(ShippingReferenceSchema).parse(product_costing_shipping_references_default);
39430
+ var SHIPPING_REFERENCES = z150.record(ShippingReferenceSchema).parse(product_costing_shipping_references_default);
39096
39431
  var PRODUCT_COSTING_COUNTRY_CODES = Object.freeze(Object.keys(SHIPPING_REFERENCES));
39097
39432
  var SPANISH_LOCALE_COUNTRIES = /* @__PURE__ */ new Set([
39098
39433
  "MX",
@@ -39274,18 +39609,18 @@ function getProductCostingCountryName(country) {
39274
39609
  __name(getProductCostingCountryName, "getProductCostingCountryName");
39275
39610
 
39276
39611
  // ../../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")
39612
+ var ProductCostingInputSchema = z151.object({
39613
+ country: z151.string().refine(isProductCostingCountryCode, "unsupported_country"),
39614
+ supplierCost: z151.number().finite().max(Number.MAX_SAFE_INTEGER, "amount_too_large").positive("supplier_cost_positive"),
39615
+ shippingCost: z151.number().finite().max(Number.MAX_SAFE_INTEGER, "amount_too_large").nonnegative("shipping_cost_nonnegative"),
39616
+ returnRate: z151.number().finite().min(0, "rate_nonnegative").lt(1, "rate_below_one"),
39617
+ cancellationRate: z151.number().finite().min(0, "rate_nonnegative").lt(1, "rate_below_one"),
39618
+ adsManagerCpa: z151.number().finite().max(Number.MAX_SAFE_INTEGER, "amount_too_large").nonnegative("cpa_nonnegative"),
39619
+ desiredMargin: z151.number().finite().min(0, "margin_nonnegative").lt(1, "margin_below_one")
39285
39620
  }).superRefine(({ returnRate, cancellationRate }, context) => {
39286
39621
  if (returnRate + cancellationRate >= 1) {
39287
39622
  context.addIssue({
39288
- code: z150.ZodIssueCode.custom,
39623
+ code: z151.ZodIssueCode.custom,
39289
39624
  message: "delivery_rate_positive",
39290
39625
  path: [
39291
39626
  "cancellationRate"
@@ -39382,15 +39717,15 @@ function calculateProductCosting(rawInput) {
39382
39717
  __name(calculateProductCosting, "calculateProductCosting");
39383
39718
 
39384
39719
  // src/tools/product-costing.ts
39385
- import { z as z151 } from "zod";
39720
+ import { z as z152 } from "zod";
39386
39721
  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, {
39722
+ var optionalAmount = /* @__PURE__ */ __name((description) => z152.number().finite().max(Number.MAX_SAFE_INTEGER).nonnegative().optional().describe(description), "optionalAmount");
39723
+ var optionalPercent = /* @__PURE__ */ __name((description) => z152.number().finite().min(0).lt(100).optional().describe(description), "optionalPercent");
39724
+ var ProductCostingToolInput = z152.object({
39725
+ country: z152.string().trim().toUpperCase().refine(isProductCostingCountryCode, {
39391
39726
  message: "Pa\xEDs no soportado por ProductMaker."
39392
39727
  }).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."),
39728
+ 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
39729
  supplierCostCop: optionalAmount("Alias anterior para costo del proveedor en Colombia. Prefiere supplierCost."),
39395
39730
  shippingCost: optionalAmount("Costo de un env\xEDo en la moneda del pa\xEDs. Omite para usar el valor gu\xEDa vigente."),
39396
39731
  shippingCostCop: optionalAmount("Alias anterior para flete en Colombia. Prefiere shippingCost."),
@@ -39553,7 +39888,7 @@ function formatPercent(value) {
39553
39888
  __name(formatPercent, "formatPercent");
39554
39889
 
39555
39890
  // src/tools/product-research.ts
39556
- import { z as z152 } from "zod";
39891
+ import { z as z153 } from "zod";
39557
39892
 
39558
39893
  // src/tools/product-research-idempotency.ts
39559
39894
  var RESEARCH_IDEMPOTENCY_WINDOW_MS = 2 * 60 * 60 * 1e3;
@@ -39924,17 +40259,17 @@ var TERMINAL2 = /* @__PURE__ */ new Set([
39924
40259
  ]);
39925
40260
  var POLL_INTERVAL_MS = 5e3;
39926
40261
  var MAX_WAIT_SECONDS = 120;
39927
- var FindInput = z152.object({
40262
+ var FindInput = z153.object({
39928
40263
  targetCountry: ResearchTargetCountryEnum.describe("Pa\xEDs objetivo. S\xF3lo CO, MX, PE, EC y CL tienen cobertura de investigaci\xF3n."),
39929
40264
  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.")
40265
+ 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.`),
40266
+ 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
40267
  });
39933
- var GetInput = z152.object({
39934
- runId: z152.string().uuid().describe("ID del run devuelto por find_winning_products.")
40268
+ var GetInput = z153.object({
40269
+ runId: z153.string().uuid().describe("ID del run devuelto por find_winning_products.")
39935
40270
  });
39936
- var ListInput = z152.object({
39937
- limit: z152.number().int().min(1).max(20).default(10)
40271
+ var ListInput = z153.object({
40272
+ limit: z153.number().int().min(1).max(20).default(10)
39938
40273
  });
39939
40274
  function textResult(text4) {
39940
40275
  return {
@@ -40073,17 +40408,17 @@ _Esta llamada no cobr\xF3 cr\xE9ditos: se reutiliz\xF3 una investigaci\xF3n reci
40073
40408
  __name(registerProductResearchTools, "registerProductResearchTools");
40074
40409
 
40075
40410
  // src/tools/dropshipping-projects.ts
40076
- import { z as z153 } from "zod";
40411
+ import { z as z154 } from "zod";
40077
40412
  var startDropshippingProjectGuidance = getProductMakerCapabilityModelGuidance("start_dropshipping_project");
40078
40413
  var costDropshippingProjectGuidance = getProductMakerCapabilityModelGuidance("cost_dropshipping_project");
40079
40414
  var guideDropshippingLaunchGuidance = getProductMakerCapabilityModelGuidance("guide_dropshipping_launch");
40080
- var ProjectId = z153.string().uuid();
40081
- var DropshippingProjectStatus = z153.enum([
40415
+ var ProjectId = z154.string().uuid();
40416
+ var DropshippingProjectStatus = z154.enum([
40082
40417
  "active",
40083
40418
  "completed",
40084
40419
  "archived"
40085
40420
  ]);
40086
- var DropshippingProjectPhase = z153.enum([
40421
+ var DropshippingProjectPhase = z154.enum([
40087
40422
  "research",
40088
40423
  "costing",
40089
40424
  "media",
@@ -40092,21 +40427,21 @@ var DropshippingProjectPhase = z153.enum([
40092
40427
  "advertising",
40093
40428
  "complete"
40094
40429
  ]);
40095
- var DropshippingRewardMilestone = z153.enum([
40430
+ var DropshippingRewardMilestone = z154.enum([
40096
40431
  "product_selected",
40097
40432
  "costing_saved",
40098
40433
  "product_media_ready",
40099
40434
  "creatives_completed",
40100
40435
  "campaign_created"
40101
40436
  ]);
40102
- var TargetCountry = z153.enum([
40437
+ var TargetCountry = z154.enum([
40103
40438
  "CO",
40104
40439
  "MX",
40105
40440
  "PE",
40106
40441
  "EC",
40107
40442
  "CL"
40108
40443
  ]);
40109
- var ResearchCategory = z153.enum([
40444
+ var ResearchCategory = z154.enum([
40110
40445
  "auto",
40111
40446
  "automotive",
40112
40447
  "kitchen",
@@ -40121,7 +40456,7 @@ var ResearchCategory = z153.enum([
40121
40456
  "garden",
40122
40457
  "cleaning"
40123
40458
  ]);
40124
- var HttpUrl3 = z153.string().url().max(2e3).refine((value) => {
40459
+ var HttpUrl3 = z154.string().url().max(2e3).refine((value) => {
40125
40460
  try {
40126
40461
  return [
40127
40462
  "http:",
@@ -40131,45 +40466,45 @@ var HttpUrl3 = z153.string().url().max(2e3).refine((value) => {
40131
40466
  return false;
40132
40467
  }
40133
40468
  }, "URL must use HTTP(S)");
40134
- var ResearchCandidateOrigin = z153.object({
40135
- type: z153.literal("research_candidate"),
40469
+ var ResearchCandidateOrigin = z154.object({
40470
+ type: z154.literal("research_candidate"),
40136
40471
  runId: ProjectId,
40137
- candidateSourceId: z153.string().min(1).max(160)
40472
+ candidateSourceId: z154.string().min(1).max(160)
40138
40473
  });
40139
- var TaskOrigin = z153.object({
40140
- type: z153.literal("task"),
40474
+ var TaskOrigin = z154.object({
40475
+ type: z154.literal("task"),
40141
40476
  taskId: ProjectId
40142
40477
  });
40143
- var ManualOrigin = z153.object({
40144
- type: z153.literal("manual"),
40478
+ var ManualOrigin = z154.object({
40479
+ type: z154.literal("manual"),
40145
40480
  clientRequestId: ProjectId,
40146
- country: z153.string().min(2).max(3).optional()
40481
+ country: z154.string().min(2).max(3).optional()
40147
40482
  });
40148
- var StartProjectInput = z153.object({
40149
- origin: z153.discriminatedUnion("type", [
40483
+ var StartProjectInput = z154.object({
40484
+ origin: z154.discriminatedUnion("type", [
40150
40485
  ResearchCandidateOrigin,
40151
40486
  TaskOrigin,
40152
40487
  ManualOrigin
40153
40488
  ]),
40154
- productName: z153.string().trim().min(1).max(240).optional(),
40489
+ productName: z154.string().trim().min(1).max(240).optional(),
40155
40490
  productUrl: HttpUrl3.optional(),
40156
- country: z153.string().min(2).max(3).optional(),
40157
- languageCode: z153.string().min(2).max(16).optional()
40491
+ country: z154.string().min(2).max(3).optional(),
40492
+ languageCode: z154.string().min(2).max(16).optional()
40158
40493
  }).strict();
40159
- var CostProjectInput = z153.object({
40494
+ var CostProjectInput = z154.object({
40160
40495
  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, {
40496
+ version: z154.number().int().positive().describe("Version le\xEDda del proyecto. Copia la \xFAltima que viste; no se la pidas al usuario."),
40497
+ country: z154.string().trim().toUpperCase().refine(isProductCostingCountryCode, {
40163
40498
  message: "Pa\xEDs no soportado por ProductMaker."
40164
40499
  }).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({
40500
+ 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."),
40501
+ 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."),
40502
+ 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."),
40503
+ 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."),
40504
+ adsManagerCpa: z154.number().finite().nonnegative().optional().describe("Costo por compra de Ads Manager. Omite para usar el valor gu\xEDa del pa\xEDs."),
40505
+ 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.")
40506
+ });
40507
+ var GuideInput = z154.object({
40173
40508
  projectId: ProjectId.optional()
40174
40509
  }).strict();
40175
40510
  var NEXT_ACTION_KINDS = [
@@ -40187,29 +40522,29 @@ var NEXT_ACTION_KINDS = [
40187
40522
  "repair_meta_campaign",
40188
40523
  "review_paused_campaign"
40189
40524
  ];
40190
- var DropshippingProjectNextAction = z153.discriminatedUnion("kind", NEXT_ACTION_KINDS.map((kind2) => z153.object({
40191
- kind: z153.literal(kind2),
40192
- href: z153.string().min(1)
40525
+ var DropshippingProjectNextAction = z154.discriminatedUnion("kind", NEXT_ACTION_KINDS.map((kind2) => z154.object({
40526
+ kind: z154.literal(kind2),
40527
+ href: z154.string().min(1)
40193
40528
  })));
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)
40529
+ var CostingInputSchema = z154.object({
40530
+ country: z154.string().min(2).max(3),
40531
+ supplierCost: z154.number().finite().positive(),
40532
+ shippingCost: z154.number().finite().nonnegative(),
40533
+ returnRate: z154.number().finite().min(0).lt(1),
40534
+ cancellationRate: z154.number().finite().min(0).lt(1),
40535
+ adsManagerCpa: z154.number().finite().nonnegative(),
40536
+ desiredMargin: z154.number().finite().min(0).lt(1)
40202
40537
  }).superRefine(({ returnRate, cancellationRate }, context) => {
40203
40538
  if (returnRate + cancellationRate >= 1) context.addIssue({
40204
- code: z153.ZodIssueCode.custom,
40539
+ code: z154.ZodIssueCode.custom,
40205
40540
  path: [
40206
40541
  "cancellationRate"
40207
40542
  ]
40208
40543
  });
40209
40544
  });
40210
- var FiniteCostingNumber = z153.number().finite();
40211
- var CostingResultSchema = z153.object({
40212
- country: z153.string().min(2).max(3),
40545
+ var FiniteCostingNumber = z154.number().finite();
40546
+ var CostingResultSchema = z154.object({
40547
+ country: z154.string().min(2).max(3),
40213
40548
  deliveryRate: FiniteCostingNumber,
40214
40549
  adjustedShippingCost: FiniteCostingNumber,
40215
40550
  adjustedCpa: FiniteCostingNumber,
@@ -40222,45 +40557,45 @@ var CostingResultSchema = z153.object({
40222
40557
  estimatedProfit: FiniteCostingNumber,
40223
40558
  actualMargin: FiniteCostingNumber,
40224
40559
  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({
40560
+ wasPriceAutoAdjusted: z154.boolean(),
40561
+ profitabilityStatus: z154.literal("healthy")
40562
+ });
40563
+ var RewardProgressSchema = z154.object({
40564
+ campaignKey: z154.literal("dropshipping-launch-v1"),
40565
+ totalCredits: z154.number().int().positive(),
40566
+ earnedCredits: z154.number().int().nonnegative(),
40567
+ milestones: z154.array(z154.object({
40233
40568
  milestone: DropshippingRewardMilestone,
40234
- credits: z153.number().int().positive(),
40235
- available: z153.boolean().optional(),
40236
- earned: z153.boolean(),
40569
+ credits: z154.number().int().positive(),
40570
+ available: z154.boolean().optional(),
40571
+ earned: z154.boolean(),
40237
40572
  grantId: ProjectId.nullable(),
40238
- grantedAt: z153.string().datetime().nullable()
40573
+ grantedAt: z154.string().datetime().nullable()
40239
40574
  })).length(DropshippingRewardMilestone.options.length)
40240
40575
  });
40241
- var ProjectResponseSchema = z153.object({
40576
+ var ProjectResponseSchema = z154.object({
40242
40577
  id: ProjectId,
40243
40578
  taskId: ProjectId.nullable(),
40244
40579
  status: DropshippingProjectStatus,
40245
- originKey: z153.string().min(1),
40580
+ originKey: z154.string().min(1),
40246
40581
  researchRunId: ProjectId.nullable(),
40247
- candidateSourceId: z153.string().nullable(),
40248
- productName: z153.string().nullable(),
40582
+ candidateSourceId: z154.string().nullable(),
40583
+ productName: z154.string().nullable(),
40249
40584
  productUrl: HttpUrl3.nullable(),
40250
40585
  referenceImageUrl: HttpUrl3.nullable(),
40251
- targetCountry: z153.string().nullable(),
40252
- languageCode: z153.string().nullable(),
40253
- hasReferenceImage: z153.boolean(),
40254
- hasWinnerReferenceImage: z153.boolean(),
40586
+ targetCountry: z154.string().nullable(),
40587
+ languageCode: z154.string().nullable(),
40588
+ hasReferenceImage: z154.boolean(),
40589
+ hasWinnerReferenceImage: z154.boolean(),
40255
40590
  costingInput: CostingInputSchema.nullable(),
40256
40591
  costingResult: CostingResultSchema.nullable(),
40257
- costingVersion: z153.number().int().positive().nullable(),
40258
- version: z153.number().int().positive(),
40592
+ costingVersion: z154.number().int().positive().nullable(),
40593
+ version: z154.number().int().positive(),
40259
40594
  phase: DropshippingProjectPhase,
40260
40595
  nextAction: DropshippingProjectNextAction.nullable(),
40261
40596
  rewards: RewardProgressSchema,
40262
- createdAt: z153.string().datetime(),
40263
- updatedAt: z153.string().datetime()
40597
+ createdAt: z154.string().datetime(),
40598
+ updatedAt: z154.string().datetime()
40264
40599
  }).passthrough();
40265
40600
  var NEXT_ACTION_GUIDANCE = {
40266
40601
  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 +40715,58 @@ function registerDropshippingProjectTools(server, api) {
40380
40715
  __name(registerDropshippingProjectTools, "registerDropshippingProjectTools");
40381
40716
 
40382
40717
  // src/tools/daily-control.ts
40383
- import { z as z154 } from "zod";
40718
+ import { z as z155 } from "zod";
40384
40719
  var getDailyControlGuidance = getProductMakerCapabilityModelGuidance("get_daily_control");
40385
40720
  var linkCampaignToProjectGuidance = getProductMakerCapabilityModelGuidance("link_campaign_to_project");
40386
40721
  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({
40722
+ var ProviderCampaignIdentifier = z155.string().min(1).max(64).regex(/^[A-Za-z0-9_-]+$/);
40723
+ var DateOnly = z155.string().regex(/^\d{4}-\d{2}-\d{2}$/);
40724
+ var DailyControlInput = z155.object({
40390
40725
  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.")
40726
+ days: z155.number().int().min(1).max(14).default(3).describe("Tama\xF1o de la ventana, 1 a 14 d\xEDas."),
40727
+ projectId: z155.string().uuid().optional().describe("Opcional: limita el reporte a un solo proyecto en vez de todos los productos activos.")
40393
40728
  }).strict();
40394
- var LinkCampaignInput = z154.object({
40395
- projectId: z154.string().uuid(),
40396
- provider: z154.enum([
40729
+ var LinkCampaignInput = z155.object({
40730
+ projectId: z155.string().uuid(),
40731
+ provider: z155.enum([
40397
40732
  "meta",
40398
40733
  "tiktok"
40399
40734
  ]),
40400
40735
  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
40736
  campaignId: ProviderCampaignIdentifier
40402
40737
  }).strict();
40403
- var ReportReturnsInput = z154.object({
40404
- projectId: z154.string().uuid(),
40738
+ var ReportReturnsInput = z155.object({
40739
+ projectId: z155.string().uuid(),
40405
40740
  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(),
40741
+ returnedOrders: z155.number().int().min(0).max(1e5),
40742
+ note: z155.string().trim().max(280).optional()
40743
+ }).strict();
40744
+ var DailyControlStatusMirror = DailyControlStatusEnum.or(z155.string());
40745
+ var DailyControlLightMirror = DailyControlLightEnum.or(z155.string());
40746
+ var DailyControlSourceMirror = DailyControlSourceEnum.or(z155.string());
40747
+ var DailyControlVerdictMirror = DailyControlVerdictEnum.or(z155.string());
40748
+ var DailyControlDayMirror = z155.object({
40749
+ date: z155.string(),
40750
+ returnedOrders: z155.number(),
40416
40751
  returnedSource: DailyControlSourceMirror,
40417
40752
  cancelledSource: DailyControlSourceMirror,
40418
- hasEstimates: z154.boolean(),
40753
+ hasEstimates: z155.boolean(),
40419
40754
  light: DailyControlLightMirror
40420
40755
  }).passthrough();
40421
- var DailyControlProductMirror = z154.object({
40422
- projectId: z154.string().uuid(),
40423
- productName: z154.string().nullable(),
40756
+ var DailyControlProductMirror = z155.object({
40757
+ projectId: z155.string().uuid(),
40758
+ productName: z155.string().nullable(),
40424
40759
  status: DailyControlStatusMirror,
40425
- statusMessage: z154.string().nullable(),
40426
- days: z154.array(DailyControlDayMirror),
40760
+ statusMessage: z155.string().nullable(),
40761
+ days: z155.array(DailyControlDayMirror),
40427
40762
  verdict: DailyControlVerdictMirror.nullable(),
40428
- rationale: z154.string().nullable()
40763
+ rationale: z155.string().nullable()
40429
40764
  }).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())
40765
+ var DailyControlResponseMirror = z155.object({
40766
+ since: z155.string(),
40767
+ until: z155.string(),
40768
+ products: z155.array(DailyControlProductMirror),
40769
+ archivedProjectSpend: z155.array(z155.unknown())
40435
40770
  }).passthrough();
40436
40771
  var MCP_WARNINGS_KEY = "_mcpDailyControlWarnings";
40437
40772
  function withWarnings(response) {
@@ -40507,7 +40842,7 @@ __name(registerDailyControlTools, "registerDailyControlTools");
40507
40842
  import { createHash as createHash5 } from "crypto";
40508
40843
  import { readFile, stat } from "fs/promises";
40509
40844
  import { basename as basename2, isAbsolute, resolve as resolve3 } from "path";
40510
- import { z as z155 } from "zod";
40845
+ import { z as z156 } from "zod";
40511
40846
  var getProductControlGuidance = getProductMakerCapabilityModelGuidance("get_product_control");
40512
40847
  var listImportableProductsGuidance = getProductMakerCapabilityModelGuidance("list_importable_products");
40513
40848
  var importProductControlsGuidance = getProductMakerCapabilityModelGuidance("import_product_controls");
@@ -40529,42 +40864,42 @@ var listLinkableCampaignsGuidance = getProductMakerCapabilityModelGuidance("list
40529
40864
  var linkProductCampaignGuidance = getProductMakerCapabilityModelGuidance("link_product_campaign");
40530
40865
  var deleteMonthlyExpenseGuidance = getProductMakerCapabilityModelGuidance("delete_monthly_expense");
40531
40866
  var removeProductControlGuidance = getProductMakerCapabilityModelGuidance("remove_product_control");
40532
- var DateOnly2 = z155.string().regex(/^\d{4}-\d{2}-\d{2}$/);
40867
+ var DateOnly2 = z156.string().regex(/^\d{4}-\d{2}-\d{2}$/);
40533
40868
  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."),
40869
+ var ProductControlListInput = z156.object({
40870
+ days: z156.number().int().min(1).max(14).default(3).describe("Tama\xF1o de la ventana, 1 a 14 d\xEDas."),
40536
40871
  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.")
40872
+ shopDomain: z156.string().min(1).optional().describe("Tienda, si tiene m\xE1s de una conectada.")
40538
40873
  }).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()
40874
+ var ImportableProductsInput = z156.object({
40875
+ q: z156.string().trim().max(120).optional().describe("B\xFAsqueda por t\xEDtulo. Sin esto un cat\xE1logo grande es inrecorrible."),
40876
+ after: z156.string().max(512).optional().describe("Cursor de la p\xE1gina anterior, tal como vino en la respuesta."),
40877
+ shopDomain: z156.string().min(1).optional()
40543
40878
  }).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."),
40879
+ var ImportProductControlsInput = z156.object({
40880
+ 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
40881
  // Obligatorio, como en el endpoint: con dos tiendas conectadas, importar
40547
40882
  // sin decir a cuál archivaría el producto bajo la tienda equivocada.
40548
- shopDomain: z155.string().min(1)
40883
+ shopDomain: z156.string().min(1)
40549
40884
  }).strict();
40550
- var SetProductCostingInput = z155.object({
40885
+ var SetProductCostingInput = z156.object({
40551
40886
  /* La identidad, de UNA forma: la del producto nuestro, o la pareja
40552
40887
  tienda + id de Shopify. El esquema canónico también las admite juntas,
40553
40888
  pero pedirlas por separado evita que el modelo emita una mezcla que
40554
40889
  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."),
40890
+ 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."),
40891
+ 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."),
40892
+ shopDomain: z156.string().min(1).optional(),
40893
+ shopifyProductId: z156.string().min(1).max(64).optional(),
40894
+ targetCountry: z156.string().length(2).describe("Pa\xEDs de venta en ISO-2, que fija moneda y referencia de flete."),
40895
+ supplierCost: z156.number().finite().positive().describe("Lo que le cuesta el producto al proveedor. Nunca cero."),
40896
+ shippingCost: z156.number().finite().nonnegative(),
40897
+ returnRate: z156.number().finite().min(0).lt(1).describe("Proporci\xF3n 0\u20131, no porcentaje: 0.15 es quince por ciento."),
40898
+ cancellationRate: z156.number().finite().min(0).lt(1)
40899
+ }).strict();
40900
+ var Count = z156.number().int().min(0).max(1e5).nullable().optional();
40901
+ var ReportProductCountsInput = z156.object({
40902
+ productControlId: z156.string().uuid().describe("El identificador que devuelve get_product_control."),
40568
40903
  date: DateOnly2,
40569
40904
  orders: Count.describe("Pedidos que entraron ese d\xEDa."),
40570
40905
  cancelledOrders: Count,
@@ -40574,64 +40909,64 @@ var ReportProductCountsInput = z155.object({
40574
40909
  }).strict();
40575
40910
  var MAX_ORDERS_FILE_BYTES = 8 * 1024 * 1024;
40576
40911
  var SyncShopifyOrdersInput = SyncShopifyOrdersSchema;
40577
- var ImportOrdersFileInput = z155.object({
40912
+ var ImportOrdersFileInput = z156.object({
40578
40913
  /** Ruta ABSOLUTA en la máquina de quien usa el MCP. */
40579
- path: z155.string().min(1),
40580
- operationId: z155.string().uuid().optional(),
40914
+ path: z156.string().min(1),
40915
+ operationId: z156.string().uuid().optional(),
40581
40916
  /** Solo mirar: devuelve el resumen y no escribe nada. */
40582
- dryRun: z155.boolean().optional()
40917
+ dryRun: z156.boolean().optional()
40583
40918
  }).strict();
40584
- var CreateOperationInput = z155.object({
40585
- countryCode: z155.string().length(2),
40586
- label: z155.string().trim().min(1).max(60).optional()
40919
+ var CreateOperationInput = z156.object({
40920
+ countryCode: z156.string().length(2),
40921
+ label: z156.string().trim().min(1).max(60).optional()
40587
40922
  }).strict();
40588
- var MergeProductsInput = z155.object({
40589
- sourceId: z155.string().uuid(),
40590
- targetId: z155.string().uuid()
40923
+ var MergeProductsInput = z156.object({
40924
+ sourceId: z156.string().uuid(),
40925
+ targetId: z156.string().uuid()
40591
40926
  }).strict();
40592
- var SetMonthlyExpenseInput = z155.object({
40593
- month: z155.string().regex(/^\d{4}-(0[1-9]|1[0-2])$/).optional(),
40927
+ var SetMonthlyExpenseInput = z156.object({
40928
+ month: z156.string().regex(/^\d{4}-(0[1-9]|1[0-2])$/).optional(),
40594
40929
  label: SaveMonthlyExpenseSchema.shape.label,
40595
- amount: z155.number().finite().min(0),
40596
- operationId: z155.string().uuid().optional()
40930
+ amount: z156.number().finite().min(0),
40931
+ operationId: z156.string().uuid().optional()
40597
40932
  }).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(),
40933
+ var SetAdSpendInput = z156.object({
40934
+ month: z156.string().regex(/^\d{4}-(0[1-9]|1[0-2])$/).optional(),
40935
+ operationId: z156.string().uuid().optional(),
40601
40936
  /** null RETIRA la corrección; cero afirma que no pautó. */
40602
- amount: z155.number().finite().min(0).nullable()
40937
+ amount: z156.number().finite().min(0).nullable()
40603
40938
  }).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.")
40939
+ var GetFinancesInput = z156.object({
40940
+ 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."),
40941
+ operationId: z156.string().uuid().optional().describe("El negocio, si tiene mas de uno. Con uno solo no hace falta decirlo.")
40607
40942
  }).strict();
40608
40943
  var GetDropiStatusInput = DropiCountryQuerySchema.extend({
40609
40944
  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.")
40945
+ 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
40946
  });
40612
40947
  var SyncDropiOrdersInput = SyncDropiFinancialSchema;
40613
40948
  var CreateManualProductInput = CreateManualProductControlSchema.innerType().omit({
40614
40949
  requestId: true
40615
40950
  });
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.")
40951
+ var ListUnmatchedProductsInput = z156.object({
40952
+ operationId: z156.string().uuid().optional().describe("El negocio, si tiene mas de uno. Con uno solo no hace falta decirlo.")
40618
40953
  }).strict();
40619
40954
  var ResolveUnmatchedProductInput = ResolveUnmatchedProductSchema;
40620
- var ListLinkableCampaignsInput = z155.object({}).strict();
40621
- var LinkProductCampaignInput = z155.object({
40622
- provider: z155.enum([
40955
+ var ListLinkableCampaignsInput = z156.object({}).strict();
40956
+ var LinkProductCampaignInput = z156.object({
40957
+ provider: z156.enum([
40623
40958
  "meta",
40624
40959
  "tiktok"
40625
40960
  ]).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.")
40961
+ accountId: z156.string().min(1).max(64).regex(/^[A-Za-z0-9_-]+$/).describe("La cuenta publicitaria. Sale de list_linkable_campaigns; nunca se inventa."),
40962
+ campaignId: z156.string().min(1).max(64).regex(/^[A-Za-z0-9_-]+$/).describe("La campa\xF1a. Sale de list_linkable_campaigns; nunca se inventa."),
40963
+ productControlId: z156.string().uuid().optional().describe("El id del producto en su control financiero."),
40964
+ shopDomain: z156.string().min(1).optional().describe("La tienda del producto; con shopifyProductId es la otra forma de decir cu\xE1l es."),
40965
+ shopifyProductId: z156.string().min(1).optional().describe("El id del producto en su tienda. Va junto a shopDomain.")
40631
40966
  }).strict();
40632
40967
  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.")
40968
+ var RemoveProductControlInput = z156.object({
40969
+ productControlId: z156.string().uuid().describe("El id del producto en su control. Sale del listado; nunca se inventa.")
40635
40970
  }).strict();
40636
40971
  function manualProductRequestId(input) {
40637
40972
  const digest3 = createHash5("sha256").update(JSON.stringify(input, [
@@ -40972,28 +41307,28 @@ function registerProductControlTools(server, api, { transport = "http" } = {}) {
40972
41307
  __name(registerProductControlTools, "registerProductControlTools");
40973
41308
 
40974
41309
  // src/tools/market-study-estimate.ts
40975
- import { z as z156 } from "zod";
41310
+ import { z as z157 } from "zod";
40976
41311
  var estimateMarketStudyGuidance = getProductMakerCapabilityModelGuidance("estimate_market_study_cost");
40977
- var ProductTaskOutputEnum = z156.enum([
41312
+ var ProductTaskOutputEnum = z157.enum([
40978
41313
  "landing",
40979
41314
  "video",
40980
41315
  "image"
40981
41316
  ]);
40982
- var EstimateMarketStudyInput = z156.object({
40983
- kind: z156.enum([
41317
+ var EstimateMarketStudyInput = z157.object({
41318
+ kind: z157.enum([
40984
41319
  "video",
40985
41320
  "product_task"
40986
41321
  ]).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."),
41322
+ videoAngleCount: z157.number().int().min(1).max(3).describe("Los mismos 1, 2 o 3 videos que vas a pedir al crear."),
41323
+ durationSeconds: z157.number().int().min(4).max(30).default(15).describe("La misma duraci\xF3n que enviar\xE1s al crear."),
40989
41324
  videoStructure: VideoStructureEnum.default("single_take"),
40990
41325
  videoQuality: VideoQualityEnum.default("economy"),
40991
41326
  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)."),
41327
+ productImageCount: z157.number().int().min(1).max(5).optional().describe("Cantidad de fotos del producto que adjuntar\xE1s (1-5)."),
40993
41328
  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(),
41329
+ generateAiMusic: z157.boolean().optional(),
40995
41330
  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.")
41331
+ 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
41332
  }).strict();
40998
41333
  function buildMarketStudyEstimateRequests(input) {
40999
41334
  const quotesVideo = input.kind === "video" || (input.outputs ?? [
@@ -41131,7 +41466,7 @@ function createServer(opts) {
41131
41466
  signal: opts.abortSignal
41132
41467
  });
41133
41468
  registerProductMakerPrompts(server);
41134
- registerMediaUploadAppResource(server, api.publicUrl("/v1/uploaded-assets/sessions/resolve"));
41469
+ registerMediaUploadAppResource(server, api.publicUrl("/v1/uploaded-assets/sessions/resolve"), opts.mediaUploadStorageOrigins);
41135
41470
  registerTaskResultsAppResource(server, proxyBaseUrl);
41136
41471
  registerProductCostingTools(server);
41137
41472
  registerProductResearchTools(server, api);
@@ -41145,6 +41480,7 @@ function createServer(opts) {
41145
41480
  registerServiceImageTools(server, api);
41146
41481
  registerAnimatedVideoTools(server, api);
41147
41482
  registerCreativeTools(server, api, {
41483
+ proxyBaseUrl,
41148
41484
  transport
41149
41485
  });
41150
41486
  registerCreativeVariantTools(server, api);
@@ -41165,26 +41501,6 @@ function createServer(opts) {
41165
41501
  }
41166
41502
  __name(createServer, "createServer");
41167
41503
 
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
41504
  // src/http.ts
41189
41505
  var ALLOWED_ORIGINS = /* @__PURE__ */ new Set([
41190
41506
  "https://chat.openai.com",
@@ -41391,6 +41707,7 @@ function createAuthenticatedMcpHandler(opts, oauthAnalytics) {
41391
41707
  credential,
41392
41708
  apiBaseUrl: opts.apiBaseUrl,
41393
41709
  proxyBaseUrl: opts.proxyBaseUrl,
41710
+ mediaUploadStorageOrigins: opts.mediaUploadStorageOrigins,
41394
41711
  transport: "http",
41395
41712
  instructions: buildServerInstructions(legacySignal),
41396
41713
  abortSignal: abortController.signal
@@ -41540,6 +41857,7 @@ async function startHttpServer(env = process.env) {
41540
41857
  const proxyBaseUrl = env.MCP_PROXY_BASE_URL ?? "https://mcp.productmaker.app";
41541
41858
  assertSafePublicBaseUrl("PM_API_URL", apiBaseUrl);
41542
41859
  assertSafePublicBaseUrl("MCP_PROXY_BASE_URL", proxyBaseUrl);
41860
+ const mediaUploadStorageOrigins = parseMediaUploadStorageOrigins(env.PM_MEDIA_UPLOAD_STORAGE_ORIGINS);
41543
41861
  const oauthConfig = loadMcpOAuthConfig(env);
41544
41862
  const auth0Metadata = await loadAuth0Metadata(oauthConfig).catch((error) => {
41545
41863
  oauthAnalytics.capture(MCP_OAUTH_ANALYTICS_EVENTS.FAILED, null, {
@@ -41552,6 +41870,7 @@ async function startHttpServer(env = process.env) {
41552
41870
  const app = createApp({
41553
41871
  apiBaseUrl,
41554
41872
  proxyBaseUrl,
41873
+ mediaUploadStorageOrigins,
41555
41874
  oauth,
41556
41875
  appsChallengeToken: env.OPENAI_APPS_CHALLENGE_TOKEN ?? null
41557
41876
  });