@productmaker/mcp 11.2.2 → 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 +1359 -472
- package/dist/stdio.js +1359 -472
- 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", [
|
|
@@ -22033,7 +22167,7 @@ var ApiClient = class {
|
|
|
22033
22167
|
};
|
|
22034
22168
|
|
|
22035
22169
|
// src/index.ts
|
|
22036
|
-
var MCP_VERSION = "11.
|
|
22170
|
+
var MCP_VERSION = "11.3.0";
|
|
22037
22171
|
|
|
22038
22172
|
// src/meta-upload-app.ts
|
|
22039
22173
|
import { readFileSync } from "fs";
|
|
@@ -26386,8 +26520,756 @@ ANTES de invocar, resume outputs, cantidad de videos y datos comerciales, explic
|
|
|
26386
26520
|
}
|
|
26387
26521
|
__name(registerTaskTools, "registerTaskTools");
|
|
26388
26522
|
|
|
26389
|
-
// src/tools/
|
|
26523
|
+
// src/tools/brands.ts
|
|
26390
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";
|
|
26391
27273
|
var generateFreeVideoGuidance = getProductMakerCapabilityModelGuidance("generate_free_video");
|
|
26392
27274
|
var generateVideoCreativeGuidance = getProductMakerCapabilityModelGuidance("generate_video_creative");
|
|
26393
27275
|
var generateImageCreativesGuidance = getProductMakerCapabilityModelGuidance("generate_image_creatives");
|
|
@@ -26398,72 +27280,72 @@ var CreativeRefs = {
|
|
|
26398
27280
|
images: ImagesInput,
|
|
26399
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.'),
|
|
26400
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.'),
|
|
26401
|
-
price:
|
|
26402
|
-
offer:
|
|
26403
|
-
compareAtPrice:
|
|
26404
|
-
productContext:
|
|
26405
|
-
productLabel:
|
|
26406
|
-
buyerPersona:
|
|
26407
|
-
isCombo:
|
|
26408
|
-
competitorScanEnabled:
|
|
26409
|
-
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.")
|
|
26410
27292
|
};
|
|
26411
|
-
var VideoPresenter =
|
|
26412
|
-
|
|
26413
|
-
mode:
|
|
27293
|
+
var VideoPresenter = z114.discriminatedUnion("mode", [
|
|
27294
|
+
z114.object({
|
|
27295
|
+
mode: z114.literal("none")
|
|
26414
27296
|
}).strict(),
|
|
26415
|
-
|
|
26416
|
-
mode:
|
|
26417
|
-
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.")
|
|
26418
27300
|
}).strict(),
|
|
26419
|
-
|
|
26420
|
-
mode:
|
|
26421
|
-
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.")
|
|
26422
27304
|
}).strict()
|
|
26423
27305
|
]);
|
|
26424
|
-
var VideoCreativeSetup =
|
|
26425
|
-
|
|
26426
|
-
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".')
|
|
26427
27309
|
}).strict(),
|
|
26428
|
-
|
|
26429
|
-
mode:
|
|
27310
|
+
z114.object({
|
|
27311
|
+
mode: z114.literal("guided"),
|
|
26430
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.'),
|
|
26431
|
-
buyerPersona:
|
|
27313
|
+
buyerPersona: z114.string().trim().min(1).max(360),
|
|
26432
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.'),
|
|
26433
27315
|
presenter: VideoPresenter,
|
|
26434
|
-
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.")
|
|
26435
27317
|
}).strict()
|
|
26436
27318
|
]);
|
|
26437
|
-
var VideoInput =
|
|
27319
|
+
var VideoInput = z114.object({
|
|
26438
27320
|
images: CreativeRefs.images.optional(),
|
|
26439
27321
|
winnerReference: WinnerReferenceInput.optional().describe(WINNER_IMAGE_CHOICE_GUIDANCE),
|
|
26440
|
-
title:
|
|
26441
|
-
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".'),
|
|
26442
27324
|
language: CreativeRefs.language,
|
|
26443
27325
|
country: CreativeRefs.country,
|
|
26444
|
-
aspectRatio:
|
|
27326
|
+
aspectRatio: z114.enum([
|
|
26445
27327
|
"9:16",
|
|
26446
27328
|
"16:9",
|
|
26447
27329
|
"Auto"
|
|
26448
27330
|
]).describe('Relaci\xF3n de aspecto. "9:16" = vertical (TikTok/Reels \u2014 default recomendado para ads). "16:9" = horizontal (YouTube). "Auto" = decide la IA.'),
|
|
26449
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.'),
|
|
26450
|
-
durationSeconds:
|
|
26451
|
-
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."),
|
|
26452
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.'),
|
|
26453
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."),
|
|
26454
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.'),
|
|
26455
27337
|
videoQuality: VideoQualityEnum.default("economy").describe('Opcional. Calidad: "economy" = Econ\xF3mico, "medium" = Medio o "pro" = Pro. El sistema elige el proveedor internamente. Default: economy.'),
|
|
26456
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.'),
|
|
26457
|
-
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"].'),
|
|
26458
27340
|
price: CreativeRefs.price,
|
|
26459
27341
|
offer: CreativeRefs.offer,
|
|
26460
27342
|
productContext: CreativeRefs.productContext,
|
|
26461
27343
|
productLabel: CreativeRefs.productLabel,
|
|
26462
|
-
cta:
|
|
26463
|
-
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.'),
|
|
26464
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.'),
|
|
26465
27347
|
competitorScanEnabled: CreativeRefs.competitorScanEnabled,
|
|
26466
|
-
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."),
|
|
26467
27349
|
brandId: CreativeRefs.brandId
|
|
26468
27350
|
}).strict();
|
|
26469
27351
|
var FreeVideoQualityEnum = VideoQualityEnum;
|
|
@@ -26481,7 +27363,7 @@ var FREE_VIDEO_DURATIONS_BY_QUALITY = {
|
|
|
26481
27363
|
length: 29
|
|
26482
27364
|
}, (_, index) => index + 2)
|
|
26483
27365
|
};
|
|
26484
|
-
var FreeVideoModelIdEnum =
|
|
27366
|
+
var FreeVideoModelIdEnum = z114.enum([
|
|
26485
27367
|
"grok-imagine-video-1-5-preview",
|
|
26486
27368
|
"minimax-h3/reference-to-video",
|
|
26487
27369
|
"wan/3-0-video",
|
|
@@ -26492,66 +27374,66 @@ var FREE_SELECTABLE_MODEL_IDS = [
|
|
|
26492
27374
|
"minimax-h3/reference-to-video",
|
|
26493
27375
|
"wan/3-0-video"
|
|
26494
27376
|
];
|
|
26495
|
-
var FreeVideoInput =
|
|
27377
|
+
var FreeVideoInput = z114.object({
|
|
26496
27378
|
images: ImagesInput.optional().describe("Opcional. 1-5 fotos del producto. Om\xEDtelo para generar desde texto puro."),
|
|
26497
|
-
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."),
|
|
26498
27380
|
language: CreativeRefs.language,
|
|
26499
27381
|
quality: FreeVideoQualityEnum.describe('Calidad del video: "economy" = Econ\xF3mico, "medium" = Medio o "pro" = Pro (Wan).'),
|
|
26500
27382
|
// Retired in MCP 7.0.0 (Task 11): kept in the schema, accepted, and
|
|
26501
27383
|
// parsed ONLY so the handler can reject it with a message naming its
|
|
26502
27384
|
// replacement — dropping it silently would turn a caller's explicit
|
|
26503
27385
|
// Pro++ pick into a quiet fallback to Wan.
|
|
26504
|
-
freeProModel:
|
|
27386
|
+
freeProModel: z114.enum([
|
|
26505
27387
|
"wan",
|
|
26506
27388
|
"seedance"
|
|
26507
27389
|
]).optional().describe("ELIMINADO en MCP 7.0.0 \u2014 usa `modelId`. Enviar este campo produce un error."),
|
|
26508
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.'),
|
|
26509
|
-
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."),
|
|
26510
27392
|
videoResolution: VideoResolutionEnum.describe("Resoluci\xF3n del video: 480p o 720p."),
|
|
26511
|
-
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.'),
|
|
26512
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.'),
|
|
26513
|
-
addSubtitles:
|
|
26514
|
-
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.")
|
|
26515
27397
|
}).strict();
|
|
26516
27398
|
var imageStyleList = ImageCreativeStyleEnum.options.join(" | ");
|
|
26517
|
-
var ImageCreativeSetup =
|
|
26518
|
-
|
|
26519
|
-
mode:
|
|
27399
|
+
var ImageCreativeSetup = z114.discriminatedUnion("mode", [
|
|
27400
|
+
z114.object({
|
|
27401
|
+
mode: z114.literal("automatic")
|
|
26520
27402
|
}).strict(),
|
|
26521
|
-
|
|
26522
|
-
mode:
|
|
26523
|
-
creativeArchetype:
|
|
27403
|
+
z114.object({
|
|
27404
|
+
mode: z114.literal("guided"),
|
|
27405
|
+
creativeArchetype: z114.union([
|
|
26524
27406
|
ImageCreativeArchetypeEnum,
|
|
26525
|
-
|
|
27407
|
+
z114.literal("ai_decides")
|
|
26526
27408
|
]),
|
|
26527
|
-
visualTreatment:
|
|
27409
|
+
visualTreatment: z114.union([
|
|
26528
27410
|
ImageCreativeVisualTreatmentEnum,
|
|
26529
|
-
|
|
27411
|
+
z114.literal("ai_decides")
|
|
26530
27412
|
]),
|
|
26531
27413
|
aspectRatio: ImageCreativeAspectRatioEnum,
|
|
26532
|
-
nVariants:
|
|
26533
|
-
|
|
26534
|
-
|
|
26535
|
-
|
|
26536
|
-
|
|
26537
|
-
|
|
27414
|
+
nVariants: z114.union([
|
|
27415
|
+
z114.literal(1),
|
|
27416
|
+
z114.literal(2),
|
|
27417
|
+
z114.literal(3),
|
|
27418
|
+
z114.literal(4),
|
|
27419
|
+
z114.literal(6)
|
|
26538
27420
|
]),
|
|
26539
|
-
imageModel:
|
|
26540
|
-
styleMix:
|
|
26541
|
-
generateAdCopy:
|
|
26542
|
-
competitorScanEnabled:
|
|
26543
|
-
productContext:
|
|
26544
|
-
creativeDirection:
|
|
26545
|
-
brandColors:
|
|
26546
|
-
headline:
|
|
26547
|
-
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()
|
|
26548
27430
|
}).strict()
|
|
26549
27431
|
]).superRefine((setup, context) => {
|
|
26550
27432
|
if (setup.mode !== "guided") return;
|
|
26551
27433
|
const needsStyleMix = setup.nVariants >= 2 && setup.nVariants <= 4;
|
|
26552
27434
|
if (needsStyleMix && setup.styleMix === null) {
|
|
26553
27435
|
context.addIssue({
|
|
26554
|
-
code:
|
|
27436
|
+
code: z114.ZodIssueCode.custom,
|
|
26555
27437
|
path: [
|
|
26556
27438
|
"styleMix"
|
|
26557
27439
|
],
|
|
@@ -26560,7 +27442,7 @@ var ImageCreativeSetup = z111.discriminatedUnion("mode", [
|
|
|
26560
27442
|
}
|
|
26561
27443
|
if (!needsStyleMix && setup.styleMix !== null) {
|
|
26562
27444
|
context.addIssue({
|
|
26563
|
-
code:
|
|
27445
|
+
code: z114.ZodIssueCode.custom,
|
|
26564
27446
|
path: [
|
|
26565
27447
|
"styleMix"
|
|
26566
27448
|
],
|
|
@@ -26569,7 +27451,7 @@ var ImageCreativeSetup = z111.discriminatedUnion("mode", [
|
|
|
26569
27451
|
}
|
|
26570
27452
|
if (imageResolutionOf(setup.imageModel) === "2k" && setup.aspectRatio === "4:5") {
|
|
26571
27453
|
context.addIssue({
|
|
26572
|
-
code:
|
|
27454
|
+
code: z114.ZodIssueCode.custom,
|
|
26573
27455
|
path: [
|
|
26574
27456
|
"aspectRatio"
|
|
26575
27457
|
],
|
|
@@ -26577,55 +27459,55 @@ var ImageCreativeSetup = z111.discriminatedUnion("mode", [
|
|
|
26577
27459
|
});
|
|
26578
27460
|
}
|
|
26579
27461
|
});
|
|
26580
|
-
var LandingCreativeSetup =
|
|
26581
|
-
|
|
26582
|
-
mode:
|
|
27462
|
+
var LandingCreativeSetup = z114.discriminatedUnion("mode", [
|
|
27463
|
+
z114.object({
|
|
27464
|
+
mode: z114.literal("automatic")
|
|
26583
27465
|
}).strict(),
|
|
26584
|
-
|
|
26585
|
-
mode:
|
|
27466
|
+
z114.object({
|
|
27467
|
+
mode: z114.literal("guided"),
|
|
26586
27468
|
landingStrategy: LandingStrategyEnum,
|
|
26587
27469
|
landingImageFormat: LandingImageFormatEnum,
|
|
26588
|
-
competitorScanEnabled:
|
|
26589
|
-
paymentMethods:
|
|
26590
|
-
productContext:
|
|
26591
|
-
creativeDirection:
|
|
26592
|
-
headline:
|
|
26593
|
-
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()
|
|
26594
27476
|
}).strict()
|
|
26595
27477
|
]);
|
|
26596
|
-
var ImageInput =
|
|
27478
|
+
var ImageInput = z114.object({
|
|
26597
27479
|
images: CreativeRefs.images.optional(),
|
|
26598
27480
|
winnerReference: WinnerReferenceInput.optional().describe(WINNER_IMAGE_CHOICE_GUIDANCE),
|
|
26599
|
-
productTitle:
|
|
26600
|
-
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.'),
|
|
26601
27483
|
buyerPersona: CreativeRefs.buyerPersona,
|
|
26602
27484
|
language: CreativeRefs.language,
|
|
26603
27485
|
country: CreativeRefs.country,
|
|
26604
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."),
|
|
26605
27487
|
imageStyle: ImageCreativeStyleEnum.optional().describe(`Opcional. Estilo del shot del producto. Valores: ${imageStyleList}. Si se omite, la IA escoge seg\xFAn el archetype.`),
|
|
26606
|
-
features:
|
|
27488
|
+
features: z114.array(z114.string()).optional().describe('Opcional. Features/beneficios a destacar visualmente. Ej. ["resistente al agua", "ultra liviano"].'),
|
|
26607
27489
|
price: CreativeRefs.price,
|
|
26608
|
-
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."),
|
|
26609
27491
|
compareAtPrice: CreativeRefs.compareAtPrice,
|
|
26610
27492
|
offer: CreativeRefs.offer,
|
|
26611
27493
|
productLabel: CreativeRefs.productLabel,
|
|
26612
27494
|
isCombo: CreativeRefs.isCombo,
|
|
26613
27495
|
brandId: CreativeRefs.brandId
|
|
26614
27496
|
});
|
|
26615
|
-
var EditImageCreativeInput =
|
|
26616
|
-
taskId:
|
|
26617
|
-
variantIndex:
|
|
26618
|
-
clientRequestId:
|
|
26619
|
-
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".')
|
|
26620
27502
|
});
|
|
26621
|
-
var LandingInput =
|
|
26622
|
-
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."),
|
|
26623
27505
|
images: CreativeRefs.images.optional(),
|
|
26624
27506
|
winnerReference: WinnerReferenceInput.optional().describe(WINNER_IMAGE_CHOICE_GUIDANCE),
|
|
26625
27507
|
language: CreativeRefs.language,
|
|
26626
27508
|
country: CreativeRefs.country,
|
|
26627
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."),
|
|
26628
|
-
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.'),
|
|
26629
27511
|
buyerPersona: CreativeRefs.buyerPersona,
|
|
26630
27512
|
price: CreativeRefs.price,
|
|
26631
27513
|
compareAtPrice: CreativeRefs.compareAtPrice,
|
|
@@ -26907,8 +27789,8 @@ function registerCreativeTools(server, api, { transport = "http" } = {}) {
|
|
|
26907
27789
|
server.registerTool("get_image_edit_status", {
|
|
26908
27790
|
description: imageEditStatusGuidance.summary,
|
|
26909
27791
|
inputSchema: {
|
|
26910
|
-
taskId:
|
|
26911
|
-
operationId:
|
|
27792
|
+
taskId: z114.string().uuid(),
|
|
27793
|
+
operationId: z114.string().uuid()
|
|
26912
27794
|
},
|
|
26913
27795
|
annotations: READS_INTERNAL
|
|
26914
27796
|
}, async (input) => {
|
|
@@ -26965,10 +27847,10 @@ function registerCreativeTools(server, api, { transport = "http" } = {}) {
|
|
|
26965
27847
|
__name(registerCreativeTools, "registerCreativeTools");
|
|
26966
27848
|
|
|
26967
27849
|
// src/tools/creative-variants.ts
|
|
26968
|
-
import { z as
|
|
27850
|
+
import { z as z115 } from "zod";
|
|
26969
27851
|
var estimateCreativeVariantGuidance = getProductMakerCapabilityModelGuidance("estimate_creative_variant");
|
|
26970
27852
|
var createCreativeVariantGuidance = getProductMakerCapabilityModelGuidance("create_creative_variant");
|
|
26971
|
-
var LandingSectionKeyEnum =
|
|
27853
|
+
var LandingSectionKeyEnum = z115.enum([
|
|
26972
27854
|
"problem_hook",
|
|
26973
27855
|
"problem_cost",
|
|
26974
27856
|
"solution_mechanism",
|
|
@@ -27008,24 +27890,24 @@ var LandingSectionKeyEnum = z112.enum([
|
|
|
27008
27890
|
"lifestyle_fit",
|
|
27009
27891
|
"join_lifestyle"
|
|
27010
27892
|
]);
|
|
27011
|
-
var CreativeVariantKindEnum =
|
|
27893
|
+
var CreativeVariantKindEnum = z115.enum([
|
|
27012
27894
|
"video",
|
|
27013
27895
|
"image",
|
|
27014
27896
|
"landing"
|
|
27015
27897
|
]);
|
|
27016
|
-
var EstimateCreativeVariantInputBase =
|
|
27017
|
-
sourceTaskId:
|
|
27898
|
+
var EstimateCreativeVariantInputBase = z115.object({
|
|
27899
|
+
sourceTaskId: z115.string().uuid().describe("ID de la tarea original completada."),
|
|
27018
27900
|
kind: CreativeVariantKindEnum,
|
|
27019
27901
|
videoPreset: TaskCreativeVariantVideoPresetEnum.optional().describe("Opcional para video. Solo cambia el preset p\xFAblico; el servidor conserva el modelo/tier del task origen."),
|
|
27020
27902
|
videoResolution: VideoResolutionEnum.optional().describe("Opcional para video. Resoluci\xF3n del nuevo render."),
|
|
27021
|
-
nVariants:
|
|
27903
|
+
nVariants: z115.number().int().min(1).max(3).optional().describe("Opcional para imagen. N\xFAmero de im\xE1genes adicionales (1-3)."),
|
|
27022
27904
|
sectionKey: LandingSectionKeyEnum.optional().describe("Opcional para landing. Si ya conoces la secci\xF3n, evita cotizar la clasificaci\xF3n con IA."),
|
|
27023
|
-
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.")
|
|
27024
27906
|
}).strict();
|
|
27025
27907
|
var EstimateCreativeVariantInput = EstimateCreativeVariantInputBase.superRefine((input, ctx) => {
|
|
27026
27908
|
if (input.kind === "video" && input.nVariants !== void 0) {
|
|
27027
27909
|
ctx.addIssue({
|
|
27028
|
-
code:
|
|
27910
|
+
code: z115.ZodIssueCode.custom,
|
|
27029
27911
|
path: [
|
|
27030
27912
|
"nVariants"
|
|
27031
27913
|
],
|
|
@@ -27035,7 +27917,7 @@ var EstimateCreativeVariantInput = EstimateCreativeVariantInputBase.superRefine(
|
|
|
27035
27917
|
if (input.kind !== "video") {
|
|
27036
27918
|
if (input.videoPreset !== void 0) {
|
|
27037
27919
|
ctx.addIssue({
|
|
27038
|
-
code:
|
|
27920
|
+
code: z115.ZodIssueCode.custom,
|
|
27039
27921
|
path: [
|
|
27040
27922
|
"videoPreset"
|
|
27041
27923
|
],
|
|
@@ -27044,7 +27926,7 @@ var EstimateCreativeVariantInput = EstimateCreativeVariantInputBase.superRefine(
|
|
|
27044
27926
|
}
|
|
27045
27927
|
if (input.videoResolution !== void 0) {
|
|
27046
27928
|
ctx.addIssue({
|
|
27047
|
-
code:
|
|
27929
|
+
code: z115.ZodIssueCode.custom,
|
|
27048
27930
|
path: [
|
|
27049
27931
|
"videoResolution"
|
|
27050
27932
|
],
|
|
@@ -27054,7 +27936,7 @@ var EstimateCreativeVariantInput = EstimateCreativeVariantInputBase.superRefine(
|
|
|
27054
27936
|
}
|
|
27055
27937
|
if (input.kind !== "image" && input.nVariants !== void 0) {
|
|
27056
27938
|
ctx.addIssue({
|
|
27057
|
-
code:
|
|
27939
|
+
code: z115.ZodIssueCode.custom,
|
|
27058
27940
|
path: [
|
|
27059
27941
|
"nVariants"
|
|
27060
27942
|
],
|
|
@@ -27064,7 +27946,7 @@ var EstimateCreativeVariantInput = EstimateCreativeVariantInputBase.superRefine(
|
|
|
27064
27946
|
if (input.kind !== "landing") {
|
|
27065
27947
|
if (input.sectionKey !== void 0) {
|
|
27066
27948
|
ctx.addIssue({
|
|
27067
|
-
code:
|
|
27949
|
+
code: z115.ZodIssueCode.custom,
|
|
27068
27950
|
path: [
|
|
27069
27951
|
"sectionKey"
|
|
27070
27952
|
],
|
|
@@ -27073,7 +27955,7 @@ var EstimateCreativeVariantInput = EstimateCreativeVariantInputBase.superRefine(
|
|
|
27073
27955
|
}
|
|
27074
27956
|
if (input.instructions !== void 0) {
|
|
27075
27957
|
ctx.addIssue({
|
|
27076
|
-
code:
|
|
27958
|
+
code: z115.ZodIssueCode.custom,
|
|
27077
27959
|
path: [
|
|
27078
27960
|
"instructions"
|
|
27079
27961
|
],
|
|
@@ -27082,31 +27964,31 @@ var EstimateCreativeVariantInput = EstimateCreativeVariantInputBase.superRefine(
|
|
|
27082
27964
|
}
|
|
27083
27965
|
}
|
|
27084
27966
|
});
|
|
27085
|
-
var CreateCreativeVariantInputBase =
|
|
27086
|
-
sourceTaskId:
|
|
27967
|
+
var CreateCreativeVariantInputBase = z115.object({
|
|
27968
|
+
sourceTaskId: z115.string().uuid().describe("ID de la tarea original completada."),
|
|
27087
27969
|
kind: CreativeVariantKindEnum,
|
|
27088
|
-
confirm:
|
|
27089
|
-
clientRequestId:
|
|
27090
|
-
angle:
|
|
27091
|
-
buyerPersona:
|
|
27092
|
-
scriptNotes:
|
|
27093
|
-
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."),
|
|
27094
27976
|
videoPreset: TaskCreativeVariantVideoPresetEnum.optional().describe("Opcional para video. Preset p\xFAblico; el servidor resuelve modelo/tier desde el task origen."),
|
|
27095
27977
|
videoResolution: VideoResolutionEnum.optional().describe("Opcional para video."),
|
|
27096
|
-
generateAiMusic:
|
|
27097
|
-
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)."),
|
|
27098
27980
|
imageStyle: ImageCreativeStyleEnum.optional().describe("Opcional para imagen."),
|
|
27099
27981
|
creativeArchetype: ImageCreativeArchetypeEnum.optional().describe("Opcional para imagen."),
|
|
27100
27982
|
visualTreatment: ImageCreativeVisualTreatmentEnum.optional().describe("Opcional para imagen."),
|
|
27101
27983
|
aspectRatio: ImageCreativeAspectRatioEnum.optional().describe("Opcional para imagen."),
|
|
27102
|
-
styleMix:
|
|
27984
|
+
styleMix: z115.boolean().optional().describe("Opcional para imagen."),
|
|
27103
27985
|
sectionKey: LandingSectionKeyEnum.optional().describe("Opcional para landing. Si se omite, se clasifica con IA a partir de instructions."),
|
|
27104
|
-
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.")
|
|
27105
27987
|
}).strict();
|
|
27106
27988
|
var CreateCreativeVariantInput = CreateCreativeVariantInputBase.superRefine((input, ctx) => {
|
|
27107
27989
|
if ((input.kind === "video" || input.kind === "image") && !input.angle) {
|
|
27108
27990
|
ctx.addIssue({
|
|
27109
|
-
code:
|
|
27991
|
+
code: z115.ZodIssueCode.custom,
|
|
27110
27992
|
path: [
|
|
27111
27993
|
"angle"
|
|
27112
27994
|
],
|
|
@@ -27115,7 +27997,7 @@ var CreateCreativeVariantInput = CreateCreativeVariantInputBase.superRefine((inp
|
|
|
27115
27997
|
}
|
|
27116
27998
|
if (input.kind === "landing" && !input.sectionKey && !input.instructions) {
|
|
27117
27999
|
ctx.addIssue({
|
|
27118
|
-
code:
|
|
28000
|
+
code: z115.ZodIssueCode.custom,
|
|
27119
28001
|
path: [
|
|
27120
28002
|
"instructions"
|
|
27121
28003
|
],
|
|
@@ -27161,7 +28043,7 @@ var CreateCreativeVariantInput = CreateCreativeVariantInputBase.superRefine((inp
|
|
|
27161
28043
|
for (const field of fieldsByKind[input.kind]) {
|
|
27162
28044
|
if (input[field] !== void 0) {
|
|
27163
28045
|
ctx.addIssue({
|
|
27164
|
-
code:
|
|
28046
|
+
code: z115.ZodIssueCode.custom,
|
|
27165
28047
|
path: [
|
|
27166
28048
|
field
|
|
27167
28049
|
],
|
|
@@ -27294,13 +28176,13 @@ function registerCreativeVariantTools(server, api) {
|
|
|
27294
28176
|
__name(registerCreativeVariantTools, "registerCreativeVariantTools");
|
|
27295
28177
|
|
|
27296
28178
|
// src/tools/script-review.ts
|
|
27297
|
-
import { z as
|
|
28179
|
+
import { z as z116 } from "zod";
|
|
27298
28180
|
var getVideoScriptGuidance = getProductMakerCapabilityModelGuidance("get_video_script");
|
|
27299
28181
|
var regenerateVideoScriptGuidance = getProductMakerCapabilityModelGuidance("regenerate_video_script");
|
|
27300
28182
|
var approveVideoScriptGuidance = getProductMakerCapabilityModelGuidance("approve_video_script");
|
|
27301
|
-
var GetVideoScriptInput =
|
|
27302
|
-
taskId:
|
|
27303
|
-
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.')
|
|
27304
28186
|
}).strict();
|
|
27305
28187
|
var ApproveVideoScriptInput = ApproveVideoScriptCapabilityRequest;
|
|
27306
28188
|
var RegenerateVideoScriptInput = RegenerateVideoScriptCapabilityRequest.extend({
|
|
@@ -27383,7 +28265,7 @@ function registerScriptReviewTools(server, api) {
|
|
|
27383
28265
|
__name(registerScriptReviewTools, "registerScriptReviewTools");
|
|
27384
28266
|
|
|
27385
28267
|
// src/tools/connections.ts
|
|
27386
|
-
import { z as
|
|
28268
|
+
import { z as z117 } from "zod";
|
|
27387
28269
|
var listShopifyShopsGuidance = getProductMakerCapabilityModelGuidance("list_shopify_shops");
|
|
27388
28270
|
var listShopifyProductsGuidance = getProductMakerCapabilityModelGuidance("list_shopify_products");
|
|
27389
28271
|
var listMetaAdAccountsGuidance = getProductMakerCapabilityModelGuidance("list_meta_ad_accounts");
|
|
@@ -27392,7 +28274,7 @@ var listTikTokAdvertisersGuidance = getProductMakerCapabilityModelGuidance("list
|
|
|
27392
28274
|
function registerConnectionTools(server, api) {
|
|
27393
28275
|
server.registerTool("list_shopify_shops", {
|
|
27394
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.",
|
|
27395
|
-
inputSchema:
|
|
28277
|
+
inputSchema: z117.object({}).shape,
|
|
27396
28278
|
annotations: READS_INTERNAL
|
|
27397
28279
|
}, async () => {
|
|
27398
28280
|
try {
|
|
@@ -27404,10 +28286,10 @@ function registerConnectionTools(server, api) {
|
|
|
27404
28286
|
});
|
|
27405
28287
|
server.registerTool("list_shopify_products", {
|
|
27406
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.',
|
|
27407
|
-
inputSchema:
|
|
27408
|
-
shopDomain:
|
|
27409
|
-
query:
|
|
27410
|
-
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()
|
|
27411
28293
|
}).shape,
|
|
27412
28294
|
annotations: READS_EXTERNAL
|
|
27413
28295
|
}, async ({ shopDomain, query, taskId }) => {
|
|
@@ -27425,7 +28307,7 @@ function registerConnectionTools(server, api) {
|
|
|
27425
28307
|
});
|
|
27426
28308
|
server.registerTool("list_meta_ad_accounts", {
|
|
27427
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.",
|
|
27428
|
-
inputSchema:
|
|
28310
|
+
inputSchema: z117.object({}).shape,
|
|
27429
28311
|
annotations: READS_EXTERNAL
|
|
27430
28312
|
}, async () => {
|
|
27431
28313
|
try {
|
|
@@ -27437,7 +28319,7 @@ function registerConnectionTools(server, api) {
|
|
|
27437
28319
|
});
|
|
27438
28320
|
server.registerTool("list_meta_destinations", {
|
|
27439
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.",
|
|
27440
|
-
inputSchema:
|
|
28322
|
+
inputSchema: z117.object({}).shape,
|
|
27441
28323
|
annotations: READS_EXTERNAL,
|
|
27442
28324
|
_meta: {
|
|
27443
28325
|
"openai/widgetAccessible": true
|
|
@@ -27452,7 +28334,7 @@ function registerConnectionTools(server, api) {
|
|
|
27452
28334
|
});
|
|
27453
28335
|
server.registerTool("list_tiktok_advertisers", {
|
|
27454
28336
|
description: listTikTokAdvertisersGuidance.summary + " Si est\xE1 vac\xEDa, ind\xEDcale al usuario conectar TikTok en productmaker.app/integrations.",
|
|
27455
|
-
inputSchema:
|
|
28337
|
+
inputSchema: z117.object({}).shape,
|
|
27456
28338
|
annotations: READS_EXTERNAL
|
|
27457
28339
|
}, async () => {
|
|
27458
28340
|
try {
|
|
@@ -27466,7 +28348,7 @@ function registerConnectionTools(server, api) {
|
|
|
27466
28348
|
__name(registerConnectionTools, "registerConnectionTools");
|
|
27467
28349
|
|
|
27468
28350
|
// src/tools/actors.ts
|
|
27469
|
-
import { z as
|
|
28351
|
+
import { z as z118 } from "zod";
|
|
27470
28352
|
var listActorsGuidance = getProductMakerCapabilityModelGuidance("list_actors");
|
|
27471
28353
|
var actorVoiceGuidance = {
|
|
27472
28354
|
capabilities: getProductMakerCapabilityModelGuidance("get_actor_voice_capabilities"),
|
|
@@ -27506,7 +28388,7 @@ function sanitizeVoiceResult(value) {
|
|
|
27506
28388
|
__name(sanitizeVoiceResult, "sanitizeVoiceResult");
|
|
27507
28389
|
function registerActorTools(server, api) {
|
|
27508
28390
|
const actor = {
|
|
27509
|
-
actorId:
|
|
28391
|
+
actorId: z118.string().min(1).max(64)
|
|
27510
28392
|
};
|
|
27511
28393
|
const safe = /* @__PURE__ */ __name(async (request) => {
|
|
27512
28394
|
try {
|
|
@@ -27535,8 +28417,8 @@ function registerActorTools(server, api) {
|
|
|
27535
28417
|
inputSchema: {
|
|
27536
28418
|
...actor,
|
|
27537
28419
|
draft: ActorVoiceDraftSchema,
|
|
27538
|
-
requestKey:
|
|
27539
|
-
acceptedQuoteId:
|
|
28420
|
+
requestKey: z118.string().trim().min(1).max(128),
|
|
28421
|
+
acceptedQuoteId: z118.string().trim().min(1).max(128)
|
|
27540
28422
|
},
|
|
27541
28423
|
annotations: CREATES_PAID
|
|
27542
28424
|
}, async ({ actorId, ...body }) => safe(api.postJson(`/v1/actors/${encodeURIComponent(actorId)}/voice/previews`, body)));
|
|
@@ -27544,7 +28426,7 @@ function registerActorTools(server, api) {
|
|
|
27544
28426
|
description: actorVoiceGuidance.get.summary,
|
|
27545
28427
|
inputSchema: {
|
|
27546
28428
|
...actor,
|
|
27547
|
-
candidateId:
|
|
28429
|
+
candidateId: z118.string().min(1).max(64)
|
|
27548
28430
|
},
|
|
27549
28431
|
annotations: READS_INTERNAL
|
|
27550
28432
|
}, async ({ actorId, candidateId }) => safe(api.getJson(`/v1/actors/${encodeURIComponent(actorId)}/voice/previews/${encodeURIComponent(candidateId)}`)));
|
|
@@ -27552,15 +28434,15 @@ function registerActorTools(server, api) {
|
|
|
27552
28434
|
description: actorVoiceGuidance.approve.summary,
|
|
27553
28435
|
inputSchema: {
|
|
27554
28436
|
...actor,
|
|
27555
|
-
candidateId:
|
|
27556
|
-
expectedRevision:
|
|
27557
|
-
expectedApprovedRevision:
|
|
28437
|
+
candidateId: z118.string().min(1).max(64),
|
|
28438
|
+
expectedRevision: z118.number().int().positive(),
|
|
28439
|
+
expectedApprovedRevision: z118.number().int().positive().nullable().optional()
|
|
27558
28440
|
},
|
|
27559
28441
|
annotations: CREATES_INTERNAL
|
|
27560
28442
|
}, async ({ actorId, ...body }) => safe(api.postJson(`/v1/actors/${encodeURIComponent(actorId)}/voice/approve`, body)));
|
|
27561
28443
|
server.registerTool("list_actors", {
|
|
27562
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.",
|
|
27563
|
-
inputSchema:
|
|
28445
|
+
inputSchema: z118.object({}).shape,
|
|
27564
28446
|
annotations: READS_INTERNAL
|
|
27565
28447
|
}, async () => {
|
|
27566
28448
|
try {
|
|
@@ -27593,7 +28475,7 @@ function registerActorTools(server, api) {
|
|
|
27593
28475
|
__name(registerActorTools, "registerActorTools");
|
|
27594
28476
|
|
|
27595
28477
|
// src/tools/publish.ts
|
|
27596
|
-
import { z as
|
|
28478
|
+
import { z as z119 } from "zod";
|
|
27597
28479
|
|
|
27598
28480
|
// src/tools/meta-publish-attempt-id.ts
|
|
27599
28481
|
var META_PUBLISH_NAMESPACE = "7b19c9ac-b4eb-4db1-970e-c2f24ce30b98";
|
|
@@ -27632,40 +28514,40 @@ var publishToShopifyGuidance = getProductMakerCapabilityModelGuidance("publish_t
|
|
|
27632
28514
|
var publishToMetaGuidance = getProductMakerCapabilityModelGuidance("publish_to_meta");
|
|
27633
28515
|
var publishCreativesToMetaGuidance = getProductMakerCapabilityModelGuidance("publish_creatives_to_meta");
|
|
27634
28516
|
var publishToTikTokGuidance = getProductMakerCapabilityModelGuidance("publish_to_tiktok");
|
|
27635
|
-
var ShopifyInput =
|
|
27636
|
-
taskId:
|
|
27637
|
-
shopDomain:
|
|
28517
|
+
var ShopifyInput = z119.object({
|
|
28518
|
+
taskId: z119.string().min(1),
|
|
28519
|
+
shopDomain: z119.string().min(1),
|
|
27638
28520
|
/** Override: number of landing images to include. Default = ALL images the
|
|
27639
28521
|
* task generated (the MCP fetches /v1/results first to count). Bounded
|
|
27640
28522
|
* generously here; the backend validates each index against
|
|
27641
28523
|
* landing.images.length and rejects out-of-range. */
|
|
27642
|
-
numLandingImages:
|
|
27643
|
-
variantIndex:
|
|
28524
|
+
numLandingImages: z119.number().int().min(1).max(20).optional(),
|
|
28525
|
+
variantIndex: z119.number().int().min(0).optional(),
|
|
27644
28526
|
/** ASK THE USER before setting: "¿Tu producto tiene variantes (talla, color, etc.)?".
|
|
27645
28527
|
* When true, the landing renders variant-picker pills; when false, it ships
|
|
27646
28528
|
* a single-option product page. Defaults to false. */
|
|
27647
|
-
showVariantPicker:
|
|
28529
|
+
showVariantPicker: z119.boolean().optional(),
|
|
27648
28530
|
/** Default `auto` updates the product previously published for this task and
|
|
27649
28531
|
* shop. Use `existing` with targetProductId to choose another product, or
|
|
27650
28532
|
* `new` only when the user explicitly wants a duplicate/new listing. */
|
|
27651
|
-
publicationMode:
|
|
28533
|
+
publicationMode: z119.enum([
|
|
27652
28534
|
"auto",
|
|
27653
28535
|
"existing",
|
|
27654
28536
|
"new"
|
|
27655
28537
|
]).optional(),
|
|
27656
28538
|
/** Shopify product GID, numeric id, or admin product URL. Required when
|
|
27657
28539
|
* publicationMode is `existing`. */
|
|
27658
|
-
targetProductId:
|
|
28540
|
+
targetProductId: z119.string().trim().min(1).max(300).optional(),
|
|
27659
28541
|
/** Add an announcement bar text above add-to-cart (optional). */
|
|
27660
|
-
announcementBarEnabled:
|
|
27661
|
-
announcementBarText:
|
|
28542
|
+
announcementBarEnabled: z119.boolean().optional(),
|
|
28543
|
+
announcementBarText: z119.string().trim().min(1).max(SHOPIFY_ANNOUNCEMENT_BAR_MAX_LENGTH).optional(),
|
|
27662
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."),
|
|
27663
|
-
confirmed:
|
|
28545
|
+
confirmed: z119.boolean().optional().describe("Required as true only when submitting commerceCorrection, after showing its values and obtaining explicit seller confirmation.")
|
|
27664
28546
|
});
|
|
27665
28547
|
var ShopifyInputWithAnnouncementBarValidation = ShopifyInput.superRefine((value, ctx) => {
|
|
27666
28548
|
if (value.commerceCorrection && value.confirmed !== true) {
|
|
27667
28549
|
ctx.addIssue({
|
|
27668
|
-
code:
|
|
28550
|
+
code: z119.ZodIssueCode.custom,
|
|
27669
28551
|
path: [
|
|
27670
28552
|
"confirmed"
|
|
27671
28553
|
],
|
|
@@ -27674,7 +28556,7 @@ var ShopifyInputWithAnnouncementBarValidation = ShopifyInput.superRefine((value,
|
|
|
27674
28556
|
}
|
|
27675
28557
|
if (value.announcementBarEnabled && !value.announcementBarText) {
|
|
27676
28558
|
ctx.addIssue({
|
|
27677
|
-
code:
|
|
28559
|
+
code: z119.ZodIssueCode.custom,
|
|
27678
28560
|
path: [
|
|
27679
28561
|
"announcementBarText"
|
|
27680
28562
|
],
|
|
@@ -27683,7 +28565,7 @@ var ShopifyInputWithAnnouncementBarValidation = ShopifyInput.superRefine((value,
|
|
|
27683
28565
|
}
|
|
27684
28566
|
if (!value.announcementBarEnabled && value.announcementBarText) {
|
|
27685
28567
|
ctx.addIssue({
|
|
27686
|
-
code:
|
|
28568
|
+
code: z119.ZodIssueCode.custom,
|
|
27687
28569
|
path: [
|
|
27688
28570
|
"announcementBarText"
|
|
27689
28571
|
],
|
|
@@ -27697,91 +28579,91 @@ function parseShopifyInput(rawInput) {
|
|
|
27697
28579
|
return parsed.data;
|
|
27698
28580
|
}
|
|
27699
28581
|
__name(parseShopifyInput, "parseShopifyInput");
|
|
27700
|
-
var McpCampaignConfig =
|
|
28582
|
+
var McpCampaignConfig = z119.object({
|
|
27701
28583
|
/** ALWAYS in US dollars, whatever the ad account's currency is. ProductMaker
|
|
27702
28584
|
* converts to the account currency server-side. Do NOT pre-convert: on a COP
|
|
27703
28585
|
* account the user's "60.000 al día" is `dailyBudgetUsd: 15`, not 60000. */
|
|
27704
|
-
dailyBudgetUsd:
|
|
28586
|
+
dailyBudgetUsd: z119.number().positive().optional(),
|
|
27705
28587
|
/** ISO-3166-1 alpha-2 country codes or resolvable names (CountryIso2Schema
|
|
27706
28588
|
* normalizes at validation). Meta accepts max 1 (single-country ad set).
|
|
27707
28589
|
* TikTok takes only the first entry. */
|
|
27708
|
-
targetCountries:
|
|
27709
|
-
campaignName:
|
|
28590
|
+
targetCountries: z119.array(CountryIso2Schema).max(1).optional(),
|
|
28591
|
+
campaignName: z119.string().trim().min(1).max(100).optional()
|
|
27710
28592
|
});
|
|
27711
28593
|
var McpAdAccountId = MetaAdAccountId.describe('Meta ad account id. Accepts "act_1234567890" or the bare numeric id "1234567890".');
|
|
27712
|
-
var MetaInput =
|
|
27713
|
-
taskId:
|
|
28594
|
+
var MetaInput = z119.object({
|
|
28595
|
+
taskId: z119.string().min(1),
|
|
27714
28596
|
/** Exact Meta Business portfolio selected from `list_meta_destinations`. */
|
|
27715
|
-
portfolioId:
|
|
28597
|
+
portfolioId: z119.string().trim().min(1),
|
|
27716
28598
|
/** Exact Facebook Page selected from `list_meta_destinations`. */
|
|
27717
|
-
pageId:
|
|
28599
|
+
pageId: z119.string().trim().min(1),
|
|
27718
28600
|
adAccountId: McpAdAccountId,
|
|
27719
|
-
pixelId:
|
|
27720
|
-
landingUrl:
|
|
28601
|
+
pixelId: z119.string().min(1).optional(),
|
|
28602
|
+
landingUrl: z119.string().url().optional(),
|
|
27721
28603
|
/** Select which angles to publish by 0-based index. Omit to publish all
|
|
27722
28604
|
* successful angles. Example: a task with 3 angles → `[0,2]` publishes #0 + #2. */
|
|
27723
|
-
angleIndexes:
|
|
28605
|
+
angleIndexes: z119.array(z119.number().int().nonnegative()).optional(),
|
|
27724
28606
|
campaignConfig: McpCampaignConfig.optional(),
|
|
27725
28607
|
/** Override the ad's primary text (body copy). If omitted, ProductMaker
|
|
27726
28608
|
* uses the auto-generated copy attached to the task. Capped at 500 chars
|
|
27727
28609
|
* (Meta's primary-text limit). */
|
|
27728
|
-
primaryText:
|
|
28610
|
+
primaryText: z119.string().trim().min(1).max(500).optional(),
|
|
27729
28611
|
/** Optional WhatsApp number. When set, the ad button opens a WhatsApp chat
|
|
27730
28612
|
* (wa.me) instead of a landing page. */
|
|
27731
|
-
phoneNumber:
|
|
28613
|
+
phoneNumber: z119.string().min(8).max(32).optional()
|
|
27732
28614
|
});
|
|
27733
|
-
var GroupedTaskSource =
|
|
27734
|
-
type:
|
|
27735
|
-
taskId:
|
|
27736
|
-
angleIndex:
|
|
27737
|
-
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([
|
|
27738
28620
|
"with_subtitles",
|
|
27739
28621
|
"without_subtitles"
|
|
27740
28622
|
]).optional(),
|
|
27741
|
-
imageVariantIndex:
|
|
27742
|
-
});
|
|
27743
|
-
var GroupedMediaSource =
|
|
27744
|
-
type:
|
|
27745
|
-
url:
|
|
27746
|
-
path:
|
|
27747
|
-
thumbnailUrl:
|
|
27748
|
-
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()
|
|
27749
28631
|
}).refine((value) => Boolean(value.url) !== Boolean(value.path), {
|
|
27750
28632
|
message: "media source needs exactly one of `url` or `path`"
|
|
27751
28633
|
}).refine((value) => !(value.thumbnailUrl && value.thumbnailPath), {
|
|
27752
28634
|
message: "media source needs at most one of `thumbnailUrl` or `thumbnailPath`"
|
|
27753
28635
|
});
|
|
27754
|
-
var GroupedUploadedAssetSource =
|
|
27755
|
-
type:
|
|
28636
|
+
var GroupedUploadedAssetSource = z119.object({
|
|
28637
|
+
type: z119.literal("asset"),
|
|
27756
28638
|
/** Returned by check_media_upload. ProductMaker resolves ownership server-side. */
|
|
27757
|
-
assetId:
|
|
27758
|
-
thumbnailAssetId:
|
|
28639
|
+
assetId: z119.string().uuid(),
|
|
28640
|
+
thumbnailAssetId: z119.string().uuid().optional()
|
|
27759
28641
|
});
|
|
27760
|
-
var GroupedCreative =
|
|
27761
|
-
type:
|
|
28642
|
+
var GroupedCreative = z119.object({
|
|
28643
|
+
type: z119.enum([
|
|
27762
28644
|
"image",
|
|
27763
28645
|
"video"
|
|
27764
28646
|
]),
|
|
27765
|
-
source:
|
|
28647
|
+
source: z119.union([
|
|
27766
28648
|
GroupedTaskSource,
|
|
27767
28649
|
GroupedMediaSource,
|
|
27768
28650
|
GroupedUploadedAssetSource
|
|
27769
28651
|
]),
|
|
27770
|
-
title:
|
|
27771
|
-
description:
|
|
28652
|
+
title: z119.string().max(120).optional(),
|
|
28653
|
+
description: z119.string().max(500).optional(),
|
|
27772
28654
|
cta: MetaCtaType.optional(),
|
|
27773
|
-
creativeId:
|
|
28655
|
+
creativeId: z119.string().min(1).optional()
|
|
27774
28656
|
});
|
|
27775
|
-
var GroupedCampaignConfigBase =
|
|
28657
|
+
var GroupedCampaignConfigBase = z119.object({
|
|
27776
28658
|
campaignType: MetaCampaignType.default("sales"),
|
|
27777
28659
|
optimizationMode: MetaOptimizationMode.default("advantage_plus"),
|
|
27778
28660
|
budgetMode: MetaBudgetMode.optional(),
|
|
27779
|
-
campaignDailyBudget:
|
|
27780
|
-
adSets:
|
|
27781
|
-
tempId:
|
|
27782
|
-
name:
|
|
27783
|
-
dailyBudget:
|
|
27784
|
-
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)
|
|
27785
28667
|
})).min(1).max(CAMPAIGN_LIMITS.MAX_AD_SETS),
|
|
27786
28668
|
/**
|
|
27787
28669
|
* Valid values are 'landing' and 'whatsapp' — there is no 'website'. This
|
|
@@ -27789,17 +28671,17 @@ var GroupedCampaignConfigBase = z116.object({
|
|
|
27789
28671
|
* opaque `invalid_literal` and made the WhatsApp destination — supported by
|
|
27790
28672
|
* the API since #849 — unreachable from this tool.
|
|
27791
28673
|
*/
|
|
27792
|
-
destination:
|
|
27793
|
-
|
|
27794
|
-
type:
|
|
27795
|
-
landingUrl:
|
|
28674
|
+
destination: z119.discriminatedUnion("type", [
|
|
28675
|
+
z119.object({
|
|
28676
|
+
type: z119.literal("landing"),
|
|
28677
|
+
landingUrl: z119.string().url()
|
|
27796
28678
|
}),
|
|
27797
|
-
|
|
27798
|
-
type:
|
|
27799
|
-
phoneNumber:
|
|
28679
|
+
z119.object({
|
|
28680
|
+
type: z119.literal("whatsapp"),
|
|
28681
|
+
phoneNumber: z119.string().min(8).max(32)
|
|
27800
28682
|
})
|
|
27801
28683
|
]),
|
|
27802
|
-
saveAsDefault:
|
|
28684
|
+
saveAsDefault: z119.boolean().default(false),
|
|
27803
28685
|
budgetPolicy: MetaBudgetPolicy.optional()
|
|
27804
28686
|
});
|
|
27805
28687
|
function resolveBudgetMode(value) {
|
|
@@ -27813,7 +28695,7 @@ var GroupedCampaignConfig = GroupedCampaignConfigBase.superRefine((value, ctx) =
|
|
|
27813
28695
|
const budgetMode = resolveBudgetMode(value);
|
|
27814
28696
|
if (budgetMode === "campaign" && value.campaignDailyBudget === void 0) {
|
|
27815
28697
|
ctx.addIssue({
|
|
27816
|
-
code:
|
|
28698
|
+
code: z119.ZodIssueCode.custom,
|
|
27817
28699
|
path: [
|
|
27818
28700
|
"campaignDailyBudget"
|
|
27819
28701
|
],
|
|
@@ -27824,7 +28706,7 @@ var GroupedCampaignConfig = GroupedCampaignConfigBase.superRefine((value, ctx) =
|
|
|
27824
28706
|
value.adSets.forEach((adSet, index) => {
|
|
27825
28707
|
if (adSet.dailyBudget === void 0) {
|
|
27826
28708
|
ctx.addIssue({
|
|
27827
|
-
code:
|
|
28709
|
+
code: z119.ZodIssueCode.custom,
|
|
27828
28710
|
path: [
|
|
27829
28711
|
"adSets",
|
|
27830
28712
|
index,
|
|
@@ -27839,28 +28721,28 @@ var GroupedCampaignConfig = GroupedCampaignConfigBase.superRefine((value, ctx) =
|
|
|
27839
28721
|
...value,
|
|
27840
28722
|
budgetMode: resolveBudgetMode(value)
|
|
27841
28723
|
}));
|
|
27842
|
-
var PublishCreativesToMetaInputBase =
|
|
27843
|
-
creatives:
|
|
28724
|
+
var PublishCreativesToMetaInputBase = z119.object({
|
|
28725
|
+
creatives: z119.array(GroupedCreative).min(1).max(CAMPAIGN_LIMITS.MAX_CREATIVES),
|
|
27844
28726
|
/** Exact Meta Business portfolio selected from `list_meta_destinations`. */
|
|
27845
|
-
portfolioId:
|
|
28727
|
+
portfolioId: z119.string().trim().min(1),
|
|
27846
28728
|
/** Exact Facebook Page selected from `list_meta_destinations`. */
|
|
27847
|
-
pageId:
|
|
28729
|
+
pageId: z119.string().trim().min(1),
|
|
27848
28730
|
adAccountId: McpAdAccountId,
|
|
27849
|
-
pixelId:
|
|
28731
|
+
pixelId: z119.string().min(1).optional(),
|
|
27850
28732
|
/** Required unless `campaignConfig.destination` is whatsapp — see the
|
|
27851
28733
|
* superRefine below. Mirrors the API's `FacebookCreateCampaignFromTasksBodySchema`. */
|
|
27852
|
-
landingUrl:
|
|
28734
|
+
landingUrl: z119.string().url().optional(),
|
|
27853
28735
|
/** ISO-2 code or resolvable country name — mirrors the API contract
|
|
27854
28736
|
* (CountryIso2Schema), which normalizes names to alpha-2 at validation. */
|
|
27855
28737
|
targetCountry: CountryIso2Schema,
|
|
27856
|
-
campaignName:
|
|
28738
|
+
campaignName: z119.string().trim().min(1).max(100),
|
|
27857
28739
|
campaignConfig: GroupedCampaignConfig.optional()
|
|
27858
28740
|
});
|
|
27859
28741
|
var PublishCreativesToMetaInput = PublishCreativesToMetaInputBase.superRefine((value, ctx) => {
|
|
27860
28742
|
const destination = value.campaignConfig?.destination;
|
|
27861
28743
|
if (destination?.type !== "whatsapp" && destination?.type !== "landing" && !value.landingUrl) {
|
|
27862
28744
|
ctx.addIssue({
|
|
27863
|
-
code:
|
|
28745
|
+
code: z119.ZodIssueCode.custom,
|
|
27864
28746
|
path: [
|
|
27865
28747
|
"landingUrl"
|
|
27866
28748
|
],
|
|
@@ -27869,7 +28751,7 @@ var PublishCreativesToMetaInput = PublishCreativesToMetaInputBase.superRefine((v
|
|
|
27869
28751
|
}
|
|
27870
28752
|
if (destination?.type === "landing" && value.landingUrl && destination.landingUrl !== value.landingUrl) {
|
|
27871
28753
|
ctx.addIssue({
|
|
27872
|
-
code:
|
|
28754
|
+
code: z119.ZodIssueCode.custom,
|
|
27873
28755
|
path: [
|
|
27874
28756
|
"campaignConfig",
|
|
27875
28757
|
"destination",
|
|
@@ -27888,16 +28770,16 @@ var PublishCreativesToMetaInput = PublishCreativesToMetaInputBase.superRefine((v
|
|
|
27888
28770
|
}
|
|
27889
28771
|
return value;
|
|
27890
28772
|
});
|
|
27891
|
-
var PresignUploadResponseSchema =
|
|
27892
|
-
uploadUrl:
|
|
28773
|
+
var PresignUploadResponseSchema = z119.object({
|
|
28774
|
+
uploadUrl: z119.string().url().refine((value) => {
|
|
27893
28775
|
const url = new URL(value);
|
|
27894
28776
|
return !url.username && !url.password && url.protocol === "https:";
|
|
27895
28777
|
}, "presigned upload URL must be HTTPS without embedded credentials"),
|
|
27896
|
-
publicUrl:
|
|
28778
|
+
publicUrl: z119.string().url().refine((value) => {
|
|
27897
28779
|
const url = new URL(value);
|
|
27898
28780
|
return !url.username && !url.password;
|
|
27899
28781
|
}, "public media URL must not contain embedded credentials"),
|
|
27900
|
-
stagingAssetId:
|
|
28782
|
+
stagingAssetId: z119.string().uuid()
|
|
27901
28783
|
});
|
|
27902
28784
|
async function uploadResolvedMedia(api, media, assetRole) {
|
|
27903
28785
|
if (!media.buffer) throw new ResolveMediaError("INVALID_INPUT", "resolved media has no bytes");
|
|
@@ -27922,16 +28804,16 @@ async function resolveAndUploadMedia(api, ref, expectedKind, assetRole, transpor
|
|
|
27922
28804
|
return uploadResolvedMedia(api, media, assetRole);
|
|
27923
28805
|
}
|
|
27924
28806
|
__name(resolveAndUploadMedia, "resolveAndUploadMedia");
|
|
27925
|
-
var TikTokInput =
|
|
27926
|
-
taskId:
|
|
27927
|
-
advertiserId:
|
|
27928
|
-
pixelId:
|
|
27929
|
-
landingUrl:
|
|
27930
|
-
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(),
|
|
27931
28813
|
campaignConfig: McpCampaignConfig.optional(),
|
|
27932
28814
|
/** Override the ad's description (TikTok's body copy). If omitted,
|
|
27933
28815
|
* ProductMaker uses the auto-generated copy attached to the task. */
|
|
27934
|
-
primaryText:
|
|
28816
|
+
primaryText: z119.string().trim().min(1).max(500).optional()
|
|
27935
28817
|
});
|
|
27936
28818
|
function registerPublishTools(server, api, opts = {}) {
|
|
27937
28819
|
const transport = opts.transport ?? "http";
|
|
@@ -28283,39 +29165,39 @@ __name(toTikTokCampaignConfig, "toTikTokCampaignConfig");
|
|
|
28283
29165
|
|
|
28284
29166
|
// src/tools/meta-upload.ts
|
|
28285
29167
|
import { registerAppTool } from "@modelcontextprotocol/ext-apps/server";
|
|
28286
|
-
import { z as
|
|
29168
|
+
import { z as z120 } from "zod";
|
|
28287
29169
|
var requestMediaUploadGuidance = getProductMakerCapabilityModelGuidance("request_media_upload");
|
|
28288
|
-
var UploadSessionInputBase =
|
|
28289
|
-
mediaKind:
|
|
29170
|
+
var UploadSessionInputBase = z120.object({
|
|
29171
|
+
mediaKind: z120.enum([
|
|
28290
29172
|
"image",
|
|
28291
29173
|
"video",
|
|
28292
29174
|
"mixed"
|
|
28293
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."),
|
|
28294
|
-
assetRole:
|
|
29176
|
+
assetRole: z120.enum([
|
|
28295
29177
|
"media",
|
|
28296
29178
|
"thumbnail"
|
|
28297
29179
|
]).default("media").describe("Use media for the creative itself; thumbnail only for an image thumbnail."),
|
|
28298
|
-
uploadPurpose:
|
|
29180
|
+
uploadPurpose: z120.enum([
|
|
28299
29181
|
"product_reference",
|
|
28300
29182
|
"creative",
|
|
28301
29183
|
"generic"
|
|
28302
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."),
|
|
28303
|
-
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.")
|
|
28304
29186
|
});
|
|
28305
|
-
var OpenAiFileReference =
|
|
28306
|
-
download_url:
|
|
28307
|
-
file_id:
|
|
28308
|
-
mime_type:
|
|
28309
|
-
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()
|
|
28310
29192
|
});
|
|
28311
29193
|
var UploadSessionToolInputShape = {
|
|
28312
29194
|
...UploadSessionInputBase.shape,
|
|
28313
|
-
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.")
|
|
28314
29196
|
};
|
|
28315
|
-
var UploadSessionToolInput =
|
|
29197
|
+
var UploadSessionToolInput = z120.object(UploadSessionToolInputShape).superRefine((value, ctx) => {
|
|
28316
29198
|
if (value.assetRole === "thumbnail" && value.mediaKind !== "image") {
|
|
28317
29199
|
ctx.addIssue({
|
|
28318
|
-
code:
|
|
29200
|
+
code: z120.ZodIssueCode.custom,
|
|
28319
29201
|
path: [
|
|
28320
29202
|
"mediaKind"
|
|
28321
29203
|
],
|
|
@@ -28324,7 +29206,7 @@ var UploadSessionToolInput = z117.object(UploadSessionToolInputShape).superRefin
|
|
|
28324
29206
|
}
|
|
28325
29207
|
if (value.mediaKind === "video" && value.maxFiles !== void 0 && value.maxFiles !== 1) {
|
|
28326
29208
|
ctx.addIssue({
|
|
28327
|
-
code:
|
|
29209
|
+
code: z120.ZodIssueCode.custom,
|
|
28328
29210
|
path: [
|
|
28329
29211
|
"maxFiles"
|
|
28330
29212
|
],
|
|
@@ -28333,7 +29215,7 @@ var UploadSessionToolInput = z117.object(UploadSessionToolInputShape).superRefin
|
|
|
28333
29215
|
}
|
|
28334
29216
|
if (value.mediaKind === "image" && (value.maxFiles ?? 1) > 5) {
|
|
28335
29217
|
ctx.addIssue({
|
|
28336
|
-
code:
|
|
29218
|
+
code: z120.ZodIssueCode.custom,
|
|
28337
29219
|
path: [
|
|
28338
29220
|
"maxFiles"
|
|
28339
29221
|
],
|
|
@@ -28341,38 +29223,38 @@ var UploadSessionToolInput = z117.object(UploadSessionToolInputShape).superRefin
|
|
|
28341
29223
|
});
|
|
28342
29224
|
}
|
|
28343
29225
|
});
|
|
28344
|
-
var UploadSessionResponse =
|
|
28345
|
-
sessionId:
|
|
28346
|
-
uploadPageUrl:
|
|
28347
|
-
mediaKind:
|
|
29226
|
+
var UploadSessionResponse = z120.object({
|
|
29227
|
+
sessionId: z120.string().uuid(),
|
|
29228
|
+
uploadPageUrl: z120.string().url(),
|
|
29229
|
+
mediaKind: z120.enum([
|
|
28348
29230
|
"image",
|
|
28349
29231
|
"video",
|
|
28350
29232
|
"mixed"
|
|
28351
29233
|
]),
|
|
28352
|
-
assetRole:
|
|
29234
|
+
assetRole: z120.enum([
|
|
28353
29235
|
"media",
|
|
28354
29236
|
"thumbnail"
|
|
28355
29237
|
]),
|
|
28356
|
-
maxFiles:
|
|
28357
|
-
expiresAt:
|
|
29238
|
+
maxFiles: z120.number().int().positive(),
|
|
29239
|
+
expiresAt: z120.string().datetime()
|
|
28358
29240
|
});
|
|
28359
|
-
var UploadStatusInput =
|
|
28360
|
-
sessionId:
|
|
29241
|
+
var UploadStatusInput = z120.object({
|
|
29242
|
+
sessionId: z120.string().uuid().describe("The sessionId returned by request_media_upload.")
|
|
28361
29243
|
});
|
|
28362
|
-
var UniversalUploadStatusResponse =
|
|
28363
|
-
|
|
28364
|
-
status:
|
|
28365
|
-
assets:
|
|
29244
|
+
var UniversalUploadStatusResponse = z120.discriminatedUnion("status", [
|
|
29245
|
+
z120.object({
|
|
29246
|
+
status: z120.literal("pending"),
|
|
29247
|
+
assets: z120.array(z120.never()).length(0)
|
|
28366
29248
|
}),
|
|
28367
|
-
|
|
28368
|
-
status:
|
|
28369
|
-
assets:
|
|
28370
|
-
assetId:
|
|
28371
|
-
mediaKind:
|
|
29249
|
+
z120.object({
|
|
29250
|
+
status: z120.literal("uploaded"),
|
|
29251
|
+
assets: z120.array(z120.object({
|
|
29252
|
+
assetId: z120.string().uuid(),
|
|
29253
|
+
mediaKind: z120.enum([
|
|
28372
29254
|
"image",
|
|
28373
29255
|
"video"
|
|
28374
29256
|
]),
|
|
28375
|
-
assetRole:
|
|
29257
|
+
assetRole: z120.enum([
|
|
28376
29258
|
"media",
|
|
28377
29259
|
"thumbnail"
|
|
28378
29260
|
])
|
|
@@ -28380,13 +29262,13 @@ var UniversalUploadStatusResponse = z117.discriminatedUnion("status", [
|
|
|
28380
29262
|
})
|
|
28381
29263
|
]);
|
|
28382
29264
|
var UniversalUploadStructuredResponse = UploadSessionResponse.extend({
|
|
28383
|
-
uploadPurpose:
|
|
29265
|
+
uploadPurpose: z120.enum([
|
|
28384
29266
|
"product_reference",
|
|
28385
29267
|
"creative",
|
|
28386
29268
|
"generic"
|
|
28387
29269
|
]),
|
|
28388
|
-
checkToolName:
|
|
28389
|
-
resolveEndpoint:
|
|
29270
|
+
checkToolName: z120.literal("check_media_upload"),
|
|
29271
|
+
resolveEndpoint: z120.string().url()
|
|
28390
29272
|
});
|
|
28391
29273
|
function contextualizeUploadPageUrl(uploadPageUrl, uploadPurpose) {
|
|
28392
29274
|
const url = new URL(uploadPageUrl);
|
|
@@ -28477,15 +29359,15 @@ function registerMediaUploadTools(server, api) {
|
|
|
28477
29359
|
__name(registerMediaUploadTools, "registerMediaUploadTools");
|
|
28478
29360
|
|
|
28479
29361
|
// src/tools/campaigns.ts
|
|
28480
|
-
import { z as
|
|
29362
|
+
import { z as z121 } from "zod";
|
|
28481
29363
|
var getMetaCampaignStatusGuidance = getProductMakerCapabilityModelGuidance("get_meta_campaign_status");
|
|
28482
29364
|
var getTikTokCampaignStatusGuidance = getProductMakerCapabilityModelGuidance("get_tiktok_campaign_status");
|
|
28483
|
-
var MetaStatusInput =
|
|
28484
|
-
campaignId:
|
|
29365
|
+
var MetaStatusInput = z121.object({
|
|
29366
|
+
campaignId: z121.string().min(1).describe("Meta campaign ID returned by publish_to_meta.")
|
|
28485
29367
|
});
|
|
28486
|
-
var TikTokStatusInput =
|
|
28487
|
-
campaignId:
|
|
28488
|
-
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.")
|
|
28489
29371
|
});
|
|
28490
29372
|
function registerCampaignTools(server, api) {
|
|
28491
29373
|
server.registerTool("get_meta_campaign_status", {
|
|
@@ -28590,10 +29472,10 @@ function renderTikTokStatusMarkdown(r) {
|
|
|
28590
29472
|
__name(renderTikTokStatusMarkdown, "renderTikTokStatusMarkdown");
|
|
28591
29473
|
|
|
28592
29474
|
// ../../packages/product-economics/src/product-costing.ts
|
|
28593
|
-
import { z as
|
|
29475
|
+
import { z as z123 } from "zod";
|
|
28594
29476
|
|
|
28595
29477
|
// ../../packages/product-economics/src/product-costing-countries.ts
|
|
28596
|
-
import { z as
|
|
29478
|
+
import { z as z122 } from "zod";
|
|
28597
29479
|
|
|
28598
29480
|
// ../../packages/product-economics/src/product-costing-shipping-references.json
|
|
28599
29481
|
var product_costing_shipping_references_default = {
|
|
@@ -28657,21 +29539,21 @@ var product_costing_shipping_references_default = {
|
|
|
28657
29539
|
};
|
|
28658
29540
|
|
|
28659
29541
|
// ../../packages/product-economics/src/product-costing-countries.ts
|
|
28660
|
-
var ShippingReferenceSchema =
|
|
28661
|
-
reference:
|
|
28662
|
-
min:
|
|
28663
|
-
max:
|
|
28664
|
-
updatedAt:
|
|
28665
|
-
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([
|
|
28666
29548
|
"high",
|
|
28667
29549
|
"medium",
|
|
28668
29550
|
"medium-low"
|
|
28669
29551
|
]),
|
|
28670
|
-
sources:
|
|
29552
|
+
sources: z122.array(z122.string().url().startsWith("https://")).min(1).max(3)
|
|
28671
29553
|
}).superRefine(({ reference, min, max }, context) => {
|
|
28672
29554
|
if (min > reference || reference > max) {
|
|
28673
29555
|
context.addIssue({
|
|
28674
|
-
code:
|
|
29556
|
+
code: z122.ZodIssueCode.custom,
|
|
28675
29557
|
message: "shipping_reference_outside_range",
|
|
28676
29558
|
path: [
|
|
28677
29559
|
"reference"
|
|
@@ -28679,7 +29561,7 @@ var ShippingReferenceSchema = z119.object({
|
|
|
28679
29561
|
});
|
|
28680
29562
|
}
|
|
28681
29563
|
});
|
|
28682
|
-
var SHIPPING_REFERENCES =
|
|
29564
|
+
var SHIPPING_REFERENCES = z122.record(ShippingReferenceSchema).parse(product_costing_shipping_references_default);
|
|
28683
29565
|
var PRODUCT_COSTING_COUNTRY_CODES = Object.freeze(Object.keys(SHIPPING_REFERENCES));
|
|
28684
29566
|
var SPANISH_LOCALE_COUNTRIES = /* @__PURE__ */ new Set([
|
|
28685
29567
|
"MX",
|
|
@@ -28861,18 +29743,18 @@ function getProductCostingCountryName(country) {
|
|
|
28861
29743
|
__name(getProductCostingCountryName, "getProductCostingCountryName");
|
|
28862
29744
|
|
|
28863
29745
|
// ../../packages/product-economics/src/product-costing.ts
|
|
28864
|
-
var ProductCostingInputSchema =
|
|
28865
|
-
country:
|
|
28866
|
-
supplierCost:
|
|
28867
|
-
shippingCost:
|
|
28868
|
-
returnRate:
|
|
28869
|
-
cancellationRate:
|
|
28870
|
-
adsManagerCpa:
|
|
28871
|
-
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")
|
|
28872
29754
|
}).superRefine(({ returnRate, cancellationRate }, context) => {
|
|
28873
29755
|
if (returnRate + cancellationRate >= 1) {
|
|
28874
29756
|
context.addIssue({
|
|
28875
|
-
code:
|
|
29757
|
+
code: z123.ZodIssueCode.custom,
|
|
28876
29758
|
message: "delivery_rate_positive",
|
|
28877
29759
|
path: [
|
|
28878
29760
|
"cancellationRate"
|
|
@@ -28969,15 +29851,15 @@ function calculateProductCosting(rawInput) {
|
|
|
28969
29851
|
__name(calculateProductCosting, "calculateProductCosting");
|
|
28970
29852
|
|
|
28971
29853
|
// src/tools/product-costing.ts
|
|
28972
|
-
import { z as
|
|
29854
|
+
import { z as z124 } from "zod";
|
|
28973
29855
|
var costProductGuidance = getProductMakerCapabilityModelGuidance("cost_product");
|
|
28974
|
-
var optionalAmount = /* @__PURE__ */ __name((description) =>
|
|
28975
|
-
var optionalPercent = /* @__PURE__ */ __name((description) =>
|
|
28976
|
-
var ProductCostingToolInput =
|
|
28977
|
-
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, {
|
|
28978
29860
|
message: "Pa\xEDs no soportado por ProductMaker."
|
|
28979
29861
|
}).optional().describe("C\xF3digo ISO-2 de cualquiera de los pa\xEDses disponibles en ProductMaker. Omite para Colombia."),
|
|
28980
|
-
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."),
|
|
28981
29863
|
supplierCostCop: optionalAmount("Alias anterior para costo del proveedor en Colombia. Prefiere supplierCost."),
|
|
28982
29864
|
shippingCost: optionalAmount("Costo de un env\xEDo en la moneda del pa\xEDs. Omite para usar el valor gu\xEDa vigente."),
|
|
28983
29865
|
shippingCostCop: optionalAmount("Alias anterior para flete en Colombia. Prefiere shippingCost."),
|
|
@@ -29140,7 +30022,7 @@ function formatPercent(value) {
|
|
|
29140
30022
|
__name(formatPercent, "formatPercent");
|
|
29141
30023
|
|
|
29142
30024
|
// src/tools/product-research.ts
|
|
29143
|
-
import { z as
|
|
30025
|
+
import { z as z125 } from "zod";
|
|
29144
30026
|
|
|
29145
30027
|
// src/tools/product-research-idempotency.ts
|
|
29146
30028
|
var RESEARCH_IDEMPOTENCY_WINDOW_MS = 2 * 60 * 60 * 1e3;
|
|
@@ -29511,17 +30393,17 @@ var TERMINAL2 = /* @__PURE__ */ new Set([
|
|
|
29511
30393
|
]);
|
|
29512
30394
|
var POLL_INTERVAL_MS = 5e3;
|
|
29513
30395
|
var MAX_WAIT_SECONDS = 120;
|
|
29514
|
-
var FindInput =
|
|
30396
|
+
var FindInput = z125.object({
|
|
29515
30397
|
targetCountry: ResearchTargetCountryEnum.describe("Pa\xEDs objetivo. S\xF3lo CO, MX, PE, EC y CL tienen cobertura de investigaci\xF3n."),
|
|
29516
30398
|
categoryKey: ResearchCategoryEnum.default("auto").describe('Categor\xEDa a investigar. Usa "auto" para que la IA elija seg\xFAn el mercado.'),
|
|
29517
|
-
confirmed:
|
|
29518
|
-
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.")
|
|
29519
30401
|
});
|
|
29520
|
-
var GetInput =
|
|
29521
|
-
runId:
|
|
30402
|
+
var GetInput = z125.object({
|
|
30403
|
+
runId: z125.string().uuid().describe("ID del run devuelto por find_winning_products.")
|
|
29522
30404
|
});
|
|
29523
|
-
var ListInput =
|
|
29524
|
-
limit:
|
|
30405
|
+
var ListInput = z125.object({
|
|
30406
|
+
limit: z125.number().int().min(1).max(20).default(10)
|
|
29525
30407
|
});
|
|
29526
30408
|
function textResult(text2) {
|
|
29527
30409
|
return {
|
|
@@ -29660,17 +30542,17 @@ _Esta llamada no cobr\xF3 cr\xE9ditos: se reutiliz\xF3 una investigaci\xF3n reci
|
|
|
29660
30542
|
__name(registerProductResearchTools, "registerProductResearchTools");
|
|
29661
30543
|
|
|
29662
30544
|
// src/tools/dropshipping-projects.ts
|
|
29663
|
-
import { z as
|
|
30545
|
+
import { z as z126 } from "zod";
|
|
29664
30546
|
var startDropshippingProjectGuidance = getProductMakerCapabilityModelGuidance("start_dropshipping_project");
|
|
29665
30547
|
var costDropshippingProjectGuidance = getProductMakerCapabilityModelGuidance("cost_dropshipping_project");
|
|
29666
30548
|
var guideDropshippingLaunchGuidance = getProductMakerCapabilityModelGuidance("guide_dropshipping_launch");
|
|
29667
|
-
var ProjectId =
|
|
29668
|
-
var DropshippingProjectStatus =
|
|
30549
|
+
var ProjectId = z126.string().uuid();
|
|
30550
|
+
var DropshippingProjectStatus = z126.enum([
|
|
29669
30551
|
"active",
|
|
29670
30552
|
"completed",
|
|
29671
30553
|
"archived"
|
|
29672
30554
|
]);
|
|
29673
|
-
var DropshippingProjectPhase =
|
|
30555
|
+
var DropshippingProjectPhase = z126.enum([
|
|
29674
30556
|
"research",
|
|
29675
30557
|
"costing",
|
|
29676
30558
|
"media",
|
|
@@ -29679,21 +30561,21 @@ var DropshippingProjectPhase = z123.enum([
|
|
|
29679
30561
|
"advertising",
|
|
29680
30562
|
"complete"
|
|
29681
30563
|
]);
|
|
29682
|
-
var DropshippingRewardMilestone =
|
|
30564
|
+
var DropshippingRewardMilestone = z126.enum([
|
|
29683
30565
|
"product_selected",
|
|
29684
30566
|
"costing_saved",
|
|
29685
30567
|
"product_media_ready",
|
|
29686
30568
|
"creatives_completed",
|
|
29687
30569
|
"campaign_created"
|
|
29688
30570
|
]);
|
|
29689
|
-
var TargetCountry =
|
|
30571
|
+
var TargetCountry = z126.enum([
|
|
29690
30572
|
"CO",
|
|
29691
30573
|
"MX",
|
|
29692
30574
|
"PE",
|
|
29693
30575
|
"EC",
|
|
29694
30576
|
"CL"
|
|
29695
30577
|
]);
|
|
29696
|
-
var ResearchCategory =
|
|
30578
|
+
var ResearchCategory = z126.enum([
|
|
29697
30579
|
"auto",
|
|
29698
30580
|
"automotive",
|
|
29699
30581
|
"kitchen",
|
|
@@ -29708,7 +30590,7 @@ var ResearchCategory = z123.enum([
|
|
|
29708
30590
|
"garden",
|
|
29709
30591
|
"cleaning"
|
|
29710
30592
|
]);
|
|
29711
|
-
var HttpUrl3 =
|
|
30593
|
+
var HttpUrl3 = z126.string().url().max(2e3).refine((value) => {
|
|
29712
30594
|
try {
|
|
29713
30595
|
return [
|
|
29714
30596
|
"http:",
|
|
@@ -29718,45 +30600,45 @@ var HttpUrl3 = z123.string().url().max(2e3).refine((value) => {
|
|
|
29718
30600
|
return false;
|
|
29719
30601
|
}
|
|
29720
30602
|
}, "URL must use HTTP(S)");
|
|
29721
|
-
var ResearchCandidateOrigin =
|
|
29722
|
-
type:
|
|
30603
|
+
var ResearchCandidateOrigin = z126.object({
|
|
30604
|
+
type: z126.literal("research_candidate"),
|
|
29723
30605
|
runId: ProjectId,
|
|
29724
|
-
candidateSourceId:
|
|
30606
|
+
candidateSourceId: z126.string().min(1).max(160)
|
|
29725
30607
|
});
|
|
29726
|
-
var TaskOrigin =
|
|
29727
|
-
type:
|
|
30608
|
+
var TaskOrigin = z126.object({
|
|
30609
|
+
type: z126.literal("task"),
|
|
29728
30610
|
taskId: ProjectId
|
|
29729
30611
|
});
|
|
29730
|
-
var ManualOrigin =
|
|
29731
|
-
type:
|
|
30612
|
+
var ManualOrigin = z126.object({
|
|
30613
|
+
type: z126.literal("manual"),
|
|
29732
30614
|
clientRequestId: ProjectId,
|
|
29733
|
-
country:
|
|
30615
|
+
country: z126.string().min(2).max(3).optional()
|
|
29734
30616
|
});
|
|
29735
|
-
var StartProjectInput =
|
|
29736
|
-
origin:
|
|
30617
|
+
var StartProjectInput = z126.object({
|
|
30618
|
+
origin: z126.discriminatedUnion("type", [
|
|
29737
30619
|
ResearchCandidateOrigin,
|
|
29738
30620
|
TaskOrigin,
|
|
29739
30621
|
ManualOrigin
|
|
29740
30622
|
]),
|
|
29741
|
-
productName:
|
|
30623
|
+
productName: z126.string().trim().min(1).max(240).optional(),
|
|
29742
30624
|
productUrl: HttpUrl3.optional(),
|
|
29743
|
-
country:
|
|
29744
|
-
languageCode:
|
|
30625
|
+
country: z126.string().min(2).max(3).optional(),
|
|
30626
|
+
languageCode: z126.string().min(2).max(16).optional()
|
|
29745
30627
|
}).strict();
|
|
29746
|
-
var CostProjectInput =
|
|
30628
|
+
var CostProjectInput = z126.object({
|
|
29747
30629
|
projectId: ProjectId,
|
|
29748
|
-
version:
|
|
29749
|
-
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, {
|
|
29750
30632
|
message: "Pa\xEDs no soportado por ProductMaker."
|
|
29751
30633
|
}).describe("C\xF3digo ISO-2 del pa\xEDs del proyecto. Usa el `targetCountry` que devuelve el proyecto."),
|
|
29752
|
-
supplierCost:
|
|
29753
|
-
shippingCost:
|
|
29754
|
-
returnRate:
|
|
29755
|
-
cancellationRate:
|
|
29756
|
-
adsManagerCpa:
|
|
29757
|
-
desiredMargin:
|
|
29758
|
-
});
|
|
29759
|
-
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({
|
|
29760
30642
|
projectId: ProjectId.optional()
|
|
29761
30643
|
}).strict();
|
|
29762
30644
|
var NEXT_ACTION_KINDS = [
|
|
@@ -29774,29 +30656,29 @@ var NEXT_ACTION_KINDS = [
|
|
|
29774
30656
|
"repair_meta_campaign",
|
|
29775
30657
|
"review_paused_campaign"
|
|
29776
30658
|
];
|
|
29777
|
-
var DropshippingProjectNextAction =
|
|
29778
|
-
kind:
|
|
29779
|
-
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)
|
|
29780
30662
|
})));
|
|
29781
|
-
var CostingInputSchema =
|
|
29782
|
-
country:
|
|
29783
|
-
supplierCost:
|
|
29784
|
-
shippingCost:
|
|
29785
|
-
returnRate:
|
|
29786
|
-
cancellationRate:
|
|
29787
|
-
adsManagerCpa:
|
|
29788
|
-
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)
|
|
29789
30671
|
}).superRefine(({ returnRate, cancellationRate }, context) => {
|
|
29790
30672
|
if (returnRate + cancellationRate >= 1) context.addIssue({
|
|
29791
|
-
code:
|
|
30673
|
+
code: z126.ZodIssueCode.custom,
|
|
29792
30674
|
path: [
|
|
29793
30675
|
"cancellationRate"
|
|
29794
30676
|
]
|
|
29795
30677
|
});
|
|
29796
30678
|
});
|
|
29797
|
-
var FiniteCostingNumber =
|
|
29798
|
-
var CostingResultSchema =
|
|
29799
|
-
country:
|
|
30679
|
+
var FiniteCostingNumber = z126.number().finite();
|
|
30680
|
+
var CostingResultSchema = z126.object({
|
|
30681
|
+
country: z126.string().min(2).max(3),
|
|
29800
30682
|
deliveryRate: FiniteCostingNumber,
|
|
29801
30683
|
adjustedShippingCost: FiniteCostingNumber,
|
|
29802
30684
|
adjustedCpa: FiniteCostingNumber,
|
|
@@ -29809,45 +30691,45 @@ var CostingResultSchema = z123.object({
|
|
|
29809
30691
|
estimatedProfit: FiniteCostingNumber,
|
|
29810
30692
|
actualMargin: FiniteCostingNumber,
|
|
29811
30693
|
breakEvenAdsManagerCpa: FiniteCostingNumber,
|
|
29812
|
-
wasPriceAutoAdjusted:
|
|
29813
|
-
profitabilityStatus:
|
|
29814
|
-
});
|
|
29815
|
-
var RewardProgressSchema =
|
|
29816
|
-
campaignKey:
|
|
29817
|
-
totalCredits:
|
|
29818
|
-
earnedCredits:
|
|
29819
|
-
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({
|
|
29820
30702
|
milestone: DropshippingRewardMilestone,
|
|
29821
|
-
credits:
|
|
29822
|
-
available:
|
|
29823
|
-
earned:
|
|
30703
|
+
credits: z126.number().int().positive(),
|
|
30704
|
+
available: z126.boolean().optional(),
|
|
30705
|
+
earned: z126.boolean(),
|
|
29824
30706
|
grantId: ProjectId.nullable(),
|
|
29825
|
-
grantedAt:
|
|
30707
|
+
grantedAt: z126.string().datetime().nullable()
|
|
29826
30708
|
})).length(DropshippingRewardMilestone.options.length)
|
|
29827
30709
|
});
|
|
29828
|
-
var ProjectResponseSchema =
|
|
30710
|
+
var ProjectResponseSchema = z126.object({
|
|
29829
30711
|
id: ProjectId,
|
|
29830
30712
|
taskId: ProjectId.nullable(),
|
|
29831
30713
|
status: DropshippingProjectStatus,
|
|
29832
|
-
originKey:
|
|
30714
|
+
originKey: z126.string().min(1),
|
|
29833
30715
|
researchRunId: ProjectId.nullable(),
|
|
29834
|
-
candidateSourceId:
|
|
29835
|
-
productName:
|
|
30716
|
+
candidateSourceId: z126.string().nullable(),
|
|
30717
|
+
productName: z126.string().nullable(),
|
|
29836
30718
|
productUrl: HttpUrl3.nullable(),
|
|
29837
30719
|
referenceImageUrl: HttpUrl3.nullable(),
|
|
29838
|
-
targetCountry:
|
|
29839
|
-
languageCode:
|
|
29840
|
-
hasReferenceImage:
|
|
29841
|
-
hasWinnerReferenceImage:
|
|
30720
|
+
targetCountry: z126.string().nullable(),
|
|
30721
|
+
languageCode: z126.string().nullable(),
|
|
30722
|
+
hasReferenceImage: z126.boolean(),
|
|
30723
|
+
hasWinnerReferenceImage: z126.boolean(),
|
|
29842
30724
|
costingInput: CostingInputSchema.nullable(),
|
|
29843
30725
|
costingResult: CostingResultSchema.nullable(),
|
|
29844
|
-
costingVersion:
|
|
29845
|
-
version:
|
|
30726
|
+
costingVersion: z126.number().int().positive().nullable(),
|
|
30727
|
+
version: z126.number().int().positive(),
|
|
29846
30728
|
phase: DropshippingProjectPhase,
|
|
29847
30729
|
nextAction: DropshippingProjectNextAction.nullable(),
|
|
29848
30730
|
rewards: RewardProgressSchema,
|
|
29849
|
-
createdAt:
|
|
29850
|
-
updatedAt:
|
|
30731
|
+
createdAt: z126.string().datetime(),
|
|
30732
|
+
updatedAt: z126.string().datetime()
|
|
29851
30733
|
}).passthrough();
|
|
29852
30734
|
var NEXT_ACTION_GUIDANCE = {
|
|
29853
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.",
|
|
@@ -29867,14 +30749,14 @@ var NEXT_ACTION_GUIDANCE = {
|
|
|
29867
30749
|
function projectText(raw, referenceValues = []) {
|
|
29868
30750
|
const project = ProjectResponseSchema.parse(raw);
|
|
29869
30751
|
const kind = project.nextAction?.kind;
|
|
29870
|
-
const
|
|
30752
|
+
const guidance5 = kind ? NEXT_ACTION_GUIDANCE[kind] : void 0;
|
|
29871
30753
|
const winnerReferenceResult = project.hasWinnerReferenceImage ? WinnerReferenceInput.safeParse({
|
|
29872
30754
|
runId: project.researchRunId,
|
|
29873
30755
|
candidateSourceId: project.candidateSourceId
|
|
29874
30756
|
}) : void 0;
|
|
29875
30757
|
const winnerReference = winnerReferenceResult?.success ? winnerReferenceResult.data : void 0;
|
|
29876
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;
|
|
29877
|
-
const guidedNextActionGuidance =
|
|
30759
|
+
const guidedNextActionGuidance = guidance5 ? `S\xF3lo si el usuario quiere continuar el lanzamiento guiado completo: ${guidance5}` : void 0;
|
|
29878
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;
|
|
29879
30761
|
return {
|
|
29880
30762
|
content: [
|
|
@@ -29967,58 +30849,58 @@ function registerDropshippingProjectTools(server, api) {
|
|
|
29967
30849
|
__name(registerDropshippingProjectTools, "registerDropshippingProjectTools");
|
|
29968
30850
|
|
|
29969
30851
|
// src/tools/daily-control.ts
|
|
29970
|
-
import { z as
|
|
30852
|
+
import { z as z127 } from "zod";
|
|
29971
30853
|
var getDailyControlGuidance = getProductMakerCapabilityModelGuidance("get_daily_control");
|
|
29972
30854
|
var linkCampaignToProjectGuidance = getProductMakerCapabilityModelGuidance("link_campaign_to_project");
|
|
29973
30855
|
var reportDailyReturnsGuidance = getProductMakerCapabilityModelGuidance("report_daily_returns");
|
|
29974
|
-
var ProviderCampaignIdentifier =
|
|
29975
|
-
var DateOnly =
|
|
29976
|
-
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({
|
|
29977
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."),
|
|
29978
|
-
days:
|
|
29979
|
-
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.")
|
|
29980
30862
|
}).strict();
|
|
29981
|
-
var LinkCampaignInput =
|
|
29982
|
-
projectId:
|
|
29983
|
-
provider:
|
|
30863
|
+
var LinkCampaignInput = z127.object({
|
|
30864
|
+
projectId: z127.string().uuid(),
|
|
30865
|
+
provider: z127.enum([
|
|
29984
30866
|
"meta",
|
|
29985
30867
|
"tiktok"
|
|
29986
30868
|
]),
|
|
29987
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."),
|
|
29988
30870
|
campaignId: ProviderCampaignIdentifier
|
|
29989
30871
|
}).strict();
|
|
29990
|
-
var ReportReturnsInput =
|
|
29991
|
-
projectId:
|
|
30872
|
+
var ReportReturnsInput = z127.object({
|
|
30873
|
+
projectId: z127.string().uuid(),
|
|
29992
30874
|
date: DateOnly,
|
|
29993
|
-
returnedOrders:
|
|
29994
|
-
note:
|
|
30875
|
+
returnedOrders: z127.number().int().min(0).max(1e5),
|
|
30876
|
+
note: z127.string().trim().max(280).optional()
|
|
29995
30877
|
}).strict();
|
|
29996
|
-
var DailyControlStatusMirror = DailyControlStatusEnum.or(
|
|
29997
|
-
var DailyControlLightMirror = DailyControlLightEnum.or(
|
|
29998
|
-
var DailyControlSourceMirror = DailyControlSourceEnum.or(
|
|
29999
|
-
var DailyControlVerdictMirror = DailyControlVerdictEnum.or(
|
|
30000
|
-
var DailyControlDayMirror =
|
|
30001
|
-
date:
|
|
30002
|
-
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(),
|
|
30003
30885
|
returnedSource: DailyControlSourceMirror,
|
|
30004
30886
|
cancelledSource: DailyControlSourceMirror,
|
|
30005
|
-
hasEstimates:
|
|
30887
|
+
hasEstimates: z127.boolean(),
|
|
30006
30888
|
light: DailyControlLightMirror
|
|
30007
30889
|
}).passthrough();
|
|
30008
|
-
var DailyControlProductMirror =
|
|
30009
|
-
projectId:
|
|
30010
|
-
productName:
|
|
30890
|
+
var DailyControlProductMirror = z127.object({
|
|
30891
|
+
projectId: z127.string().uuid(),
|
|
30892
|
+
productName: z127.string().nullable(),
|
|
30011
30893
|
status: DailyControlStatusMirror,
|
|
30012
|
-
statusMessage:
|
|
30013
|
-
days:
|
|
30894
|
+
statusMessage: z127.string().nullable(),
|
|
30895
|
+
days: z127.array(DailyControlDayMirror),
|
|
30014
30896
|
verdict: DailyControlVerdictMirror.nullable(),
|
|
30015
|
-
rationale:
|
|
30897
|
+
rationale: z127.string().nullable()
|
|
30016
30898
|
}).passthrough();
|
|
30017
|
-
var DailyControlResponseMirror =
|
|
30018
|
-
since:
|
|
30019
|
-
until:
|
|
30020
|
-
products:
|
|
30021
|
-
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())
|
|
30022
30904
|
}).passthrough();
|
|
30023
30905
|
var MCP_WARNINGS_KEY = "_mcpDailyControlWarnings";
|
|
30024
30906
|
function withWarnings(response) {
|
|
@@ -30093,7 +30975,7 @@ __name(registerDailyControlTools, "registerDailyControlTools");
|
|
|
30093
30975
|
// src/tools/product-control.ts
|
|
30094
30976
|
import { readFile, stat } from "fs/promises";
|
|
30095
30977
|
import { basename as basename2, isAbsolute, resolve as resolve3 } from "path";
|
|
30096
|
-
import { z as
|
|
30978
|
+
import { z as z128 } from "zod";
|
|
30097
30979
|
var getProductControlGuidance = getProductMakerCapabilityModelGuidance("get_product_control");
|
|
30098
30980
|
var listImportableProductsGuidance = getProductMakerCapabilityModelGuidance("list_importable_products");
|
|
30099
30981
|
var importProductControlsGuidance = getProductMakerCapabilityModelGuidance("import_product_controls");
|
|
@@ -30106,36 +30988,36 @@ var createOperationGuidance = getProductMakerCapabilityModelGuidance("create_ope
|
|
|
30106
30988
|
var mergeProductsGuidance = getProductMakerCapabilityModelGuidance("merge_products");
|
|
30107
30989
|
var syncShopifyOrdersGuidance = getProductMakerCapabilityModelGuidance("sync_shopify_orders");
|
|
30108
30990
|
var importOrdersFileGuidance = getProductMakerCapabilityModelGuidance("import_orders_file");
|
|
30109
|
-
var DateOnly2 =
|
|
30991
|
+
var DateOnly2 = z128.string().regex(/^\d{4}-\d{2}-\d{2}$/);
|
|
30110
30992
|
var IMPORT_BATCH_MAX2 = 50;
|
|
30111
|
-
var ProductControlListInput =
|
|
30112
|
-
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."),
|
|
30113
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."),
|
|
30114
|
-
shopDomain:
|
|
30996
|
+
shopDomain: z128.string().min(1).optional().describe("Tienda, si tiene m\xE1s de una conectada.")
|
|
30115
30997
|
}).strict();
|
|
30116
|
-
var ImportableProductsInput =
|
|
30117
|
-
q:
|
|
30118
|
-
after:
|
|
30119
|
-
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()
|
|
30120
31002
|
}).strict();
|
|
30121
|
-
var ImportProductControlsInput =
|
|
30122
|
-
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."),
|
|
30123
31005
|
// Obligatorio, como en el endpoint: con dos tiendas conectadas, importar
|
|
30124
31006
|
// sin decir a cuál archivaría el producto bajo la tienda equivocada.
|
|
30125
|
-
shopDomain:
|
|
31007
|
+
shopDomain: z128.string().min(1)
|
|
30126
31008
|
}).strict();
|
|
30127
|
-
var SetProductCostingInput =
|
|
30128
|
-
shopDomain:
|
|
30129
|
-
shopifyProductId:
|
|
30130
|
-
targetCountry:
|
|
30131
|
-
supplierCost:
|
|
30132
|
-
shippingCost:
|
|
30133
|
-
returnRate:
|
|
30134
|
-
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)
|
|
30135
31017
|
}).strict();
|
|
30136
|
-
var Count =
|
|
30137
|
-
var ReportProductCountsInput =
|
|
30138
|
-
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."),
|
|
30139
31021
|
date: DateOnly2,
|
|
30140
31022
|
orders: Count.describe("Pedidos que entraron ese d\xEDa."),
|
|
30141
31023
|
cancelledOrders: Count,
|
|
@@ -30145,36 +31027,36 @@ var ReportProductCountsInput = z125.object({
|
|
|
30145
31027
|
}).strict();
|
|
30146
31028
|
var MAX_ORDERS_FILE_BYTES = 8 * 1024 * 1024;
|
|
30147
31029
|
var SyncShopifyOrdersInput = SyncShopifyOrdersSchema;
|
|
30148
|
-
var ImportOrdersFileInput =
|
|
31030
|
+
var ImportOrdersFileInput = z128.object({
|
|
30149
31031
|
/** Ruta ABSOLUTA en la máquina de quien usa el MCP. */
|
|
30150
|
-
path:
|
|
30151
|
-
operationId:
|
|
31032
|
+
path: z128.string().min(1),
|
|
31033
|
+
operationId: z128.string().uuid().optional(),
|
|
30152
31034
|
/** Solo mirar: devuelve el resumen y no escribe nada. */
|
|
30153
|
-
dryRun:
|
|
31035
|
+
dryRun: z128.boolean().optional()
|
|
30154
31036
|
}).strict();
|
|
30155
|
-
var CreateOperationInput =
|
|
30156
|
-
countryCode:
|
|
30157
|
-
label:
|
|
31037
|
+
var CreateOperationInput = z128.object({
|
|
31038
|
+
countryCode: z128.string().length(2),
|
|
31039
|
+
label: z128.string().trim().min(1).max(60).optional()
|
|
30158
31040
|
}).strict();
|
|
30159
|
-
var MergeProductsInput =
|
|
30160
|
-
sourceId:
|
|
30161
|
-
targetId:
|
|
31041
|
+
var MergeProductsInput = z128.object({
|
|
31042
|
+
sourceId: z128.string().uuid(),
|
|
31043
|
+
targetId: z128.string().uuid()
|
|
30162
31044
|
}).strict();
|
|
30163
|
-
var SetMonthlyExpenseInput =
|
|
30164
|
-
month:
|
|
31045
|
+
var SetMonthlyExpenseInput = z128.object({
|
|
31046
|
+
month: z128.string().regex(/^\d{4}-(0[1-9]|1[0-2])$/).optional(),
|
|
30165
31047
|
label: SaveMonthlyExpenseSchema.shape.label,
|
|
30166
|
-
amount:
|
|
30167
|
-
operationId:
|
|
31048
|
+
amount: z128.number().finite().min(0),
|
|
31049
|
+
operationId: z128.string().uuid().optional()
|
|
30168
31050
|
}).strict();
|
|
30169
|
-
var SetAdSpendInput =
|
|
30170
|
-
month:
|
|
30171
|
-
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(),
|
|
30172
31054
|
/** null RETIRA la corrección; cero afirma que no pautó. */
|
|
30173
|
-
amount:
|
|
31055
|
+
amount: z128.number().finite().min(0).nullable()
|
|
30174
31056
|
}).strict();
|
|
30175
|
-
var GetFinancesInput =
|
|
30176
|
-
month:
|
|
30177
|
-
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.")
|
|
30178
31060
|
}).strict();
|
|
30179
31061
|
function registerProductControlTools(server, api, { transport = "http" } = {}) {
|
|
30180
31062
|
server.registerTool("get_product_control", {
|
|
@@ -30364,28 +31246,28 @@ function registerProductControlTools(server, api, { transport = "http" } = {}) {
|
|
|
30364
31246
|
__name(registerProductControlTools, "registerProductControlTools");
|
|
30365
31247
|
|
|
30366
31248
|
// src/tools/market-study-estimate.ts
|
|
30367
|
-
import { z as
|
|
31249
|
+
import { z as z129 } from "zod";
|
|
30368
31250
|
var estimateMarketStudyGuidance = getProductMakerCapabilityModelGuidance("estimate_market_study_cost");
|
|
30369
|
-
var ProductTaskOutputEnum =
|
|
31251
|
+
var ProductTaskOutputEnum = z129.enum([
|
|
30370
31252
|
"landing",
|
|
30371
31253
|
"video",
|
|
30372
31254
|
"image"
|
|
30373
31255
|
]);
|
|
30374
|
-
var EstimateMarketStudyInput =
|
|
30375
|
-
kind:
|
|
31256
|
+
var EstimateMarketStudyInput = z129.object({
|
|
31257
|
+
kind: z129.enum([
|
|
30376
31258
|
"video",
|
|
30377
31259
|
"product_task"
|
|
30378
31260
|
]).describe('"video" para generate_video_creative; "product_task" para create_product_task.'),
|
|
30379
|
-
videoAngleCount:
|
|
30380
|
-
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."),
|
|
30381
31263
|
videoStructure: VideoStructureEnum.default("single_take"),
|
|
30382
31264
|
videoQuality: VideoQualityEnum.default("economy"),
|
|
30383
31265
|
videoResolution: VideoResolutionEnum.default("480p"),
|
|
30384
|
-
productImageCount:
|
|
31266
|
+
productImageCount: z129.number().int().min(1).max(5).optional().describe("Cantidad de fotos del producto que adjuntar\xE1s (1-5)."),
|
|
30385
31267
|
outputs: ProductTaskOutputEnum.array().min(1).max(3).optional().describe('Solo para kind="product_task": los mismos outputs de create_product_task. Default: todos.'),
|
|
30386
|
-
generateAiMusic:
|
|
31268
|
+
generateAiMusic: z129.boolean().optional(),
|
|
30387
31269
|
generateAiEdit: AiEditRequestSchema.optional(),
|
|
30388
|
-
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.")
|
|
30389
31271
|
}).strict();
|
|
30390
31272
|
function buildMarketStudyEstimateRequests(input) {
|
|
30391
31273
|
const quotesVideo = input.kind === "video" || (input.outputs ?? [
|
|
@@ -30521,6 +31403,8 @@ var PRODUCTMAKER_SERVER_INSTRUCTIONS = [
|
|
|
30521
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.",
|
|
30522
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.",
|
|
30523
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.",
|
|
30524
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.",
|
|
30525
31409
|
"ProductMaker Meta campaigns remain PAUSED.",
|
|
30526
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.",
|
|
@@ -30589,6 +31473,9 @@ function createServer(opts) {
|
|
|
30589
31473
|
proxyBaseUrl,
|
|
30590
31474
|
transport
|
|
30591
31475
|
});
|
|
31476
|
+
registerBrandTools(server, api);
|
|
31477
|
+
registerServiceLandingTools(server, api);
|
|
31478
|
+
registerServiceImageTools(server, api);
|
|
30592
31479
|
registerAnimatedVideoTools(server, api);
|
|
30593
31480
|
registerCreativeTools(server, api, {
|
|
30594
31481
|
transport
|