@productmaker/mcp 11.2.1 → 11.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +12 -0
- package/dist/http.js +1363 -473
- package/dist/stdio.js +1363 -473
- package/package.json +1 -1
package/dist/stdio.js
CHANGED
|
@@ -15706,6 +15706,54 @@ var CAPABILITY_MODEL_GUIDANCE = {
|
|
|
15706
15706
|
"es trabajo pagado: se cobra el m\xE1ximo del rango de su duraci\xF3n (m\xE1s cada versi\xF3n extra del gancho) y al terminar se devuelve lo que no use; requiere confirmaci\xF3n expl\xEDcita; tarda varios minutos"
|
|
15707
15707
|
]
|
|
15708
15708
|
},
|
|
15709
|
+
create_brand: {
|
|
15710
|
+
summary: "Analiza la marca de un negocio de servicios o marca personal desde su Instagram, Facebook, TikTok o web.",
|
|
15711
|
+
useWhen: [
|
|
15712
|
+
"toma mi Instagram y hazme el an\xE1lisis de marca",
|
|
15713
|
+
"la persona comparte el Instagram o la web de su negocio de servicios y quiere su marca, landing o video"
|
|
15714
|
+
],
|
|
15715
|
+
avoidWhen: [
|
|
15716
|
+
"la persona habla de un producto de dropshipping, un proveedor o un enlace de AliExpress o Dropi"
|
|
15717
|
+
],
|
|
15718
|
+
defaults: [
|
|
15719
|
+
"reutiliza una marca existente de la misma direcci\xF3n antes de pagar otro an\xE1lisis"
|
|
15720
|
+
],
|
|
15721
|
+
disclosures: [
|
|
15722
|
+
"es trabajo pagado: muestra el precio y requiere confirmaci\xF3n expl\xEDcita"
|
|
15723
|
+
]
|
|
15724
|
+
},
|
|
15725
|
+
create_service_landing: {
|
|
15726
|
+
summary: "Crea y publica la landing page de un negocio de servicios a partir de su marca.",
|
|
15727
|
+
useWhen: [
|
|
15728
|
+
"hazme la landing page de mi negocio",
|
|
15729
|
+
"la persona tiene una marca de servicios y quiere su p\xE1gina para recibir clientes"
|
|
15730
|
+
],
|
|
15731
|
+
avoidWhen: [
|
|
15732
|
+
"la persona quiere una landing de un producto de dropshipping o de una tarea de producto"
|
|
15733
|
+
],
|
|
15734
|
+
defaults: [
|
|
15735
|
+
"pregunta una sola vez el contacto, la zona y los servicios con precio que falten"
|
|
15736
|
+
],
|
|
15737
|
+
disclosures: [
|
|
15738
|
+
"es trabajo pagado: muestra el precio total y requiere confirmaci\xF3n expl\xEDcita; publicar no cobra"
|
|
15739
|
+
]
|
|
15740
|
+
},
|
|
15741
|
+
create_service_images: {
|
|
15742
|
+
summary: "Crea im\xE1genes para anuncios de un negocio de servicios con su marca, sin foto de producto.",
|
|
15743
|
+
useWhen: [
|
|
15744
|
+
"hazme im\xE1genes para anunciar mi servicio",
|
|
15745
|
+
"la persona quiere im\xE1genes publicitarias de su negocio de servicios"
|
|
15746
|
+
],
|
|
15747
|
+
avoidWhen: [
|
|
15748
|
+
"la persona quiere im\xE1genes de un producto de dropshipping con sus fotos"
|
|
15749
|
+
],
|
|
15750
|
+
defaults: [
|
|
15751
|
+
"una imagen 4:5 de calidad est\xE1ndar con la marca"
|
|
15752
|
+
],
|
|
15753
|
+
disclosures: [
|
|
15754
|
+
"es trabajo pagado: muestra el precio y requiere confirmaci\xF3n expl\xEDcita"
|
|
15755
|
+
]
|
|
15756
|
+
},
|
|
15709
15757
|
edit_image_creative: {
|
|
15710
15758
|
summary: "Corrige UNA imagen ya generada de una tarea de creativos, sin regenerar el set completo.",
|
|
15711
15759
|
useWhen: [
|
|
@@ -18799,6 +18847,92 @@ var PRODUCTMAKER_CAPABILITY_REGISTRY = deepFreeze([
|
|
|
18799
18847
|
"task",
|
|
18800
18848
|
"video"
|
|
18801
18849
|
], MCP_ONLY_CHANNEL_EXPOSURE),
|
|
18850
|
+
// MCP-only service journey (2026-10-11): brand analysis → service landing →
|
|
18851
|
+
// create_animated_video. WhatsApp has no adapter for either, so both are
|
|
18852
|
+
// declared unavailable there and its parity gate is unchanged.
|
|
18853
|
+
defineCapability("create_brand", "7f5680574f38f1005d162c2a2a3805be82896508793ebcdd5491b8459c803158", "credit_consuming", "creative", [
|
|
18854
|
+
"credit_consuming",
|
|
18855
|
+
"state_change"
|
|
18856
|
+
], [
|
|
18857
|
+
slot("brand_action", false, [
|
|
18858
|
+
"action",
|
|
18859
|
+
"brandId",
|
|
18860
|
+
"confirmed"
|
|
18861
|
+
], []),
|
|
18862
|
+
slot("brand_source", false, [
|
|
18863
|
+
"sourceUrl"
|
|
18864
|
+
], []),
|
|
18865
|
+
slot("business_notes", false, [
|
|
18866
|
+
"businessNotes"
|
|
18867
|
+
], []),
|
|
18868
|
+
slot("locale", false, [
|
|
18869
|
+
"language"
|
|
18870
|
+
], [])
|
|
18871
|
+
], [
|
|
18872
|
+
"owner_context"
|
|
18873
|
+
], [
|
|
18874
|
+
"brand"
|
|
18875
|
+
], MCP_ONLY_CHANNEL_EXPOSURE),
|
|
18876
|
+
defineCapability("create_service_landing", "f668f49e8006c610102ecb1760cdd9a238a9750c77aecf1762b58b8474e5495e", "credit_consuming", "creative", [
|
|
18877
|
+
"credit_consuming",
|
|
18878
|
+
"state_change"
|
|
18879
|
+
], [
|
|
18880
|
+
slot("service_landing_action", false, [
|
|
18881
|
+
"action",
|
|
18882
|
+
"servicePageId",
|
|
18883
|
+
"operationId",
|
|
18884
|
+
"confirmed",
|
|
18885
|
+
"maxCredits"
|
|
18886
|
+
], []),
|
|
18887
|
+
slot("brand", false, [
|
|
18888
|
+
"brandId"
|
|
18889
|
+
], []),
|
|
18890
|
+
slot("business_info", false, [
|
|
18891
|
+
"businessInfo"
|
|
18892
|
+
], []),
|
|
18893
|
+
slot("slug", false, [
|
|
18894
|
+
"slug"
|
|
18895
|
+
], [])
|
|
18896
|
+
], [
|
|
18897
|
+
"brand"
|
|
18898
|
+
], [
|
|
18899
|
+
"service_landing"
|
|
18900
|
+
], MCP_ONLY_CHANNEL_EXPOSURE),
|
|
18901
|
+
defineCapability("create_service_images", "1df35139abd6b04c5ebcdd5945154555a3f0d0e49b61f7d70c98cc5dad1e1347", "credit_consuming", "creative", [
|
|
18902
|
+
"credit_consuming",
|
|
18903
|
+
"state_change"
|
|
18904
|
+
], [
|
|
18905
|
+
slot("brand", false, [
|
|
18906
|
+
"brandId"
|
|
18907
|
+
], []),
|
|
18908
|
+
slot("service_brief", false, [
|
|
18909
|
+
"instruction",
|
|
18910
|
+
"delegateToAi"
|
|
18911
|
+
], []),
|
|
18912
|
+
slot("format", false, [
|
|
18913
|
+
"format"
|
|
18914
|
+
], []),
|
|
18915
|
+
slot("quantity", false, [
|
|
18916
|
+
"count"
|
|
18917
|
+
], []),
|
|
18918
|
+
slot("quality", false, [
|
|
18919
|
+
"quality"
|
|
18920
|
+
], []),
|
|
18921
|
+
slot("locale", false, [
|
|
18922
|
+
"language"
|
|
18923
|
+
], []),
|
|
18924
|
+
slot("ad_copy", false, [
|
|
18925
|
+
"adCopy"
|
|
18926
|
+
], []),
|
|
18927
|
+
slot("confirmation", false, [
|
|
18928
|
+
"confirmed"
|
|
18929
|
+
], [])
|
|
18930
|
+
], [
|
|
18931
|
+
"brand"
|
|
18932
|
+
], [
|
|
18933
|
+
"task",
|
|
18934
|
+
"creative"
|
|
18935
|
+
], MCP_ONLY_CHANNEL_EXPOSURE),
|
|
18802
18936
|
// MCP-only recovery of an existing code edit. WhatsApp has no code-landing
|
|
18803
18937
|
// editor or edit receipt input; it hands this recovery to ProductMaker Web.
|
|
18804
18938
|
defineCapability("verify_landing_commerce", "4b98ea73f291428fa19ec91ec37deb44a776b76cb19b0d47aef20ee905da7d10", "external_mutation", "publishing", [
|
|
@@ -21715,7 +21849,10 @@ var WebsiteCaptureAccessSchema = z105.object({
|
|
|
21715
21849
|
publicContentText: z105.string().max(8e3),
|
|
21716
21850
|
passwordGate: z105.boolean(),
|
|
21717
21851
|
challengeGate: z105.boolean(),
|
|
21718
|
-
blockingOverlay: z105.boolean()
|
|
21852
|
+
blockingOverlay: z105.boolean(),
|
|
21853
|
+
// Decoded, substantial images visible outside access walls and page chrome.
|
|
21854
|
+
// Optional for compatibility with already captured text-led research.
|
|
21855
|
+
publicImageUrls: z105.array(z105.string().url().max(8192)).max(20).optional()
|
|
21719
21856
|
});
|
|
21720
21857
|
var WebsiteCaptureResearchSchema = z105.object({
|
|
21721
21858
|
requestedUrl: z105.string().url(),
|
|
@@ -22030,7 +22167,7 @@ var ApiClient = class {
|
|
|
22030
22167
|
};
|
|
22031
22168
|
|
|
22032
22169
|
// src/index.ts
|
|
22033
|
-
var MCP_VERSION = "11.
|
|
22170
|
+
var MCP_VERSION = "11.3.0";
|
|
22034
22171
|
|
|
22035
22172
|
// src/meta-upload-app.ts
|
|
22036
22173
|
import { readFileSync } from "fs";
|
|
@@ -26383,8 +26520,756 @@ ANTES de invocar, resume outputs, cantidad de videos y datos comerciales, explic
|
|
|
26383
26520
|
}
|
|
26384
26521
|
__name(registerTaskTools, "registerTaskTools");
|
|
26385
26522
|
|
|
26386
|
-
// src/tools/
|
|
26523
|
+
// src/tools/brands.ts
|
|
26387
26524
|
import { z as z111 } from "zod";
|
|
26525
|
+
var guidance2 = getProductMakerCapabilityModelGuidance("create_brand");
|
|
26526
|
+
var BRAND_START_REPLAY_WINDOW_MS = 15 * 60 * 1e3;
|
|
26527
|
+
var BRAND_NOTES_MAX_CHARS = 2e4;
|
|
26528
|
+
var CreateBrandInput = z111.object({
|
|
26529
|
+
action: z111.enum([
|
|
26530
|
+
"start",
|
|
26531
|
+
"status",
|
|
26532
|
+
"list"
|
|
26533
|
+
]).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."),
|
|
26534
|
+
sourceUrl: z111.string().trim().min(1).max(500).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."),
|
|
26535
|
+
businessNotes: z111.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."),
|
|
26536
|
+
language: z111.enum([
|
|
26537
|
+
"es",
|
|
26538
|
+
"en",
|
|
26539
|
+
"pt"
|
|
26540
|
+
]).optional().describe("Idioma de la conversaci\xF3n. Solo se usa si la p\xE1gina del negocio no deja claro su idioma."),
|
|
26541
|
+
confirmed: z111.boolean().optional().describe("Solo true despu\xE9s de mostrar el precio y que el usuario acepte. Sin confirmed, start solo cotiza y no cobra."),
|
|
26542
|
+
brandId: z111.string().uuid().optional().describe("ID de la marca. Obligatorio para status; no lo inventes.")
|
|
26543
|
+
}).strict();
|
|
26544
|
+
function normalizeBrandSourceUrl(raw) {
|
|
26545
|
+
const value = raw.trim();
|
|
26546
|
+
const handle = /^@([A-Za-z0-9._]{1,30})$/.exec(value);
|
|
26547
|
+
if (handle) return `https://www.instagram.com/${handle[1]}/`;
|
|
26548
|
+
return normalizeWebAddress(value);
|
|
26549
|
+
}
|
|
26550
|
+
__name(normalizeBrandSourceUrl, "normalizeBrandSourceUrl");
|
|
26551
|
+
function brandSourceKey(url) {
|
|
26552
|
+
try {
|
|
26553
|
+
const parsed = new URL(url);
|
|
26554
|
+
const host = parsed.hostname.toLowerCase().replace(/^www\./, "");
|
|
26555
|
+
const path3 = parsed.pathname.replace(/\/+$/, "").toLowerCase();
|
|
26556
|
+
return `${host}${path3}`;
|
|
26557
|
+
} catch {
|
|
26558
|
+
return url.trim().toLowerCase();
|
|
26559
|
+
}
|
|
26560
|
+
}
|
|
26561
|
+
__name(brandSourceKey, "brandSourceKey");
|
|
26562
|
+
var EXTRACTION_ERROR_TEXT = {
|
|
26563
|
+
invalid_url: "La direcci\xF3n no es v\xE1lida o no es p\xFAblica. Pide el enlace que abre sin iniciar sesi\xF3n.",
|
|
26564
|
+
not_business_page: "No encontramos contenido del negocio en esa p\xE1gina. Pide el Instagram o la web principal del negocio.",
|
|
26565
|
+
scrape_failed: "No pudimos abrir la p\xE1gina (puede estar ca\xEDda o bloqueando visitas autom\xE1ticas). Pide otro enlace del negocio.",
|
|
26566
|
+
scrape_too_large: "La p\xE1gina es demasiado grande. Pide otro enlace m\xE1s simple del negocio, por ejemplo su Instagram.",
|
|
26567
|
+
scrape_timeout: "La p\xE1gina tard\xF3 demasiado en responder. Se puede volver a intentar en unos minutos.",
|
|
26568
|
+
llm_failed: "Hubo un error temporal al analizar la marca. Se puede volver a intentar en unos minutos.",
|
|
26569
|
+
llm_invalid_output: "El an\xE1lisis devolvi\xF3 algo inesperado. Se puede volver a intentar.",
|
|
26570
|
+
worker_exception: "Ocurri\xF3 un error inesperado durante el an\xE1lisis. Se puede volver a intentar.",
|
|
26571
|
+
BRAND_EXTRACTION_REQUIRES_REVIEW: "El an\xE1lisis se detuvo de forma segura y necesita revisi\xF3n del equipo de ProductMaker antes de reintentarlo."
|
|
26572
|
+
};
|
|
26573
|
+
function describeExtractionError(code) {
|
|
26574
|
+
return (code ? EXTRACTION_ERROR_TEXT[code] : void 0) ?? "No pudimos analizar la marca. Se puede volver a intentar o probar con otro enlace del negocio.";
|
|
26575
|
+
}
|
|
26576
|
+
__name(describeExtractionError, "describeExtractionError");
|
|
26577
|
+
function paletteColors(palette) {
|
|
26578
|
+
if (!palette) return [];
|
|
26579
|
+
const colors = [];
|
|
26580
|
+
for (const key of [
|
|
26581
|
+
"primary",
|
|
26582
|
+
"secondary",
|
|
26583
|
+
"tertiary",
|
|
26584
|
+
"neutral"
|
|
26585
|
+
]) {
|
|
26586
|
+
const value = palette[key];
|
|
26587
|
+
if (typeof value === "string") colors.push(value);
|
|
26588
|
+
}
|
|
26589
|
+
const accents = palette["accents"];
|
|
26590
|
+
if (Array.isArray(accents)) {
|
|
26591
|
+
for (const accent of accents) {
|
|
26592
|
+
if (typeof accent === "string") colors.push(accent);
|
|
26593
|
+
}
|
|
26594
|
+
}
|
|
26595
|
+
return colors.slice(0, 6);
|
|
26596
|
+
}
|
|
26597
|
+
__name(paletteColors, "paletteColors");
|
|
26598
|
+
function summarizeBrand(brand) {
|
|
26599
|
+
return {
|
|
26600
|
+
brandId: brand.id,
|
|
26601
|
+
status: brand.status,
|
|
26602
|
+
name: brand.name,
|
|
26603
|
+
...brand.sourceUrl ? {
|
|
26604
|
+
source: brand.sourceUrl
|
|
26605
|
+
} : {},
|
|
26606
|
+
...brand.oneLiner ? {
|
|
26607
|
+
oneLiner: brand.oneLiner
|
|
26608
|
+
} : {},
|
|
26609
|
+
...brand.overview ? {
|
|
26610
|
+
overview: brand.overview
|
|
26611
|
+
} : {},
|
|
26612
|
+
tone: brand.toneAdjectives ?? [],
|
|
26613
|
+
values: brand.coreValues ?? [],
|
|
26614
|
+
keyMessages: brand.keyMessages ?? [],
|
|
26615
|
+
colors: paletteColors(brand.palette),
|
|
26616
|
+
hasLogo: Boolean(brand.logoKey),
|
|
26617
|
+
audiences: (brand.personas ?? []).map((persona) => ({
|
|
26618
|
+
name: persona.name,
|
|
26619
|
+
...persona.demographics ? {
|
|
26620
|
+
who: persona.demographics
|
|
26621
|
+
} : {},
|
|
26622
|
+
...persona.painPoints ? {
|
|
26623
|
+
painPoints: persona.painPoints
|
|
26624
|
+
} : {},
|
|
26625
|
+
...persona.motivations ? {
|
|
26626
|
+
motivations: persona.motivations
|
|
26627
|
+
} : {}
|
|
26628
|
+
}))
|
|
26629
|
+
};
|
|
26630
|
+
}
|
|
26631
|
+
__name(summarizeBrand, "summarizeBrand");
|
|
26632
|
+
function describeBrandLanding(page) {
|
|
26633
|
+
if (!page) return null;
|
|
26634
|
+
return {
|
|
26635
|
+
servicePageId: page.id,
|
|
26636
|
+
status: page.publicUrl ? "published" : "draft",
|
|
26637
|
+
...page.publicUrl ? {
|
|
26638
|
+
publicUrl: page.publicUrl
|
|
26639
|
+
} : {}
|
|
26640
|
+
};
|
|
26641
|
+
}
|
|
26642
|
+
__name(describeBrandLanding, "describeBrandLanding");
|
|
26643
|
+
function statusResponse(brand, landing = null) {
|
|
26644
|
+
if (brand.status === "ready") {
|
|
26645
|
+
const landingNext = landing?.publicUrl ? `Esta marca ya tiene su landing publicada en ${landing.publicUrl}: comp\xE1rtela y ofrece el video animado (create_animated_video con ese enlace) o im\xE1genes (create_service_images con este brandId).` : landing ? "Esta marca ya tiene una landing en borrador: ret\xF3mala con create_service_landing action=prepare y este brandId (no crea otra)." : "Luego ofrece crear su landing page con create_service_landing action=prepare y este brandId. Si pidi\xF3 tambi\xE9n un video, dile que va despu\xE9s de publicar la landing.";
|
|
26646
|
+
return asText({
|
|
26647
|
+
brand: summarizeBrand(brand),
|
|
26648
|
+
...landing ? {
|
|
26649
|
+
serviceLanding: landing
|
|
26650
|
+
} : {},
|
|
26651
|
+
nextAction: `Muestra al usuario el an\xE1lisis de su marca (nombre, frase, tono, colores, mensajes clave y p\xFAblico) en pocas l\xEDneas. ${landingNext}`
|
|
26652
|
+
});
|
|
26653
|
+
}
|
|
26654
|
+
if (brand.status === "failed") {
|
|
26655
|
+
return asText({
|
|
26656
|
+
brandId: brand.id,
|
|
26657
|
+
status: "failed",
|
|
26658
|
+
reason: describeExtractionError(brand.extractionError),
|
|
26659
|
+
nextAction: "Explica el motivo en una frase y ofrece la salida que indica. No reintentes sin que el usuario lo pida: un nuevo an\xE1lisis se cobra de nuevo."
|
|
26660
|
+
});
|
|
26661
|
+
}
|
|
26662
|
+
return asText({
|
|
26663
|
+
brandId: brand.id,
|
|
26664
|
+
status: "extracting",
|
|
26665
|
+
nextAction: "El an\xE1lisis sigue en curso (suele tardar 1 a 3 minutos). Llama create_brand action=status con este brandId otra vez; no inicies otro an\xE1lisis."
|
|
26666
|
+
});
|
|
26667
|
+
}
|
|
26668
|
+
__name(statusResponse, "statusResponse");
|
|
26669
|
+
async function findRecentBrand(api, url, nowMs) {
|
|
26670
|
+
const brands = await api.getJson("/v1/brands");
|
|
26671
|
+
const key = brandSourceKey(url);
|
|
26672
|
+
return brands.find((brand) => {
|
|
26673
|
+
if (!brand.sourceUrl || brandSourceKey(brand.sourceUrl) !== key) return false;
|
|
26674
|
+
const updatedMs = Date.parse(brand.updatedAt);
|
|
26675
|
+
return brand.status !== "failed" && Number.isFinite(updatedMs) && nowMs - updatedMs < BRAND_START_REPLAY_WINDOW_MS;
|
|
26676
|
+
}) ?? null;
|
|
26677
|
+
}
|
|
26678
|
+
__name(findRecentBrand, "findRecentBrand");
|
|
26679
|
+
function registerBrandTools(server, api, options = {}) {
|
|
26680
|
+
const now = options.now ?? Date.now;
|
|
26681
|
+
async function findBrandLanding(brandId) {
|
|
26682
|
+
try {
|
|
26683
|
+
const { pages } = await api.getJson("/v1/service-pages");
|
|
26684
|
+
return describeBrandLanding(pages.find((page) => page.brandId === brandId) ?? null);
|
|
26685
|
+
} catch {
|
|
26686
|
+
return null;
|
|
26687
|
+
}
|
|
26688
|
+
}
|
|
26689
|
+
__name(findBrandLanding, "findBrandLanding");
|
|
26690
|
+
server.registerTool("create_brand", {
|
|
26691
|
+
description: guidance2.summary + " Sirve para NEGOCIOS DE SERVICIOS y marcas personales (barber\xEDa, consultorio, estudio, academia, agencia\u2026) y devuelve nombre, frase, tono, colores, logo, mensajes clave y p\xFAblico. Es el primer paso para su landing page (create_service_landing) y su video animado (create_animated_video). NO es para productos de dropshipping: para un producto, proveedor o enlace de AliExpress/Dropi usa las herramientas de producto. Flujo: action=start con sourceUrl (y businessNotes si el usuario cont\xF3 precios, WhatsApp o zona) devuelve el precio SIN cobrar; mu\xE9stralo y, cuando el usuario acepte, repite start con confirmed=true. Despu\xE9s llama action=status con el brandId hasta ready (1 a 3 minutos) o failed. Si el usuario ya tiene marcas, action=list te deja reutilizar una sin pagar otro an\xE1lisis. Repetir un start confirmado de la misma direcci\xF3n devuelve la marca que ya se est\xE1 analizando, sin cobrar dos veces.",
|
|
26692
|
+
inputSchema: CreateBrandInput.shape,
|
|
26693
|
+
annotations: CREATES_PAID_OPEN_WORLD
|
|
26694
|
+
}, async (rawInput) => {
|
|
26695
|
+
try {
|
|
26696
|
+
const input = CreateBrandInput.parse(rawInput);
|
|
26697
|
+
if (input.action === "list") {
|
|
26698
|
+
const brands = await api.getJson("/v1/brands");
|
|
26699
|
+
return asText({
|
|
26700
|
+
brands: brands.map((brand) => ({
|
|
26701
|
+
brandId: brand.id,
|
|
26702
|
+
name: brand.name,
|
|
26703
|
+
status: brand.status,
|
|
26704
|
+
...brand.sourceUrl ? {
|
|
26705
|
+
source: brand.sourceUrl
|
|
26706
|
+
} : {}
|
|
26707
|
+
})),
|
|
26708
|
+
nextAction: brands.length === 0 ? "El usuario a\xFAn no tiene marcas. Pide el Instagram o la web de su negocio y usa action=start." : "Si una de estas es la marca del negocio, \xFAsala (create_brand action=status para ver su an\xE1lisis, o create_service_landing con su brandId). Si no, usa action=start."
|
|
26709
|
+
});
|
|
26710
|
+
}
|
|
26711
|
+
if (input.action === "status") {
|
|
26712
|
+
if (!input.brandId) {
|
|
26713
|
+
throw mcp(McpErrorCode.INVALID_INPUT, "brandId es obligatorio para consultar una marca. Usa el que devolvi\xF3 start o list.");
|
|
26714
|
+
}
|
|
26715
|
+
const brand = await api.getJson(`/v1/brands/${encodeURIComponent(input.brandId)}`);
|
|
26716
|
+
return statusResponse(brand, brand.status === "ready" ? await findBrandLanding(brand.id) : null);
|
|
26717
|
+
}
|
|
26718
|
+
const sourceUrl = input.sourceUrl ? normalizeBrandSourceUrl(input.sourceUrl) : null;
|
|
26719
|
+
if (!sourceUrl) {
|
|
26720
|
+
throw mcp(McpErrorCode.INVALID_INPUT, "sourceUrl debe ser el Instagram, Facebook, TikTok o la web p\xFAblica del negocio (ej. @mibarberia o mibarberia.com). No se cobr\xF3 nada.");
|
|
26721
|
+
}
|
|
26722
|
+
const existing = await findRecentBrand(api, sourceUrl, now());
|
|
26723
|
+
if (existing && input.confirmed === true) {
|
|
26724
|
+
const brand = await api.getJson(`/v1/brands/${encodeURIComponent(existing.id)}`);
|
|
26725
|
+
if (!input.businessNotes) return statusResponse(brand);
|
|
26726
|
+
return asText({
|
|
26727
|
+
brandId: brand.id,
|
|
26728
|
+
status: brand.status,
|
|
26729
|
+
replayed: true,
|
|
26730
|
+
notesSaved: false,
|
|
26731
|
+
nextAction: "Esta marca ya se estaba analizando (no se cobr\xF3 otra vez) y las notas del negocio NO entraron en este an\xE1lisis. P\xE1salas tal cual en businessInfo de create_service_landing (prepare o build) para que la landing las use. Sigue con create_brand action=status y este brandId."
|
|
26732
|
+
});
|
|
26733
|
+
}
|
|
26734
|
+
const estimate = await api.postJson("/v1/billing/estimate", {
|
|
26735
|
+
type: "brand-extract",
|
|
26736
|
+
options: {}
|
|
26737
|
+
});
|
|
26738
|
+
const priceCredits = Math.ceil(estimate.estimatedMaxCostCredits);
|
|
26739
|
+
if (input.confirmed !== true) {
|
|
26740
|
+
return asText({
|
|
26741
|
+
sourceUrl,
|
|
26742
|
+
charged: false,
|
|
26743
|
+
...existing ? {
|
|
26744
|
+
existingBrand: {
|
|
26745
|
+
brandId: existing.id,
|
|
26746
|
+
status: existing.status
|
|
26747
|
+
}
|
|
26748
|
+
} : {},
|
|
26749
|
+
priceCredits,
|
|
26750
|
+
userBalanceCredits: estimate.userBalanceCredits,
|
|
26751
|
+
hasSufficientBalance: estimate.hasSufficientBalance,
|
|
26752
|
+
nextAction: existing ? `Esta direcci\xF3n ya tiene una marca analizada hace pocos minutos (brandId ${existing.id}): \xFAsala con create_brand action=status, sin pagar otro an\xE1lisis. Si el usuario cont\xF3 datos nuevos del negocio, p\xE1salos en businessInfo de create_service_landing.` : estimate.hasSufficientBalance ? `Dile al usuario que el an\xE1lisis de su marca cuesta hasta ${priceCredits} cr\xE9ditos y preg\xFAntale si quiere hacerlo. Solo si acepta, llama create_brand action=start con el mismo sourceUrl${input.businessNotes ? ", las mismas businessNotes" : ""} y confirmed=true.` : `El saldo (${estimate.userBalanceCredits} cr\xE9ditos) no alcanza para el an\xE1lisis (${priceCredits} cr\xE9ditos). Dile que recargue en productmaker.app y no inicies nada.`
|
|
26753
|
+
});
|
|
26754
|
+
}
|
|
26755
|
+
await requirePaidCreativeConfirmation(server, `An\xE1lisis de marca de ${sourceUrl}${input.businessNotes ? " con los datos que contaste del negocio" : ""}. Precio: hasta ${priceCredits} cr\xE9ditos.`);
|
|
26756
|
+
const notes = input.businessNotes?.trim();
|
|
26757
|
+
const created = await api.postMultipart("/v1/brands/extract", notes ? {
|
|
26758
|
+
files: {
|
|
26759
|
+
buffer: Buffer.from(notes, "utf8"),
|
|
26760
|
+
mimetype: "text/plain",
|
|
26761
|
+
filename: "notas-del-negocio.txt"
|
|
26762
|
+
}
|
|
26763
|
+
} : {}, {
|
|
26764
|
+
url: sourceUrl,
|
|
26765
|
+
locale: input.language
|
|
26766
|
+
});
|
|
26767
|
+
return asText({
|
|
26768
|
+
brandId: created.brandId,
|
|
26769
|
+
status: "extracting",
|
|
26770
|
+
priceCredits,
|
|
26771
|
+
nextAction: "El an\xE1lisis empez\xF3 (1 a 3 minutos). Dile al usuario que lo est\xE1s preparando y llama create_brand action=status con este brandId hasta ready o failed."
|
|
26772
|
+
});
|
|
26773
|
+
} catch (error) {
|
|
26774
|
+
throw translateError(error);
|
|
26775
|
+
}
|
|
26776
|
+
});
|
|
26777
|
+
}
|
|
26778
|
+
__name(registerBrandTools, "registerBrandTools");
|
|
26779
|
+
|
|
26780
|
+
// src/tools/service-landing.ts
|
|
26781
|
+
import { createHash as createHash3 } from "crypto";
|
|
26782
|
+
import { z as z112 } from "zod";
|
|
26783
|
+
var guidance3 = getProductMakerCapabilityModelGuidance("create_service_landing");
|
|
26784
|
+
var SERVICE_LANDING_KEY_NAMESPACE = "6f1d8c2a-3b4e-5f60-8a7b-9c0d1e2f3a4b";
|
|
26785
|
+
var SERVICE_LANDING_INFO_MAX_CHARS = 2e4;
|
|
26786
|
+
var CreateServiceLandingInput = z112.object({
|
|
26787
|
+
action: z112.enum([
|
|
26788
|
+
"prepare",
|
|
26789
|
+
"build",
|
|
26790
|
+
"status",
|
|
26791
|
+
"publish"
|
|
26792
|
+
]).default("prepare").describe("prepare (gratis) crea o retoma la landing de la marca y da el precio; build (pagado, confirmed=true) lee los datos del negocio y luego crea la p\xE1gina; status sigue el avance; publish la publica y devuelve su enlace."),
|
|
26793
|
+
brandId: z112.string().uuid().optional().describe("ID de la marca (de create_brand). Obligatorio para prepare. Nunca un taskId de producto."),
|
|
26794
|
+
servicePageId: z112.string().uuid().optional().describe("ID de la landing que devolvi\xF3 prepare. Obligatorio para build, status y publish."),
|
|
26795
|
+
businessInfo: z112.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."),
|
|
26796
|
+
confirmed: z112.boolean().optional().describe("Solo true despu\xE9s de mostrar el precio total y que el usuario acepte. Obligatorio para build."),
|
|
26797
|
+
maxCredits: z112.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."),
|
|
26798
|
+
operationId: z112.string().uuid().optional().describe("Opcional en status: la operaci\xF3n que devolvi\xF3 build."),
|
|
26799
|
+
slug: z112.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.")
|
|
26800
|
+
}).strict();
|
|
26801
|
+
function serviceKey(...parts) {
|
|
26802
|
+
return deriveUuidV5(parts.join(":"), SERVICE_LANDING_KEY_NAMESPACE);
|
|
26803
|
+
}
|
|
26804
|
+
__name(serviceKey, "serviceKey");
|
|
26805
|
+
function textHash(text2) {
|
|
26806
|
+
return createHash3("sha256").update(text2, "utf8").digest("hex").slice(0, 16);
|
|
26807
|
+
}
|
|
26808
|
+
__name(textHash, "textHash");
|
|
26809
|
+
function quoteRange(quote) {
|
|
26810
|
+
const max = Math.ceil(quote.estimatedMaxCostCredits ?? quote.credits);
|
|
26811
|
+
const min = Math.ceil(Math.min(quote.estimatedMinCostCredits ?? max, max));
|
|
26812
|
+
return {
|
|
26813
|
+
min,
|
|
26814
|
+
max
|
|
26815
|
+
};
|
|
26816
|
+
}
|
|
26817
|
+
__name(quoteRange, "quoteRange");
|
|
26818
|
+
function serviceApiCode(error) {
|
|
26819
|
+
if (!(error instanceof ApiHttpError)) return null;
|
|
26820
|
+
const body = error.body;
|
|
26821
|
+
if (!body || typeof body !== "object") return null;
|
|
26822
|
+
const record = body;
|
|
26823
|
+
for (const value of [
|
|
26824
|
+
record.code,
|
|
26825
|
+
record.errorCode,
|
|
26826
|
+
record.message
|
|
26827
|
+
]) {
|
|
26828
|
+
if (typeof value === "string" && /^[A-Z][A-Z0-9_]{2,63}$/.test(value)) return value;
|
|
26829
|
+
}
|
|
26830
|
+
return null;
|
|
26831
|
+
}
|
|
26832
|
+
__name(serviceApiCode, "serviceApiCode");
|
|
26833
|
+
function suggestServiceSlug(name) {
|
|
26834
|
+
const slug = (name ?? "").normalize("NFD").replace(/[̀-ͯ]/g, "").toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 48).replace(/-+$/g, "");
|
|
26835
|
+
return slug || "mi-servicio";
|
|
26836
|
+
}
|
|
26837
|
+
__name(suggestServiceSlug, "suggestServiceSlug");
|
|
26838
|
+
function serviceEditorUrl(api, pageId) {
|
|
26839
|
+
try {
|
|
26840
|
+
const url = new URL(api.publicUrl("/"));
|
|
26841
|
+
if (!url.hostname.startsWith("api.")) return null;
|
|
26842
|
+
url.hostname = url.hostname.slice("api.".length);
|
|
26843
|
+
url.pathname = `/es/service-pages/${encodeURIComponent(pageId)}/edit`;
|
|
26844
|
+
return url.toString();
|
|
26845
|
+
} catch {
|
|
26846
|
+
return null;
|
|
26847
|
+
}
|
|
26848
|
+
}
|
|
26849
|
+
__name(serviceEditorUrl, "serviceEditorUrl");
|
|
26850
|
+
var MISSING_LABELS = {
|
|
26851
|
+
"business.name": "nombre del negocio",
|
|
26852
|
+
services: "servicios (y su precio, si lo tiene)",
|
|
26853
|
+
"contact.channels": "WhatsApp, tel\xE9fono o enlace para reservar",
|
|
26854
|
+
"contact.primaryCta": "c\xF3mo quiere que lo contacten primero"
|
|
26855
|
+
};
|
|
26856
|
+
function describeMissing(fields) {
|
|
26857
|
+
return [
|
|
26858
|
+
...new Set(fields.map((field) => MISSING_LABELS[field]).filter((label) => Boolean(label)))
|
|
26859
|
+
];
|
|
26860
|
+
}
|
|
26861
|
+
__name(describeMissing, "describeMissing");
|
|
26862
|
+
function summarizeFacts(facts) {
|
|
26863
|
+
return {
|
|
26864
|
+
...facts.business.name ? {
|
|
26865
|
+
business: facts.business.name
|
|
26866
|
+
} : {},
|
|
26867
|
+
...facts.business.description ? {
|
|
26868
|
+
description: facts.business.description
|
|
26869
|
+
} : {},
|
|
26870
|
+
...facts.business.coverage ? {
|
|
26871
|
+
area: facts.business.coverage
|
|
26872
|
+
} : {},
|
|
26873
|
+
services: facts.services.filter((service) => service.name).map((service) => ({
|
|
26874
|
+
name: service.name,
|
|
26875
|
+
...service.price ? {
|
|
26876
|
+
price: service.price
|
|
26877
|
+
} : {}
|
|
26878
|
+
})),
|
|
26879
|
+
contact: facts.contact.channels.map((channel) => channel.value ? `${channel.kind}: ${channel.value}` : channel.kind)
|
|
26880
|
+
};
|
|
26881
|
+
}
|
|
26882
|
+
__name(summarizeFacts, "summarizeFacts");
|
|
26883
|
+
var OPERATION_ERROR_TEXT = {
|
|
26884
|
+
SERVICE_PAGE_SOURCES_REQUIRED: "No hay informaci\xF3n del negocio para escribir la p\xE1gina. Pide al usuario que cuente sus servicios y vuelve a llamar build con businessInfo.",
|
|
26885
|
+
SOURCE_TEXT_REQUIRED: "No hay informaci\xF3n del negocio para escribir la p\xE1gina. Pide al usuario que cuente sus servicios y vuelve a llamar build con businessInfo.",
|
|
26886
|
+
SERVICE_PAGE_PROVIDER_REJECTED: "El servicio de IA no estuvo disponible y no se cobr\xF3. Ofrece reintentar en unos minutos con build (mismo maxCredits)."
|
|
26887
|
+
};
|
|
26888
|
+
function operationFailureText(code) {
|
|
26889
|
+
return (code ? OPERATION_ERROR_TEXT[code] : void 0) ?? "La etapa fall\xF3 y no se cobr\xF3. Ofrece volver a intentarlo con build (mismo maxCredits); si vuelve a fallar, sugiere terminarla en la web de ProductMaker.";
|
|
26890
|
+
}
|
|
26891
|
+
__name(operationFailureText, "operationFailureText");
|
|
26892
|
+
function isPageGenerated(page) {
|
|
26893
|
+
return page.artifact !== null && page.artifact !== void 0;
|
|
26894
|
+
}
|
|
26895
|
+
__name(isPageGenerated, "isPageGenerated");
|
|
26896
|
+
function registerServiceLandingTools(server, api) {
|
|
26897
|
+
const pagePath = /* @__PURE__ */ __name((id) => `/v1/service-pages/${encodeURIComponent(id)}`, "pagePath");
|
|
26898
|
+
const readPage = /* @__PURE__ */ __name((id) => api.getJson(pagePath(id)), "readPage");
|
|
26899
|
+
async function findPageForBrand(brandId) {
|
|
26900
|
+
const { pages } = await api.getJson("/v1/service-pages");
|
|
26901
|
+
return pages.find((page) => page.brandId === brandId) ?? null;
|
|
26902
|
+
}
|
|
26903
|
+
__name(findPageForBrand, "findPageForBrand");
|
|
26904
|
+
async function addBusinessInfo(page, info) {
|
|
26905
|
+
try {
|
|
26906
|
+
await api.postMultipart(`${pagePath(page.id)}/intake`, {}, {
|
|
26907
|
+
expectedRevision: String(page.draftRevision),
|
|
26908
|
+
idempotencyKey: serviceKey(page.id, "intake", textHash(info)),
|
|
26909
|
+
text: info
|
|
26910
|
+
});
|
|
26911
|
+
} catch (error) {
|
|
26912
|
+
if (serviceApiCode(error) !== "SERVICE_PAGE_CONFLICT_REVISION") throw error;
|
|
26913
|
+
}
|
|
26914
|
+
return readPage(page.id);
|
|
26915
|
+
}
|
|
26916
|
+
__name(addBusinessInfo, "addBusinessInfo");
|
|
26917
|
+
async function quote(page, purpose) {
|
|
26918
|
+
return api.postJson(`${pagePath(page.id)}/quotes/${purpose}`, {
|
|
26919
|
+
expectedRevision: page.draftRevision
|
|
26920
|
+
});
|
|
26921
|
+
}
|
|
26922
|
+
__name(quote, "quote");
|
|
26923
|
+
async function startOperation(page, purpose, quoted) {
|
|
26924
|
+
const path3 = purpose === "fact-extraction" ? `${pagePath(page.id)}/intake/extract` : `${pagePath(page.id)}/generate`;
|
|
26925
|
+
return api.postJson(path3, {
|
|
26926
|
+
quoteId: quoted.quoteId,
|
|
26927
|
+
idempotencyKey: serviceKey(page.id, purpose, page.draftRevision),
|
|
26928
|
+
expectedRevision: page.draftRevision
|
|
26929
|
+
});
|
|
26930
|
+
}
|
|
26931
|
+
__name(startOperation, "startOperation");
|
|
26932
|
+
function generatedResponse(page) {
|
|
26933
|
+
const editorUrl = serviceEditorUrl(api, page.id);
|
|
26934
|
+
return asText({
|
|
26935
|
+
servicePageId: page.id,
|
|
26936
|
+
status: "generated",
|
|
26937
|
+
...page.publicUrl ? {
|
|
26938
|
+
publicUrl: page.publicUrl
|
|
26939
|
+
} : {},
|
|
26940
|
+
...editorUrl ? {
|
|
26941
|
+
editorUrl
|
|
26942
|
+
} : {},
|
|
26943
|
+
suggestedSlug: page.slug ?? suggestServiceSlug(page.businessName),
|
|
26944
|
+
nextAction: page.publicUrl ? `La p\xE1gina ya est\xE1 publicada en ${page.publicUrl}. Si el usuario pidi\xF3 un video, sigue con create_animated_video action=prepare y landingUrl=${page.publicUrl}; si pidi\xF3 im\xE1genes, con create_service_images y brandId=${page.brandId ?? "(el de su marca)"}.` : `La p\xE1gina est\xE1 lista. ${editorUrl ? `Dale al usuario el enlace para verla y editarla (${editorUrl}). ` : ""}Preg\xFAntale si la publicas con la direcci\xF3n sugerida (o la que prefiera) y, si acepta, llama action=publish.`
|
|
26945
|
+
});
|
|
26946
|
+
}
|
|
26947
|
+
__name(generatedResponse, "generatedResponse");
|
|
26948
|
+
server.registerTool("create_service_landing", {
|
|
26949
|
+
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): crea o retoma la landing y devuelve el precio estimado total. 2) Antes de cobrar, pregunta UNA sola vez lo que el usuario no haya dicho ya (WhatsApp o tel\xE9fono, ciudad o zona, servicios y precios); puede omitirlo. 3) Cuando acepte el precio, action=build con servicePageId, confirmed=true, maxCredits y businessInfo con sus respuestas: primero lee los datos del negocio. 4) action=status con servicePageId y operationId hasta que termine; cuando lo indique, vuelve a llamar build con el mismo maxCredits para crear la p\xE1gina (1 a 3 minutos). 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 el usuario quiere un video, y create_service_images (con el mismo brandId) si quiere im\xE1genes.",
|
|
26950
|
+
inputSchema: CreateServiceLandingInput.shape,
|
|
26951
|
+
annotations: CREATES_PAID
|
|
26952
|
+
}, async (rawInput) => {
|
|
26953
|
+
try {
|
|
26954
|
+
const input = CreateServiceLandingInput.parse(rawInput);
|
|
26955
|
+
if (input.action === "prepare") {
|
|
26956
|
+
if (!input.brandId) {
|
|
26957
|
+
throw mcp(McpErrorCode.INVALID_INPUT, "brandId es obligatorio: primero analiza la marca con create_brand (o elige una con create_brand action=list). No se cobr\xF3 nada.");
|
|
26958
|
+
}
|
|
26959
|
+
const brand = await api.getJson(`/v1/brands/${encodeURIComponent(input.brandId)}`);
|
|
26960
|
+
if (brand.status !== "ready") {
|
|
26961
|
+
return asText({
|
|
26962
|
+
brandId: input.brandId,
|
|
26963
|
+
charged: false,
|
|
26964
|
+
nextAction: brand.status === "extracting" ? "La marca todav\xEDa se est\xE1 analizando. Llama create_brand action=status hasta ready y luego vuelve a prepare." : "El an\xE1lisis de esta marca fall\xF3. Revisa create_brand action=status y ofrece analizarla de nuevo."
|
|
26965
|
+
});
|
|
26966
|
+
}
|
|
26967
|
+
const existing = await findPageForBrand(input.brandId);
|
|
26968
|
+
let page2 = existing ? await readPage(existing.id) : await api.postJson("/v1/service-pages", {
|
|
26969
|
+
brandId: input.brandId,
|
|
26970
|
+
includeBrandLogo: Boolean(brand.logoKey),
|
|
26971
|
+
idempotencyKey: serviceKey(input.brandId, "create")
|
|
26972
|
+
});
|
|
26973
|
+
if (isPageGenerated(page2)) return generatedResponse(page2);
|
|
26974
|
+
if (input.businessInfo) page2 = await addBusinessInfo(page2, input.businessInfo);
|
|
26975
|
+
const factsQuote = page2.factExtractionCompleted ? null : await quote(page2, "fact-extraction");
|
|
26976
|
+
const generationQuote = await quote(page2, "generation");
|
|
26977
|
+
const facts = factsQuote ? quoteRange(factsQuote) : {
|
|
26978
|
+
min: 0,
|
|
26979
|
+
max: 0
|
|
26980
|
+
};
|
|
26981
|
+
const build = quoteRange(generationQuote);
|
|
26982
|
+
const totalMin = facts.min + build.min;
|
|
26983
|
+
const totalCredits = facts.max + build.max;
|
|
26984
|
+
const requiredBalanceCredits = Math.max(factsQuote?.credits ?? 0, generationQuote.credits);
|
|
26985
|
+
const priceText = totalMin === totalCredits ? `${totalCredits} cr\xE9ditos` : `entre ${totalMin} y ${totalCredits} cr\xE9ditos`;
|
|
26986
|
+
return asText({
|
|
26987
|
+
servicePageId: page2.id,
|
|
26988
|
+
charged: false,
|
|
26989
|
+
...existing ? {
|
|
26990
|
+
resumed: true
|
|
26991
|
+
} : {},
|
|
26992
|
+
business: page2.businessName,
|
|
26993
|
+
known: summarizeFacts(page2.facts),
|
|
26994
|
+
price: {
|
|
26995
|
+
...factsQuote ? {
|
|
26996
|
+
readBusinessInfoCredits: facts
|
|
26997
|
+
} : {},
|
|
26998
|
+
buildPageCredits: build,
|
|
26999
|
+
totalMinCredits: totalMin,
|
|
27000
|
+
totalCredits,
|
|
27001
|
+
requiredBalanceCredits
|
|
27002
|
+
},
|
|
27003
|
+
nextAction: `En un solo mensaje: dile al usuario que crear su landing cuesta ${priceText} en total (se cobra lo que se use; necesita al menos ${requiredBalanceCredits} cr\xE9ditos de saldo para empezar) y preg\xFAntale, solo si no lo dijo ya en esta conversaci\xF3n, su WhatsApp o tel\xE9fono, su ciudad o zona y sus servicios con precio (puede omitirlo). Cuando acepte el precio, llama action=build con servicePageId=${page2.id}, confirmed=true, maxCredits=${totalCredits} y businessInfo con lo que respondi\xF3.`
|
|
27004
|
+
});
|
|
27005
|
+
}
|
|
27006
|
+
if (!input.servicePageId) {
|
|
27007
|
+
throw mcp(McpErrorCode.INVALID_INPUT, "servicePageId es obligatorio: usa el que devolvi\xF3 prepare. No se cobr\xF3 nada.");
|
|
27008
|
+
}
|
|
27009
|
+
if (input.action === "status") {
|
|
27010
|
+
let operation2 = null;
|
|
27011
|
+
if (input.operationId) {
|
|
27012
|
+
operation2 = await api.getJson(`/v1/service-pages/operations/${encodeURIComponent(input.operationId)}`);
|
|
27013
|
+
if (operation2.recoverable && (operation2.status === "failed" || operation2.status === "requires_reconciliation")) {
|
|
27014
|
+
operation2 = await api.postJson(`${pagePath(input.servicePageId)}/operations/${encodeURIComponent(operation2.opId)}/recover`, {});
|
|
27015
|
+
}
|
|
27016
|
+
if (operation2.status === "queued" || operation2.status === "running") {
|
|
27017
|
+
return asText({
|
|
27018
|
+
servicePageId: input.servicePageId,
|
|
27019
|
+
operationId: operation2.opId,
|
|
27020
|
+
step: operation2.kind === "generation" ? "creando la p\xE1gina" : "leyendo los datos del negocio",
|
|
27021
|
+
status: operation2.status,
|
|
27022
|
+
nextAction: "Sigue en curso. Llama action=status otra vez con el mismo operationId en unos segundos; no inicies otra etapa."
|
|
27023
|
+
});
|
|
27024
|
+
}
|
|
27025
|
+
if (operation2.status === "requires_reconciliation") {
|
|
27026
|
+
return asText({
|
|
27027
|
+
servicePageId: input.servicePageId,
|
|
27028
|
+
operationId: operation2.opId,
|
|
27029
|
+
status: "in_review",
|
|
27030
|
+
// Unknown on purpose: the charge is exactly what is being
|
|
27031
|
+
// confirmed. Settlement only charges a completed step.
|
|
27032
|
+
nextAction: "Esta etapa qued\xF3 en revisi\xF3n por un problema temporal: estamos confirmando si se complet\xF3. Si no se complet\xF3, no se cobra. Dile al usuario que sus datos est\xE1n guardados; vuelve a llamar action=status con el mismo operationId en unos minutos. No inicies otra etapa mientras tanto; si sigue igual, sugiere escribir a soporte de ProductMaker desde la web."
|
|
27033
|
+
});
|
|
27034
|
+
}
|
|
27035
|
+
if (operation2.status !== "succeeded") {
|
|
27036
|
+
return asText({
|
|
27037
|
+
servicePageId: input.servicePageId,
|
|
27038
|
+
operationId: operation2.opId,
|
|
27039
|
+
status: "failed",
|
|
27040
|
+
charged: false,
|
|
27041
|
+
reason: operationFailureText(operation2.errorCode)
|
|
27042
|
+
});
|
|
27043
|
+
}
|
|
27044
|
+
}
|
|
27045
|
+
const page2 = await readPage(input.servicePageId);
|
|
27046
|
+
if (isPageGenerated(page2)) return generatedResponse(page2);
|
|
27047
|
+
const missing = describeMissing(page2.missingFields);
|
|
27048
|
+
return asText({
|
|
27049
|
+
servicePageId: page2.id,
|
|
27050
|
+
status: page2.factExtractionCompleted ? "business_info_read" : "waiting_for_build",
|
|
27051
|
+
found: summarizeFacts(page2.facts),
|
|
27052
|
+
...missing.length ? {
|
|
27053
|
+
stillMissing: missing
|
|
27054
|
+
} : {},
|
|
27055
|
+
nextAction: page2.factExtractionCompleted ? `Muestra en pocas l\xEDneas lo que encontramos del negocio${missing.length ? " y lo que falta (puede agregarlo en businessInfo o seguir sin eso)" : ""}. Luego llama action=build con el mismo servicePageId, confirmed=true y el mismo maxCredits para crear la p\xE1gina.` : "A\xFAn no se leyeron los datos del negocio. Llama action=build con confirmed=true y el maxCredits que acept\xF3 el usuario."
|
|
27056
|
+
});
|
|
27057
|
+
}
|
|
27058
|
+
if (input.action === "publish") {
|
|
27059
|
+
const page2 = await readPage(input.servicePageId);
|
|
27060
|
+
if (!isPageGenerated(page2)) {
|
|
27061
|
+
throw mcp(McpErrorCode.INVALID_INPUT, "La p\xE1gina todav\xEDa no est\xE1 creada. Termina action=build y action=status antes de publicar.");
|
|
27062
|
+
}
|
|
27063
|
+
if (page2.publicUrl) return generatedResponse(page2);
|
|
27064
|
+
const slug = input.slug ?? suggestServiceSlug(page2.businessName);
|
|
27065
|
+
try {
|
|
27066
|
+
const published = await api.postJson(`${pagePath(page2.id)}/publish`, {
|
|
27067
|
+
expectedRevision: page2.draftRevision,
|
|
27068
|
+
slug,
|
|
27069
|
+
idempotencyKey: serviceKey(page2.id, "publish", slug)
|
|
27070
|
+
});
|
|
27071
|
+
return asText({
|
|
27072
|
+
servicePageId: page2.id,
|
|
27073
|
+
status: "published",
|
|
27074
|
+
publicUrl: published.publicUrl,
|
|
27075
|
+
nextAction: `Comparte el enlace ${published.publicUrl} con el usuario. Si pidi\xF3 un video, sigue con create_animated_video action=prepare y landingUrl=${published.publicUrl}; si pidi\xF3 im\xE1genes, con create_service_images y brandId=${page2.brandId ?? "(el de su marca)"}.`
|
|
27076
|
+
});
|
|
27077
|
+
} catch (error) {
|
|
27078
|
+
if (serviceApiCode(error)?.includes("SLUG")) {
|
|
27079
|
+
return asText({
|
|
27080
|
+
servicePageId: page2.id,
|
|
27081
|
+
status: "slug_taken",
|
|
27082
|
+
slug,
|
|
27083
|
+
nextAction: `La direcci\xF3n \xAB${slug}\xBB ya est\xE1 en uso. Prop\xF3n otra (por ejemplo \xAB${slug}-${(/* @__PURE__ */ new Date()).getFullYear()}\xBB o con la ciudad) y llama action=publish con slug.`
|
|
27084
|
+
});
|
|
27085
|
+
}
|
|
27086
|
+
throw error;
|
|
27087
|
+
}
|
|
27088
|
+
}
|
|
27089
|
+
if (input.confirmed !== true || !input.maxCredits) {
|
|
27090
|
+
throw mcp(McpErrorCode.INVALID_INPUT, "build cobra cr\xE9ditos: primero llama action=prepare, muestra el precio total y, cuando el usuario acepte, env\xEDa confirmed=true y maxCredits. No se cobr\xF3 nada.");
|
|
27091
|
+
}
|
|
27092
|
+
let page = await readPage(input.servicePageId);
|
|
27093
|
+
if (isPageGenerated(page)) return generatedResponse(page);
|
|
27094
|
+
const revisionBefore = page.draftRevision;
|
|
27095
|
+
if (input.businessInfo) page = await addBusinessInfo(page, input.businessInfo);
|
|
27096
|
+
const addedInfo = page.draftRevision !== revisionBefore;
|
|
27097
|
+
const needsFacts = !page.factExtractionCompleted || addedInfo;
|
|
27098
|
+
const purpose = needsFacts ? "fact-extraction" : "generation";
|
|
27099
|
+
const quoted = await quote(page, purpose);
|
|
27100
|
+
const credits = quoteRange(quoted).max;
|
|
27101
|
+
if (credits > input.maxCredits) {
|
|
27102
|
+
return asText({
|
|
27103
|
+
servicePageId: page.id,
|
|
27104
|
+
charged: false,
|
|
27105
|
+
priceCredits: credits,
|
|
27106
|
+
nextAction: `El precio subi\xF3 a ${credits} cr\xE9ditos, m\xE1s de lo aceptado (${input.maxCredits}). Preg\xFAntale al usuario si sigue y, si acepta, repite build con maxCredits=${credits}.`
|
|
27107
|
+
});
|
|
27108
|
+
}
|
|
27109
|
+
await requirePaidCreativeConfirmation(server, needsFacts ? `Leer los datos del negocio para su landing. Precio: hasta ${credits} cr\xE9ditos (total aceptado: ${input.maxCredits}).` : `Crear la landing page del negocio. Precio: hasta ${credits} cr\xE9ditos (total aceptado: ${input.maxCredits}).`);
|
|
27110
|
+
const operation = await startOperation(page, purpose, quoted);
|
|
27111
|
+
return asText({
|
|
27112
|
+
servicePageId: page.id,
|
|
27113
|
+
operationId: operation.opId,
|
|
27114
|
+
step: needsFacts ? "leyendo los datos del negocio" : "creando la p\xE1gina",
|
|
27115
|
+
status: operation.status,
|
|
27116
|
+
priceCredits: credits,
|
|
27117
|
+
nextAction: needsFacts ? "Dile al usuario que est\xE1s leyendo los datos de su negocio. Llama action=status con servicePageId y este operationId hasta que termine." : "Dile al usuario que est\xE1s creando su p\xE1gina (1 a 3 minutos). Llama action=status con servicePageId y este operationId hasta que termine."
|
|
27118
|
+
});
|
|
27119
|
+
} catch (error) {
|
|
27120
|
+
throw translateError(error);
|
|
27121
|
+
}
|
|
27122
|
+
});
|
|
27123
|
+
}
|
|
27124
|
+
__name(registerServiceLandingTools, "registerServiceLandingTools");
|
|
27125
|
+
|
|
27126
|
+
// src/tools/service-images.ts
|
|
27127
|
+
import { z as z113 } from "zod";
|
|
27128
|
+
var guidance4 = getProductMakerCapabilityModelGuidance("create_service_images");
|
|
27129
|
+
var SERVICE_IMAGE_FORMATS = [
|
|
27130
|
+
"4:5",
|
|
27131
|
+
"9:16",
|
|
27132
|
+
"1:1"
|
|
27133
|
+
];
|
|
27134
|
+
var SERVICE_IMAGE_MAX_COUNT = 3;
|
|
27135
|
+
var LANGUAGE_TAGS = {
|
|
27136
|
+
es: "es-CO",
|
|
27137
|
+
en: "en-US",
|
|
27138
|
+
pt: "pt-BR"
|
|
27139
|
+
};
|
|
27140
|
+
var CreateServiceImagesInput = z113.object({
|
|
27141
|
+
brandId: z113.string().uuid().optional().describe("ID de la marca (de create_brand). Recomendado: da nombre, colores, tono y servicios. Nunca un taskId de producto."),
|
|
27142
|
+
instruction: z113.string().trim().max(4e3).optional().describe("Qu\xE9 servicio o promoci\xF3n mostrar, con las palabras del usuario (ej. \xABcorte + barba a $35.000 los martes\xBB). Si el usuario deja que la IA decida, om\xEDtelo y usa delegateToAi=true."),
|
|
27143
|
+
delegateToAi: z113.boolean().optional().describe("true si el usuario prefiere que la IA elija qu\xE9 mostrar a partir de su marca."),
|
|
27144
|
+
format: z113.enum(SERVICE_IMAGE_FORMATS).optional().describe('Formato: "4:5" (feed, por defecto), "9:16" (historias/reels) o "1:1" (cuadrado).'),
|
|
27145
|
+
count: z113.number().int().min(1).max(SERVICE_IMAGE_MAX_COUNT).optional().describe("Cu\xE1ntas im\xE1genes, de 1 a 3 (por defecto 1)."),
|
|
27146
|
+
quality: z113.enum([
|
|
27147
|
+
"standard",
|
|
27148
|
+
"high"
|
|
27149
|
+
]).optional().describe('Calidad: "standard" (por defecto) o "high" (m\xE1s cara; no disponible en 4:5).'),
|
|
27150
|
+
language: z113.enum([
|
|
27151
|
+
"es",
|
|
27152
|
+
"en",
|
|
27153
|
+
"pt"
|
|
27154
|
+
]).optional().describe("Idioma de los textos de la imagen (por defecto es)."),
|
|
27155
|
+
adCopy: z113.boolean().optional().describe("Opcional. true agrega textos para el anuncio (titular y descripci\xF3n). Por defecto false."),
|
|
27156
|
+
confirmed: z113.boolean().optional().describe("Solo true despu\xE9s de mostrar el precio y que el usuario acepte. Sin confirmed, solo cotiza y no cobra.")
|
|
27157
|
+
}).strict();
|
|
27158
|
+
function serviceImageModel(quality) {
|
|
27159
|
+
return quality === "high" ? AD_CREATIVE_MODELS.PREMIUM : AD_CREATIVE_MODELS.STANDARD;
|
|
27160
|
+
}
|
|
27161
|
+
__name(serviceImageModel, "serviceImageModel");
|
|
27162
|
+
function registerServiceImageTools(server, api, options = {}) {
|
|
27163
|
+
const now = options.now ?? Date.now;
|
|
27164
|
+
server.registerTool("create_service_images", {
|
|
27165
|
+
description: guidance4.summary + " Im\xE1genes para anuncios de un NEGOCIO DE SERVICIOS o marca personal, con su marca (brandId de create_brand), sin foto de producto. NO es para productos de dropshipping (para eso generate_image_creatives). Llama primero sin confirmed: devuelve el precio SIN cobrar; mu\xE9stralo y, cuando el usuario acepte, repite la llamada igual con confirmed=true. Pregunta qu\xE9 servicio o promoci\xF3n mostrar si el usuario no lo dijo (o usa delegateToAi=true si prefiere que la IA elija). Devuelve un taskId: sigue get_task_status hasta done (trae las im\xE1genes) o error. Repetir la misma llamada confirmada no cobra dos veces.",
|
|
27166
|
+
inputSchema: CreateServiceImagesInput.shape,
|
|
27167
|
+
annotations: CREATES_PAID
|
|
27168
|
+
}, async (rawInput) => {
|
|
27169
|
+
try {
|
|
27170
|
+
const input = CreateServiceImagesInput.parse(rawInput);
|
|
27171
|
+
const brief = ServiceImageInputSchema.safeParse({
|
|
27172
|
+
instruction: input.instruction ?? "",
|
|
27173
|
+
intent: input.delegateToAi ? "delegate_to_ai" : "user_brief"
|
|
27174
|
+
});
|
|
27175
|
+
if (!brief.success) {
|
|
27176
|
+
throw mcp(McpErrorCode.INVALID_INPUT, "Pregunta al usuario qu\xE9 servicio o promoci\xF3n quiere mostrar (instruction), o usa delegateToAi=true si prefiere que la IA elija. No se cobr\xF3 nada.");
|
|
27177
|
+
}
|
|
27178
|
+
const format = input.format ?? "4:5";
|
|
27179
|
+
const count = input.count ?? 1;
|
|
27180
|
+
const imageModel = serviceImageModel(input.quality ?? "standard");
|
|
27181
|
+
const tierError = validateImageTierForRatio(format, imageModel);
|
|
27182
|
+
if (tierError) {
|
|
27183
|
+
throw mcp(McpErrorCode.INVALID_INPUT, `${tierError} Usa quality="standard" o otro formato. No se cobr\xF3 nada.`);
|
|
27184
|
+
}
|
|
27185
|
+
const language = LANGUAGE_TAGS[input.language ?? "es"];
|
|
27186
|
+
const adCopy = input.adCopy ?? false;
|
|
27187
|
+
const estimate = await api.postJson("/v1/billing/estimate", {
|
|
27188
|
+
type: "image-creative",
|
|
27189
|
+
options: {
|
|
27190
|
+
subject: "service",
|
|
27191
|
+
nVariants: count,
|
|
27192
|
+
imageModel,
|
|
27193
|
+
aspectRatio: format,
|
|
27194
|
+
generateAdCopy: adCopy,
|
|
27195
|
+
requiresExtraction: false,
|
|
27196
|
+
requiresAngles: false,
|
|
27197
|
+
competitorScanEnabled: false
|
|
27198
|
+
}
|
|
27199
|
+
});
|
|
27200
|
+
const max = Math.ceil(estimate.estimatedMaxCostCredits);
|
|
27201
|
+
const min = Math.ceil(Math.min(estimate.estimatedMinCostCredits ?? max, max));
|
|
27202
|
+
const priceText = min === max ? `${max} cr\xE9ditos` : `entre ${min} y ${max} cr\xE9ditos`;
|
|
27203
|
+
const summary = `${count} imagen${count === 1 ? "" : "es"} ${format} de calidad ${input.quality === "high" ? "alta" : "est\xE1ndar"}${input.brandId ? " con la marca" : ""}`;
|
|
27204
|
+
if (input.confirmed !== true) {
|
|
27205
|
+
return asText({
|
|
27206
|
+
charged: false,
|
|
27207
|
+
images: {
|
|
27208
|
+
count,
|
|
27209
|
+
format,
|
|
27210
|
+
quality: input.quality ?? "standard"
|
|
27211
|
+
},
|
|
27212
|
+
priceCredits: {
|
|
27213
|
+
min,
|
|
27214
|
+
max
|
|
27215
|
+
},
|
|
27216
|
+
userBalanceCredits: estimate.userBalanceCredits,
|
|
27217
|
+
nextAction: estimate.hasSufficientBalance ? `Dile al usuario que ${summary} cuesta ${priceText} y preg\xFAntale si sigue. Solo si acepta, repite la llamada con los mismos datos y confirmed=true.` : `El saldo (${estimate.userBalanceCredits} cr\xE9ditos) no alcanza (${priceText}). Dile que recargue en productmaker.app y no generes nada.`
|
|
27218
|
+
});
|
|
27219
|
+
}
|
|
27220
|
+
await requirePaidCreativeConfirmation(server, `${summary}. Precio: ${priceText}.`);
|
|
27221
|
+
const fields = {
|
|
27222
|
+
serviceBrief: JSON.stringify(brief.data),
|
|
27223
|
+
brandId: input.brandId,
|
|
27224
|
+
aspectRatio: format,
|
|
27225
|
+
imageModel,
|
|
27226
|
+
nVariants: String(count),
|
|
27227
|
+
language,
|
|
27228
|
+
competitorScanEnabled: "false",
|
|
27229
|
+
generateAdCopy: String(adCopy)
|
|
27230
|
+
};
|
|
27231
|
+
const created = await api.postMultipart("/v1/creatives/images", {}, fields, {
|
|
27232
|
+
idempotencyKey: derivePaidRequestKey({
|
|
27233
|
+
fingerprint: api.fingerprint(),
|
|
27234
|
+
tool: "create_service_images",
|
|
27235
|
+
input: {
|
|
27236
|
+
brief: brief.data,
|
|
27237
|
+
brandId: input.brandId ?? null,
|
|
27238
|
+
format,
|
|
27239
|
+
count,
|
|
27240
|
+
imageModel,
|
|
27241
|
+
language,
|
|
27242
|
+
adCopy
|
|
27243
|
+
},
|
|
27244
|
+
nowMs: now()
|
|
27245
|
+
})
|
|
27246
|
+
});
|
|
27247
|
+
const taskId = created.taskId ?? created.jobId;
|
|
27248
|
+
if (!taskId) {
|
|
27249
|
+
throw new Error("ProductMaker no devolvi\xF3 el id de la tarea de im\xE1genes.");
|
|
27250
|
+
}
|
|
27251
|
+
return asText({
|
|
27252
|
+
taskId,
|
|
27253
|
+
status: created.status ?? "queued",
|
|
27254
|
+
...created.replayed === true ? {
|
|
27255
|
+
replayed: true
|
|
27256
|
+
} : {},
|
|
27257
|
+
priceCredits: {
|
|
27258
|
+
min,
|
|
27259
|
+
max
|
|
27260
|
+
},
|
|
27261
|
+
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) o error."
|
|
27262
|
+
});
|
|
27263
|
+
} catch (error) {
|
|
27264
|
+
if (error instanceof z113.ZodError) throw invalidInputFromZod(error);
|
|
27265
|
+
throw translateError(error);
|
|
27266
|
+
}
|
|
27267
|
+
});
|
|
27268
|
+
}
|
|
27269
|
+
__name(registerServiceImageTools, "registerServiceImageTools");
|
|
27270
|
+
|
|
27271
|
+
// src/tools/creatives.ts
|
|
27272
|
+
import { z as z114 } from "zod";
|
|
26388
27273
|
var generateFreeVideoGuidance = getProductMakerCapabilityModelGuidance("generate_free_video");
|
|
26389
27274
|
var generateVideoCreativeGuidance = getProductMakerCapabilityModelGuidance("generate_video_creative");
|
|
26390
27275
|
var generateImageCreativesGuidance = getProductMakerCapabilityModelGuidance("generate_image_creatives");
|
|
@@ -26395,72 +27280,72 @@ var CreativeRefs = {
|
|
|
26395
27280
|
images: ImagesInput,
|
|
26396
27281
|
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.'),
|
|
26397
27282
|
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.'),
|
|
26398
|
-
price:
|
|
26399
|
-
offer:
|
|
26400
|
-
compareAtPrice:
|
|
26401
|
-
productContext:
|
|
26402
|
-
productLabel:
|
|
26403
|
-
buyerPersona:
|
|
26404
|
-
isCombo:
|
|
26405
|
-
competitorScanEnabled:
|
|
26406
|
-
brandId:
|
|
27283
|
+
price: z114.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.'),
|
|
27284
|
+
offer: z114.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.'),
|
|
27285
|
+
compareAtPrice: z114.string().min(1).max(120).optional().describe('Opcional. Precio anterior o de referencia para mostrar descuento. Ej. "$129.000" cuando price="$89.900".'),
|
|
27286
|
+
productContext: z114.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."),
|
|
27287
|
+
productLabel: z114.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.'),
|
|
27288
|
+
buyerPersona: z114.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.'),
|
|
27289
|
+
isCombo: z114.boolean().optional().describe("Opcional. Si true, el creativo presenta el producto como combo/pack. Default false."),
|
|
27290
|
+
competitorScanEnabled: z114.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."),
|
|
27291
|
+
brandId: z114.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.")
|
|
26407
27292
|
};
|
|
26408
|
-
var VideoPresenter =
|
|
26409
|
-
|
|
26410
|
-
mode:
|
|
27293
|
+
var VideoPresenter = z114.discriminatedUnion("mode", [
|
|
27294
|
+
z114.object({
|
|
27295
|
+
mode: z114.literal("none")
|
|
26411
27296
|
}).strict(),
|
|
26412
|
-
|
|
26413
|
-
mode:
|
|
26414
|
-
actorId:
|
|
27297
|
+
z114.object({
|
|
27298
|
+
mode: z114.literal("saved_actor"),
|
|
27299
|
+
actorId: z114.string().trim().min(1).max(120).describe("ID devuelto por list_actors para un actor guardado del usuario.")
|
|
26415
27300
|
}).strict(),
|
|
26416
|
-
|
|
26417
|
-
mode:
|
|
26418
|
-
description:
|
|
27301
|
+
z114.object({
|
|
27302
|
+
mode: z114.literal("described"),
|
|
27303
|
+
description: z114.string().trim().min(1).max(500).describe("Descripci\xF3n concreta de la persona que presenta el producto.")
|
|
26419
27304
|
}).strict()
|
|
26420
27305
|
]);
|
|
26421
|
-
var VideoCreativeSetup =
|
|
26422
|
-
|
|
26423
|
-
mode:
|
|
27306
|
+
var VideoCreativeSetup = z114.discriminatedUnion("mode", [
|
|
27307
|
+
z114.object({
|
|
27308
|
+
mode: z114.literal("automatic").describe('S\xF3lo cuando el usuario pidi\xF3 expl\xEDcitamente "decide todo".')
|
|
26424
27309
|
}).strict(),
|
|
26425
|
-
|
|
26426
|
-
mode:
|
|
27310
|
+
z114.object({
|
|
27311
|
+
mode: z114.literal("guided"),
|
|
26427
27312
|
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.'),
|
|
26428
|
-
buyerPersona:
|
|
27313
|
+
buyerPersona: z114.string().trim().min(1).max(360),
|
|
26429
27314
|
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.'),
|
|
26430
27315
|
presenter: VideoPresenter,
|
|
26431
|
-
creativeDirection:
|
|
27316
|
+
creativeDirection: z114.string().trim().min(1).max(500).nullable().describe("Direcci\xF3n visual elegida. null significa que el usuario deleg\xF3 expl\xEDcitamente esta decisi\xF3n.")
|
|
26432
27317
|
}).strict()
|
|
26433
27318
|
]);
|
|
26434
|
-
var VideoInput =
|
|
27319
|
+
var VideoInput = z114.object({
|
|
26435
27320
|
images: CreativeRefs.images.optional(),
|
|
26436
27321
|
winnerReference: WinnerReferenceInput.optional().describe(WINNER_IMAGE_CHOICE_GUIDANCE),
|
|
26437
|
-
title:
|
|
26438
|
-
primaryAngle:
|
|
27322
|
+
title: z114.string().min(1).describe('T\xEDtulo corto del producto. Aparece en la primera vista del video. Ej. "Crema Anti-Edad Premium".'),
|
|
27323
|
+
primaryAngle: z114.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".'),
|
|
26439
27324
|
language: CreativeRefs.language,
|
|
26440
27325
|
country: CreativeRefs.country,
|
|
26441
|
-
aspectRatio:
|
|
27326
|
+
aspectRatio: z114.enum([
|
|
26442
27327
|
"9:16",
|
|
26443
27328
|
"16:9",
|
|
26444
27329
|
"Auto"
|
|
26445
27330
|
]).describe('Relaci\xF3n de aspecto. "9:16" = vertical (TikTok/Reels \u2014 default recomendado para ads). "16:9" = horizontal (YouTube). "Auto" = decide la IA.'),
|
|
26446
27331
|
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.'),
|
|
26447
|
-
durationSeconds:
|
|
26448
|
-
videoAngleCount:
|
|
27332
|
+
durationSeconds: z114.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."),
|
|
27333
|
+
videoAngleCount: z114.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."),
|
|
26449
27334
|
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.'),
|
|
26450
27335
|
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."),
|
|
26451
27336
|
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.'),
|
|
26452
27337
|
videoQuality: VideoQualityEnum.default("economy").describe('Opcional. Calidad: "economy" = Econ\xF3mico, "medium" = Medio o "pro" = Pro. El sistema elige el proveedor internamente. Default: economy.'),
|
|
26453
27338
|
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.'),
|
|
26454
|
-
features:
|
|
27339
|
+
features: z114.array(z114.string()).optional().describe('Opcional. Lista de features/beneficios clave a mencionar. Ej. ["resistente al agua", "bater\xEDa 24h", "carga inal\xE1mbrica"].'),
|
|
26455
27340
|
price: CreativeRefs.price,
|
|
26456
27341
|
offer: CreativeRefs.offer,
|
|
26457
27342
|
productContext: CreativeRefs.productContext,
|
|
26458
27343
|
productLabel: CreativeRefs.productLabel,
|
|
26459
|
-
cta:
|
|
26460
|
-
scriptNotes:
|
|
27344
|
+
cta: z114.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.'),
|
|
27345
|
+
scriptNotes: z114.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.'),
|
|
26461
27346
|
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.'),
|
|
26462
27347
|
competitorScanEnabled: CreativeRefs.competitorScanEnabled,
|
|
26463
|
-
replicateWinnerWhenFound:
|
|
27348
|
+
replicateWinnerWhenFound: z114.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."),
|
|
26464
27349
|
brandId: CreativeRefs.brandId
|
|
26465
27350
|
}).strict();
|
|
26466
27351
|
var FreeVideoQualityEnum = VideoQualityEnum;
|
|
@@ -26478,7 +27363,7 @@ var FREE_VIDEO_DURATIONS_BY_QUALITY = {
|
|
|
26478
27363
|
length: 29
|
|
26479
27364
|
}, (_, index) => index + 2)
|
|
26480
27365
|
};
|
|
26481
|
-
var FreeVideoModelIdEnum =
|
|
27366
|
+
var FreeVideoModelIdEnum = z114.enum([
|
|
26482
27367
|
"grok-imagine-video-1-5-preview",
|
|
26483
27368
|
"minimax-h3/reference-to-video",
|
|
26484
27369
|
"wan/3-0-video",
|
|
@@ -26489,66 +27374,66 @@ var FREE_SELECTABLE_MODEL_IDS = [
|
|
|
26489
27374
|
"minimax-h3/reference-to-video",
|
|
26490
27375
|
"wan/3-0-video"
|
|
26491
27376
|
];
|
|
26492
|
-
var FreeVideoInput =
|
|
27377
|
+
var FreeVideoInput = z114.object({
|
|
26493
27378
|
images: ImagesInput.optional().describe("Opcional. 1-5 fotos del producto. Om\xEDtelo para generar desde texto puro."),
|
|
26494
|
-
rawPrompt:
|
|
27379
|
+
rawPrompt: z114.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."),
|
|
26495
27380
|
language: CreativeRefs.language,
|
|
26496
27381
|
quality: FreeVideoQualityEnum.describe('Calidad del video: "economy" = Econ\xF3mico, "medium" = Medio o "pro" = Pro (Wan).'),
|
|
26497
27382
|
// Retired in MCP 7.0.0 (Task 11): kept in the schema, accepted, and
|
|
26498
27383
|
// parsed ONLY so the handler can reject it with a message naming its
|
|
26499
27384
|
// replacement — dropping it silently would turn a caller's explicit
|
|
26500
27385
|
// Pro++ pick into a quiet fallback to Wan.
|
|
26501
|
-
freeProModel:
|
|
27386
|
+
freeProModel: z114.enum([
|
|
26502
27387
|
"wan",
|
|
26503
27388
|
"seedance"
|
|
26504
27389
|
]).optional().describe("ELIMINADO en MCP 7.0.0 \u2014 usa `modelId`. Enviar este campo produce un error."),
|
|
26505
27390
|
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.'),
|
|
26506
|
-
durationSeconds:
|
|
27391
|
+
durationSeconds: z114.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."),
|
|
26507
27392
|
videoResolution: VideoResolutionEnum.describe("Resoluci\xF3n del video: 480p o 720p."),
|
|
26508
|
-
generateAiMusic:
|
|
27393
|
+
generateAiMusic: z114.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.'),
|
|
26509
27394
|
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.'),
|
|
26510
|
-
addSubtitles:
|
|
26511
|
-
idempotencyKey:
|
|
27395
|
+
addSubtitles: z114.boolean().default(false).describe("Opcional. Quema subt\xEDtulos usando el rawPrompt exacto. Default false."),
|
|
27396
|
+
idempotencyKey: z114.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.")
|
|
26512
27397
|
}).strict();
|
|
26513
27398
|
var imageStyleList = ImageCreativeStyleEnum.options.join(" | ");
|
|
26514
|
-
var ImageCreativeSetup =
|
|
26515
|
-
|
|
26516
|
-
mode:
|
|
27399
|
+
var ImageCreativeSetup = z114.discriminatedUnion("mode", [
|
|
27400
|
+
z114.object({
|
|
27401
|
+
mode: z114.literal("automatic")
|
|
26517
27402
|
}).strict(),
|
|
26518
|
-
|
|
26519
|
-
mode:
|
|
26520
|
-
creativeArchetype:
|
|
27403
|
+
z114.object({
|
|
27404
|
+
mode: z114.literal("guided"),
|
|
27405
|
+
creativeArchetype: z114.union([
|
|
26521
27406
|
ImageCreativeArchetypeEnum,
|
|
26522
|
-
|
|
27407
|
+
z114.literal("ai_decides")
|
|
26523
27408
|
]),
|
|
26524
|
-
visualTreatment:
|
|
27409
|
+
visualTreatment: z114.union([
|
|
26525
27410
|
ImageCreativeVisualTreatmentEnum,
|
|
26526
|
-
|
|
27411
|
+
z114.literal("ai_decides")
|
|
26527
27412
|
]),
|
|
26528
27413
|
aspectRatio: ImageCreativeAspectRatioEnum,
|
|
26529
|
-
nVariants:
|
|
26530
|
-
|
|
26531
|
-
|
|
26532
|
-
|
|
26533
|
-
|
|
26534
|
-
|
|
27414
|
+
nVariants: z114.union([
|
|
27415
|
+
z114.literal(1),
|
|
27416
|
+
z114.literal(2),
|
|
27417
|
+
z114.literal(3),
|
|
27418
|
+
z114.literal(4),
|
|
27419
|
+
z114.literal(6)
|
|
26535
27420
|
]),
|
|
26536
|
-
imageModel:
|
|
26537
|
-
styleMix:
|
|
26538
|
-
generateAdCopy:
|
|
26539
|
-
competitorScanEnabled:
|
|
26540
|
-
productContext:
|
|
26541
|
-
creativeDirection:
|
|
26542
|
-
brandColors:
|
|
26543
|
-
headline:
|
|
26544
|
-
subheadline:
|
|
27421
|
+
imageModel: z114.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.'),
|
|
27422
|
+
styleMix: z114.boolean().nullable().describe("Decisi\xF3n s\xF3lo para 2\u20134 variantes. Usa null para 1 o 6, cuando la UI no hace esta pregunta."),
|
|
27423
|
+
generateAdCopy: z114.boolean(),
|
|
27424
|
+
competitorScanEnabled: z114.boolean(),
|
|
27425
|
+
productContext: z114.string().trim().min(1).max(3e3).nullable(),
|
|
27426
|
+
creativeDirection: z114.string().trim().min(1).max(500).nullable(),
|
|
27427
|
+
brandColors: z114.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.'),
|
|
27428
|
+
headline: z114.string().trim().min(1).max(120).nullable(),
|
|
27429
|
+
subheadline: z114.string().trim().min(1).max(160).nullable()
|
|
26545
27430
|
}).strict()
|
|
26546
27431
|
]).superRefine((setup, context) => {
|
|
26547
27432
|
if (setup.mode !== "guided") return;
|
|
26548
27433
|
const needsStyleMix = setup.nVariants >= 2 && setup.nVariants <= 4;
|
|
26549
27434
|
if (needsStyleMix && setup.styleMix === null) {
|
|
26550
27435
|
context.addIssue({
|
|
26551
|
-
code:
|
|
27436
|
+
code: z114.ZodIssueCode.custom,
|
|
26552
27437
|
path: [
|
|
26553
27438
|
"styleMix"
|
|
26554
27439
|
],
|
|
@@ -26557,7 +27442,7 @@ var ImageCreativeSetup = z111.discriminatedUnion("mode", [
|
|
|
26557
27442
|
}
|
|
26558
27443
|
if (!needsStyleMix && setup.styleMix !== null) {
|
|
26559
27444
|
context.addIssue({
|
|
26560
|
-
code:
|
|
27445
|
+
code: z114.ZodIssueCode.custom,
|
|
26561
27446
|
path: [
|
|
26562
27447
|
"styleMix"
|
|
26563
27448
|
],
|
|
@@ -26566,7 +27451,7 @@ var ImageCreativeSetup = z111.discriminatedUnion("mode", [
|
|
|
26566
27451
|
}
|
|
26567
27452
|
if (imageResolutionOf(setup.imageModel) === "2k" && setup.aspectRatio === "4:5") {
|
|
26568
27453
|
context.addIssue({
|
|
26569
|
-
code:
|
|
27454
|
+
code: z114.ZodIssueCode.custom,
|
|
26570
27455
|
path: [
|
|
26571
27456
|
"aspectRatio"
|
|
26572
27457
|
],
|
|
@@ -26574,55 +27459,55 @@ var ImageCreativeSetup = z111.discriminatedUnion("mode", [
|
|
|
26574
27459
|
});
|
|
26575
27460
|
}
|
|
26576
27461
|
});
|
|
26577
|
-
var LandingCreativeSetup =
|
|
26578
|
-
|
|
26579
|
-
mode:
|
|
27462
|
+
var LandingCreativeSetup = z114.discriminatedUnion("mode", [
|
|
27463
|
+
z114.object({
|
|
27464
|
+
mode: z114.literal("automatic")
|
|
26580
27465
|
}).strict(),
|
|
26581
|
-
|
|
26582
|
-
mode:
|
|
27466
|
+
z114.object({
|
|
27467
|
+
mode: z114.literal("guided"),
|
|
26583
27468
|
landingStrategy: LandingStrategyEnum,
|
|
26584
27469
|
landingImageFormat: LandingImageFormatEnum,
|
|
26585
|
-
competitorScanEnabled:
|
|
26586
|
-
paymentMethods:
|
|
26587
|
-
productContext:
|
|
26588
|
-
creativeDirection:
|
|
26589
|
-
headline:
|
|
26590
|
-
subheadline:
|
|
27470
|
+
competitorScanEnabled: z114.boolean(),
|
|
27471
|
+
paymentMethods: z114.string().trim().min(1).max(240).nullable(),
|
|
27472
|
+
productContext: z114.string().trim().min(1).max(3e3).nullable(),
|
|
27473
|
+
creativeDirection: z114.string().trim().min(1).max(500).nullable(),
|
|
27474
|
+
headline: z114.string().trim().min(1).max(120).nullable(),
|
|
27475
|
+
subheadline: z114.string().trim().min(1).max(160).nullable()
|
|
26591
27476
|
}).strict()
|
|
26592
27477
|
]);
|
|
26593
|
-
var ImageInput =
|
|
27478
|
+
var ImageInput = z114.object({
|
|
26594
27479
|
images: CreativeRefs.images.optional(),
|
|
26595
27480
|
winnerReference: WinnerReferenceInput.optional().describe(WINNER_IMAGE_CHOICE_GUIDANCE),
|
|
26596
|
-
productTitle:
|
|
26597
|
-
primaryAngle:
|
|
27481
|
+
productTitle: z114.string().min(1).describe('Nombre del producto. Ej. "Crema Anti-Edad Premium".'),
|
|
27482
|
+
primaryAngle: z114.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.'),
|
|
26598
27483
|
buyerPersona: CreativeRefs.buyerPersona,
|
|
26599
27484
|
language: CreativeRefs.language,
|
|
26600
27485
|
country: CreativeRefs.country,
|
|
26601
27486
|
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."),
|
|
26602
27487
|
imageStyle: ImageCreativeStyleEnum.optional().describe(`Opcional. Estilo del shot del producto. Valores: ${imageStyleList}. Si se omite, la IA escoge seg\xFAn el archetype.`),
|
|
26603
|
-
features:
|
|
27488
|
+
features: z114.array(z114.string()).optional().describe('Opcional. Features/beneficios a destacar visualmente. Ej. ["resistente al agua", "ultra liviano"].'),
|
|
26604
27489
|
price: CreativeRefs.price,
|
|
26605
|
-
omitPrice:
|
|
27490
|
+
omitPrice: z114.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."),
|
|
26606
27491
|
compareAtPrice: CreativeRefs.compareAtPrice,
|
|
26607
27492
|
offer: CreativeRefs.offer,
|
|
26608
27493
|
productLabel: CreativeRefs.productLabel,
|
|
26609
27494
|
isCombo: CreativeRefs.isCombo,
|
|
26610
27495
|
brandId: CreativeRefs.brandId
|
|
26611
27496
|
});
|
|
26612
|
-
var EditImageCreativeInput =
|
|
26613
|
-
taskId:
|
|
26614
|
-
variantIndex:
|
|
26615
|
-
clientRequestId:
|
|
26616
|
-
instruction:
|
|
27497
|
+
var EditImageCreativeInput = z114.object({
|
|
27498
|
+
taskId: z114.string().uuid().describe("ID de la tarea de imagen ya finalizada."),
|
|
27499
|
+
variantIndex: z114.number().int().min(0).max(10).describe("\xCDndice de la variante a corregir (0 = primera imagen)."),
|
|
27500
|
+
clientRequestId: z114.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."),
|
|
27501
|
+
instruction: z114.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".')
|
|
26617
27502
|
});
|
|
26618
|
-
var LandingInput =
|
|
26619
|
-
includeInitialLandingGif:
|
|
27503
|
+
var LandingInput = z114.object({
|
|
27504
|
+
includeInitialLandingGif: z114.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."),
|
|
26620
27505
|
images: CreativeRefs.images.optional(),
|
|
26621
27506
|
winnerReference: WinnerReferenceInput.optional().describe(WINNER_IMAGE_CHOICE_GUIDANCE),
|
|
26622
27507
|
language: CreativeRefs.language,
|
|
26623
27508
|
country: CreativeRefs.country,
|
|
26624
27509
|
creativeSetup: LandingCreativeSetup.describe("Configuraci\xF3n obligatoria de landing cl\xE1sica equivalente al wizard web. No expone landing editable/code mientras siga siendo admin-only."),
|
|
26625
|
-
salesAngle:
|
|
27510
|
+
salesAngle: z114.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.'),
|
|
26626
27511
|
buyerPersona: CreativeRefs.buyerPersona,
|
|
26627
27512
|
price: CreativeRefs.price,
|
|
26628
27513
|
compareAtPrice: CreativeRefs.compareAtPrice,
|
|
@@ -26904,8 +27789,8 @@ function registerCreativeTools(server, api, { transport = "http" } = {}) {
|
|
|
26904
27789
|
server.registerTool("get_image_edit_status", {
|
|
26905
27790
|
description: imageEditStatusGuidance.summary,
|
|
26906
27791
|
inputSchema: {
|
|
26907
|
-
taskId:
|
|
26908
|
-
operationId:
|
|
27792
|
+
taskId: z114.string().uuid(),
|
|
27793
|
+
operationId: z114.string().uuid()
|
|
26909
27794
|
},
|
|
26910
27795
|
annotations: READS_INTERNAL
|
|
26911
27796
|
}, async (input) => {
|
|
@@ -26962,10 +27847,10 @@ function registerCreativeTools(server, api, { transport = "http" } = {}) {
|
|
|
26962
27847
|
__name(registerCreativeTools, "registerCreativeTools");
|
|
26963
27848
|
|
|
26964
27849
|
// src/tools/creative-variants.ts
|
|
26965
|
-
import { z as
|
|
27850
|
+
import { z as z115 } from "zod";
|
|
26966
27851
|
var estimateCreativeVariantGuidance = getProductMakerCapabilityModelGuidance("estimate_creative_variant");
|
|
26967
27852
|
var createCreativeVariantGuidance = getProductMakerCapabilityModelGuidance("create_creative_variant");
|
|
26968
|
-
var LandingSectionKeyEnum =
|
|
27853
|
+
var LandingSectionKeyEnum = z115.enum([
|
|
26969
27854
|
"problem_hook",
|
|
26970
27855
|
"problem_cost",
|
|
26971
27856
|
"solution_mechanism",
|
|
@@ -27005,24 +27890,24 @@ var LandingSectionKeyEnum = z112.enum([
|
|
|
27005
27890
|
"lifestyle_fit",
|
|
27006
27891
|
"join_lifestyle"
|
|
27007
27892
|
]);
|
|
27008
|
-
var CreativeVariantKindEnum =
|
|
27893
|
+
var CreativeVariantKindEnum = z115.enum([
|
|
27009
27894
|
"video",
|
|
27010
27895
|
"image",
|
|
27011
27896
|
"landing"
|
|
27012
27897
|
]);
|
|
27013
|
-
var EstimateCreativeVariantInputBase =
|
|
27014
|
-
sourceTaskId:
|
|
27898
|
+
var EstimateCreativeVariantInputBase = z115.object({
|
|
27899
|
+
sourceTaskId: z115.string().uuid().describe("ID de la tarea original completada."),
|
|
27015
27900
|
kind: CreativeVariantKindEnum,
|
|
27016
27901
|
videoPreset: TaskCreativeVariantVideoPresetEnum.optional().describe("Opcional para video. Solo cambia el preset p\xFAblico; el servidor conserva el modelo/tier del task origen."),
|
|
27017
27902
|
videoResolution: VideoResolutionEnum.optional().describe("Opcional para video. Resoluci\xF3n del nuevo render."),
|
|
27018
|
-
nVariants:
|
|
27903
|
+
nVariants: z115.number().int().min(1).max(3).optional().describe("Opcional para imagen. N\xFAmero de im\xE1genes adicionales (1-3)."),
|
|
27019
27904
|
sectionKey: LandingSectionKeyEnum.optional().describe("Opcional para landing. Si ya conoces la secci\xF3n, evita cotizar la clasificaci\xF3n con IA."),
|
|
27020
|
-
instructions:
|
|
27905
|
+
instructions: z115.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.")
|
|
27021
27906
|
}).strict();
|
|
27022
27907
|
var EstimateCreativeVariantInput = EstimateCreativeVariantInputBase.superRefine((input, ctx) => {
|
|
27023
27908
|
if (input.kind === "video" && input.nVariants !== void 0) {
|
|
27024
27909
|
ctx.addIssue({
|
|
27025
|
-
code:
|
|
27910
|
+
code: z115.ZodIssueCode.custom,
|
|
27026
27911
|
path: [
|
|
27027
27912
|
"nVariants"
|
|
27028
27913
|
],
|
|
@@ -27032,7 +27917,7 @@ var EstimateCreativeVariantInput = EstimateCreativeVariantInputBase.superRefine(
|
|
|
27032
27917
|
if (input.kind !== "video") {
|
|
27033
27918
|
if (input.videoPreset !== void 0) {
|
|
27034
27919
|
ctx.addIssue({
|
|
27035
|
-
code:
|
|
27920
|
+
code: z115.ZodIssueCode.custom,
|
|
27036
27921
|
path: [
|
|
27037
27922
|
"videoPreset"
|
|
27038
27923
|
],
|
|
@@ -27041,7 +27926,7 @@ var EstimateCreativeVariantInput = EstimateCreativeVariantInputBase.superRefine(
|
|
|
27041
27926
|
}
|
|
27042
27927
|
if (input.videoResolution !== void 0) {
|
|
27043
27928
|
ctx.addIssue({
|
|
27044
|
-
code:
|
|
27929
|
+
code: z115.ZodIssueCode.custom,
|
|
27045
27930
|
path: [
|
|
27046
27931
|
"videoResolution"
|
|
27047
27932
|
],
|
|
@@ -27051,7 +27936,7 @@ var EstimateCreativeVariantInput = EstimateCreativeVariantInputBase.superRefine(
|
|
|
27051
27936
|
}
|
|
27052
27937
|
if (input.kind !== "image" && input.nVariants !== void 0) {
|
|
27053
27938
|
ctx.addIssue({
|
|
27054
|
-
code:
|
|
27939
|
+
code: z115.ZodIssueCode.custom,
|
|
27055
27940
|
path: [
|
|
27056
27941
|
"nVariants"
|
|
27057
27942
|
],
|
|
@@ -27061,7 +27946,7 @@ var EstimateCreativeVariantInput = EstimateCreativeVariantInputBase.superRefine(
|
|
|
27061
27946
|
if (input.kind !== "landing") {
|
|
27062
27947
|
if (input.sectionKey !== void 0) {
|
|
27063
27948
|
ctx.addIssue({
|
|
27064
|
-
code:
|
|
27949
|
+
code: z115.ZodIssueCode.custom,
|
|
27065
27950
|
path: [
|
|
27066
27951
|
"sectionKey"
|
|
27067
27952
|
],
|
|
@@ -27070,7 +27955,7 @@ var EstimateCreativeVariantInput = EstimateCreativeVariantInputBase.superRefine(
|
|
|
27070
27955
|
}
|
|
27071
27956
|
if (input.instructions !== void 0) {
|
|
27072
27957
|
ctx.addIssue({
|
|
27073
|
-
code:
|
|
27958
|
+
code: z115.ZodIssueCode.custom,
|
|
27074
27959
|
path: [
|
|
27075
27960
|
"instructions"
|
|
27076
27961
|
],
|
|
@@ -27079,31 +27964,31 @@ var EstimateCreativeVariantInput = EstimateCreativeVariantInputBase.superRefine(
|
|
|
27079
27964
|
}
|
|
27080
27965
|
}
|
|
27081
27966
|
});
|
|
27082
|
-
var CreateCreativeVariantInputBase =
|
|
27083
|
-
sourceTaskId:
|
|
27967
|
+
var CreateCreativeVariantInputBase = z115.object({
|
|
27968
|
+
sourceTaskId: z115.string().uuid().describe("ID de la tarea original completada."),
|
|
27084
27969
|
kind: CreativeVariantKindEnum,
|
|
27085
|
-
confirm:
|
|
27086
|
-
clientRequestId:
|
|
27087
|
-
angle:
|
|
27088
|
-
buyerPersona:
|
|
27089
|
-
scriptNotes:
|
|
27090
|
-
creativeDirection:
|
|
27970
|
+
confirm: z115.literal(true).describe("Confirmaci\xF3n expl\xEDcita del usuario despu\xE9s de mostrarle el estimado de cr\xE9ditos."),
|
|
27971
|
+
clientRequestId: z115.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."),
|
|
27972
|
+
angle: z115.string().trim().min(1).max(500).optional().describe("\xC1ngulo nuevo para video o imagen."),
|
|
27973
|
+
buyerPersona: z115.string().trim().min(1).max(360).optional().describe("Buyer persona coherente con el \xE1ngulo."),
|
|
27974
|
+
scriptNotes: z115.string().trim().min(1).max(2e3).optional().describe("Direcci\xF3n breve del guion para una variante de video."),
|
|
27975
|
+
creativeDirection: z115.string().trim().min(1).max(2e3).optional().describe("Direcci\xF3n creativa para una variante de imagen."),
|
|
27091
27976
|
videoPreset: TaskCreativeVariantVideoPresetEnum.optional().describe("Opcional para video. Preset p\xFAblico; el servidor resuelve modelo/tier desde el task origen."),
|
|
27092
27977
|
videoResolution: VideoResolutionEnum.optional().describe("Opcional para video."),
|
|
27093
|
-
generateAiMusic:
|
|
27094
|
-
nVariants:
|
|
27978
|
+
generateAiMusic: z115.boolean().optional().describe("Opcional para video."),
|
|
27979
|
+
nVariants: z115.number().int().min(1).max(3).optional().describe("Opcional para imagen. N\xFAmero de im\xE1genes adicionales (1-3)."),
|
|
27095
27980
|
imageStyle: ImageCreativeStyleEnum.optional().describe("Opcional para imagen."),
|
|
27096
27981
|
creativeArchetype: ImageCreativeArchetypeEnum.optional().describe("Opcional para imagen."),
|
|
27097
27982
|
visualTreatment: ImageCreativeVisualTreatmentEnum.optional().describe("Opcional para imagen."),
|
|
27098
27983
|
aspectRatio: ImageCreativeAspectRatioEnum.optional().describe("Opcional para imagen."),
|
|
27099
|
-
styleMix:
|
|
27984
|
+
styleMix: z115.boolean().optional().describe("Opcional para imagen."),
|
|
27100
27985
|
sectionKey: LandingSectionKeyEnum.optional().describe("Opcional para landing. Si se omite, se clasifica con IA a partir de instructions."),
|
|
27101
|
-
instructions:
|
|
27986
|
+
instructions: z115.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.")
|
|
27102
27987
|
}).strict();
|
|
27103
27988
|
var CreateCreativeVariantInput = CreateCreativeVariantInputBase.superRefine((input, ctx) => {
|
|
27104
27989
|
if ((input.kind === "video" || input.kind === "image") && !input.angle) {
|
|
27105
27990
|
ctx.addIssue({
|
|
27106
|
-
code:
|
|
27991
|
+
code: z115.ZodIssueCode.custom,
|
|
27107
27992
|
path: [
|
|
27108
27993
|
"angle"
|
|
27109
27994
|
],
|
|
@@ -27112,7 +27997,7 @@ var CreateCreativeVariantInput = CreateCreativeVariantInputBase.superRefine((inp
|
|
|
27112
27997
|
}
|
|
27113
27998
|
if (input.kind === "landing" && !input.sectionKey && !input.instructions) {
|
|
27114
27999
|
ctx.addIssue({
|
|
27115
|
-
code:
|
|
28000
|
+
code: z115.ZodIssueCode.custom,
|
|
27116
28001
|
path: [
|
|
27117
28002
|
"instructions"
|
|
27118
28003
|
],
|
|
@@ -27158,7 +28043,7 @@ var CreateCreativeVariantInput = CreateCreativeVariantInputBase.superRefine((inp
|
|
|
27158
28043
|
for (const field of fieldsByKind[input.kind]) {
|
|
27159
28044
|
if (input[field] !== void 0) {
|
|
27160
28045
|
ctx.addIssue({
|
|
27161
|
-
code:
|
|
28046
|
+
code: z115.ZodIssueCode.custom,
|
|
27162
28047
|
path: [
|
|
27163
28048
|
field
|
|
27164
28049
|
],
|
|
@@ -27291,13 +28176,13 @@ function registerCreativeVariantTools(server, api) {
|
|
|
27291
28176
|
__name(registerCreativeVariantTools, "registerCreativeVariantTools");
|
|
27292
28177
|
|
|
27293
28178
|
// src/tools/script-review.ts
|
|
27294
|
-
import { z as
|
|
28179
|
+
import { z as z116 } from "zod";
|
|
27295
28180
|
var getVideoScriptGuidance = getProductMakerCapabilityModelGuidance("get_video_script");
|
|
27296
28181
|
var regenerateVideoScriptGuidance = getProductMakerCapabilityModelGuidance("regenerate_video_script");
|
|
27297
28182
|
var approveVideoScriptGuidance = getProductMakerCapabilityModelGuidance("approve_video_script");
|
|
27298
|
-
var GetVideoScriptInput =
|
|
27299
|
-
taskId:
|
|
27300
|
-
angleKey:
|
|
28183
|
+
var GetVideoScriptInput = z116.object({
|
|
28184
|
+
taskId: z116.string().min(1).describe("ID de la tarea (la misma que devolvi\xF3 create_product_task / generate_video_creative)."),
|
|
28185
|
+
angleKey: z116.string().min(1).optional().describe('Clave del \xE1ngulo cuyo guion se quiere leer (formato "angle_0", "angle_1", ...). REQUERIDO para tareas multi-angle: si se omite, el backend resuelve a "angle_0" LITERAL (no "el primer pendiente"), as\xED que en multi-angle donde angle_0 ya est\xE9 aprobado/saltado obtendr\xEDas status="approved" y nunca alcanzar\xEDas el \xE1ngulo realmente pendiente. Para single-angle puede omitirse.')
|
|
27301
28186
|
}).strict();
|
|
27302
28187
|
var ApproveVideoScriptInput = ApproveVideoScriptCapabilityRequest;
|
|
27303
28188
|
var RegenerateVideoScriptInput = RegenerateVideoScriptCapabilityRequest.extend({
|
|
@@ -27380,7 +28265,7 @@ function registerScriptReviewTools(server, api) {
|
|
|
27380
28265
|
__name(registerScriptReviewTools, "registerScriptReviewTools");
|
|
27381
28266
|
|
|
27382
28267
|
// src/tools/connections.ts
|
|
27383
|
-
import { z as
|
|
28268
|
+
import { z as z117 } from "zod";
|
|
27384
28269
|
var listShopifyShopsGuidance = getProductMakerCapabilityModelGuidance("list_shopify_shops");
|
|
27385
28270
|
var listShopifyProductsGuidance = getProductMakerCapabilityModelGuidance("list_shopify_products");
|
|
27386
28271
|
var listMetaAdAccountsGuidance = getProductMakerCapabilityModelGuidance("list_meta_ad_accounts");
|
|
@@ -27389,7 +28274,7 @@ var listTikTokAdvertisersGuidance = getProductMakerCapabilityModelGuidance("list
|
|
|
27389
28274
|
function registerConnectionTools(server, api) {
|
|
27390
28275
|
server.registerTool("list_shopify_shops", {
|
|
27391
28276
|
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.",
|
|
27392
|
-
inputSchema:
|
|
28277
|
+
inputSchema: z117.object({}).shape,
|
|
27393
28278
|
annotations: READS_INTERNAL
|
|
27394
28279
|
}, async () => {
|
|
27395
28280
|
try {
|
|
@@ -27401,10 +28286,10 @@ function registerConnectionTools(server, api) {
|
|
|
27401
28286
|
});
|
|
27402
28287
|
server.registerTool("list_shopify_products", {
|
|
27403
28288
|
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.',
|
|
27404
|
-
inputSchema:
|
|
27405
|
-
shopDomain:
|
|
27406
|
-
query:
|
|
27407
|
-
taskId:
|
|
28289
|
+
inputSchema: z117.object({
|
|
28290
|
+
shopDomain: z117.string().min(3).max(120),
|
|
28291
|
+
query: z117.string().trim().max(120).optional(),
|
|
28292
|
+
taskId: z117.string().uuid().optional()
|
|
27408
28293
|
}).shape,
|
|
27409
28294
|
annotations: READS_EXTERNAL
|
|
27410
28295
|
}, async ({ shopDomain, query, taskId }) => {
|
|
@@ -27422,7 +28307,7 @@ function registerConnectionTools(server, api) {
|
|
|
27422
28307
|
});
|
|
27423
28308
|
server.registerTool("list_meta_ad_accounts", {
|
|
27424
28309
|
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.",
|
|
27425
|
-
inputSchema:
|
|
28310
|
+
inputSchema: z117.object({}).shape,
|
|
27426
28311
|
annotations: READS_EXTERNAL
|
|
27427
28312
|
}, async () => {
|
|
27428
28313
|
try {
|
|
@@ -27434,7 +28319,7 @@ function registerConnectionTools(server, api) {
|
|
|
27434
28319
|
});
|
|
27435
28320
|
server.registerTool("list_meta_destinations", {
|
|
27436
28321
|
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.",
|
|
27437
|
-
inputSchema:
|
|
28322
|
+
inputSchema: z117.object({}).shape,
|
|
27438
28323
|
annotations: READS_EXTERNAL,
|
|
27439
28324
|
_meta: {
|
|
27440
28325
|
"openai/widgetAccessible": true
|
|
@@ -27449,7 +28334,7 @@ function registerConnectionTools(server, api) {
|
|
|
27449
28334
|
});
|
|
27450
28335
|
server.registerTool("list_tiktok_advertisers", {
|
|
27451
28336
|
description: listTikTokAdvertisersGuidance.summary + " Si est\xE1 vac\xEDa, ind\xEDcale al usuario conectar TikTok en productmaker.app/integrations.",
|
|
27452
|
-
inputSchema:
|
|
28337
|
+
inputSchema: z117.object({}).shape,
|
|
27453
28338
|
annotations: READS_EXTERNAL
|
|
27454
28339
|
}, async () => {
|
|
27455
28340
|
try {
|
|
@@ -27463,7 +28348,7 @@ function registerConnectionTools(server, api) {
|
|
|
27463
28348
|
__name(registerConnectionTools, "registerConnectionTools");
|
|
27464
28349
|
|
|
27465
28350
|
// src/tools/actors.ts
|
|
27466
|
-
import { z as
|
|
28351
|
+
import { z as z118 } from "zod";
|
|
27467
28352
|
var listActorsGuidance = getProductMakerCapabilityModelGuidance("list_actors");
|
|
27468
28353
|
var actorVoiceGuidance = {
|
|
27469
28354
|
capabilities: getProductMakerCapabilityModelGuidance("get_actor_voice_capabilities"),
|
|
@@ -27503,7 +28388,7 @@ function sanitizeVoiceResult(value) {
|
|
|
27503
28388
|
__name(sanitizeVoiceResult, "sanitizeVoiceResult");
|
|
27504
28389
|
function registerActorTools(server, api) {
|
|
27505
28390
|
const actor = {
|
|
27506
|
-
actorId:
|
|
28391
|
+
actorId: z118.string().min(1).max(64)
|
|
27507
28392
|
};
|
|
27508
28393
|
const safe = /* @__PURE__ */ __name(async (request) => {
|
|
27509
28394
|
try {
|
|
@@ -27532,8 +28417,8 @@ function registerActorTools(server, api) {
|
|
|
27532
28417
|
inputSchema: {
|
|
27533
28418
|
...actor,
|
|
27534
28419
|
draft: ActorVoiceDraftSchema,
|
|
27535
|
-
requestKey:
|
|
27536
|
-
acceptedQuoteId:
|
|
28420
|
+
requestKey: z118.string().trim().min(1).max(128),
|
|
28421
|
+
acceptedQuoteId: z118.string().trim().min(1).max(128)
|
|
27537
28422
|
},
|
|
27538
28423
|
annotations: CREATES_PAID
|
|
27539
28424
|
}, async ({ actorId, ...body }) => safe(api.postJson(`/v1/actors/${encodeURIComponent(actorId)}/voice/previews`, body)));
|
|
@@ -27541,7 +28426,7 @@ function registerActorTools(server, api) {
|
|
|
27541
28426
|
description: actorVoiceGuidance.get.summary,
|
|
27542
28427
|
inputSchema: {
|
|
27543
28428
|
...actor,
|
|
27544
|
-
candidateId:
|
|
28429
|
+
candidateId: z118.string().min(1).max(64)
|
|
27545
28430
|
},
|
|
27546
28431
|
annotations: READS_INTERNAL
|
|
27547
28432
|
}, async ({ actorId, candidateId }) => safe(api.getJson(`/v1/actors/${encodeURIComponent(actorId)}/voice/previews/${encodeURIComponent(candidateId)}`)));
|
|
@@ -27549,15 +28434,15 @@ function registerActorTools(server, api) {
|
|
|
27549
28434
|
description: actorVoiceGuidance.approve.summary,
|
|
27550
28435
|
inputSchema: {
|
|
27551
28436
|
...actor,
|
|
27552
|
-
candidateId:
|
|
27553
|
-
expectedRevision:
|
|
27554
|
-
expectedApprovedRevision:
|
|
28437
|
+
candidateId: z118.string().min(1).max(64),
|
|
28438
|
+
expectedRevision: z118.number().int().positive(),
|
|
28439
|
+
expectedApprovedRevision: z118.number().int().positive().nullable().optional()
|
|
27555
28440
|
},
|
|
27556
28441
|
annotations: CREATES_INTERNAL
|
|
27557
28442
|
}, async ({ actorId, ...body }) => safe(api.postJson(`/v1/actors/${encodeURIComponent(actorId)}/voice/approve`, body)));
|
|
27558
28443
|
server.registerTool("list_actors", {
|
|
27559
28444
|
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.",
|
|
27560
|
-
inputSchema:
|
|
28445
|
+
inputSchema: z118.object({}).shape,
|
|
27561
28446
|
annotations: READS_INTERNAL
|
|
27562
28447
|
}, async () => {
|
|
27563
28448
|
try {
|
|
@@ -27590,7 +28475,7 @@ function registerActorTools(server, api) {
|
|
|
27590
28475
|
__name(registerActorTools, "registerActorTools");
|
|
27591
28476
|
|
|
27592
28477
|
// src/tools/publish.ts
|
|
27593
|
-
import { z as
|
|
28478
|
+
import { z as z119 } from "zod";
|
|
27594
28479
|
|
|
27595
28480
|
// src/tools/meta-publish-attempt-id.ts
|
|
27596
28481
|
var META_PUBLISH_NAMESPACE = "7b19c9ac-b4eb-4db1-970e-c2f24ce30b98";
|
|
@@ -27629,40 +28514,40 @@ var publishToShopifyGuidance = getProductMakerCapabilityModelGuidance("publish_t
|
|
|
27629
28514
|
var publishToMetaGuidance = getProductMakerCapabilityModelGuidance("publish_to_meta");
|
|
27630
28515
|
var publishCreativesToMetaGuidance = getProductMakerCapabilityModelGuidance("publish_creatives_to_meta");
|
|
27631
28516
|
var publishToTikTokGuidance = getProductMakerCapabilityModelGuidance("publish_to_tiktok");
|
|
27632
|
-
var ShopifyInput =
|
|
27633
|
-
taskId:
|
|
27634
|
-
shopDomain:
|
|
28517
|
+
var ShopifyInput = z119.object({
|
|
28518
|
+
taskId: z119.string().min(1),
|
|
28519
|
+
shopDomain: z119.string().min(1),
|
|
27635
28520
|
/** Override: number of landing images to include. Default = ALL images the
|
|
27636
28521
|
* task generated (the MCP fetches /v1/results first to count). Bounded
|
|
27637
28522
|
* generously here; the backend validates each index against
|
|
27638
28523
|
* landing.images.length and rejects out-of-range. */
|
|
27639
|
-
numLandingImages:
|
|
27640
|
-
variantIndex:
|
|
28524
|
+
numLandingImages: z119.number().int().min(1).max(20).optional(),
|
|
28525
|
+
variantIndex: z119.number().int().min(0).optional(),
|
|
27641
28526
|
/** ASK THE USER before setting: "¿Tu producto tiene variantes (talla, color, etc.)?".
|
|
27642
28527
|
* When true, the landing renders variant-picker pills; when false, it ships
|
|
27643
28528
|
* a single-option product page. Defaults to false. */
|
|
27644
|
-
showVariantPicker:
|
|
28529
|
+
showVariantPicker: z119.boolean().optional(),
|
|
27645
28530
|
/** Default `auto` updates the product previously published for this task and
|
|
27646
28531
|
* shop. Use `existing` with targetProductId to choose another product, or
|
|
27647
28532
|
* `new` only when the user explicitly wants a duplicate/new listing. */
|
|
27648
|
-
publicationMode:
|
|
28533
|
+
publicationMode: z119.enum([
|
|
27649
28534
|
"auto",
|
|
27650
28535
|
"existing",
|
|
27651
28536
|
"new"
|
|
27652
28537
|
]).optional(),
|
|
27653
28538
|
/** Shopify product GID, numeric id, or admin product URL. Required when
|
|
27654
28539
|
* publicationMode is `existing`. */
|
|
27655
|
-
targetProductId:
|
|
28540
|
+
targetProductId: z119.string().trim().min(1).max(300).optional(),
|
|
27656
28541
|
/** Add an announcement bar text above add-to-cart (optional). */
|
|
27657
|
-
announcementBarEnabled:
|
|
27658
|
-
announcementBarText:
|
|
28542
|
+
announcementBarEnabled: z119.boolean().optional(),
|
|
28543
|
+
announcementBarText: z119.string().trim().min(1).max(SHOPIFY_ANNOUNCEMENT_BAR_MAX_LENGTH).optional(),
|
|
27659
28544
|
commerceCorrection: PublishCommerceCorrectionSchema.optional().describe("Complete seller-confirmed commerce values, with the revision and fingerprint returned in recovery. Never invent missing prices or convert currency."),
|
|
27660
|
-
confirmed:
|
|
28545
|
+
confirmed: z119.boolean().optional().describe("Required as true only when submitting commerceCorrection, after showing its values and obtaining explicit seller confirmation.")
|
|
27661
28546
|
});
|
|
27662
28547
|
var ShopifyInputWithAnnouncementBarValidation = ShopifyInput.superRefine((value, ctx) => {
|
|
27663
28548
|
if (value.commerceCorrection && value.confirmed !== true) {
|
|
27664
28549
|
ctx.addIssue({
|
|
27665
|
-
code:
|
|
28550
|
+
code: z119.ZodIssueCode.custom,
|
|
27666
28551
|
path: [
|
|
27667
28552
|
"confirmed"
|
|
27668
28553
|
],
|
|
@@ -27671,7 +28556,7 @@ var ShopifyInputWithAnnouncementBarValidation = ShopifyInput.superRefine((value,
|
|
|
27671
28556
|
}
|
|
27672
28557
|
if (value.announcementBarEnabled && !value.announcementBarText) {
|
|
27673
28558
|
ctx.addIssue({
|
|
27674
|
-
code:
|
|
28559
|
+
code: z119.ZodIssueCode.custom,
|
|
27675
28560
|
path: [
|
|
27676
28561
|
"announcementBarText"
|
|
27677
28562
|
],
|
|
@@ -27680,7 +28565,7 @@ var ShopifyInputWithAnnouncementBarValidation = ShopifyInput.superRefine((value,
|
|
|
27680
28565
|
}
|
|
27681
28566
|
if (!value.announcementBarEnabled && value.announcementBarText) {
|
|
27682
28567
|
ctx.addIssue({
|
|
27683
|
-
code:
|
|
28568
|
+
code: z119.ZodIssueCode.custom,
|
|
27684
28569
|
path: [
|
|
27685
28570
|
"announcementBarText"
|
|
27686
28571
|
],
|
|
@@ -27694,91 +28579,91 @@ function parseShopifyInput(rawInput) {
|
|
|
27694
28579
|
return parsed.data;
|
|
27695
28580
|
}
|
|
27696
28581
|
__name(parseShopifyInput, "parseShopifyInput");
|
|
27697
|
-
var McpCampaignConfig =
|
|
28582
|
+
var McpCampaignConfig = z119.object({
|
|
27698
28583
|
/** ALWAYS in US dollars, whatever the ad account's currency is. ProductMaker
|
|
27699
28584
|
* converts to the account currency server-side. Do NOT pre-convert: on a COP
|
|
27700
28585
|
* account the user's "60.000 al día" is `dailyBudgetUsd: 15`, not 60000. */
|
|
27701
|
-
dailyBudgetUsd:
|
|
28586
|
+
dailyBudgetUsd: z119.number().positive().optional(),
|
|
27702
28587
|
/** ISO-3166-1 alpha-2 country codes or resolvable names (CountryIso2Schema
|
|
27703
28588
|
* normalizes at validation). Meta accepts max 1 (single-country ad set).
|
|
27704
28589
|
* TikTok takes only the first entry. */
|
|
27705
|
-
targetCountries:
|
|
27706
|
-
campaignName:
|
|
28590
|
+
targetCountries: z119.array(CountryIso2Schema).max(1).optional(),
|
|
28591
|
+
campaignName: z119.string().trim().min(1).max(100).optional()
|
|
27707
28592
|
});
|
|
27708
28593
|
var McpAdAccountId = MetaAdAccountId.describe('Meta ad account id. Accepts "act_1234567890" or the bare numeric id "1234567890".');
|
|
27709
|
-
var MetaInput =
|
|
27710
|
-
taskId:
|
|
28594
|
+
var MetaInput = z119.object({
|
|
28595
|
+
taskId: z119.string().min(1),
|
|
27711
28596
|
/** Exact Meta Business portfolio selected from `list_meta_destinations`. */
|
|
27712
|
-
portfolioId:
|
|
28597
|
+
portfolioId: z119.string().trim().min(1),
|
|
27713
28598
|
/** Exact Facebook Page selected from `list_meta_destinations`. */
|
|
27714
|
-
pageId:
|
|
28599
|
+
pageId: z119.string().trim().min(1),
|
|
27715
28600
|
adAccountId: McpAdAccountId,
|
|
27716
|
-
pixelId:
|
|
27717
|
-
landingUrl:
|
|
28601
|
+
pixelId: z119.string().min(1).optional(),
|
|
28602
|
+
landingUrl: z119.string().url().optional(),
|
|
27718
28603
|
/** Select which angles to publish by 0-based index. Omit to publish all
|
|
27719
28604
|
* successful angles. Example: a task with 3 angles → `[0,2]` publishes #0 + #2. */
|
|
27720
|
-
angleIndexes:
|
|
28605
|
+
angleIndexes: z119.array(z119.number().int().nonnegative()).optional(),
|
|
27721
28606
|
campaignConfig: McpCampaignConfig.optional(),
|
|
27722
28607
|
/** Override the ad's primary text (body copy). If omitted, ProductMaker
|
|
27723
28608
|
* uses the auto-generated copy attached to the task. Capped at 500 chars
|
|
27724
28609
|
* (Meta's primary-text limit). */
|
|
27725
|
-
primaryText:
|
|
28610
|
+
primaryText: z119.string().trim().min(1).max(500).optional(),
|
|
27726
28611
|
/** Optional WhatsApp number. When set, the ad button opens a WhatsApp chat
|
|
27727
28612
|
* (wa.me) instead of a landing page. */
|
|
27728
|
-
phoneNumber:
|
|
28613
|
+
phoneNumber: z119.string().min(8).max(32).optional()
|
|
27729
28614
|
});
|
|
27730
|
-
var GroupedTaskSource =
|
|
27731
|
-
type:
|
|
27732
|
-
taskId:
|
|
27733
|
-
angleIndex:
|
|
27734
|
-
videoVariant:
|
|
28615
|
+
var GroupedTaskSource = z119.object({
|
|
28616
|
+
type: z119.literal("task"),
|
|
28617
|
+
taskId: z119.string().min(1),
|
|
28618
|
+
angleIndex: z119.number().int().min(0).optional(),
|
|
28619
|
+
videoVariant: z119.enum([
|
|
27735
28620
|
"with_subtitles",
|
|
27736
28621
|
"without_subtitles"
|
|
27737
28622
|
]).optional(),
|
|
27738
|
-
imageVariantIndex:
|
|
27739
|
-
});
|
|
27740
|
-
var GroupedMediaSource =
|
|
27741
|
-
type:
|
|
27742
|
-
url:
|
|
27743
|
-
path:
|
|
27744
|
-
thumbnailUrl:
|
|
27745
|
-
thumbnailPath:
|
|
28623
|
+
imageVariantIndex: z119.number().int().min(0).optional()
|
|
28624
|
+
});
|
|
28625
|
+
var GroupedMediaSource = z119.object({
|
|
28626
|
+
type: z119.literal("media"),
|
|
28627
|
+
url: z119.string().url().optional().describe("Public HTTPS media URL. For a file on the user laptop, use request_media_upload on the hosted MCP instead."),
|
|
28628
|
+
path: z119.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".'),
|
|
28629
|
+
thumbnailUrl: z119.string().url().optional(),
|
|
28630
|
+
thumbnailPath: z119.string().min(1).optional()
|
|
27746
28631
|
}).refine((value) => Boolean(value.url) !== Boolean(value.path), {
|
|
27747
28632
|
message: "media source needs exactly one of `url` or `path`"
|
|
27748
28633
|
}).refine((value) => !(value.thumbnailUrl && value.thumbnailPath), {
|
|
27749
28634
|
message: "media source needs at most one of `thumbnailUrl` or `thumbnailPath`"
|
|
27750
28635
|
});
|
|
27751
|
-
var GroupedUploadedAssetSource =
|
|
27752
|
-
type:
|
|
28636
|
+
var GroupedUploadedAssetSource = z119.object({
|
|
28637
|
+
type: z119.literal("asset"),
|
|
27753
28638
|
/** Returned by check_media_upload. ProductMaker resolves ownership server-side. */
|
|
27754
|
-
assetId:
|
|
27755
|
-
thumbnailAssetId:
|
|
28639
|
+
assetId: z119.string().uuid(),
|
|
28640
|
+
thumbnailAssetId: z119.string().uuid().optional()
|
|
27756
28641
|
});
|
|
27757
|
-
var GroupedCreative =
|
|
27758
|
-
type:
|
|
28642
|
+
var GroupedCreative = z119.object({
|
|
28643
|
+
type: z119.enum([
|
|
27759
28644
|
"image",
|
|
27760
28645
|
"video"
|
|
27761
28646
|
]),
|
|
27762
|
-
source:
|
|
28647
|
+
source: z119.union([
|
|
27763
28648
|
GroupedTaskSource,
|
|
27764
28649
|
GroupedMediaSource,
|
|
27765
28650
|
GroupedUploadedAssetSource
|
|
27766
28651
|
]),
|
|
27767
|
-
title:
|
|
27768
|
-
description:
|
|
28652
|
+
title: z119.string().max(120).optional(),
|
|
28653
|
+
description: z119.string().max(500).optional(),
|
|
27769
28654
|
cta: MetaCtaType.optional(),
|
|
27770
|
-
creativeId:
|
|
28655
|
+
creativeId: z119.string().min(1).optional()
|
|
27771
28656
|
});
|
|
27772
|
-
var GroupedCampaignConfigBase =
|
|
28657
|
+
var GroupedCampaignConfigBase = z119.object({
|
|
27773
28658
|
campaignType: MetaCampaignType.default("sales"),
|
|
27774
28659
|
optimizationMode: MetaOptimizationMode.default("advantage_plus"),
|
|
27775
28660
|
budgetMode: MetaBudgetMode.optional(),
|
|
27776
|
-
campaignDailyBudget:
|
|
27777
|
-
adSets:
|
|
27778
|
-
tempId:
|
|
27779
|
-
name:
|
|
27780
|
-
dailyBudget:
|
|
27781
|
-
creativeIds:
|
|
28661
|
+
campaignDailyBudget: z119.number().int().positive().optional(),
|
|
28662
|
+
adSets: z119.array(z119.object({
|
|
28663
|
+
tempId: z119.string().min(1).max(80),
|
|
28664
|
+
name: z119.string().min(1).max(100),
|
|
28665
|
+
dailyBudget: z119.number().int().positive().optional(),
|
|
28666
|
+
creativeIds: z119.array(z119.string().min(1)).min(1)
|
|
27782
28667
|
})).min(1).max(CAMPAIGN_LIMITS.MAX_AD_SETS),
|
|
27783
28668
|
/**
|
|
27784
28669
|
* Valid values are 'landing' and 'whatsapp' — there is no 'website'. This
|
|
@@ -27786,17 +28671,17 @@ var GroupedCampaignConfigBase = z116.object({
|
|
|
27786
28671
|
* opaque `invalid_literal` and made the WhatsApp destination — supported by
|
|
27787
28672
|
* the API since #849 — unreachable from this tool.
|
|
27788
28673
|
*/
|
|
27789
|
-
destination:
|
|
27790
|
-
|
|
27791
|
-
type:
|
|
27792
|
-
landingUrl:
|
|
28674
|
+
destination: z119.discriminatedUnion("type", [
|
|
28675
|
+
z119.object({
|
|
28676
|
+
type: z119.literal("landing"),
|
|
28677
|
+
landingUrl: z119.string().url()
|
|
27793
28678
|
}),
|
|
27794
|
-
|
|
27795
|
-
type:
|
|
27796
|
-
phoneNumber:
|
|
28679
|
+
z119.object({
|
|
28680
|
+
type: z119.literal("whatsapp"),
|
|
28681
|
+
phoneNumber: z119.string().min(8).max(32)
|
|
27797
28682
|
})
|
|
27798
28683
|
]),
|
|
27799
|
-
saveAsDefault:
|
|
28684
|
+
saveAsDefault: z119.boolean().default(false),
|
|
27800
28685
|
budgetPolicy: MetaBudgetPolicy.optional()
|
|
27801
28686
|
});
|
|
27802
28687
|
function resolveBudgetMode(value) {
|
|
@@ -27810,7 +28695,7 @@ var GroupedCampaignConfig = GroupedCampaignConfigBase.superRefine((value, ctx) =
|
|
|
27810
28695
|
const budgetMode = resolveBudgetMode(value);
|
|
27811
28696
|
if (budgetMode === "campaign" && value.campaignDailyBudget === void 0) {
|
|
27812
28697
|
ctx.addIssue({
|
|
27813
|
-
code:
|
|
28698
|
+
code: z119.ZodIssueCode.custom,
|
|
27814
28699
|
path: [
|
|
27815
28700
|
"campaignDailyBudget"
|
|
27816
28701
|
],
|
|
@@ -27821,7 +28706,7 @@ var GroupedCampaignConfig = GroupedCampaignConfigBase.superRefine((value, ctx) =
|
|
|
27821
28706
|
value.adSets.forEach((adSet, index) => {
|
|
27822
28707
|
if (adSet.dailyBudget === void 0) {
|
|
27823
28708
|
ctx.addIssue({
|
|
27824
|
-
code:
|
|
28709
|
+
code: z119.ZodIssueCode.custom,
|
|
27825
28710
|
path: [
|
|
27826
28711
|
"adSets",
|
|
27827
28712
|
index,
|
|
@@ -27836,28 +28721,28 @@ var GroupedCampaignConfig = GroupedCampaignConfigBase.superRefine((value, ctx) =
|
|
|
27836
28721
|
...value,
|
|
27837
28722
|
budgetMode: resolveBudgetMode(value)
|
|
27838
28723
|
}));
|
|
27839
|
-
var PublishCreativesToMetaInputBase =
|
|
27840
|
-
creatives:
|
|
28724
|
+
var PublishCreativesToMetaInputBase = z119.object({
|
|
28725
|
+
creatives: z119.array(GroupedCreative).min(1).max(CAMPAIGN_LIMITS.MAX_CREATIVES),
|
|
27841
28726
|
/** Exact Meta Business portfolio selected from `list_meta_destinations`. */
|
|
27842
|
-
portfolioId:
|
|
28727
|
+
portfolioId: z119.string().trim().min(1),
|
|
27843
28728
|
/** Exact Facebook Page selected from `list_meta_destinations`. */
|
|
27844
|
-
pageId:
|
|
28729
|
+
pageId: z119.string().trim().min(1),
|
|
27845
28730
|
adAccountId: McpAdAccountId,
|
|
27846
|
-
pixelId:
|
|
28731
|
+
pixelId: z119.string().min(1).optional(),
|
|
27847
28732
|
/** Required unless `campaignConfig.destination` is whatsapp — see the
|
|
27848
28733
|
* superRefine below. Mirrors the API's `FacebookCreateCampaignFromTasksBodySchema`. */
|
|
27849
|
-
landingUrl:
|
|
28734
|
+
landingUrl: z119.string().url().optional(),
|
|
27850
28735
|
/** ISO-2 code or resolvable country name — mirrors the API contract
|
|
27851
28736
|
* (CountryIso2Schema), which normalizes names to alpha-2 at validation. */
|
|
27852
28737
|
targetCountry: CountryIso2Schema,
|
|
27853
|
-
campaignName:
|
|
28738
|
+
campaignName: z119.string().trim().min(1).max(100),
|
|
27854
28739
|
campaignConfig: GroupedCampaignConfig.optional()
|
|
27855
28740
|
});
|
|
27856
28741
|
var PublishCreativesToMetaInput = PublishCreativesToMetaInputBase.superRefine((value, ctx) => {
|
|
27857
28742
|
const destination = value.campaignConfig?.destination;
|
|
27858
28743
|
if (destination?.type !== "whatsapp" && destination?.type !== "landing" && !value.landingUrl) {
|
|
27859
28744
|
ctx.addIssue({
|
|
27860
|
-
code:
|
|
28745
|
+
code: z119.ZodIssueCode.custom,
|
|
27861
28746
|
path: [
|
|
27862
28747
|
"landingUrl"
|
|
27863
28748
|
],
|
|
@@ -27866,7 +28751,7 @@ var PublishCreativesToMetaInput = PublishCreativesToMetaInputBase.superRefine((v
|
|
|
27866
28751
|
}
|
|
27867
28752
|
if (destination?.type === "landing" && value.landingUrl && destination.landingUrl !== value.landingUrl) {
|
|
27868
28753
|
ctx.addIssue({
|
|
27869
|
-
code:
|
|
28754
|
+
code: z119.ZodIssueCode.custom,
|
|
27870
28755
|
path: [
|
|
27871
28756
|
"campaignConfig",
|
|
27872
28757
|
"destination",
|
|
@@ -27885,16 +28770,16 @@ var PublishCreativesToMetaInput = PublishCreativesToMetaInputBase.superRefine((v
|
|
|
27885
28770
|
}
|
|
27886
28771
|
return value;
|
|
27887
28772
|
});
|
|
27888
|
-
var PresignUploadResponseSchema =
|
|
27889
|
-
uploadUrl:
|
|
28773
|
+
var PresignUploadResponseSchema = z119.object({
|
|
28774
|
+
uploadUrl: z119.string().url().refine((value) => {
|
|
27890
28775
|
const url = new URL(value);
|
|
27891
28776
|
return !url.username && !url.password && url.protocol === "https:";
|
|
27892
28777
|
}, "presigned upload URL must be HTTPS without embedded credentials"),
|
|
27893
|
-
publicUrl:
|
|
28778
|
+
publicUrl: z119.string().url().refine((value) => {
|
|
27894
28779
|
const url = new URL(value);
|
|
27895
28780
|
return !url.username && !url.password;
|
|
27896
28781
|
}, "public media URL must not contain embedded credentials"),
|
|
27897
|
-
stagingAssetId:
|
|
28782
|
+
stagingAssetId: z119.string().uuid()
|
|
27898
28783
|
});
|
|
27899
28784
|
async function uploadResolvedMedia(api, media, assetRole) {
|
|
27900
28785
|
if (!media.buffer) throw new ResolveMediaError("INVALID_INPUT", "resolved media has no bytes");
|
|
@@ -27919,16 +28804,16 @@ async function resolveAndUploadMedia(api, ref, expectedKind, assetRole, transpor
|
|
|
27919
28804
|
return uploadResolvedMedia(api, media, assetRole);
|
|
27920
28805
|
}
|
|
27921
28806
|
__name(resolveAndUploadMedia, "resolveAndUploadMedia");
|
|
27922
|
-
var TikTokInput =
|
|
27923
|
-
taskId:
|
|
27924
|
-
advertiserId:
|
|
27925
|
-
pixelId:
|
|
27926
|
-
landingUrl:
|
|
27927
|
-
angleIndexes:
|
|
28807
|
+
var TikTokInput = z119.object({
|
|
28808
|
+
taskId: z119.string().min(1),
|
|
28809
|
+
advertiserId: z119.string().min(1),
|
|
28810
|
+
pixelId: z119.string().min(1).optional(),
|
|
28811
|
+
landingUrl: z119.string().url().optional(),
|
|
28812
|
+
angleIndexes: z119.array(z119.number().int().nonnegative()).optional(),
|
|
27928
28813
|
campaignConfig: McpCampaignConfig.optional(),
|
|
27929
28814
|
/** Override the ad's description (TikTok's body copy). If omitted,
|
|
27930
28815
|
* ProductMaker uses the auto-generated copy attached to the task. */
|
|
27931
|
-
primaryText:
|
|
28816
|
+
primaryText: z119.string().trim().min(1).max(500).optional()
|
|
27932
28817
|
});
|
|
27933
28818
|
function registerPublishTools(server, api, opts = {}) {
|
|
27934
28819
|
const transport = opts.transport ?? "http";
|
|
@@ -28280,39 +29165,39 @@ __name(toTikTokCampaignConfig, "toTikTokCampaignConfig");
|
|
|
28280
29165
|
|
|
28281
29166
|
// src/tools/meta-upload.ts
|
|
28282
29167
|
import { registerAppTool } from "@modelcontextprotocol/ext-apps/server";
|
|
28283
|
-
import { z as
|
|
29168
|
+
import { z as z120 } from "zod";
|
|
28284
29169
|
var requestMediaUploadGuidance = getProductMakerCapabilityModelGuidance("request_media_upload");
|
|
28285
|
-
var UploadSessionInputBase =
|
|
28286
|
-
mediaKind:
|
|
29170
|
+
var UploadSessionInputBase = z120.object({
|
|
29171
|
+
mediaKind: z120.enum([
|
|
28287
29172
|
"image",
|
|
28288
29173
|
"video",
|
|
28289
29174
|
"mixed"
|
|
28290
29175
|
]).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."),
|
|
28291
|
-
assetRole:
|
|
29176
|
+
assetRole: z120.enum([
|
|
28292
29177
|
"media",
|
|
28293
29178
|
"thumbnail"
|
|
28294
29179
|
]).default("media").describe("Use media for the creative itself; thumbnail only for an image thumbnail."),
|
|
28295
|
-
uploadPurpose:
|
|
29180
|
+
uploadPurpose: z120.enum([
|
|
28296
29181
|
"product_reference",
|
|
28297
29182
|
"creative",
|
|
28298
29183
|
"generic"
|
|
28299
29184
|
]).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."),
|
|
28300
|
-
maxFiles:
|
|
29185
|
+
maxFiles: z120.number().int().min(1).max(10).optional().describe("Optional batch limit: image accepts 1-5, video exactly 1, and mixed 1-10.")
|
|
28301
29186
|
});
|
|
28302
|
-
var OpenAiFileReference =
|
|
28303
|
-
download_url:
|
|
28304
|
-
file_id:
|
|
28305
|
-
mime_type:
|
|
28306
|
-
file_name:
|
|
29187
|
+
var OpenAiFileReference = z120.object({
|
|
29188
|
+
download_url: z120.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."),
|
|
29189
|
+
file_id: z120.string().min(1),
|
|
29190
|
+
mime_type: z120.string().min(1).optional(),
|
|
29191
|
+
file_name: z120.string().min(1).optional()
|
|
28307
29192
|
});
|
|
28308
29193
|
var UploadSessionToolInputShape = {
|
|
28309
29194
|
...UploadSessionInputBase.shape,
|
|
28310
|
-
files:
|
|
29195
|
+
files: z120.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.")
|
|
28311
29196
|
};
|
|
28312
|
-
var UploadSessionToolInput =
|
|
29197
|
+
var UploadSessionToolInput = z120.object(UploadSessionToolInputShape).superRefine((value, ctx) => {
|
|
28313
29198
|
if (value.assetRole === "thumbnail" && value.mediaKind !== "image") {
|
|
28314
29199
|
ctx.addIssue({
|
|
28315
|
-
code:
|
|
29200
|
+
code: z120.ZodIssueCode.custom,
|
|
28316
29201
|
path: [
|
|
28317
29202
|
"mediaKind"
|
|
28318
29203
|
],
|
|
@@ -28321,7 +29206,7 @@ var UploadSessionToolInput = z117.object(UploadSessionToolInputShape).superRefin
|
|
|
28321
29206
|
}
|
|
28322
29207
|
if (value.mediaKind === "video" && value.maxFiles !== void 0 && value.maxFiles !== 1) {
|
|
28323
29208
|
ctx.addIssue({
|
|
28324
|
-
code:
|
|
29209
|
+
code: z120.ZodIssueCode.custom,
|
|
28325
29210
|
path: [
|
|
28326
29211
|
"maxFiles"
|
|
28327
29212
|
],
|
|
@@ -28330,7 +29215,7 @@ var UploadSessionToolInput = z117.object(UploadSessionToolInputShape).superRefin
|
|
|
28330
29215
|
}
|
|
28331
29216
|
if (value.mediaKind === "image" && (value.maxFiles ?? 1) > 5) {
|
|
28332
29217
|
ctx.addIssue({
|
|
28333
|
-
code:
|
|
29218
|
+
code: z120.ZodIssueCode.custom,
|
|
28334
29219
|
path: [
|
|
28335
29220
|
"maxFiles"
|
|
28336
29221
|
],
|
|
@@ -28338,38 +29223,38 @@ var UploadSessionToolInput = z117.object(UploadSessionToolInputShape).superRefin
|
|
|
28338
29223
|
});
|
|
28339
29224
|
}
|
|
28340
29225
|
});
|
|
28341
|
-
var UploadSessionResponse =
|
|
28342
|
-
sessionId:
|
|
28343
|
-
uploadPageUrl:
|
|
28344
|
-
mediaKind:
|
|
29226
|
+
var UploadSessionResponse = z120.object({
|
|
29227
|
+
sessionId: z120.string().uuid(),
|
|
29228
|
+
uploadPageUrl: z120.string().url(),
|
|
29229
|
+
mediaKind: z120.enum([
|
|
28345
29230
|
"image",
|
|
28346
29231
|
"video",
|
|
28347
29232
|
"mixed"
|
|
28348
29233
|
]),
|
|
28349
|
-
assetRole:
|
|
29234
|
+
assetRole: z120.enum([
|
|
28350
29235
|
"media",
|
|
28351
29236
|
"thumbnail"
|
|
28352
29237
|
]),
|
|
28353
|
-
maxFiles:
|
|
28354
|
-
expiresAt:
|
|
29238
|
+
maxFiles: z120.number().int().positive(),
|
|
29239
|
+
expiresAt: z120.string().datetime()
|
|
28355
29240
|
});
|
|
28356
|
-
var UploadStatusInput =
|
|
28357
|
-
sessionId:
|
|
29241
|
+
var UploadStatusInput = z120.object({
|
|
29242
|
+
sessionId: z120.string().uuid().describe("The sessionId returned by request_media_upload.")
|
|
28358
29243
|
});
|
|
28359
|
-
var UniversalUploadStatusResponse =
|
|
28360
|
-
|
|
28361
|
-
status:
|
|
28362
|
-
assets:
|
|
29244
|
+
var UniversalUploadStatusResponse = z120.discriminatedUnion("status", [
|
|
29245
|
+
z120.object({
|
|
29246
|
+
status: z120.literal("pending"),
|
|
29247
|
+
assets: z120.array(z120.never()).length(0)
|
|
28363
29248
|
}),
|
|
28364
|
-
|
|
28365
|
-
status:
|
|
28366
|
-
assets:
|
|
28367
|
-
assetId:
|
|
28368
|
-
mediaKind:
|
|
29249
|
+
z120.object({
|
|
29250
|
+
status: z120.literal("uploaded"),
|
|
29251
|
+
assets: z120.array(z120.object({
|
|
29252
|
+
assetId: z120.string().uuid(),
|
|
29253
|
+
mediaKind: z120.enum([
|
|
28369
29254
|
"image",
|
|
28370
29255
|
"video"
|
|
28371
29256
|
]),
|
|
28372
|
-
assetRole:
|
|
29257
|
+
assetRole: z120.enum([
|
|
28373
29258
|
"media",
|
|
28374
29259
|
"thumbnail"
|
|
28375
29260
|
])
|
|
@@ -28377,13 +29262,13 @@ var UniversalUploadStatusResponse = z117.discriminatedUnion("status", [
|
|
|
28377
29262
|
})
|
|
28378
29263
|
]);
|
|
28379
29264
|
var UniversalUploadStructuredResponse = UploadSessionResponse.extend({
|
|
28380
|
-
uploadPurpose:
|
|
29265
|
+
uploadPurpose: z120.enum([
|
|
28381
29266
|
"product_reference",
|
|
28382
29267
|
"creative",
|
|
28383
29268
|
"generic"
|
|
28384
29269
|
]),
|
|
28385
|
-
checkToolName:
|
|
28386
|
-
resolveEndpoint:
|
|
29270
|
+
checkToolName: z120.literal("check_media_upload"),
|
|
29271
|
+
resolveEndpoint: z120.string().url()
|
|
28387
29272
|
});
|
|
28388
29273
|
function contextualizeUploadPageUrl(uploadPageUrl, uploadPurpose) {
|
|
28389
29274
|
const url = new URL(uploadPageUrl);
|
|
@@ -28474,15 +29359,15 @@ function registerMediaUploadTools(server, api) {
|
|
|
28474
29359
|
__name(registerMediaUploadTools, "registerMediaUploadTools");
|
|
28475
29360
|
|
|
28476
29361
|
// src/tools/campaigns.ts
|
|
28477
|
-
import { z as
|
|
29362
|
+
import { z as z121 } from "zod";
|
|
28478
29363
|
var getMetaCampaignStatusGuidance = getProductMakerCapabilityModelGuidance("get_meta_campaign_status");
|
|
28479
29364
|
var getTikTokCampaignStatusGuidance = getProductMakerCapabilityModelGuidance("get_tiktok_campaign_status");
|
|
28480
|
-
var MetaStatusInput =
|
|
28481
|
-
campaignId:
|
|
29365
|
+
var MetaStatusInput = z121.object({
|
|
29366
|
+
campaignId: z121.string().min(1).describe("Meta campaign ID returned by publish_to_meta.")
|
|
28482
29367
|
});
|
|
28483
|
-
var TikTokStatusInput =
|
|
28484
|
-
campaignId:
|
|
28485
|
-
advertiserId:
|
|
29368
|
+
var TikTokStatusInput = z121.object({
|
|
29369
|
+
campaignId: z121.string().min(1).describe("TikTok campaign ID returned by publish_to_tiktok."),
|
|
29370
|
+
advertiserId: z121.string().min(1).describe("The same advertiserId used to publish the campaign.")
|
|
28486
29371
|
});
|
|
28487
29372
|
function registerCampaignTools(server, api) {
|
|
28488
29373
|
server.registerTool("get_meta_campaign_status", {
|
|
@@ -28587,10 +29472,10 @@ function renderTikTokStatusMarkdown(r) {
|
|
|
28587
29472
|
__name(renderTikTokStatusMarkdown, "renderTikTokStatusMarkdown");
|
|
28588
29473
|
|
|
28589
29474
|
// ../../packages/product-economics/src/product-costing.ts
|
|
28590
|
-
import { z as
|
|
29475
|
+
import { z as z123 } from "zod";
|
|
28591
29476
|
|
|
28592
29477
|
// ../../packages/product-economics/src/product-costing-countries.ts
|
|
28593
|
-
import { z as
|
|
29478
|
+
import { z as z122 } from "zod";
|
|
28594
29479
|
|
|
28595
29480
|
// ../../packages/product-economics/src/product-costing-shipping-references.json
|
|
28596
29481
|
var product_costing_shipping_references_default = {
|
|
@@ -28654,21 +29539,21 @@ var product_costing_shipping_references_default = {
|
|
|
28654
29539
|
};
|
|
28655
29540
|
|
|
28656
29541
|
// ../../packages/product-economics/src/product-costing-countries.ts
|
|
28657
|
-
var ShippingReferenceSchema =
|
|
28658
|
-
reference:
|
|
28659
|
-
min:
|
|
28660
|
-
max:
|
|
28661
|
-
updatedAt:
|
|
28662
|
-
confidence:
|
|
29542
|
+
var ShippingReferenceSchema = z122.object({
|
|
29543
|
+
reference: z122.number().positive(),
|
|
29544
|
+
min: z122.number().nonnegative(),
|
|
29545
|
+
max: z122.number().positive(),
|
|
29546
|
+
updatedAt: z122.string().date(),
|
|
29547
|
+
confidence: z122.enum([
|
|
28663
29548
|
"high",
|
|
28664
29549
|
"medium",
|
|
28665
29550
|
"medium-low"
|
|
28666
29551
|
]),
|
|
28667
|
-
sources:
|
|
29552
|
+
sources: z122.array(z122.string().url().startsWith("https://")).min(1).max(3)
|
|
28668
29553
|
}).superRefine(({ reference, min, max }, context) => {
|
|
28669
29554
|
if (min > reference || reference > max) {
|
|
28670
29555
|
context.addIssue({
|
|
28671
|
-
code:
|
|
29556
|
+
code: z122.ZodIssueCode.custom,
|
|
28672
29557
|
message: "shipping_reference_outside_range",
|
|
28673
29558
|
path: [
|
|
28674
29559
|
"reference"
|
|
@@ -28676,7 +29561,7 @@ var ShippingReferenceSchema = z119.object({
|
|
|
28676
29561
|
});
|
|
28677
29562
|
}
|
|
28678
29563
|
});
|
|
28679
|
-
var SHIPPING_REFERENCES =
|
|
29564
|
+
var SHIPPING_REFERENCES = z122.record(ShippingReferenceSchema).parse(product_costing_shipping_references_default);
|
|
28680
29565
|
var PRODUCT_COSTING_COUNTRY_CODES = Object.freeze(Object.keys(SHIPPING_REFERENCES));
|
|
28681
29566
|
var SPANISH_LOCALE_COUNTRIES = /* @__PURE__ */ new Set([
|
|
28682
29567
|
"MX",
|
|
@@ -28858,18 +29743,18 @@ function getProductCostingCountryName(country) {
|
|
|
28858
29743
|
__name(getProductCostingCountryName, "getProductCostingCountryName");
|
|
28859
29744
|
|
|
28860
29745
|
// ../../packages/product-economics/src/product-costing.ts
|
|
28861
|
-
var ProductCostingInputSchema =
|
|
28862
|
-
country:
|
|
28863
|
-
supplierCost:
|
|
28864
|
-
shippingCost:
|
|
28865
|
-
returnRate:
|
|
28866
|
-
cancellationRate:
|
|
28867
|
-
adsManagerCpa:
|
|
28868
|
-
desiredMargin:
|
|
29746
|
+
var ProductCostingInputSchema = z123.object({
|
|
29747
|
+
country: z123.string().refine(isProductCostingCountryCode, "unsupported_country"),
|
|
29748
|
+
supplierCost: z123.number().finite().max(Number.MAX_SAFE_INTEGER, "amount_too_large").positive("supplier_cost_positive"),
|
|
29749
|
+
shippingCost: z123.number().finite().max(Number.MAX_SAFE_INTEGER, "amount_too_large").nonnegative("shipping_cost_nonnegative"),
|
|
29750
|
+
returnRate: z123.number().finite().min(0, "rate_nonnegative").lt(1, "rate_below_one"),
|
|
29751
|
+
cancellationRate: z123.number().finite().min(0, "rate_nonnegative").lt(1, "rate_below_one"),
|
|
29752
|
+
adsManagerCpa: z123.number().finite().max(Number.MAX_SAFE_INTEGER, "amount_too_large").nonnegative("cpa_nonnegative"),
|
|
29753
|
+
desiredMargin: z123.number().finite().min(0, "margin_nonnegative").lt(1, "margin_below_one")
|
|
28869
29754
|
}).superRefine(({ returnRate, cancellationRate }, context) => {
|
|
28870
29755
|
if (returnRate + cancellationRate >= 1) {
|
|
28871
29756
|
context.addIssue({
|
|
28872
|
-
code:
|
|
29757
|
+
code: z123.ZodIssueCode.custom,
|
|
28873
29758
|
message: "delivery_rate_positive",
|
|
28874
29759
|
path: [
|
|
28875
29760
|
"cancellationRate"
|
|
@@ -28966,15 +29851,15 @@ function calculateProductCosting(rawInput) {
|
|
|
28966
29851
|
__name(calculateProductCosting, "calculateProductCosting");
|
|
28967
29852
|
|
|
28968
29853
|
// src/tools/product-costing.ts
|
|
28969
|
-
import { z as
|
|
29854
|
+
import { z as z124 } from "zod";
|
|
28970
29855
|
var costProductGuidance = getProductMakerCapabilityModelGuidance("cost_product");
|
|
28971
|
-
var optionalAmount = /* @__PURE__ */ __name((description) =>
|
|
28972
|
-
var optionalPercent = /* @__PURE__ */ __name((description) =>
|
|
28973
|
-
var ProductCostingToolInput =
|
|
28974
|
-
country:
|
|
29856
|
+
var optionalAmount = /* @__PURE__ */ __name((description) => z124.number().finite().max(Number.MAX_SAFE_INTEGER).nonnegative().optional().describe(description), "optionalAmount");
|
|
29857
|
+
var optionalPercent = /* @__PURE__ */ __name((description) => z124.number().finite().min(0).lt(100).optional().describe(description), "optionalPercent");
|
|
29858
|
+
var ProductCostingToolInput = z124.object({
|
|
29859
|
+
country: z124.string().trim().toUpperCase().refine(isProductCostingCountryCode, {
|
|
28975
29860
|
message: "Pa\xEDs no soportado por ProductMaker."
|
|
28976
29861
|
}).optional().describe("C\xF3digo ISO-2 de cualquiera de los pa\xEDses disponibles en ProductMaker. Omite para Colombia."),
|
|
28977
|
-
supplierCost:
|
|
29862
|
+
supplierCost: z124.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."),
|
|
28978
29863
|
supplierCostCop: optionalAmount("Alias anterior para costo del proveedor en Colombia. Prefiere supplierCost."),
|
|
28979
29864
|
shippingCost: optionalAmount("Costo de un env\xEDo en la moneda del pa\xEDs. Omite para usar el valor gu\xEDa vigente."),
|
|
28980
29865
|
shippingCostCop: optionalAmount("Alias anterior para flete en Colombia. Prefiere shippingCost."),
|
|
@@ -29137,7 +30022,7 @@ function formatPercent(value) {
|
|
|
29137
30022
|
__name(formatPercent, "formatPercent");
|
|
29138
30023
|
|
|
29139
30024
|
// src/tools/product-research.ts
|
|
29140
|
-
import { z as
|
|
30025
|
+
import { z as z125 } from "zod";
|
|
29141
30026
|
|
|
29142
30027
|
// src/tools/product-research-idempotency.ts
|
|
29143
30028
|
var RESEARCH_IDEMPOTENCY_WINDOW_MS = 2 * 60 * 60 * 1e3;
|
|
@@ -29508,17 +30393,17 @@ var TERMINAL2 = /* @__PURE__ */ new Set([
|
|
|
29508
30393
|
]);
|
|
29509
30394
|
var POLL_INTERVAL_MS = 5e3;
|
|
29510
30395
|
var MAX_WAIT_SECONDS = 120;
|
|
29511
|
-
var FindInput =
|
|
30396
|
+
var FindInput = z125.object({
|
|
29512
30397
|
targetCountry: ResearchTargetCountryEnum.describe("Pa\xEDs objetivo. S\xF3lo CO, MX, PE, EC y CL tienen cobertura de investigaci\xF3n."),
|
|
29513
30398
|
categoryKey: ResearchCategoryEnum.default("auto").describe('Categor\xEDa a investigar. Usa "auto" para que la IA elija seg\xFAn el mercado.'),
|
|
29514
|
-
confirmed:
|
|
29515
|
-
waitSeconds:
|
|
30399
|
+
confirmed: z125.boolean().describe(`Ponlo en true SOLO despu\xE9s de que el usuario haya aceptado el cargo de ${PRICE_CREDITS} cr\xE9ditos. No lo asumas.`),
|
|
30400
|
+
waitSeconds: z125.number().int().min(0).max(MAX_WAIT_SECONDS).optional().describe("Segundos a esperar el reporte antes de devolver el runId. M\xE1ximo 120.")
|
|
29516
30401
|
});
|
|
29517
|
-
var GetInput =
|
|
29518
|
-
runId:
|
|
30402
|
+
var GetInput = z125.object({
|
|
30403
|
+
runId: z125.string().uuid().describe("ID del run devuelto por find_winning_products.")
|
|
29519
30404
|
});
|
|
29520
|
-
var ListInput =
|
|
29521
|
-
limit:
|
|
30405
|
+
var ListInput = z125.object({
|
|
30406
|
+
limit: z125.number().int().min(1).max(20).default(10)
|
|
29522
30407
|
});
|
|
29523
30408
|
function textResult(text2) {
|
|
29524
30409
|
return {
|
|
@@ -29657,17 +30542,17 @@ _Esta llamada no cobr\xF3 cr\xE9ditos: se reutiliz\xF3 una investigaci\xF3n reci
|
|
|
29657
30542
|
__name(registerProductResearchTools, "registerProductResearchTools");
|
|
29658
30543
|
|
|
29659
30544
|
// src/tools/dropshipping-projects.ts
|
|
29660
|
-
import { z as
|
|
30545
|
+
import { z as z126 } from "zod";
|
|
29661
30546
|
var startDropshippingProjectGuidance = getProductMakerCapabilityModelGuidance("start_dropshipping_project");
|
|
29662
30547
|
var costDropshippingProjectGuidance = getProductMakerCapabilityModelGuidance("cost_dropshipping_project");
|
|
29663
30548
|
var guideDropshippingLaunchGuidance = getProductMakerCapabilityModelGuidance("guide_dropshipping_launch");
|
|
29664
|
-
var ProjectId =
|
|
29665
|
-
var DropshippingProjectStatus =
|
|
30549
|
+
var ProjectId = z126.string().uuid();
|
|
30550
|
+
var DropshippingProjectStatus = z126.enum([
|
|
29666
30551
|
"active",
|
|
29667
30552
|
"completed",
|
|
29668
30553
|
"archived"
|
|
29669
30554
|
]);
|
|
29670
|
-
var DropshippingProjectPhase =
|
|
30555
|
+
var DropshippingProjectPhase = z126.enum([
|
|
29671
30556
|
"research",
|
|
29672
30557
|
"costing",
|
|
29673
30558
|
"media",
|
|
@@ -29676,21 +30561,21 @@ var DropshippingProjectPhase = z123.enum([
|
|
|
29676
30561
|
"advertising",
|
|
29677
30562
|
"complete"
|
|
29678
30563
|
]);
|
|
29679
|
-
var DropshippingRewardMilestone =
|
|
30564
|
+
var DropshippingRewardMilestone = z126.enum([
|
|
29680
30565
|
"product_selected",
|
|
29681
30566
|
"costing_saved",
|
|
29682
30567
|
"product_media_ready",
|
|
29683
30568
|
"creatives_completed",
|
|
29684
30569
|
"campaign_created"
|
|
29685
30570
|
]);
|
|
29686
|
-
var TargetCountry =
|
|
30571
|
+
var TargetCountry = z126.enum([
|
|
29687
30572
|
"CO",
|
|
29688
30573
|
"MX",
|
|
29689
30574
|
"PE",
|
|
29690
30575
|
"EC",
|
|
29691
30576
|
"CL"
|
|
29692
30577
|
]);
|
|
29693
|
-
var ResearchCategory =
|
|
30578
|
+
var ResearchCategory = z126.enum([
|
|
29694
30579
|
"auto",
|
|
29695
30580
|
"automotive",
|
|
29696
30581
|
"kitchen",
|
|
@@ -29705,7 +30590,7 @@ var ResearchCategory = z123.enum([
|
|
|
29705
30590
|
"garden",
|
|
29706
30591
|
"cleaning"
|
|
29707
30592
|
]);
|
|
29708
|
-
var HttpUrl3 =
|
|
30593
|
+
var HttpUrl3 = z126.string().url().max(2e3).refine((value) => {
|
|
29709
30594
|
try {
|
|
29710
30595
|
return [
|
|
29711
30596
|
"http:",
|
|
@@ -29715,45 +30600,45 @@ var HttpUrl3 = z123.string().url().max(2e3).refine((value) => {
|
|
|
29715
30600
|
return false;
|
|
29716
30601
|
}
|
|
29717
30602
|
}, "URL must use HTTP(S)");
|
|
29718
|
-
var ResearchCandidateOrigin =
|
|
29719
|
-
type:
|
|
30603
|
+
var ResearchCandidateOrigin = z126.object({
|
|
30604
|
+
type: z126.literal("research_candidate"),
|
|
29720
30605
|
runId: ProjectId,
|
|
29721
|
-
candidateSourceId:
|
|
30606
|
+
candidateSourceId: z126.string().min(1).max(160)
|
|
29722
30607
|
});
|
|
29723
|
-
var TaskOrigin =
|
|
29724
|
-
type:
|
|
30608
|
+
var TaskOrigin = z126.object({
|
|
30609
|
+
type: z126.literal("task"),
|
|
29725
30610
|
taskId: ProjectId
|
|
29726
30611
|
});
|
|
29727
|
-
var ManualOrigin =
|
|
29728
|
-
type:
|
|
30612
|
+
var ManualOrigin = z126.object({
|
|
30613
|
+
type: z126.literal("manual"),
|
|
29729
30614
|
clientRequestId: ProjectId,
|
|
29730
|
-
country:
|
|
30615
|
+
country: z126.string().min(2).max(3).optional()
|
|
29731
30616
|
});
|
|
29732
|
-
var StartProjectInput =
|
|
29733
|
-
origin:
|
|
30617
|
+
var StartProjectInput = z126.object({
|
|
30618
|
+
origin: z126.discriminatedUnion("type", [
|
|
29734
30619
|
ResearchCandidateOrigin,
|
|
29735
30620
|
TaskOrigin,
|
|
29736
30621
|
ManualOrigin
|
|
29737
30622
|
]),
|
|
29738
|
-
productName:
|
|
30623
|
+
productName: z126.string().trim().min(1).max(240).optional(),
|
|
29739
30624
|
productUrl: HttpUrl3.optional(),
|
|
29740
|
-
country:
|
|
29741
|
-
languageCode:
|
|
30625
|
+
country: z126.string().min(2).max(3).optional(),
|
|
30626
|
+
languageCode: z126.string().min(2).max(16).optional()
|
|
29742
30627
|
}).strict();
|
|
29743
|
-
var CostProjectInput =
|
|
30628
|
+
var CostProjectInput = z126.object({
|
|
29744
30629
|
projectId: ProjectId,
|
|
29745
|
-
version:
|
|
29746
|
-
country:
|
|
30630
|
+
version: z126.number().int().positive().describe("Version le\xEDda del proyecto. Copia la \xFAltima que viste; no se la pidas al usuario."),
|
|
30631
|
+
country: z126.string().trim().toUpperCase().refine(isProductCostingCountryCode, {
|
|
29747
30632
|
message: "Pa\xEDs no soportado por ProductMaker."
|
|
29748
30633
|
}).describe("C\xF3digo ISO-2 del pa\xEDs del proyecto. Usa el `targetCountry` que devuelve el proyecto."),
|
|
29749
|
-
supplierCost:
|
|
29750
|
-
shippingCost:
|
|
29751
|
-
returnRate:
|
|
29752
|
-
cancellationRate:
|
|
29753
|
-
adsManagerCpa:
|
|
29754
|
-
desiredMargin:
|
|
29755
|
-
});
|
|
29756
|
-
var GuideInput =
|
|
30634
|
+
supplierCost: z126.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."),
|
|
30635
|
+
shippingCost: z126.number().finite().nonnegative().optional().describe("Costo de un env\xEDo. Omite para usar el valor gu\xEDa del pa\xEDs en vez de inventarlo."),
|
|
30636
|
+
returnRate: z126.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."),
|
|
30637
|
+
cancellationRate: z126.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."),
|
|
30638
|
+
adsManagerCpa: z126.number().finite().nonnegative().optional().describe("Costo por compra de Ads Manager. Omite para usar el valor gu\xEDa del pa\xEDs."),
|
|
30639
|
+
desiredMargin: z126.number().finite().min(0).lt(1).optional().describe("Margen deseado (0.1 = 10%). Omite para usar el valor gu\xEDa del pa\xEDs.")
|
|
30640
|
+
});
|
|
30641
|
+
var GuideInput = z126.object({
|
|
29757
30642
|
projectId: ProjectId.optional()
|
|
29758
30643
|
}).strict();
|
|
29759
30644
|
var NEXT_ACTION_KINDS = [
|
|
@@ -29771,29 +30656,29 @@ var NEXT_ACTION_KINDS = [
|
|
|
29771
30656
|
"repair_meta_campaign",
|
|
29772
30657
|
"review_paused_campaign"
|
|
29773
30658
|
];
|
|
29774
|
-
var DropshippingProjectNextAction =
|
|
29775
|
-
kind:
|
|
29776
|
-
href:
|
|
30659
|
+
var DropshippingProjectNextAction = z126.discriminatedUnion("kind", NEXT_ACTION_KINDS.map((kind) => z126.object({
|
|
30660
|
+
kind: z126.literal(kind),
|
|
30661
|
+
href: z126.string().min(1)
|
|
29777
30662
|
})));
|
|
29778
|
-
var CostingInputSchema =
|
|
29779
|
-
country:
|
|
29780
|
-
supplierCost:
|
|
29781
|
-
shippingCost:
|
|
29782
|
-
returnRate:
|
|
29783
|
-
cancellationRate:
|
|
29784
|
-
adsManagerCpa:
|
|
29785
|
-
desiredMargin:
|
|
30663
|
+
var CostingInputSchema = z126.object({
|
|
30664
|
+
country: z126.string().min(2).max(3),
|
|
30665
|
+
supplierCost: z126.number().finite().positive(),
|
|
30666
|
+
shippingCost: z126.number().finite().nonnegative(),
|
|
30667
|
+
returnRate: z126.number().finite().min(0).lt(1),
|
|
30668
|
+
cancellationRate: z126.number().finite().min(0).lt(1),
|
|
30669
|
+
adsManagerCpa: z126.number().finite().nonnegative(),
|
|
30670
|
+
desiredMargin: z126.number().finite().min(0).lt(1)
|
|
29786
30671
|
}).superRefine(({ returnRate, cancellationRate }, context) => {
|
|
29787
30672
|
if (returnRate + cancellationRate >= 1) context.addIssue({
|
|
29788
|
-
code:
|
|
30673
|
+
code: z126.ZodIssueCode.custom,
|
|
29789
30674
|
path: [
|
|
29790
30675
|
"cancellationRate"
|
|
29791
30676
|
]
|
|
29792
30677
|
});
|
|
29793
30678
|
});
|
|
29794
|
-
var FiniteCostingNumber =
|
|
29795
|
-
var CostingResultSchema =
|
|
29796
|
-
country:
|
|
30679
|
+
var FiniteCostingNumber = z126.number().finite();
|
|
30680
|
+
var CostingResultSchema = z126.object({
|
|
30681
|
+
country: z126.string().min(2).max(3),
|
|
29797
30682
|
deliveryRate: FiniteCostingNumber,
|
|
29798
30683
|
adjustedShippingCost: FiniteCostingNumber,
|
|
29799
30684
|
adjustedCpa: FiniteCostingNumber,
|
|
@@ -29806,45 +30691,45 @@ var CostingResultSchema = z123.object({
|
|
|
29806
30691
|
estimatedProfit: FiniteCostingNumber,
|
|
29807
30692
|
actualMargin: FiniteCostingNumber,
|
|
29808
30693
|
breakEvenAdsManagerCpa: FiniteCostingNumber,
|
|
29809
|
-
wasPriceAutoAdjusted:
|
|
29810
|
-
profitabilityStatus:
|
|
29811
|
-
});
|
|
29812
|
-
var RewardProgressSchema =
|
|
29813
|
-
campaignKey:
|
|
29814
|
-
totalCredits:
|
|
29815
|
-
earnedCredits:
|
|
29816
|
-
milestones:
|
|
30694
|
+
wasPriceAutoAdjusted: z126.boolean(),
|
|
30695
|
+
profitabilityStatus: z126.literal("healthy")
|
|
30696
|
+
});
|
|
30697
|
+
var RewardProgressSchema = z126.object({
|
|
30698
|
+
campaignKey: z126.literal("dropshipping-launch-v1"),
|
|
30699
|
+
totalCredits: z126.number().int().positive(),
|
|
30700
|
+
earnedCredits: z126.number().int().nonnegative(),
|
|
30701
|
+
milestones: z126.array(z126.object({
|
|
29817
30702
|
milestone: DropshippingRewardMilestone,
|
|
29818
|
-
credits:
|
|
29819
|
-
available:
|
|
29820
|
-
earned:
|
|
30703
|
+
credits: z126.number().int().positive(),
|
|
30704
|
+
available: z126.boolean().optional(),
|
|
30705
|
+
earned: z126.boolean(),
|
|
29821
30706
|
grantId: ProjectId.nullable(),
|
|
29822
|
-
grantedAt:
|
|
30707
|
+
grantedAt: z126.string().datetime().nullable()
|
|
29823
30708
|
})).length(DropshippingRewardMilestone.options.length)
|
|
29824
30709
|
});
|
|
29825
|
-
var ProjectResponseSchema =
|
|
30710
|
+
var ProjectResponseSchema = z126.object({
|
|
29826
30711
|
id: ProjectId,
|
|
29827
30712
|
taskId: ProjectId.nullable(),
|
|
29828
30713
|
status: DropshippingProjectStatus,
|
|
29829
|
-
originKey:
|
|
30714
|
+
originKey: z126.string().min(1),
|
|
29830
30715
|
researchRunId: ProjectId.nullable(),
|
|
29831
|
-
candidateSourceId:
|
|
29832
|
-
productName:
|
|
30716
|
+
candidateSourceId: z126.string().nullable(),
|
|
30717
|
+
productName: z126.string().nullable(),
|
|
29833
30718
|
productUrl: HttpUrl3.nullable(),
|
|
29834
30719
|
referenceImageUrl: HttpUrl3.nullable(),
|
|
29835
|
-
targetCountry:
|
|
29836
|
-
languageCode:
|
|
29837
|
-
hasReferenceImage:
|
|
29838
|
-
hasWinnerReferenceImage:
|
|
30720
|
+
targetCountry: z126.string().nullable(),
|
|
30721
|
+
languageCode: z126.string().nullable(),
|
|
30722
|
+
hasReferenceImage: z126.boolean(),
|
|
30723
|
+
hasWinnerReferenceImage: z126.boolean(),
|
|
29839
30724
|
costingInput: CostingInputSchema.nullable(),
|
|
29840
30725
|
costingResult: CostingResultSchema.nullable(),
|
|
29841
|
-
costingVersion:
|
|
29842
|
-
version:
|
|
30726
|
+
costingVersion: z126.number().int().positive().nullable(),
|
|
30727
|
+
version: z126.number().int().positive(),
|
|
29843
30728
|
phase: DropshippingProjectPhase,
|
|
29844
30729
|
nextAction: DropshippingProjectNextAction.nullable(),
|
|
29845
30730
|
rewards: RewardProgressSchema,
|
|
29846
|
-
createdAt:
|
|
29847
|
-
updatedAt:
|
|
30731
|
+
createdAt: z126.string().datetime(),
|
|
30732
|
+
updatedAt: z126.string().datetime()
|
|
29848
30733
|
}).passthrough();
|
|
29849
30734
|
var NEXT_ACTION_GUIDANCE = {
|
|
29850
30735
|
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.",
|
|
@@ -29864,14 +30749,14 @@ var NEXT_ACTION_GUIDANCE = {
|
|
|
29864
30749
|
function projectText(raw, referenceValues = []) {
|
|
29865
30750
|
const project = ProjectResponseSchema.parse(raw);
|
|
29866
30751
|
const kind = project.nextAction?.kind;
|
|
29867
|
-
const
|
|
30752
|
+
const guidance5 = kind ? NEXT_ACTION_GUIDANCE[kind] : void 0;
|
|
29868
30753
|
const winnerReferenceResult = project.hasWinnerReferenceImage ? WinnerReferenceInput.safeParse({
|
|
29869
30754
|
runId: project.researchRunId,
|
|
29870
30755
|
candidateSourceId: project.candidateSourceId
|
|
29871
30756
|
}) : void 0;
|
|
29872
30757
|
const winnerReference = winnerReferenceResult?.success ? winnerReferenceResult.data : void 0;
|
|
29873
30758
|
const standaloneCreativeGuidance = winnerReference ? "Si el usuario pide s\xF3lo crear contenido con este producto, no contin\xFAes el costeo y no pidas el costo del proveedor. Llama `prepare_winning_product_image` con `winnerReference`, muestra la foto y, despu\xE9s de su elecci\xF3n, pasa el mismo `winnerReference` a la herramienta creativa independiente solicitada." : void 0;
|
|
29874
|
-
const guidedNextActionGuidance =
|
|
30759
|
+
const guidedNextActionGuidance = guidance5 ? `S\xF3lo si el usuario quiere continuar el lanzamiento guiado completo: ${guidance5}` : void 0;
|
|
29875
30760
|
const referenceNotice = referenceValues.length > 0 ? `Valores gu\xEDa de ${project.targetCountry ?? "el pa\xEDs"} que ProductMaker us\xF3 porque no los diste: ${referenceValues.join(", ")}. Son un punto de partida editable, no datos del usuario: dile cu\xE1les fueron y ofr\xE9cele corregirlos. Si te da los suyos, vuelve a llamar cost_dropshipping_project con la version nueva.` : void 0;
|
|
29876
30761
|
return {
|
|
29877
30762
|
content: [
|
|
@@ -29964,58 +30849,58 @@ function registerDropshippingProjectTools(server, api) {
|
|
|
29964
30849
|
__name(registerDropshippingProjectTools, "registerDropshippingProjectTools");
|
|
29965
30850
|
|
|
29966
30851
|
// src/tools/daily-control.ts
|
|
29967
|
-
import { z as
|
|
30852
|
+
import { z as z127 } from "zod";
|
|
29968
30853
|
var getDailyControlGuidance = getProductMakerCapabilityModelGuidance("get_daily_control");
|
|
29969
30854
|
var linkCampaignToProjectGuidance = getProductMakerCapabilityModelGuidance("link_campaign_to_project");
|
|
29970
30855
|
var reportDailyReturnsGuidance = getProductMakerCapabilityModelGuidance("report_daily_returns");
|
|
29971
|
-
var ProviderCampaignIdentifier =
|
|
29972
|
-
var DateOnly =
|
|
29973
|
-
var DailyControlInput =
|
|
30856
|
+
var ProviderCampaignIdentifier = z127.string().min(1).max(64).regex(/^[A-Za-z0-9_-]+$/);
|
|
30857
|
+
var DateOnly = z127.string().regex(/^\d{4}-\d{2}-\d{2}$/);
|
|
30858
|
+
var DailyControlInput = z127.object({
|
|
29974
30859
|
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."),
|
|
29975
|
-
days:
|
|
29976
|
-
projectId:
|
|
30860
|
+
days: z127.number().int().min(1).max(14).default(3).describe("Tama\xF1o de la ventana, 1 a 14 d\xEDas."),
|
|
30861
|
+
projectId: z127.string().uuid().optional().describe("Opcional: limita el reporte a un solo proyecto en vez de todos los productos activos.")
|
|
29977
30862
|
}).strict();
|
|
29978
|
-
var LinkCampaignInput =
|
|
29979
|
-
projectId:
|
|
29980
|
-
provider:
|
|
30863
|
+
var LinkCampaignInput = z127.object({
|
|
30864
|
+
projectId: z127.string().uuid(),
|
|
30865
|
+
provider: z127.enum([
|
|
29981
30866
|
"meta",
|
|
29982
30867
|
"tiktok"
|
|
29983
30868
|
]),
|
|
29984
30869
|
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."),
|
|
29985
30870
|
campaignId: ProviderCampaignIdentifier
|
|
29986
30871
|
}).strict();
|
|
29987
|
-
var ReportReturnsInput =
|
|
29988
|
-
projectId:
|
|
30872
|
+
var ReportReturnsInput = z127.object({
|
|
30873
|
+
projectId: z127.string().uuid(),
|
|
29989
30874
|
date: DateOnly,
|
|
29990
|
-
returnedOrders:
|
|
29991
|
-
note:
|
|
30875
|
+
returnedOrders: z127.number().int().min(0).max(1e5),
|
|
30876
|
+
note: z127.string().trim().max(280).optional()
|
|
29992
30877
|
}).strict();
|
|
29993
|
-
var DailyControlStatusMirror = DailyControlStatusEnum.or(
|
|
29994
|
-
var DailyControlLightMirror = DailyControlLightEnum.or(
|
|
29995
|
-
var DailyControlSourceMirror = DailyControlSourceEnum.or(
|
|
29996
|
-
var DailyControlVerdictMirror = DailyControlVerdictEnum.or(
|
|
29997
|
-
var DailyControlDayMirror =
|
|
29998
|
-
date:
|
|
29999
|
-
returnedOrders:
|
|
30878
|
+
var DailyControlStatusMirror = DailyControlStatusEnum.or(z127.string());
|
|
30879
|
+
var DailyControlLightMirror = DailyControlLightEnum.or(z127.string());
|
|
30880
|
+
var DailyControlSourceMirror = DailyControlSourceEnum.or(z127.string());
|
|
30881
|
+
var DailyControlVerdictMirror = DailyControlVerdictEnum.or(z127.string());
|
|
30882
|
+
var DailyControlDayMirror = z127.object({
|
|
30883
|
+
date: z127.string(),
|
|
30884
|
+
returnedOrders: z127.number(),
|
|
30000
30885
|
returnedSource: DailyControlSourceMirror,
|
|
30001
30886
|
cancelledSource: DailyControlSourceMirror,
|
|
30002
|
-
hasEstimates:
|
|
30887
|
+
hasEstimates: z127.boolean(),
|
|
30003
30888
|
light: DailyControlLightMirror
|
|
30004
30889
|
}).passthrough();
|
|
30005
|
-
var DailyControlProductMirror =
|
|
30006
|
-
projectId:
|
|
30007
|
-
productName:
|
|
30890
|
+
var DailyControlProductMirror = z127.object({
|
|
30891
|
+
projectId: z127.string().uuid(),
|
|
30892
|
+
productName: z127.string().nullable(),
|
|
30008
30893
|
status: DailyControlStatusMirror,
|
|
30009
|
-
statusMessage:
|
|
30010
|
-
days:
|
|
30894
|
+
statusMessage: z127.string().nullable(),
|
|
30895
|
+
days: z127.array(DailyControlDayMirror),
|
|
30011
30896
|
verdict: DailyControlVerdictMirror.nullable(),
|
|
30012
|
-
rationale:
|
|
30897
|
+
rationale: z127.string().nullable()
|
|
30013
30898
|
}).passthrough();
|
|
30014
|
-
var DailyControlResponseMirror =
|
|
30015
|
-
since:
|
|
30016
|
-
until:
|
|
30017
|
-
products:
|
|
30018
|
-
archivedProjectSpend:
|
|
30899
|
+
var DailyControlResponseMirror = z127.object({
|
|
30900
|
+
since: z127.string(),
|
|
30901
|
+
until: z127.string(),
|
|
30902
|
+
products: z127.array(DailyControlProductMirror),
|
|
30903
|
+
archivedProjectSpend: z127.array(z127.unknown())
|
|
30019
30904
|
}).passthrough();
|
|
30020
30905
|
var MCP_WARNINGS_KEY = "_mcpDailyControlWarnings";
|
|
30021
30906
|
function withWarnings(response) {
|
|
@@ -30090,7 +30975,7 @@ __name(registerDailyControlTools, "registerDailyControlTools");
|
|
|
30090
30975
|
// src/tools/product-control.ts
|
|
30091
30976
|
import { readFile, stat } from "fs/promises";
|
|
30092
30977
|
import { basename as basename2, isAbsolute, resolve as resolve3 } from "path";
|
|
30093
|
-
import { z as
|
|
30978
|
+
import { z as z128 } from "zod";
|
|
30094
30979
|
var getProductControlGuidance = getProductMakerCapabilityModelGuidance("get_product_control");
|
|
30095
30980
|
var listImportableProductsGuidance = getProductMakerCapabilityModelGuidance("list_importable_products");
|
|
30096
30981
|
var importProductControlsGuidance = getProductMakerCapabilityModelGuidance("import_product_controls");
|
|
@@ -30103,36 +30988,36 @@ var createOperationGuidance = getProductMakerCapabilityModelGuidance("create_ope
|
|
|
30103
30988
|
var mergeProductsGuidance = getProductMakerCapabilityModelGuidance("merge_products");
|
|
30104
30989
|
var syncShopifyOrdersGuidance = getProductMakerCapabilityModelGuidance("sync_shopify_orders");
|
|
30105
30990
|
var importOrdersFileGuidance = getProductMakerCapabilityModelGuidance("import_orders_file");
|
|
30106
|
-
var DateOnly2 =
|
|
30991
|
+
var DateOnly2 = z128.string().regex(/^\d{4}-\d{2}-\d{2}$/);
|
|
30107
30992
|
var IMPORT_BATCH_MAX2 = 50;
|
|
30108
|
-
var ProductControlListInput =
|
|
30109
|
-
days:
|
|
30993
|
+
var ProductControlListInput = z128.object({
|
|
30994
|
+
days: z128.number().int().min(1).max(14).default(3).describe("Tama\xF1o de la ventana, 1 a 14 d\xEDas."),
|
|
30110
30995
|
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."),
|
|
30111
|
-
shopDomain:
|
|
30996
|
+
shopDomain: z128.string().min(1).optional().describe("Tienda, si tiene m\xE1s de una conectada.")
|
|
30112
30997
|
}).strict();
|
|
30113
|
-
var ImportableProductsInput =
|
|
30114
|
-
q:
|
|
30115
|
-
after:
|
|
30116
|
-
shopDomain:
|
|
30998
|
+
var ImportableProductsInput = z128.object({
|
|
30999
|
+
q: z128.string().trim().max(120).optional().describe("B\xFAsqueda por t\xEDtulo. Sin esto un cat\xE1logo grande es inrecorrible."),
|
|
31000
|
+
after: z128.string().max(512).optional().describe("Cursor de la p\xE1gina anterior, tal como vino en la respuesta."),
|
|
31001
|
+
shopDomain: z128.string().min(1).optional()
|
|
30117
31002
|
}).strict();
|
|
30118
|
-
var ImportProductControlsInput =
|
|
30119
|
-
shopifyProductIds:
|
|
31003
|
+
var ImportProductControlsInput = z128.object({
|
|
31004
|
+
shopifyProductIds: z128.array(z128.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."),
|
|
30120
31005
|
// Obligatorio, como en el endpoint: con dos tiendas conectadas, importar
|
|
30121
31006
|
// sin decir a cuál archivaría el producto bajo la tienda equivocada.
|
|
30122
|
-
shopDomain:
|
|
31007
|
+
shopDomain: z128.string().min(1)
|
|
30123
31008
|
}).strict();
|
|
30124
|
-
var SetProductCostingInput =
|
|
30125
|
-
shopDomain:
|
|
30126
|
-
shopifyProductId:
|
|
30127
|
-
targetCountry:
|
|
30128
|
-
supplierCost:
|
|
30129
|
-
shippingCost:
|
|
30130
|
-
returnRate:
|
|
30131
|
-
cancellationRate:
|
|
31009
|
+
var SetProductCostingInput = z128.object({
|
|
31010
|
+
shopDomain: z128.string().min(1),
|
|
31011
|
+
shopifyProductId: z128.string().min(1).max(64),
|
|
31012
|
+
targetCountry: z128.string().length(2).describe("Pa\xEDs de venta en ISO-2, que fija moneda y referencia de flete."),
|
|
31013
|
+
supplierCost: z128.number().finite().positive().describe("Lo que le cuesta el producto al proveedor. Nunca cero."),
|
|
31014
|
+
shippingCost: z128.number().finite().nonnegative(),
|
|
31015
|
+
returnRate: z128.number().finite().min(0).lt(1).describe("Proporci\xF3n 0\u20131, no porcentaje: 0.15 es quince por ciento."),
|
|
31016
|
+
cancellationRate: z128.number().finite().min(0).lt(1)
|
|
30132
31017
|
}).strict();
|
|
30133
|
-
var Count =
|
|
30134
|
-
var ReportProductCountsInput =
|
|
30135
|
-
productControlId:
|
|
31018
|
+
var Count = z128.number().int().min(0).max(1e5).nullable().optional();
|
|
31019
|
+
var ReportProductCountsInput = z128.object({
|
|
31020
|
+
productControlId: z128.string().uuid().describe("El identificador que devuelve get_product_control."),
|
|
30136
31021
|
date: DateOnly2,
|
|
30137
31022
|
orders: Count.describe("Pedidos que entraron ese d\xEDa."),
|
|
30138
31023
|
cancelledOrders: Count,
|
|
@@ -30142,36 +31027,36 @@ var ReportProductCountsInput = z125.object({
|
|
|
30142
31027
|
}).strict();
|
|
30143
31028
|
var MAX_ORDERS_FILE_BYTES = 8 * 1024 * 1024;
|
|
30144
31029
|
var SyncShopifyOrdersInput = SyncShopifyOrdersSchema;
|
|
30145
|
-
var ImportOrdersFileInput =
|
|
31030
|
+
var ImportOrdersFileInput = z128.object({
|
|
30146
31031
|
/** Ruta ABSOLUTA en la máquina de quien usa el MCP. */
|
|
30147
|
-
path:
|
|
30148
|
-
operationId:
|
|
31032
|
+
path: z128.string().min(1),
|
|
31033
|
+
operationId: z128.string().uuid().optional(),
|
|
30149
31034
|
/** Solo mirar: devuelve el resumen y no escribe nada. */
|
|
30150
|
-
dryRun:
|
|
31035
|
+
dryRun: z128.boolean().optional()
|
|
30151
31036
|
}).strict();
|
|
30152
|
-
var CreateOperationInput =
|
|
30153
|
-
countryCode:
|
|
30154
|
-
label:
|
|
31037
|
+
var CreateOperationInput = z128.object({
|
|
31038
|
+
countryCode: z128.string().length(2),
|
|
31039
|
+
label: z128.string().trim().min(1).max(60).optional()
|
|
30155
31040
|
}).strict();
|
|
30156
|
-
var MergeProductsInput =
|
|
30157
|
-
sourceId:
|
|
30158
|
-
targetId:
|
|
31041
|
+
var MergeProductsInput = z128.object({
|
|
31042
|
+
sourceId: z128.string().uuid(),
|
|
31043
|
+
targetId: z128.string().uuid()
|
|
30159
31044
|
}).strict();
|
|
30160
|
-
var SetMonthlyExpenseInput =
|
|
30161
|
-
month:
|
|
31045
|
+
var SetMonthlyExpenseInput = z128.object({
|
|
31046
|
+
month: z128.string().regex(/^\d{4}-(0[1-9]|1[0-2])$/).optional(),
|
|
30162
31047
|
label: SaveMonthlyExpenseSchema.shape.label,
|
|
30163
|
-
amount:
|
|
30164
|
-
operationId:
|
|
31048
|
+
amount: z128.number().finite().min(0),
|
|
31049
|
+
operationId: z128.string().uuid().optional()
|
|
30165
31050
|
}).strict();
|
|
30166
|
-
var SetAdSpendInput =
|
|
30167
|
-
month:
|
|
30168
|
-
operationId:
|
|
31051
|
+
var SetAdSpendInput = z128.object({
|
|
31052
|
+
month: z128.string().regex(/^\d{4}-(0[1-9]|1[0-2])$/).optional(),
|
|
31053
|
+
operationId: z128.string().uuid().optional(),
|
|
30169
31054
|
/** null RETIRA la corrección; cero afirma que no pautó. */
|
|
30170
|
-
amount:
|
|
31055
|
+
amount: z128.number().finite().min(0).nullable()
|
|
30171
31056
|
}).strict();
|
|
30172
|
-
var GetFinancesInput =
|
|
30173
|
-
month:
|
|
30174
|
-
operationId:
|
|
31057
|
+
var GetFinancesInput = z128.object({
|
|
31058
|
+
month: z128.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."),
|
|
31059
|
+
operationId: z128.string().uuid().optional().describe("El negocio, si tiene mas de uno. Con uno solo no hace falta decirlo.")
|
|
30175
31060
|
}).strict();
|
|
30176
31061
|
function registerProductControlTools(server, api, { transport = "http" } = {}) {
|
|
30177
31062
|
server.registerTool("get_product_control", {
|
|
@@ -30361,28 +31246,28 @@ function registerProductControlTools(server, api, { transport = "http" } = {}) {
|
|
|
30361
31246
|
__name(registerProductControlTools, "registerProductControlTools");
|
|
30362
31247
|
|
|
30363
31248
|
// src/tools/market-study-estimate.ts
|
|
30364
|
-
import { z as
|
|
31249
|
+
import { z as z129 } from "zod";
|
|
30365
31250
|
var estimateMarketStudyGuidance = getProductMakerCapabilityModelGuidance("estimate_market_study_cost");
|
|
30366
|
-
var ProductTaskOutputEnum =
|
|
31251
|
+
var ProductTaskOutputEnum = z129.enum([
|
|
30367
31252
|
"landing",
|
|
30368
31253
|
"video",
|
|
30369
31254
|
"image"
|
|
30370
31255
|
]);
|
|
30371
|
-
var EstimateMarketStudyInput =
|
|
30372
|
-
kind:
|
|
31256
|
+
var EstimateMarketStudyInput = z129.object({
|
|
31257
|
+
kind: z129.enum([
|
|
30373
31258
|
"video",
|
|
30374
31259
|
"product_task"
|
|
30375
31260
|
]).describe('"video" para generate_video_creative; "product_task" para create_product_task.'),
|
|
30376
|
-
videoAngleCount:
|
|
30377
|
-
durationSeconds:
|
|
31261
|
+
videoAngleCount: z129.number().int().min(1).max(3).describe("Los mismos 1, 2 o 3 videos que vas a pedir al crear."),
|
|
31262
|
+
durationSeconds: z129.number().int().min(4).max(30).default(15).describe("La misma duraci\xF3n que enviar\xE1s al crear."),
|
|
30378
31263
|
videoStructure: VideoStructureEnum.default("single_take"),
|
|
30379
31264
|
videoQuality: VideoQualityEnum.default("economy"),
|
|
30380
31265
|
videoResolution: VideoResolutionEnum.default("480p"),
|
|
30381
|
-
productImageCount:
|
|
31266
|
+
productImageCount: z129.number().int().min(1).max(5).optional().describe("Cantidad de fotos del producto que adjuntar\xE1s (1-5)."),
|
|
30382
31267
|
outputs: ProductTaskOutputEnum.array().min(1).max(3).optional().describe('Solo para kind="product_task": los mismos outputs de create_product_task. Default: todos.'),
|
|
30383
|
-
generateAiMusic:
|
|
31268
|
+
generateAiMusic: z129.boolean().optional(),
|
|
30384
31269
|
generateAiEdit: AiEditRequestSchema.optional(),
|
|
30385
|
-
replicateWinnerWhenFound:
|
|
31270
|
+
replicateWinnerWhenFound: z129.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.")
|
|
30386
31271
|
}).strict();
|
|
30387
31272
|
function buildMarketStudyEstimateRequests(input) {
|
|
30388
31273
|
const quotesVideo = input.kind === "video" || (input.outputs ?? [
|
|
@@ -30518,6 +31403,8 @@ var PRODUCTMAKER_SERVER_INSTRUCTIONS = [
|
|
|
30518
31403
|
"Preserve all price disclosures and explicit confirmations. New enabled classic landings include the initial product GIF and its cost by default; disclose that choice before confirming. Pass includeInitialLandingGif=false when the user excludes it, and preserve that flag on retries.",
|
|
30519
31404
|
"For Shopify publication recovery, preserve the canonical recovery issues, revision and fingerprint. Ask the seller for missing values and confirm the exact currency, unit price and pack totals before submitting commerceCorrection with confirmed:true. Never invent prices, convert currency or erase a visible offer. Follow open_editor in ProductMaker when required; a changed context needs fresh review. commerceCorrectionSaved proves saved inputs only: check any uncertain publication in the task and store before another attempt, especially publicationMode=new.",
|
|
30520
31405
|
"Before a paid creative call, show a compact summary of what will be generated and the commercial inputs, then wait for explicit confirmation. Whenever video is requested, ask videoAngleCount (1, 2 or 3) even if the user delegates creative decisions, because quantity changes cost. Use create_product_task for a combined/autopilot request and generate_video_creative when the user asks only for one to three videos. Every standalone image, landing, and video generation requires creativeSetup. Use guided unless the user explicitly delegates every creative decision; only then use automatic, which maps to the web AI-decides-all toggle. Ask each guided setup as one compact checklist, not a serial interrogation: image covers archetype, treatment, format, quantity, quality, style mix, ad copy, competitor scan, context, direction, colors, headline and subheadline; landing covers classic strategy, format, competitor scan, payment methods, context, direction, headline and subheadline; video covers one explicit yes/no market-video study choice, narrative preset, buyer persona, presenter and creative direction. The market study is OFF by default: omitting competitorScanEnabled means no study. Before asking that yes/no choice for a video or a combined task with video, call estimate_market_study_cost and show the user both totals (without and with the study); never set competitorScanEnabled=true unless the user accepted that study (or requested the narrativa an\xE1lisis de mercado). Callers can mix narratives per video using angleNarratives (e.g. ['market', 'review', 'sassy_product_pixar']) in create_product_task and generate_video_creative; 'market' represents the narrativa an\xE1lisis de mercado. Guided mode requests angle reviews, and video also requests script review; show every numbered angle candidate, let the user select up to the authorized videoAngleCount and apply edits only to the numbered angles they name. Warn before confirmation that multi-product combo mode currently skips the script-review pause. Show each returned draft and concrete choices, then wait for the user. When get_video_script returns ready or interactionState awaiting_user_review, present it once and NEVER call get_video_script or get_task_status again until the user answers. After resolving a video angle review, follow reviewNextAction and poll get_video_script directly until ready/error/approved/skipped; approved or skipped resumes get_task_status. After other resolve_pending_action calls or approve_video_script succeeds, resume get_task_status until another review or a terminal state.",
|
|
31406
|
+
"Route by business type before choosing tools. A service business or personal brand (barbershop, clinic, studio, academy, agency, consultant), its Instagram/Facebook/TikTok/website, or a request for a brand analysis uses the service journey: create_brand, then create_service_landing, then create_animated_video with the published landing URL and create_service_images with the brandId. A physical product, supplier, marketplace or AliExpress/Dropi link uses the dropshipping tools. Never mix them: never pass a product task to the service tools or a brandId to the product tools. If the type is unclear, ask one short question.",
|
|
31407
|
+
"In the service journey, run each step's free action first (create_brand action=start without confirmed, create_service_landing action=prepare), show its price, and wait for explicit acceptance before the paid call. Ask the user for missing business data (contact, area, services and prices) once, in a single message, and pass their own words as businessNotes or businessInfo; never invent them. Keep polling status while a step runs and follow each result's nextAction. Publish the landing only after the user agrees, then offer the animated video from its publicUrl and the service images.",
|
|
30521
31408
|
"For an animated service video, call create_animated_video action=prepare first: URL capture is free and never generates. Poll action=status until ready or a terminal failure. Show the ready preview and exact format, duration, language and price, then wait for explicit user confirmation before action=generate with websiteCaptureId and confirmed=true. A blocked page needs another public URL without login; never ask for passwords. Do not treat capture readiness as permission to spend credits. Retry a lost generation receipt with the same websiteCaptureId and choices even after expiry: this replays the paid task. Keep generationRequestId unchanged when supplied. For another video explicitly requested and confirmed by the user, supply a fresh generationRequestId; never rotate it automatically after an error.",
|
|
30522
31409
|
"ProductMaker Meta campaigns remain PAUSED.",
|
|
30523
31410
|
"If the user asks for deeper Meta metrics and no suitable Meta tools are connected, suggest Meta's official MCP as an optional extension. Point only to Meta-owned documentation or repositories, never auto-install it or request tokens, and never claim that it is installed.",
|
|
@@ -30586,6 +31473,9 @@ function createServer(opts) {
|
|
|
30586
31473
|
proxyBaseUrl,
|
|
30587
31474
|
transport
|
|
30588
31475
|
});
|
|
31476
|
+
registerBrandTools(server, api);
|
|
31477
|
+
registerServiceLandingTools(server, api);
|
|
31478
|
+
registerServiceImageTools(server, api);
|
|
30589
31479
|
registerAnimatedVideoTools(server, api);
|
|
30590
31480
|
registerCreativeTools(server, api, {
|
|
30591
31481
|
transport
|