@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/http.js
CHANGED
|
@@ -15684,6 +15684,54 @@ var CAPABILITY_MODEL_GUIDANCE = {
|
|
|
15684
15684
|
"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"
|
|
15685
15685
|
]
|
|
15686
15686
|
},
|
|
15687
|
+
create_brand: {
|
|
15688
|
+
summary: "Analiza la marca de un negocio de servicios o marca personal desde su Instagram, Facebook, TikTok o web.",
|
|
15689
|
+
useWhen: [
|
|
15690
|
+
"toma mi Instagram y hazme el an\xE1lisis de marca",
|
|
15691
|
+
"la persona comparte el Instagram o la web de su negocio de servicios y quiere su marca, landing o video"
|
|
15692
|
+
],
|
|
15693
|
+
avoidWhen: [
|
|
15694
|
+
"la persona habla de un producto de dropshipping, un proveedor o un enlace de AliExpress o Dropi"
|
|
15695
|
+
],
|
|
15696
|
+
defaults: [
|
|
15697
|
+
"reutiliza una marca existente de la misma direcci\xF3n antes de pagar otro an\xE1lisis"
|
|
15698
|
+
],
|
|
15699
|
+
disclosures: [
|
|
15700
|
+
"es trabajo pagado: muestra el precio y requiere confirmaci\xF3n expl\xEDcita"
|
|
15701
|
+
]
|
|
15702
|
+
},
|
|
15703
|
+
create_service_landing: {
|
|
15704
|
+
summary: "Crea y publica la landing page de un negocio de servicios a partir de su marca.",
|
|
15705
|
+
useWhen: [
|
|
15706
|
+
"hazme la landing page de mi negocio",
|
|
15707
|
+
"la persona tiene una marca de servicios y quiere su p\xE1gina para recibir clientes"
|
|
15708
|
+
],
|
|
15709
|
+
avoidWhen: [
|
|
15710
|
+
"la persona quiere una landing de un producto de dropshipping o de una tarea de producto"
|
|
15711
|
+
],
|
|
15712
|
+
defaults: [
|
|
15713
|
+
"pregunta una sola vez el contacto, la zona y los servicios con precio que falten"
|
|
15714
|
+
],
|
|
15715
|
+
disclosures: [
|
|
15716
|
+
"es trabajo pagado: muestra el precio total y requiere confirmaci\xF3n expl\xEDcita; publicar no cobra"
|
|
15717
|
+
]
|
|
15718
|
+
},
|
|
15719
|
+
create_service_images: {
|
|
15720
|
+
summary: "Crea im\xE1genes para anuncios de un negocio de servicios con su marca, sin foto de producto.",
|
|
15721
|
+
useWhen: [
|
|
15722
|
+
"hazme im\xE1genes para anunciar mi servicio",
|
|
15723
|
+
"la persona quiere im\xE1genes publicitarias de su negocio de servicios"
|
|
15724
|
+
],
|
|
15725
|
+
avoidWhen: [
|
|
15726
|
+
"la persona quiere im\xE1genes de un producto de dropshipping con sus fotos"
|
|
15727
|
+
],
|
|
15728
|
+
defaults: [
|
|
15729
|
+
"una imagen 4:5 de calidad est\xE1ndar con la marca"
|
|
15730
|
+
],
|
|
15731
|
+
disclosures: [
|
|
15732
|
+
"es trabajo pagado: muestra el precio y requiere confirmaci\xF3n expl\xEDcita"
|
|
15733
|
+
]
|
|
15734
|
+
},
|
|
15687
15735
|
edit_image_creative: {
|
|
15688
15736
|
summary: "Corrige UNA imagen ya generada de una tarea de creativos, sin regenerar el set completo.",
|
|
15689
15737
|
useWhen: [
|
|
@@ -18777,6 +18825,92 @@ var PRODUCTMAKER_CAPABILITY_REGISTRY = deepFreeze([
|
|
|
18777
18825
|
"task",
|
|
18778
18826
|
"video"
|
|
18779
18827
|
], MCP_ONLY_CHANNEL_EXPOSURE),
|
|
18828
|
+
// MCP-only service journey (2026-10-11): brand analysis → service landing →
|
|
18829
|
+
// create_animated_video. WhatsApp has no adapter for either, so both are
|
|
18830
|
+
// declared unavailable there and its parity gate is unchanged.
|
|
18831
|
+
defineCapability("create_brand", "7f5680574f38f1005d162c2a2a3805be82896508793ebcdd5491b8459c803158", "credit_consuming", "creative", [
|
|
18832
|
+
"credit_consuming",
|
|
18833
|
+
"state_change"
|
|
18834
|
+
], [
|
|
18835
|
+
slot("brand_action", false, [
|
|
18836
|
+
"action",
|
|
18837
|
+
"brandId",
|
|
18838
|
+
"confirmed"
|
|
18839
|
+
], []),
|
|
18840
|
+
slot("brand_source", false, [
|
|
18841
|
+
"sourceUrl"
|
|
18842
|
+
], []),
|
|
18843
|
+
slot("business_notes", false, [
|
|
18844
|
+
"businessNotes"
|
|
18845
|
+
], []),
|
|
18846
|
+
slot("locale", false, [
|
|
18847
|
+
"language"
|
|
18848
|
+
], [])
|
|
18849
|
+
], [
|
|
18850
|
+
"owner_context"
|
|
18851
|
+
], [
|
|
18852
|
+
"brand"
|
|
18853
|
+
], MCP_ONLY_CHANNEL_EXPOSURE),
|
|
18854
|
+
defineCapability("create_service_landing", "f668f49e8006c610102ecb1760cdd9a238a9750c77aecf1762b58b8474e5495e", "credit_consuming", "creative", [
|
|
18855
|
+
"credit_consuming",
|
|
18856
|
+
"state_change"
|
|
18857
|
+
], [
|
|
18858
|
+
slot("service_landing_action", false, [
|
|
18859
|
+
"action",
|
|
18860
|
+
"servicePageId",
|
|
18861
|
+
"operationId",
|
|
18862
|
+
"confirmed",
|
|
18863
|
+
"maxCredits"
|
|
18864
|
+
], []),
|
|
18865
|
+
slot("brand", false, [
|
|
18866
|
+
"brandId"
|
|
18867
|
+
], []),
|
|
18868
|
+
slot("business_info", false, [
|
|
18869
|
+
"businessInfo"
|
|
18870
|
+
], []),
|
|
18871
|
+
slot("slug", false, [
|
|
18872
|
+
"slug"
|
|
18873
|
+
], [])
|
|
18874
|
+
], [
|
|
18875
|
+
"brand"
|
|
18876
|
+
], [
|
|
18877
|
+
"service_landing"
|
|
18878
|
+
], MCP_ONLY_CHANNEL_EXPOSURE),
|
|
18879
|
+
defineCapability("create_service_images", "1df35139abd6b04c5ebcdd5945154555a3f0d0e49b61f7d70c98cc5dad1e1347", "credit_consuming", "creative", [
|
|
18880
|
+
"credit_consuming",
|
|
18881
|
+
"state_change"
|
|
18882
|
+
], [
|
|
18883
|
+
slot("brand", false, [
|
|
18884
|
+
"brandId"
|
|
18885
|
+
], []),
|
|
18886
|
+
slot("service_brief", false, [
|
|
18887
|
+
"instruction",
|
|
18888
|
+
"delegateToAi"
|
|
18889
|
+
], []),
|
|
18890
|
+
slot("format", false, [
|
|
18891
|
+
"format"
|
|
18892
|
+
], []),
|
|
18893
|
+
slot("quantity", false, [
|
|
18894
|
+
"count"
|
|
18895
|
+
], []),
|
|
18896
|
+
slot("quality", false, [
|
|
18897
|
+
"quality"
|
|
18898
|
+
], []),
|
|
18899
|
+
slot("locale", false, [
|
|
18900
|
+
"language"
|
|
18901
|
+
], []),
|
|
18902
|
+
slot("ad_copy", false, [
|
|
18903
|
+
"adCopy"
|
|
18904
|
+
], []),
|
|
18905
|
+
slot("confirmation", false, [
|
|
18906
|
+
"confirmed"
|
|
18907
|
+
], [])
|
|
18908
|
+
], [
|
|
18909
|
+
"brand"
|
|
18910
|
+
], [
|
|
18911
|
+
"task",
|
|
18912
|
+
"creative"
|
|
18913
|
+
], MCP_ONLY_CHANNEL_EXPOSURE),
|
|
18780
18914
|
// MCP-only recovery of an existing code edit. WhatsApp has no code-landing
|
|
18781
18915
|
// editor or edit receipt input; it hands this recovery to ProductMaker Web.
|
|
18782
18916
|
defineCapability("verify_landing_commerce", "4b98ea73f291428fa19ec91ec37deb44a776b76cb19b0d47aef20ee905da7d10", "external_mutation", "publishing", [
|
|
@@ -23463,6 +23597,8 @@ var PRODUCTMAKER_SERVER_INSTRUCTIONS = [
|
|
|
23463
23597
|
"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.",
|
|
23464
23598
|
"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.",
|
|
23465
23599
|
"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.",
|
|
23600
|
+
"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.",
|
|
23601
|
+
"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.",
|
|
23466
23602
|
"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.",
|
|
23467
23603
|
"ProductMaker Meta campaigns remain PAUSED.",
|
|
23468
23604
|
"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.",
|
|
@@ -23702,7 +23838,7 @@ var ApiClient = class {
|
|
|
23702
23838
|
};
|
|
23703
23839
|
|
|
23704
23840
|
// src/index.ts
|
|
23705
|
-
var MCP_VERSION = "11.
|
|
23841
|
+
var MCP_VERSION = "11.3.0";
|
|
23706
23842
|
|
|
23707
23843
|
// src/meta-upload-app.ts
|
|
23708
23844
|
import { readFileSync } from "fs";
|
|
@@ -28002,8 +28138,756 @@ ANTES de invocar, resume outputs, cantidad de videos y datos comerciales, explic
|
|
|
28002
28138
|
}
|
|
28003
28139
|
__name(registerTaskTools, "registerTaskTools");
|
|
28004
28140
|
|
|
28005
|
-
// src/tools/
|
|
28141
|
+
// src/tools/brands.ts
|
|
28006
28142
|
import { z as z112 } from "zod";
|
|
28143
|
+
var guidance2 = getProductMakerCapabilityModelGuidance("create_brand");
|
|
28144
|
+
var BRAND_START_REPLAY_WINDOW_MS = 15 * 60 * 1e3;
|
|
28145
|
+
var BRAND_NOTES_MAX_CHARS = 2e4;
|
|
28146
|
+
var CreateBrandInput = z112.object({
|
|
28147
|
+
action: z112.enum([
|
|
28148
|
+
"start",
|
|
28149
|
+
"status",
|
|
28150
|
+
"list"
|
|
28151
|
+
]).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."),
|
|
28152
|
+
sourceUrl: z112.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."),
|
|
28153
|
+
businessNotes: z112.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."),
|
|
28154
|
+
language: z112.enum([
|
|
28155
|
+
"es",
|
|
28156
|
+
"en",
|
|
28157
|
+
"pt"
|
|
28158
|
+
]).optional().describe("Idioma de la conversaci\xF3n. Solo se usa si la p\xE1gina del negocio no deja claro su idioma."),
|
|
28159
|
+
confirmed: z112.boolean().optional().describe("Solo true despu\xE9s de mostrar el precio y que el usuario acepte. Sin confirmed, start solo cotiza y no cobra."),
|
|
28160
|
+
brandId: z112.string().uuid().optional().describe("ID de la marca. Obligatorio para status; no lo inventes.")
|
|
28161
|
+
}).strict();
|
|
28162
|
+
function normalizeBrandSourceUrl(raw) {
|
|
28163
|
+
const value = raw.trim();
|
|
28164
|
+
const handle = /^@([A-Za-z0-9._]{1,30})$/.exec(value);
|
|
28165
|
+
if (handle) return `https://www.instagram.com/${handle[1]}/`;
|
|
28166
|
+
return normalizeWebAddress(value);
|
|
28167
|
+
}
|
|
28168
|
+
__name(normalizeBrandSourceUrl, "normalizeBrandSourceUrl");
|
|
28169
|
+
function brandSourceKey(url) {
|
|
28170
|
+
try {
|
|
28171
|
+
const parsed = new URL(url);
|
|
28172
|
+
const host = parsed.hostname.toLowerCase().replace(/^www\./, "");
|
|
28173
|
+
const path3 = parsed.pathname.replace(/\/+$/, "").toLowerCase();
|
|
28174
|
+
return `${host}${path3}`;
|
|
28175
|
+
} catch {
|
|
28176
|
+
return url.trim().toLowerCase();
|
|
28177
|
+
}
|
|
28178
|
+
}
|
|
28179
|
+
__name(brandSourceKey, "brandSourceKey");
|
|
28180
|
+
var EXTRACTION_ERROR_TEXT = {
|
|
28181
|
+
invalid_url: "La direcci\xF3n no es v\xE1lida o no es p\xFAblica. Pide el enlace que abre sin iniciar sesi\xF3n.",
|
|
28182
|
+
not_business_page: "No encontramos contenido del negocio en esa p\xE1gina. Pide el Instagram o la web principal del negocio.",
|
|
28183
|
+
scrape_failed: "No pudimos abrir la p\xE1gina (puede estar ca\xEDda o bloqueando visitas autom\xE1ticas). Pide otro enlace del negocio.",
|
|
28184
|
+
scrape_too_large: "La p\xE1gina es demasiado grande. Pide otro enlace m\xE1s simple del negocio, por ejemplo su Instagram.",
|
|
28185
|
+
scrape_timeout: "La p\xE1gina tard\xF3 demasiado en responder. Se puede volver a intentar en unos minutos.",
|
|
28186
|
+
llm_failed: "Hubo un error temporal al analizar la marca. Se puede volver a intentar en unos minutos.",
|
|
28187
|
+
llm_invalid_output: "El an\xE1lisis devolvi\xF3 algo inesperado. Se puede volver a intentar.",
|
|
28188
|
+
worker_exception: "Ocurri\xF3 un error inesperado durante el an\xE1lisis. Se puede volver a intentar.",
|
|
28189
|
+
BRAND_EXTRACTION_REQUIRES_REVIEW: "El an\xE1lisis se detuvo de forma segura y necesita revisi\xF3n del equipo de ProductMaker antes de reintentarlo."
|
|
28190
|
+
};
|
|
28191
|
+
function describeExtractionError(code) {
|
|
28192
|
+
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.";
|
|
28193
|
+
}
|
|
28194
|
+
__name(describeExtractionError, "describeExtractionError");
|
|
28195
|
+
function paletteColors(palette) {
|
|
28196
|
+
if (!palette) return [];
|
|
28197
|
+
const colors = [];
|
|
28198
|
+
for (const key of [
|
|
28199
|
+
"primary",
|
|
28200
|
+
"secondary",
|
|
28201
|
+
"tertiary",
|
|
28202
|
+
"neutral"
|
|
28203
|
+
]) {
|
|
28204
|
+
const value = palette[key];
|
|
28205
|
+
if (typeof value === "string") colors.push(value);
|
|
28206
|
+
}
|
|
28207
|
+
const accents = palette["accents"];
|
|
28208
|
+
if (Array.isArray(accents)) {
|
|
28209
|
+
for (const accent of accents) {
|
|
28210
|
+
if (typeof accent === "string") colors.push(accent);
|
|
28211
|
+
}
|
|
28212
|
+
}
|
|
28213
|
+
return colors.slice(0, 6);
|
|
28214
|
+
}
|
|
28215
|
+
__name(paletteColors, "paletteColors");
|
|
28216
|
+
function summarizeBrand(brand) {
|
|
28217
|
+
return {
|
|
28218
|
+
brandId: brand.id,
|
|
28219
|
+
status: brand.status,
|
|
28220
|
+
name: brand.name,
|
|
28221
|
+
...brand.sourceUrl ? {
|
|
28222
|
+
source: brand.sourceUrl
|
|
28223
|
+
} : {},
|
|
28224
|
+
...brand.oneLiner ? {
|
|
28225
|
+
oneLiner: brand.oneLiner
|
|
28226
|
+
} : {},
|
|
28227
|
+
...brand.overview ? {
|
|
28228
|
+
overview: brand.overview
|
|
28229
|
+
} : {},
|
|
28230
|
+
tone: brand.toneAdjectives ?? [],
|
|
28231
|
+
values: brand.coreValues ?? [],
|
|
28232
|
+
keyMessages: brand.keyMessages ?? [],
|
|
28233
|
+
colors: paletteColors(brand.palette),
|
|
28234
|
+
hasLogo: Boolean(brand.logoKey),
|
|
28235
|
+
audiences: (brand.personas ?? []).map((persona) => ({
|
|
28236
|
+
name: persona.name,
|
|
28237
|
+
...persona.demographics ? {
|
|
28238
|
+
who: persona.demographics
|
|
28239
|
+
} : {},
|
|
28240
|
+
...persona.painPoints ? {
|
|
28241
|
+
painPoints: persona.painPoints
|
|
28242
|
+
} : {},
|
|
28243
|
+
...persona.motivations ? {
|
|
28244
|
+
motivations: persona.motivations
|
|
28245
|
+
} : {}
|
|
28246
|
+
}))
|
|
28247
|
+
};
|
|
28248
|
+
}
|
|
28249
|
+
__name(summarizeBrand, "summarizeBrand");
|
|
28250
|
+
function describeBrandLanding(page) {
|
|
28251
|
+
if (!page) return null;
|
|
28252
|
+
return {
|
|
28253
|
+
servicePageId: page.id,
|
|
28254
|
+
status: page.publicUrl ? "published" : "draft",
|
|
28255
|
+
...page.publicUrl ? {
|
|
28256
|
+
publicUrl: page.publicUrl
|
|
28257
|
+
} : {}
|
|
28258
|
+
};
|
|
28259
|
+
}
|
|
28260
|
+
__name(describeBrandLanding, "describeBrandLanding");
|
|
28261
|
+
function statusResponse(brand, landing = null) {
|
|
28262
|
+
if (brand.status === "ready") {
|
|
28263
|
+
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.";
|
|
28264
|
+
return asText({
|
|
28265
|
+
brand: summarizeBrand(brand),
|
|
28266
|
+
...landing ? {
|
|
28267
|
+
serviceLanding: landing
|
|
28268
|
+
} : {},
|
|
28269
|
+
nextAction: `Muestra al usuario el an\xE1lisis de su marca (nombre, frase, tono, colores, mensajes clave y p\xFAblico) en pocas l\xEDneas. ${landingNext}`
|
|
28270
|
+
});
|
|
28271
|
+
}
|
|
28272
|
+
if (brand.status === "failed") {
|
|
28273
|
+
return asText({
|
|
28274
|
+
brandId: brand.id,
|
|
28275
|
+
status: "failed",
|
|
28276
|
+
reason: describeExtractionError(brand.extractionError),
|
|
28277
|
+
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."
|
|
28278
|
+
});
|
|
28279
|
+
}
|
|
28280
|
+
return asText({
|
|
28281
|
+
brandId: brand.id,
|
|
28282
|
+
status: "extracting",
|
|
28283
|
+
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."
|
|
28284
|
+
});
|
|
28285
|
+
}
|
|
28286
|
+
__name(statusResponse, "statusResponse");
|
|
28287
|
+
async function findRecentBrand(api, url, nowMs) {
|
|
28288
|
+
const brands = await api.getJson("/v1/brands");
|
|
28289
|
+
const key = brandSourceKey(url);
|
|
28290
|
+
return brands.find((brand) => {
|
|
28291
|
+
if (!brand.sourceUrl || brandSourceKey(brand.sourceUrl) !== key) return false;
|
|
28292
|
+
const updatedMs = Date.parse(brand.updatedAt);
|
|
28293
|
+
return brand.status !== "failed" && Number.isFinite(updatedMs) && nowMs - updatedMs < BRAND_START_REPLAY_WINDOW_MS;
|
|
28294
|
+
}) ?? null;
|
|
28295
|
+
}
|
|
28296
|
+
__name(findRecentBrand, "findRecentBrand");
|
|
28297
|
+
function registerBrandTools(server, api, options = {}) {
|
|
28298
|
+
const now = options.now ?? Date.now;
|
|
28299
|
+
async function findBrandLanding(brandId) {
|
|
28300
|
+
try {
|
|
28301
|
+
const { pages } = await api.getJson("/v1/service-pages");
|
|
28302
|
+
return describeBrandLanding(pages.find((page) => page.brandId === brandId) ?? null);
|
|
28303
|
+
} catch {
|
|
28304
|
+
return null;
|
|
28305
|
+
}
|
|
28306
|
+
}
|
|
28307
|
+
__name(findBrandLanding, "findBrandLanding");
|
|
28308
|
+
server.registerTool("create_brand", {
|
|
28309
|
+
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.",
|
|
28310
|
+
inputSchema: CreateBrandInput.shape,
|
|
28311
|
+
annotations: CREATES_PAID_OPEN_WORLD
|
|
28312
|
+
}, async (rawInput) => {
|
|
28313
|
+
try {
|
|
28314
|
+
const input = CreateBrandInput.parse(rawInput);
|
|
28315
|
+
if (input.action === "list") {
|
|
28316
|
+
const brands = await api.getJson("/v1/brands");
|
|
28317
|
+
return asText({
|
|
28318
|
+
brands: brands.map((brand) => ({
|
|
28319
|
+
brandId: brand.id,
|
|
28320
|
+
name: brand.name,
|
|
28321
|
+
status: brand.status,
|
|
28322
|
+
...brand.sourceUrl ? {
|
|
28323
|
+
source: brand.sourceUrl
|
|
28324
|
+
} : {}
|
|
28325
|
+
})),
|
|
28326
|
+
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."
|
|
28327
|
+
});
|
|
28328
|
+
}
|
|
28329
|
+
if (input.action === "status") {
|
|
28330
|
+
if (!input.brandId) {
|
|
28331
|
+
throw mcp(McpErrorCode.INVALID_INPUT, "brandId es obligatorio para consultar una marca. Usa el que devolvi\xF3 start o list.");
|
|
28332
|
+
}
|
|
28333
|
+
const brand = await api.getJson(`/v1/brands/${encodeURIComponent(input.brandId)}`);
|
|
28334
|
+
return statusResponse(brand, brand.status === "ready" ? await findBrandLanding(brand.id) : null);
|
|
28335
|
+
}
|
|
28336
|
+
const sourceUrl = input.sourceUrl ? normalizeBrandSourceUrl(input.sourceUrl) : null;
|
|
28337
|
+
if (!sourceUrl) {
|
|
28338
|
+
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.");
|
|
28339
|
+
}
|
|
28340
|
+
const existing = await findRecentBrand(api, sourceUrl, now());
|
|
28341
|
+
if (existing && input.confirmed === true) {
|
|
28342
|
+
const brand = await api.getJson(`/v1/brands/${encodeURIComponent(existing.id)}`);
|
|
28343
|
+
if (!input.businessNotes) return statusResponse(brand);
|
|
28344
|
+
return asText({
|
|
28345
|
+
brandId: brand.id,
|
|
28346
|
+
status: brand.status,
|
|
28347
|
+
replayed: true,
|
|
28348
|
+
notesSaved: false,
|
|
28349
|
+
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."
|
|
28350
|
+
});
|
|
28351
|
+
}
|
|
28352
|
+
const estimate = await api.postJson("/v1/billing/estimate", {
|
|
28353
|
+
type: "brand-extract",
|
|
28354
|
+
options: {}
|
|
28355
|
+
});
|
|
28356
|
+
const priceCredits = Math.ceil(estimate.estimatedMaxCostCredits);
|
|
28357
|
+
if (input.confirmed !== true) {
|
|
28358
|
+
return asText({
|
|
28359
|
+
sourceUrl,
|
|
28360
|
+
charged: false,
|
|
28361
|
+
...existing ? {
|
|
28362
|
+
existingBrand: {
|
|
28363
|
+
brandId: existing.id,
|
|
28364
|
+
status: existing.status
|
|
28365
|
+
}
|
|
28366
|
+
} : {},
|
|
28367
|
+
priceCredits,
|
|
28368
|
+
userBalanceCredits: estimate.userBalanceCredits,
|
|
28369
|
+
hasSufficientBalance: estimate.hasSufficientBalance,
|
|
28370
|
+
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.`
|
|
28371
|
+
});
|
|
28372
|
+
}
|
|
28373
|
+
await requirePaidCreativeConfirmation(server, `An\xE1lisis de marca de ${sourceUrl}${input.businessNotes ? " con los datos que contaste del negocio" : ""}. Precio: hasta ${priceCredits} cr\xE9ditos.`);
|
|
28374
|
+
const notes = input.businessNotes?.trim();
|
|
28375
|
+
const created = await api.postMultipart("/v1/brands/extract", notes ? {
|
|
28376
|
+
files: {
|
|
28377
|
+
buffer: Buffer.from(notes, "utf8"),
|
|
28378
|
+
mimetype: "text/plain",
|
|
28379
|
+
filename: "notas-del-negocio.txt"
|
|
28380
|
+
}
|
|
28381
|
+
} : {}, {
|
|
28382
|
+
url: sourceUrl,
|
|
28383
|
+
locale: input.language
|
|
28384
|
+
});
|
|
28385
|
+
return asText({
|
|
28386
|
+
brandId: created.brandId,
|
|
28387
|
+
status: "extracting",
|
|
28388
|
+
priceCredits,
|
|
28389
|
+
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."
|
|
28390
|
+
});
|
|
28391
|
+
} catch (error) {
|
|
28392
|
+
throw translateError(error);
|
|
28393
|
+
}
|
|
28394
|
+
});
|
|
28395
|
+
}
|
|
28396
|
+
__name(registerBrandTools, "registerBrandTools");
|
|
28397
|
+
|
|
28398
|
+
// src/tools/service-landing.ts
|
|
28399
|
+
import { createHash as createHash4 } from "crypto";
|
|
28400
|
+
import { z as z113 } from "zod";
|
|
28401
|
+
var guidance3 = getProductMakerCapabilityModelGuidance("create_service_landing");
|
|
28402
|
+
var SERVICE_LANDING_KEY_NAMESPACE = "6f1d8c2a-3b4e-5f60-8a7b-9c0d1e2f3a4b";
|
|
28403
|
+
var SERVICE_LANDING_INFO_MAX_CHARS = 2e4;
|
|
28404
|
+
var CreateServiceLandingInput = z113.object({
|
|
28405
|
+
action: z113.enum([
|
|
28406
|
+
"prepare",
|
|
28407
|
+
"build",
|
|
28408
|
+
"status",
|
|
28409
|
+
"publish"
|
|
28410
|
+
]).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."),
|
|
28411
|
+
brandId: z113.string().uuid().optional().describe("ID de la marca (de create_brand). Obligatorio para prepare. Nunca un taskId de producto."),
|
|
28412
|
+
servicePageId: z113.string().uuid().optional().describe("ID de la landing que devolvi\xF3 prepare. Obligatorio para build, status y publish."),
|
|
28413
|
+
businessInfo: z113.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."),
|
|
28414
|
+
confirmed: z113.boolean().optional().describe("Solo true despu\xE9s de mostrar el precio total y que el usuario acepte. Obligatorio para build."),
|
|
28415
|
+
maxCredits: z113.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."),
|
|
28416
|
+
operationId: z113.string().uuid().optional().describe("Opcional en status: la operaci\xF3n que devolvi\xF3 build."),
|
|
28417
|
+
slug: z113.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.")
|
|
28418
|
+
}).strict();
|
|
28419
|
+
function serviceKey(...parts) {
|
|
28420
|
+
return deriveUuidV5(parts.join(":"), SERVICE_LANDING_KEY_NAMESPACE);
|
|
28421
|
+
}
|
|
28422
|
+
__name(serviceKey, "serviceKey");
|
|
28423
|
+
function textHash(text2) {
|
|
28424
|
+
return createHash4("sha256").update(text2, "utf8").digest("hex").slice(0, 16);
|
|
28425
|
+
}
|
|
28426
|
+
__name(textHash, "textHash");
|
|
28427
|
+
function quoteRange(quote) {
|
|
28428
|
+
const max = Math.ceil(quote.estimatedMaxCostCredits ?? quote.credits);
|
|
28429
|
+
const min = Math.ceil(Math.min(quote.estimatedMinCostCredits ?? max, max));
|
|
28430
|
+
return {
|
|
28431
|
+
min,
|
|
28432
|
+
max
|
|
28433
|
+
};
|
|
28434
|
+
}
|
|
28435
|
+
__name(quoteRange, "quoteRange");
|
|
28436
|
+
function serviceApiCode(error) {
|
|
28437
|
+
if (!(error instanceof ApiHttpError)) return null;
|
|
28438
|
+
const body = error.body;
|
|
28439
|
+
if (!body || typeof body !== "object") return null;
|
|
28440
|
+
const record = body;
|
|
28441
|
+
for (const value of [
|
|
28442
|
+
record.code,
|
|
28443
|
+
record.errorCode,
|
|
28444
|
+
record.message
|
|
28445
|
+
]) {
|
|
28446
|
+
if (typeof value === "string" && /^[A-Z][A-Z0-9_]{2,63}$/.test(value)) return value;
|
|
28447
|
+
}
|
|
28448
|
+
return null;
|
|
28449
|
+
}
|
|
28450
|
+
__name(serviceApiCode, "serviceApiCode");
|
|
28451
|
+
function suggestServiceSlug(name) {
|
|
28452
|
+
const slug = (name ?? "").normalize("NFD").replace(/[̀-ͯ]/g, "").toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 48).replace(/-+$/g, "");
|
|
28453
|
+
return slug || "mi-servicio";
|
|
28454
|
+
}
|
|
28455
|
+
__name(suggestServiceSlug, "suggestServiceSlug");
|
|
28456
|
+
function serviceEditorUrl(api, pageId) {
|
|
28457
|
+
try {
|
|
28458
|
+
const url = new URL(api.publicUrl("/"));
|
|
28459
|
+
if (!url.hostname.startsWith("api.")) return null;
|
|
28460
|
+
url.hostname = url.hostname.slice("api.".length);
|
|
28461
|
+
url.pathname = `/es/service-pages/${encodeURIComponent(pageId)}/edit`;
|
|
28462
|
+
return url.toString();
|
|
28463
|
+
} catch {
|
|
28464
|
+
return null;
|
|
28465
|
+
}
|
|
28466
|
+
}
|
|
28467
|
+
__name(serviceEditorUrl, "serviceEditorUrl");
|
|
28468
|
+
var MISSING_LABELS = {
|
|
28469
|
+
"business.name": "nombre del negocio",
|
|
28470
|
+
services: "servicios (y su precio, si lo tiene)",
|
|
28471
|
+
"contact.channels": "WhatsApp, tel\xE9fono o enlace para reservar",
|
|
28472
|
+
"contact.primaryCta": "c\xF3mo quiere que lo contacten primero"
|
|
28473
|
+
};
|
|
28474
|
+
function describeMissing(fields) {
|
|
28475
|
+
return [
|
|
28476
|
+
...new Set(fields.map((field) => MISSING_LABELS[field]).filter((label) => Boolean(label)))
|
|
28477
|
+
];
|
|
28478
|
+
}
|
|
28479
|
+
__name(describeMissing, "describeMissing");
|
|
28480
|
+
function summarizeFacts(facts) {
|
|
28481
|
+
return {
|
|
28482
|
+
...facts.business.name ? {
|
|
28483
|
+
business: facts.business.name
|
|
28484
|
+
} : {},
|
|
28485
|
+
...facts.business.description ? {
|
|
28486
|
+
description: facts.business.description
|
|
28487
|
+
} : {},
|
|
28488
|
+
...facts.business.coverage ? {
|
|
28489
|
+
area: facts.business.coverage
|
|
28490
|
+
} : {},
|
|
28491
|
+
services: facts.services.filter((service) => service.name).map((service) => ({
|
|
28492
|
+
name: service.name,
|
|
28493
|
+
...service.price ? {
|
|
28494
|
+
price: service.price
|
|
28495
|
+
} : {}
|
|
28496
|
+
})),
|
|
28497
|
+
contact: facts.contact.channels.map((channel) => channel.value ? `${channel.kind}: ${channel.value}` : channel.kind)
|
|
28498
|
+
};
|
|
28499
|
+
}
|
|
28500
|
+
__name(summarizeFacts, "summarizeFacts");
|
|
28501
|
+
var OPERATION_ERROR_TEXT = {
|
|
28502
|
+
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.",
|
|
28503
|
+
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.",
|
|
28504
|
+
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)."
|
|
28505
|
+
};
|
|
28506
|
+
function operationFailureText(code) {
|
|
28507
|
+
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.";
|
|
28508
|
+
}
|
|
28509
|
+
__name(operationFailureText, "operationFailureText");
|
|
28510
|
+
function isPageGenerated(page) {
|
|
28511
|
+
return page.artifact !== null && page.artifact !== void 0;
|
|
28512
|
+
}
|
|
28513
|
+
__name(isPageGenerated, "isPageGenerated");
|
|
28514
|
+
function registerServiceLandingTools(server, api) {
|
|
28515
|
+
const pagePath = /* @__PURE__ */ __name((id) => `/v1/service-pages/${encodeURIComponent(id)}`, "pagePath");
|
|
28516
|
+
const readPage = /* @__PURE__ */ __name((id) => api.getJson(pagePath(id)), "readPage");
|
|
28517
|
+
async function findPageForBrand(brandId) {
|
|
28518
|
+
const { pages } = await api.getJson("/v1/service-pages");
|
|
28519
|
+
return pages.find((page) => page.brandId === brandId) ?? null;
|
|
28520
|
+
}
|
|
28521
|
+
__name(findPageForBrand, "findPageForBrand");
|
|
28522
|
+
async function addBusinessInfo(page, info) {
|
|
28523
|
+
try {
|
|
28524
|
+
await api.postMultipart(`${pagePath(page.id)}/intake`, {}, {
|
|
28525
|
+
expectedRevision: String(page.draftRevision),
|
|
28526
|
+
idempotencyKey: serviceKey(page.id, "intake", textHash(info)),
|
|
28527
|
+
text: info
|
|
28528
|
+
});
|
|
28529
|
+
} catch (error) {
|
|
28530
|
+
if (serviceApiCode(error) !== "SERVICE_PAGE_CONFLICT_REVISION") throw error;
|
|
28531
|
+
}
|
|
28532
|
+
return readPage(page.id);
|
|
28533
|
+
}
|
|
28534
|
+
__name(addBusinessInfo, "addBusinessInfo");
|
|
28535
|
+
async function quote(page, purpose) {
|
|
28536
|
+
return api.postJson(`${pagePath(page.id)}/quotes/${purpose}`, {
|
|
28537
|
+
expectedRevision: page.draftRevision
|
|
28538
|
+
});
|
|
28539
|
+
}
|
|
28540
|
+
__name(quote, "quote");
|
|
28541
|
+
async function startOperation(page, purpose, quoted) {
|
|
28542
|
+
const path3 = purpose === "fact-extraction" ? `${pagePath(page.id)}/intake/extract` : `${pagePath(page.id)}/generate`;
|
|
28543
|
+
return api.postJson(path3, {
|
|
28544
|
+
quoteId: quoted.quoteId,
|
|
28545
|
+
idempotencyKey: serviceKey(page.id, purpose, page.draftRevision),
|
|
28546
|
+
expectedRevision: page.draftRevision
|
|
28547
|
+
});
|
|
28548
|
+
}
|
|
28549
|
+
__name(startOperation, "startOperation");
|
|
28550
|
+
function generatedResponse(page) {
|
|
28551
|
+
const editorUrl = serviceEditorUrl(api, page.id);
|
|
28552
|
+
return asText({
|
|
28553
|
+
servicePageId: page.id,
|
|
28554
|
+
status: "generated",
|
|
28555
|
+
...page.publicUrl ? {
|
|
28556
|
+
publicUrl: page.publicUrl
|
|
28557
|
+
} : {},
|
|
28558
|
+
...editorUrl ? {
|
|
28559
|
+
editorUrl
|
|
28560
|
+
} : {},
|
|
28561
|
+
suggestedSlug: page.slug ?? suggestServiceSlug(page.businessName),
|
|
28562
|
+
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.`
|
|
28563
|
+
});
|
|
28564
|
+
}
|
|
28565
|
+
__name(generatedResponse, "generatedResponse");
|
|
28566
|
+
server.registerTool("create_service_landing", {
|
|
28567
|
+
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.",
|
|
28568
|
+
inputSchema: CreateServiceLandingInput.shape,
|
|
28569
|
+
annotations: CREATES_PAID
|
|
28570
|
+
}, async (rawInput) => {
|
|
28571
|
+
try {
|
|
28572
|
+
const input = CreateServiceLandingInput.parse(rawInput);
|
|
28573
|
+
if (input.action === "prepare") {
|
|
28574
|
+
if (!input.brandId) {
|
|
28575
|
+
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.");
|
|
28576
|
+
}
|
|
28577
|
+
const brand = await api.getJson(`/v1/brands/${encodeURIComponent(input.brandId)}`);
|
|
28578
|
+
if (brand.status !== "ready") {
|
|
28579
|
+
return asText({
|
|
28580
|
+
brandId: input.brandId,
|
|
28581
|
+
charged: false,
|
|
28582
|
+
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."
|
|
28583
|
+
});
|
|
28584
|
+
}
|
|
28585
|
+
const existing = await findPageForBrand(input.brandId);
|
|
28586
|
+
let page2 = existing ? await readPage(existing.id) : await api.postJson("/v1/service-pages", {
|
|
28587
|
+
brandId: input.brandId,
|
|
28588
|
+
includeBrandLogo: Boolean(brand.logoKey),
|
|
28589
|
+
idempotencyKey: serviceKey(input.brandId, "create")
|
|
28590
|
+
});
|
|
28591
|
+
if (isPageGenerated(page2)) return generatedResponse(page2);
|
|
28592
|
+
if (input.businessInfo) page2 = await addBusinessInfo(page2, input.businessInfo);
|
|
28593
|
+
const factsQuote = page2.factExtractionCompleted ? null : await quote(page2, "fact-extraction");
|
|
28594
|
+
const generationQuote = await quote(page2, "generation");
|
|
28595
|
+
const facts = factsQuote ? quoteRange(factsQuote) : {
|
|
28596
|
+
min: 0,
|
|
28597
|
+
max: 0
|
|
28598
|
+
};
|
|
28599
|
+
const build = quoteRange(generationQuote);
|
|
28600
|
+
const totalMin = facts.min + build.min;
|
|
28601
|
+
const totalCredits = facts.max + build.max;
|
|
28602
|
+
const requiredBalanceCredits = Math.max(factsQuote?.credits ?? 0, generationQuote.credits);
|
|
28603
|
+
const priceText = totalMin === totalCredits ? `${totalCredits} cr\xE9ditos` : `entre ${totalMin} y ${totalCredits} cr\xE9ditos`;
|
|
28604
|
+
return asText({
|
|
28605
|
+
servicePageId: page2.id,
|
|
28606
|
+
charged: false,
|
|
28607
|
+
...existing ? {
|
|
28608
|
+
resumed: true
|
|
28609
|
+
} : {},
|
|
28610
|
+
business: page2.businessName,
|
|
28611
|
+
known: summarizeFacts(page2.facts),
|
|
28612
|
+
price: {
|
|
28613
|
+
...factsQuote ? {
|
|
28614
|
+
readBusinessInfoCredits: facts
|
|
28615
|
+
} : {},
|
|
28616
|
+
buildPageCredits: build,
|
|
28617
|
+
totalMinCredits: totalMin,
|
|
28618
|
+
totalCredits,
|
|
28619
|
+
requiredBalanceCredits
|
|
28620
|
+
},
|
|
28621
|
+
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.`
|
|
28622
|
+
});
|
|
28623
|
+
}
|
|
28624
|
+
if (!input.servicePageId) {
|
|
28625
|
+
throw mcp(McpErrorCode.INVALID_INPUT, "servicePageId es obligatorio: usa el que devolvi\xF3 prepare. No se cobr\xF3 nada.");
|
|
28626
|
+
}
|
|
28627
|
+
if (input.action === "status") {
|
|
28628
|
+
let operation2 = null;
|
|
28629
|
+
if (input.operationId) {
|
|
28630
|
+
operation2 = await api.getJson(`/v1/service-pages/operations/${encodeURIComponent(input.operationId)}`);
|
|
28631
|
+
if (operation2.recoverable && (operation2.status === "failed" || operation2.status === "requires_reconciliation")) {
|
|
28632
|
+
operation2 = await api.postJson(`${pagePath(input.servicePageId)}/operations/${encodeURIComponent(operation2.opId)}/recover`, {});
|
|
28633
|
+
}
|
|
28634
|
+
if (operation2.status === "queued" || operation2.status === "running") {
|
|
28635
|
+
return asText({
|
|
28636
|
+
servicePageId: input.servicePageId,
|
|
28637
|
+
operationId: operation2.opId,
|
|
28638
|
+
step: operation2.kind === "generation" ? "creando la p\xE1gina" : "leyendo los datos del negocio",
|
|
28639
|
+
status: operation2.status,
|
|
28640
|
+
nextAction: "Sigue en curso. Llama action=status otra vez con el mismo operationId en unos segundos; no inicies otra etapa."
|
|
28641
|
+
});
|
|
28642
|
+
}
|
|
28643
|
+
if (operation2.status === "requires_reconciliation") {
|
|
28644
|
+
return asText({
|
|
28645
|
+
servicePageId: input.servicePageId,
|
|
28646
|
+
operationId: operation2.opId,
|
|
28647
|
+
status: "in_review",
|
|
28648
|
+
// Unknown on purpose: the charge is exactly what is being
|
|
28649
|
+
// confirmed. Settlement only charges a completed step.
|
|
28650
|
+
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."
|
|
28651
|
+
});
|
|
28652
|
+
}
|
|
28653
|
+
if (operation2.status !== "succeeded") {
|
|
28654
|
+
return asText({
|
|
28655
|
+
servicePageId: input.servicePageId,
|
|
28656
|
+
operationId: operation2.opId,
|
|
28657
|
+
status: "failed",
|
|
28658
|
+
charged: false,
|
|
28659
|
+
reason: operationFailureText(operation2.errorCode)
|
|
28660
|
+
});
|
|
28661
|
+
}
|
|
28662
|
+
}
|
|
28663
|
+
const page2 = await readPage(input.servicePageId);
|
|
28664
|
+
if (isPageGenerated(page2)) return generatedResponse(page2);
|
|
28665
|
+
const missing = describeMissing(page2.missingFields);
|
|
28666
|
+
return asText({
|
|
28667
|
+
servicePageId: page2.id,
|
|
28668
|
+
status: page2.factExtractionCompleted ? "business_info_read" : "waiting_for_build",
|
|
28669
|
+
found: summarizeFacts(page2.facts),
|
|
28670
|
+
...missing.length ? {
|
|
28671
|
+
stillMissing: missing
|
|
28672
|
+
} : {},
|
|
28673
|
+
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."
|
|
28674
|
+
});
|
|
28675
|
+
}
|
|
28676
|
+
if (input.action === "publish") {
|
|
28677
|
+
const page2 = await readPage(input.servicePageId);
|
|
28678
|
+
if (!isPageGenerated(page2)) {
|
|
28679
|
+
throw mcp(McpErrorCode.INVALID_INPUT, "La p\xE1gina todav\xEDa no est\xE1 creada. Termina action=build y action=status antes de publicar.");
|
|
28680
|
+
}
|
|
28681
|
+
if (page2.publicUrl) return generatedResponse(page2);
|
|
28682
|
+
const slug = input.slug ?? suggestServiceSlug(page2.businessName);
|
|
28683
|
+
try {
|
|
28684
|
+
const published = await api.postJson(`${pagePath(page2.id)}/publish`, {
|
|
28685
|
+
expectedRevision: page2.draftRevision,
|
|
28686
|
+
slug,
|
|
28687
|
+
idempotencyKey: serviceKey(page2.id, "publish", slug)
|
|
28688
|
+
});
|
|
28689
|
+
return asText({
|
|
28690
|
+
servicePageId: page2.id,
|
|
28691
|
+
status: "published",
|
|
28692
|
+
publicUrl: published.publicUrl,
|
|
28693
|
+
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)"}.`
|
|
28694
|
+
});
|
|
28695
|
+
} catch (error) {
|
|
28696
|
+
if (serviceApiCode(error)?.includes("SLUG")) {
|
|
28697
|
+
return asText({
|
|
28698
|
+
servicePageId: page2.id,
|
|
28699
|
+
status: "slug_taken",
|
|
28700
|
+
slug,
|
|
28701
|
+
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.`
|
|
28702
|
+
});
|
|
28703
|
+
}
|
|
28704
|
+
throw error;
|
|
28705
|
+
}
|
|
28706
|
+
}
|
|
28707
|
+
if (input.confirmed !== true || !input.maxCredits) {
|
|
28708
|
+
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.");
|
|
28709
|
+
}
|
|
28710
|
+
let page = await readPage(input.servicePageId);
|
|
28711
|
+
if (isPageGenerated(page)) return generatedResponse(page);
|
|
28712
|
+
const revisionBefore = page.draftRevision;
|
|
28713
|
+
if (input.businessInfo) page = await addBusinessInfo(page, input.businessInfo);
|
|
28714
|
+
const addedInfo = page.draftRevision !== revisionBefore;
|
|
28715
|
+
const needsFacts = !page.factExtractionCompleted || addedInfo;
|
|
28716
|
+
const purpose = needsFacts ? "fact-extraction" : "generation";
|
|
28717
|
+
const quoted = await quote(page, purpose);
|
|
28718
|
+
const credits = quoteRange(quoted).max;
|
|
28719
|
+
if (credits > input.maxCredits) {
|
|
28720
|
+
return asText({
|
|
28721
|
+
servicePageId: page.id,
|
|
28722
|
+
charged: false,
|
|
28723
|
+
priceCredits: credits,
|
|
28724
|
+
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}.`
|
|
28725
|
+
});
|
|
28726
|
+
}
|
|
28727
|
+
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}).`);
|
|
28728
|
+
const operation = await startOperation(page, purpose, quoted);
|
|
28729
|
+
return asText({
|
|
28730
|
+
servicePageId: page.id,
|
|
28731
|
+
operationId: operation.opId,
|
|
28732
|
+
step: needsFacts ? "leyendo los datos del negocio" : "creando la p\xE1gina",
|
|
28733
|
+
status: operation.status,
|
|
28734
|
+
priceCredits: credits,
|
|
28735
|
+
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."
|
|
28736
|
+
});
|
|
28737
|
+
} catch (error) {
|
|
28738
|
+
throw translateError(error);
|
|
28739
|
+
}
|
|
28740
|
+
});
|
|
28741
|
+
}
|
|
28742
|
+
__name(registerServiceLandingTools, "registerServiceLandingTools");
|
|
28743
|
+
|
|
28744
|
+
// src/tools/service-images.ts
|
|
28745
|
+
import { z as z114 } from "zod";
|
|
28746
|
+
var guidance4 = getProductMakerCapabilityModelGuidance("create_service_images");
|
|
28747
|
+
var SERVICE_IMAGE_FORMATS = [
|
|
28748
|
+
"4:5",
|
|
28749
|
+
"9:16",
|
|
28750
|
+
"1:1"
|
|
28751
|
+
];
|
|
28752
|
+
var SERVICE_IMAGE_MAX_COUNT = 3;
|
|
28753
|
+
var LANGUAGE_TAGS = {
|
|
28754
|
+
es: "es-CO",
|
|
28755
|
+
en: "en-US",
|
|
28756
|
+
pt: "pt-BR"
|
|
28757
|
+
};
|
|
28758
|
+
var CreateServiceImagesInput = z114.object({
|
|
28759
|
+
brandId: z114.string().uuid().optional().describe("ID de la marca (de create_brand). Recomendado: da nombre, colores, tono y servicios. Nunca un taskId de producto."),
|
|
28760
|
+
instruction: z114.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."),
|
|
28761
|
+
delegateToAi: z114.boolean().optional().describe("true si el usuario prefiere que la IA elija qu\xE9 mostrar a partir de su marca."),
|
|
28762
|
+
format: z114.enum(SERVICE_IMAGE_FORMATS).optional().describe('Formato: "4:5" (feed, por defecto), "9:16" (historias/reels) o "1:1" (cuadrado).'),
|
|
28763
|
+
count: z114.number().int().min(1).max(SERVICE_IMAGE_MAX_COUNT).optional().describe("Cu\xE1ntas im\xE1genes, de 1 a 3 (por defecto 1)."),
|
|
28764
|
+
quality: z114.enum([
|
|
28765
|
+
"standard",
|
|
28766
|
+
"high"
|
|
28767
|
+
]).optional().describe('Calidad: "standard" (por defecto) o "high" (m\xE1s cara; no disponible en 4:5).'),
|
|
28768
|
+
language: z114.enum([
|
|
28769
|
+
"es",
|
|
28770
|
+
"en",
|
|
28771
|
+
"pt"
|
|
28772
|
+
]).optional().describe("Idioma de los textos de la imagen (por defecto es)."),
|
|
28773
|
+
adCopy: z114.boolean().optional().describe("Opcional. true agrega textos para el anuncio (titular y descripci\xF3n). Por defecto false."),
|
|
28774
|
+
confirmed: z114.boolean().optional().describe("Solo true despu\xE9s de mostrar el precio y que el usuario acepte. Sin confirmed, solo cotiza y no cobra.")
|
|
28775
|
+
}).strict();
|
|
28776
|
+
function serviceImageModel(quality) {
|
|
28777
|
+
return quality === "high" ? AD_CREATIVE_MODELS.PREMIUM : AD_CREATIVE_MODELS.STANDARD;
|
|
28778
|
+
}
|
|
28779
|
+
__name(serviceImageModel, "serviceImageModel");
|
|
28780
|
+
function registerServiceImageTools(server, api, options = {}) {
|
|
28781
|
+
const now = options.now ?? Date.now;
|
|
28782
|
+
server.registerTool("create_service_images", {
|
|
28783
|
+
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.",
|
|
28784
|
+
inputSchema: CreateServiceImagesInput.shape,
|
|
28785
|
+
annotations: CREATES_PAID
|
|
28786
|
+
}, async (rawInput) => {
|
|
28787
|
+
try {
|
|
28788
|
+
const input = CreateServiceImagesInput.parse(rawInput);
|
|
28789
|
+
const brief = ServiceImageInputSchema.safeParse({
|
|
28790
|
+
instruction: input.instruction ?? "",
|
|
28791
|
+
intent: input.delegateToAi ? "delegate_to_ai" : "user_brief"
|
|
28792
|
+
});
|
|
28793
|
+
if (!brief.success) {
|
|
28794
|
+
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.");
|
|
28795
|
+
}
|
|
28796
|
+
const format = input.format ?? "4:5";
|
|
28797
|
+
const count = input.count ?? 1;
|
|
28798
|
+
const imageModel = serviceImageModel(input.quality ?? "standard");
|
|
28799
|
+
const tierError = validateImageTierForRatio(format, imageModel);
|
|
28800
|
+
if (tierError) {
|
|
28801
|
+
throw mcp(McpErrorCode.INVALID_INPUT, `${tierError} Usa quality="standard" o otro formato. No se cobr\xF3 nada.`);
|
|
28802
|
+
}
|
|
28803
|
+
const language = LANGUAGE_TAGS[input.language ?? "es"];
|
|
28804
|
+
const adCopy = input.adCopy ?? false;
|
|
28805
|
+
const estimate = await api.postJson("/v1/billing/estimate", {
|
|
28806
|
+
type: "image-creative",
|
|
28807
|
+
options: {
|
|
28808
|
+
subject: "service",
|
|
28809
|
+
nVariants: count,
|
|
28810
|
+
imageModel,
|
|
28811
|
+
aspectRatio: format,
|
|
28812
|
+
generateAdCopy: adCopy,
|
|
28813
|
+
requiresExtraction: false,
|
|
28814
|
+
requiresAngles: false,
|
|
28815
|
+
competitorScanEnabled: false
|
|
28816
|
+
}
|
|
28817
|
+
});
|
|
28818
|
+
const max = Math.ceil(estimate.estimatedMaxCostCredits);
|
|
28819
|
+
const min = Math.ceil(Math.min(estimate.estimatedMinCostCredits ?? max, max));
|
|
28820
|
+
const priceText = min === max ? `${max} cr\xE9ditos` : `entre ${min} y ${max} cr\xE9ditos`;
|
|
28821
|
+
const summary = `${count} imagen${count === 1 ? "" : "es"} ${format} de calidad ${input.quality === "high" ? "alta" : "est\xE1ndar"}${input.brandId ? " con la marca" : ""}`;
|
|
28822
|
+
if (input.confirmed !== true) {
|
|
28823
|
+
return asText({
|
|
28824
|
+
charged: false,
|
|
28825
|
+
images: {
|
|
28826
|
+
count,
|
|
28827
|
+
format,
|
|
28828
|
+
quality: input.quality ?? "standard"
|
|
28829
|
+
},
|
|
28830
|
+
priceCredits: {
|
|
28831
|
+
min,
|
|
28832
|
+
max
|
|
28833
|
+
},
|
|
28834
|
+
userBalanceCredits: estimate.userBalanceCredits,
|
|
28835
|
+
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.`
|
|
28836
|
+
});
|
|
28837
|
+
}
|
|
28838
|
+
await requirePaidCreativeConfirmation(server, `${summary}. Precio: ${priceText}.`);
|
|
28839
|
+
const fields = {
|
|
28840
|
+
serviceBrief: JSON.stringify(brief.data),
|
|
28841
|
+
brandId: input.brandId,
|
|
28842
|
+
aspectRatio: format,
|
|
28843
|
+
imageModel,
|
|
28844
|
+
nVariants: String(count),
|
|
28845
|
+
language,
|
|
28846
|
+
competitorScanEnabled: "false",
|
|
28847
|
+
generateAdCopy: String(adCopy)
|
|
28848
|
+
};
|
|
28849
|
+
const created = await api.postMultipart("/v1/creatives/images", {}, fields, {
|
|
28850
|
+
idempotencyKey: derivePaidRequestKey({
|
|
28851
|
+
fingerprint: api.fingerprint(),
|
|
28852
|
+
tool: "create_service_images",
|
|
28853
|
+
input: {
|
|
28854
|
+
brief: brief.data,
|
|
28855
|
+
brandId: input.brandId ?? null,
|
|
28856
|
+
format,
|
|
28857
|
+
count,
|
|
28858
|
+
imageModel,
|
|
28859
|
+
language,
|
|
28860
|
+
adCopy
|
|
28861
|
+
},
|
|
28862
|
+
nowMs: now()
|
|
28863
|
+
})
|
|
28864
|
+
});
|
|
28865
|
+
const taskId = created.taskId ?? created.jobId;
|
|
28866
|
+
if (!taskId) {
|
|
28867
|
+
throw new Error("ProductMaker no devolvi\xF3 el id de la tarea de im\xE1genes.");
|
|
28868
|
+
}
|
|
28869
|
+
return asText({
|
|
28870
|
+
taskId,
|
|
28871
|
+
status: created.status ?? "queued",
|
|
28872
|
+
...created.replayed === true ? {
|
|
28873
|
+
replayed: true
|
|
28874
|
+
} : {},
|
|
28875
|
+
priceCredits: {
|
|
28876
|
+
min,
|
|
28877
|
+
max
|
|
28878
|
+
},
|
|
28879
|
+
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."
|
|
28880
|
+
});
|
|
28881
|
+
} catch (error) {
|
|
28882
|
+
if (error instanceof z114.ZodError) throw invalidInputFromZod(error);
|
|
28883
|
+
throw translateError(error);
|
|
28884
|
+
}
|
|
28885
|
+
});
|
|
28886
|
+
}
|
|
28887
|
+
__name(registerServiceImageTools, "registerServiceImageTools");
|
|
28888
|
+
|
|
28889
|
+
// src/tools/creatives.ts
|
|
28890
|
+
import { z as z115 } from "zod";
|
|
28007
28891
|
var generateFreeVideoGuidance = getProductMakerCapabilityModelGuidance("generate_free_video");
|
|
28008
28892
|
var generateVideoCreativeGuidance = getProductMakerCapabilityModelGuidance("generate_video_creative");
|
|
28009
28893
|
var generateImageCreativesGuidance = getProductMakerCapabilityModelGuidance("generate_image_creatives");
|
|
@@ -28014,72 +28898,72 @@ var CreativeRefs = {
|
|
|
28014
28898
|
images: ImagesInput,
|
|
28015
28899
|
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.'),
|
|
28016
28900
|
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.'),
|
|
28017
|
-
price:
|
|
28018
|
-
offer:
|
|
28019
|
-
compareAtPrice:
|
|
28020
|
-
productContext:
|
|
28021
|
-
productLabel:
|
|
28022
|
-
buyerPersona:
|
|
28023
|
-
isCombo:
|
|
28024
|
-
competitorScanEnabled:
|
|
28025
|
-
brandId:
|
|
28901
|
+
price: z115.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.'),
|
|
28902
|
+
offer: z115.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.'),
|
|
28903
|
+
compareAtPrice: z115.string().min(1).max(120).optional().describe('Opcional. Precio anterior o de referencia para mostrar descuento. Ej. "$129.000" cuando price="$89.900".'),
|
|
28904
|
+
productContext: z115.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."),
|
|
28905
|
+
productLabel: z115.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.'),
|
|
28906
|
+
buyerPersona: z115.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.'),
|
|
28907
|
+
isCombo: z115.boolean().optional().describe("Opcional. Si true, el creativo presenta el producto como combo/pack. Default false."),
|
|
28908
|
+
competitorScanEnabled: z115.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."),
|
|
28909
|
+
brandId: z115.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.")
|
|
28026
28910
|
};
|
|
28027
|
-
var VideoPresenter =
|
|
28028
|
-
|
|
28029
|
-
mode:
|
|
28911
|
+
var VideoPresenter = z115.discriminatedUnion("mode", [
|
|
28912
|
+
z115.object({
|
|
28913
|
+
mode: z115.literal("none")
|
|
28030
28914
|
}).strict(),
|
|
28031
|
-
|
|
28032
|
-
mode:
|
|
28033
|
-
actorId:
|
|
28915
|
+
z115.object({
|
|
28916
|
+
mode: z115.literal("saved_actor"),
|
|
28917
|
+
actorId: z115.string().trim().min(1).max(120).describe("ID devuelto por list_actors para un actor guardado del usuario.")
|
|
28034
28918
|
}).strict(),
|
|
28035
|
-
|
|
28036
|
-
mode:
|
|
28037
|
-
description:
|
|
28919
|
+
z115.object({
|
|
28920
|
+
mode: z115.literal("described"),
|
|
28921
|
+
description: z115.string().trim().min(1).max(500).describe("Descripci\xF3n concreta de la persona que presenta el producto.")
|
|
28038
28922
|
}).strict()
|
|
28039
28923
|
]);
|
|
28040
|
-
var VideoCreativeSetup =
|
|
28041
|
-
|
|
28042
|
-
mode:
|
|
28924
|
+
var VideoCreativeSetup = z115.discriminatedUnion("mode", [
|
|
28925
|
+
z115.object({
|
|
28926
|
+
mode: z115.literal("automatic").describe('S\xF3lo cuando el usuario pidi\xF3 expl\xEDcitamente "decide todo".')
|
|
28043
28927
|
}).strict(),
|
|
28044
|
-
|
|
28045
|
-
mode:
|
|
28928
|
+
z115.object({
|
|
28929
|
+
mode: z115.literal("guided"),
|
|
28046
28930
|
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.'),
|
|
28047
|
-
buyerPersona:
|
|
28931
|
+
buyerPersona: z115.string().trim().min(1).max(360),
|
|
28048
28932
|
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.'),
|
|
28049
28933
|
presenter: VideoPresenter,
|
|
28050
|
-
creativeDirection:
|
|
28934
|
+
creativeDirection: z115.string().trim().min(1).max(500).nullable().describe("Direcci\xF3n visual elegida. null significa que el usuario deleg\xF3 expl\xEDcitamente esta decisi\xF3n.")
|
|
28051
28935
|
}).strict()
|
|
28052
28936
|
]);
|
|
28053
|
-
var VideoInput =
|
|
28937
|
+
var VideoInput = z115.object({
|
|
28054
28938
|
images: CreativeRefs.images.optional(),
|
|
28055
28939
|
winnerReference: WinnerReferenceInput.optional().describe(WINNER_IMAGE_CHOICE_GUIDANCE),
|
|
28056
|
-
title:
|
|
28057
|
-
primaryAngle:
|
|
28940
|
+
title: z115.string().min(1).describe('T\xEDtulo corto del producto. Aparece en la primera vista del video. Ej. "Crema Anti-Edad Premium".'),
|
|
28941
|
+
primaryAngle: z115.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".'),
|
|
28058
28942
|
language: CreativeRefs.language,
|
|
28059
28943
|
country: CreativeRefs.country,
|
|
28060
|
-
aspectRatio:
|
|
28944
|
+
aspectRatio: z115.enum([
|
|
28061
28945
|
"9:16",
|
|
28062
28946
|
"16:9",
|
|
28063
28947
|
"Auto"
|
|
28064
28948
|
]).describe('Relaci\xF3n de aspecto. "9:16" = vertical (TikTok/Reels \u2014 default recomendado para ads). "16:9" = horizontal (YouTube). "Auto" = decide la IA.'),
|
|
28065
28949
|
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.'),
|
|
28066
|
-
durationSeconds:
|
|
28067
|
-
videoAngleCount:
|
|
28950
|
+
durationSeconds: z115.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."),
|
|
28951
|
+
videoAngleCount: z115.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."),
|
|
28068
28952
|
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.'),
|
|
28069
28953
|
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."),
|
|
28070
28954
|
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.'),
|
|
28071
28955
|
videoQuality: VideoQualityEnum.default("economy").describe('Opcional. Calidad: "economy" = Econ\xF3mico, "medium" = Medio o "pro" = Pro. El sistema elige el proveedor internamente. Default: economy.'),
|
|
28072
28956
|
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.'),
|
|
28073
|
-
features:
|
|
28957
|
+
features: z115.array(z115.string()).optional().describe('Opcional. Lista de features/beneficios clave a mencionar. Ej. ["resistente al agua", "bater\xEDa 24h", "carga inal\xE1mbrica"].'),
|
|
28074
28958
|
price: CreativeRefs.price,
|
|
28075
28959
|
offer: CreativeRefs.offer,
|
|
28076
28960
|
productContext: CreativeRefs.productContext,
|
|
28077
28961
|
productLabel: CreativeRefs.productLabel,
|
|
28078
|
-
cta:
|
|
28079
|
-
scriptNotes:
|
|
28962
|
+
cta: z115.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.'),
|
|
28963
|
+
scriptNotes: z115.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.'),
|
|
28080
28964
|
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.'),
|
|
28081
28965
|
competitorScanEnabled: CreativeRefs.competitorScanEnabled,
|
|
28082
|
-
replicateWinnerWhenFound:
|
|
28966
|
+
replicateWinnerWhenFound: z115.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."),
|
|
28083
28967
|
brandId: CreativeRefs.brandId
|
|
28084
28968
|
}).strict();
|
|
28085
28969
|
var FreeVideoQualityEnum = VideoQualityEnum;
|
|
@@ -28097,7 +28981,7 @@ var FREE_VIDEO_DURATIONS_BY_QUALITY = {
|
|
|
28097
28981
|
length: 29
|
|
28098
28982
|
}, (_, index) => index + 2)
|
|
28099
28983
|
};
|
|
28100
|
-
var FreeVideoModelIdEnum =
|
|
28984
|
+
var FreeVideoModelIdEnum = z115.enum([
|
|
28101
28985
|
"grok-imagine-video-1-5-preview",
|
|
28102
28986
|
"minimax-h3/reference-to-video",
|
|
28103
28987
|
"wan/3-0-video",
|
|
@@ -28108,66 +28992,66 @@ var FREE_SELECTABLE_MODEL_IDS = [
|
|
|
28108
28992
|
"minimax-h3/reference-to-video",
|
|
28109
28993
|
"wan/3-0-video"
|
|
28110
28994
|
];
|
|
28111
|
-
var FreeVideoInput =
|
|
28995
|
+
var FreeVideoInput = z115.object({
|
|
28112
28996
|
images: ImagesInput.optional().describe("Opcional. 1-5 fotos del producto. Om\xEDtelo para generar desde texto puro."),
|
|
28113
|
-
rawPrompt:
|
|
28997
|
+
rawPrompt: z115.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."),
|
|
28114
28998
|
language: CreativeRefs.language,
|
|
28115
28999
|
quality: FreeVideoQualityEnum.describe('Calidad del video: "economy" = Econ\xF3mico, "medium" = Medio o "pro" = Pro (Wan).'),
|
|
28116
29000
|
// Retired in MCP 7.0.0 (Task 11): kept in the schema, accepted, and
|
|
28117
29001
|
// parsed ONLY so the handler can reject it with a message naming its
|
|
28118
29002
|
// replacement — dropping it silently would turn a caller's explicit
|
|
28119
29003
|
// Pro++ pick into a quiet fallback to Wan.
|
|
28120
|
-
freeProModel:
|
|
29004
|
+
freeProModel: z115.enum([
|
|
28121
29005
|
"wan",
|
|
28122
29006
|
"seedance"
|
|
28123
29007
|
]).optional().describe("ELIMINADO en MCP 7.0.0 \u2014 usa `modelId`. Enviar este campo produce un error."),
|
|
28124
29008
|
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.'),
|
|
28125
|
-
durationSeconds:
|
|
29009
|
+
durationSeconds: z115.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."),
|
|
28126
29010
|
videoResolution: VideoResolutionEnum.describe("Resoluci\xF3n del video: 480p o 720p."),
|
|
28127
|
-
generateAiMusic:
|
|
29011
|
+
generateAiMusic: z115.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.'),
|
|
28128
29012
|
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.'),
|
|
28129
|
-
addSubtitles:
|
|
28130
|
-
idempotencyKey:
|
|
29013
|
+
addSubtitles: z115.boolean().default(false).describe("Opcional. Quema subt\xEDtulos usando el rawPrompt exacto. Default false."),
|
|
29014
|
+
idempotencyKey: z115.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.")
|
|
28131
29015
|
}).strict();
|
|
28132
29016
|
var imageStyleList = ImageCreativeStyleEnum.options.join(" | ");
|
|
28133
|
-
var ImageCreativeSetup =
|
|
28134
|
-
|
|
28135
|
-
mode:
|
|
29017
|
+
var ImageCreativeSetup = z115.discriminatedUnion("mode", [
|
|
29018
|
+
z115.object({
|
|
29019
|
+
mode: z115.literal("automatic")
|
|
28136
29020
|
}).strict(),
|
|
28137
|
-
|
|
28138
|
-
mode:
|
|
28139
|
-
creativeArchetype:
|
|
29021
|
+
z115.object({
|
|
29022
|
+
mode: z115.literal("guided"),
|
|
29023
|
+
creativeArchetype: z115.union([
|
|
28140
29024
|
ImageCreativeArchetypeEnum,
|
|
28141
|
-
|
|
29025
|
+
z115.literal("ai_decides")
|
|
28142
29026
|
]),
|
|
28143
|
-
visualTreatment:
|
|
29027
|
+
visualTreatment: z115.union([
|
|
28144
29028
|
ImageCreativeVisualTreatmentEnum,
|
|
28145
|
-
|
|
29029
|
+
z115.literal("ai_decides")
|
|
28146
29030
|
]),
|
|
28147
29031
|
aspectRatio: ImageCreativeAspectRatioEnum,
|
|
28148
|
-
nVariants:
|
|
28149
|
-
|
|
28150
|
-
|
|
28151
|
-
|
|
28152
|
-
|
|
28153
|
-
|
|
29032
|
+
nVariants: z115.union([
|
|
29033
|
+
z115.literal(1),
|
|
29034
|
+
z115.literal(2),
|
|
29035
|
+
z115.literal(3),
|
|
29036
|
+
z115.literal(4),
|
|
29037
|
+
z115.literal(6)
|
|
28154
29038
|
]),
|
|
28155
|
-
imageModel:
|
|
28156
|
-
styleMix:
|
|
28157
|
-
generateAdCopy:
|
|
28158
|
-
competitorScanEnabled:
|
|
28159
|
-
productContext:
|
|
28160
|
-
creativeDirection:
|
|
28161
|
-
brandColors:
|
|
28162
|
-
headline:
|
|
28163
|
-
subheadline:
|
|
29039
|
+
imageModel: z115.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.'),
|
|
29040
|
+
styleMix: z115.boolean().nullable().describe("Decisi\xF3n s\xF3lo para 2\u20134 variantes. Usa null para 1 o 6, cuando la UI no hace esta pregunta."),
|
|
29041
|
+
generateAdCopy: z115.boolean(),
|
|
29042
|
+
competitorScanEnabled: z115.boolean(),
|
|
29043
|
+
productContext: z115.string().trim().min(1).max(3e3).nullable(),
|
|
29044
|
+
creativeDirection: z115.string().trim().min(1).max(500).nullable(),
|
|
29045
|
+
brandColors: z115.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.'),
|
|
29046
|
+
headline: z115.string().trim().min(1).max(120).nullable(),
|
|
29047
|
+
subheadline: z115.string().trim().min(1).max(160).nullable()
|
|
28164
29048
|
}).strict()
|
|
28165
29049
|
]).superRefine((setup, context) => {
|
|
28166
29050
|
if (setup.mode !== "guided") return;
|
|
28167
29051
|
const needsStyleMix = setup.nVariants >= 2 && setup.nVariants <= 4;
|
|
28168
29052
|
if (needsStyleMix && setup.styleMix === null) {
|
|
28169
29053
|
context.addIssue({
|
|
28170
|
-
code:
|
|
29054
|
+
code: z115.ZodIssueCode.custom,
|
|
28171
29055
|
path: [
|
|
28172
29056
|
"styleMix"
|
|
28173
29057
|
],
|
|
@@ -28176,7 +29060,7 @@ var ImageCreativeSetup = z112.discriminatedUnion("mode", [
|
|
|
28176
29060
|
}
|
|
28177
29061
|
if (!needsStyleMix && setup.styleMix !== null) {
|
|
28178
29062
|
context.addIssue({
|
|
28179
|
-
code:
|
|
29063
|
+
code: z115.ZodIssueCode.custom,
|
|
28180
29064
|
path: [
|
|
28181
29065
|
"styleMix"
|
|
28182
29066
|
],
|
|
@@ -28185,7 +29069,7 @@ var ImageCreativeSetup = z112.discriminatedUnion("mode", [
|
|
|
28185
29069
|
}
|
|
28186
29070
|
if (imageResolutionOf(setup.imageModel) === "2k" && setup.aspectRatio === "4:5") {
|
|
28187
29071
|
context.addIssue({
|
|
28188
|
-
code:
|
|
29072
|
+
code: z115.ZodIssueCode.custom,
|
|
28189
29073
|
path: [
|
|
28190
29074
|
"aspectRatio"
|
|
28191
29075
|
],
|
|
@@ -28193,55 +29077,55 @@ var ImageCreativeSetup = z112.discriminatedUnion("mode", [
|
|
|
28193
29077
|
});
|
|
28194
29078
|
}
|
|
28195
29079
|
});
|
|
28196
|
-
var LandingCreativeSetup =
|
|
28197
|
-
|
|
28198
|
-
mode:
|
|
29080
|
+
var LandingCreativeSetup = z115.discriminatedUnion("mode", [
|
|
29081
|
+
z115.object({
|
|
29082
|
+
mode: z115.literal("automatic")
|
|
28199
29083
|
}).strict(),
|
|
28200
|
-
|
|
28201
|
-
mode:
|
|
29084
|
+
z115.object({
|
|
29085
|
+
mode: z115.literal("guided"),
|
|
28202
29086
|
landingStrategy: LandingStrategyEnum,
|
|
28203
29087
|
landingImageFormat: LandingImageFormatEnum,
|
|
28204
|
-
competitorScanEnabled:
|
|
28205
|
-
paymentMethods:
|
|
28206
|
-
productContext:
|
|
28207
|
-
creativeDirection:
|
|
28208
|
-
headline:
|
|
28209
|
-
subheadline:
|
|
29088
|
+
competitorScanEnabled: z115.boolean(),
|
|
29089
|
+
paymentMethods: z115.string().trim().min(1).max(240).nullable(),
|
|
29090
|
+
productContext: z115.string().trim().min(1).max(3e3).nullable(),
|
|
29091
|
+
creativeDirection: z115.string().trim().min(1).max(500).nullable(),
|
|
29092
|
+
headline: z115.string().trim().min(1).max(120).nullable(),
|
|
29093
|
+
subheadline: z115.string().trim().min(1).max(160).nullable()
|
|
28210
29094
|
}).strict()
|
|
28211
29095
|
]);
|
|
28212
|
-
var ImageInput =
|
|
29096
|
+
var ImageInput = z115.object({
|
|
28213
29097
|
images: CreativeRefs.images.optional(),
|
|
28214
29098
|
winnerReference: WinnerReferenceInput.optional().describe(WINNER_IMAGE_CHOICE_GUIDANCE),
|
|
28215
|
-
productTitle:
|
|
28216
|
-
primaryAngle:
|
|
29099
|
+
productTitle: z115.string().min(1).describe('Nombre del producto. Ej. "Crema Anti-Edad Premium".'),
|
|
29100
|
+
primaryAngle: z115.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.'),
|
|
28217
29101
|
buyerPersona: CreativeRefs.buyerPersona,
|
|
28218
29102
|
language: CreativeRefs.language,
|
|
28219
29103
|
country: CreativeRefs.country,
|
|
28220
29104
|
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."),
|
|
28221
29105
|
imageStyle: ImageCreativeStyleEnum.optional().describe(`Opcional. Estilo del shot del producto. Valores: ${imageStyleList}. Si se omite, la IA escoge seg\xFAn el archetype.`),
|
|
28222
|
-
features:
|
|
29106
|
+
features: z115.array(z115.string()).optional().describe('Opcional. Features/beneficios a destacar visualmente. Ej. ["resistente al agua", "ultra liviano"].'),
|
|
28223
29107
|
price: CreativeRefs.price,
|
|
28224
|
-
omitPrice:
|
|
29108
|
+
omitPrice: z115.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."),
|
|
28225
29109
|
compareAtPrice: CreativeRefs.compareAtPrice,
|
|
28226
29110
|
offer: CreativeRefs.offer,
|
|
28227
29111
|
productLabel: CreativeRefs.productLabel,
|
|
28228
29112
|
isCombo: CreativeRefs.isCombo,
|
|
28229
29113
|
brandId: CreativeRefs.brandId
|
|
28230
29114
|
});
|
|
28231
|
-
var EditImageCreativeInput =
|
|
28232
|
-
taskId:
|
|
28233
|
-
variantIndex:
|
|
28234
|
-
clientRequestId:
|
|
28235
|
-
instruction:
|
|
29115
|
+
var EditImageCreativeInput = z115.object({
|
|
29116
|
+
taskId: z115.string().uuid().describe("ID de la tarea de imagen ya finalizada."),
|
|
29117
|
+
variantIndex: z115.number().int().min(0).max(10).describe("\xCDndice de la variante a corregir (0 = primera imagen)."),
|
|
29118
|
+
clientRequestId: z115.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."),
|
|
29119
|
+
instruction: z115.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".')
|
|
28236
29120
|
});
|
|
28237
|
-
var LandingInput =
|
|
28238
|
-
includeInitialLandingGif:
|
|
29121
|
+
var LandingInput = z115.object({
|
|
29122
|
+
includeInitialLandingGif: z115.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."),
|
|
28239
29123
|
images: CreativeRefs.images.optional(),
|
|
28240
29124
|
winnerReference: WinnerReferenceInput.optional().describe(WINNER_IMAGE_CHOICE_GUIDANCE),
|
|
28241
29125
|
language: CreativeRefs.language,
|
|
28242
29126
|
country: CreativeRefs.country,
|
|
28243
29127
|
creativeSetup: LandingCreativeSetup.describe("Configuraci\xF3n obligatoria de landing cl\xE1sica equivalente al wizard web. No expone landing editable/code mientras siga siendo admin-only."),
|
|
28244
|
-
salesAngle:
|
|
29128
|
+
salesAngle: z115.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.'),
|
|
28245
29129
|
buyerPersona: CreativeRefs.buyerPersona,
|
|
28246
29130
|
price: CreativeRefs.price,
|
|
28247
29131
|
compareAtPrice: CreativeRefs.compareAtPrice,
|
|
@@ -28523,8 +29407,8 @@ function registerCreativeTools(server, api, { transport = "http" } = {}) {
|
|
|
28523
29407
|
server.registerTool("get_image_edit_status", {
|
|
28524
29408
|
description: imageEditStatusGuidance.summary,
|
|
28525
29409
|
inputSchema: {
|
|
28526
|
-
taskId:
|
|
28527
|
-
operationId:
|
|
29410
|
+
taskId: z115.string().uuid(),
|
|
29411
|
+
operationId: z115.string().uuid()
|
|
28528
29412
|
},
|
|
28529
29413
|
annotations: READS_INTERNAL
|
|
28530
29414
|
}, async (input) => {
|
|
@@ -28581,10 +29465,10 @@ function registerCreativeTools(server, api, { transport = "http" } = {}) {
|
|
|
28581
29465
|
__name(registerCreativeTools, "registerCreativeTools");
|
|
28582
29466
|
|
|
28583
29467
|
// src/tools/creative-variants.ts
|
|
28584
|
-
import { z as
|
|
29468
|
+
import { z as z116 } from "zod";
|
|
28585
29469
|
var estimateCreativeVariantGuidance = getProductMakerCapabilityModelGuidance("estimate_creative_variant");
|
|
28586
29470
|
var createCreativeVariantGuidance = getProductMakerCapabilityModelGuidance("create_creative_variant");
|
|
28587
|
-
var LandingSectionKeyEnum =
|
|
29471
|
+
var LandingSectionKeyEnum = z116.enum([
|
|
28588
29472
|
"problem_hook",
|
|
28589
29473
|
"problem_cost",
|
|
28590
29474
|
"solution_mechanism",
|
|
@@ -28624,24 +29508,24 @@ var LandingSectionKeyEnum = z113.enum([
|
|
|
28624
29508
|
"lifestyle_fit",
|
|
28625
29509
|
"join_lifestyle"
|
|
28626
29510
|
]);
|
|
28627
|
-
var CreativeVariantKindEnum =
|
|
29511
|
+
var CreativeVariantKindEnum = z116.enum([
|
|
28628
29512
|
"video",
|
|
28629
29513
|
"image",
|
|
28630
29514
|
"landing"
|
|
28631
29515
|
]);
|
|
28632
|
-
var EstimateCreativeVariantInputBase =
|
|
28633
|
-
sourceTaskId:
|
|
29516
|
+
var EstimateCreativeVariantInputBase = z116.object({
|
|
29517
|
+
sourceTaskId: z116.string().uuid().describe("ID de la tarea original completada."),
|
|
28634
29518
|
kind: CreativeVariantKindEnum,
|
|
28635
29519
|
videoPreset: TaskCreativeVariantVideoPresetEnum.optional().describe("Opcional para video. Solo cambia el preset p\xFAblico; el servidor conserva el modelo/tier del task origen."),
|
|
28636
29520
|
videoResolution: VideoResolutionEnum.optional().describe("Opcional para video. Resoluci\xF3n del nuevo render."),
|
|
28637
|
-
nVariants:
|
|
29521
|
+
nVariants: z116.number().int().min(1).max(3).optional().describe("Opcional para imagen. N\xFAmero de im\xE1genes adicionales (1-3)."),
|
|
28638
29522
|
sectionKey: LandingSectionKeyEnum.optional().describe("Opcional para landing. Si ya conoces la secci\xF3n, evita cotizar la clasificaci\xF3n con IA."),
|
|
28639
|
-
instructions:
|
|
29523
|
+
instructions: z116.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.")
|
|
28640
29524
|
}).strict();
|
|
28641
29525
|
var EstimateCreativeVariantInput = EstimateCreativeVariantInputBase.superRefine((input, ctx) => {
|
|
28642
29526
|
if (input.kind === "video" && input.nVariants !== void 0) {
|
|
28643
29527
|
ctx.addIssue({
|
|
28644
|
-
code:
|
|
29528
|
+
code: z116.ZodIssueCode.custom,
|
|
28645
29529
|
path: [
|
|
28646
29530
|
"nVariants"
|
|
28647
29531
|
],
|
|
@@ -28651,7 +29535,7 @@ var EstimateCreativeVariantInput = EstimateCreativeVariantInputBase.superRefine(
|
|
|
28651
29535
|
if (input.kind !== "video") {
|
|
28652
29536
|
if (input.videoPreset !== void 0) {
|
|
28653
29537
|
ctx.addIssue({
|
|
28654
|
-
code:
|
|
29538
|
+
code: z116.ZodIssueCode.custom,
|
|
28655
29539
|
path: [
|
|
28656
29540
|
"videoPreset"
|
|
28657
29541
|
],
|
|
@@ -28660,7 +29544,7 @@ var EstimateCreativeVariantInput = EstimateCreativeVariantInputBase.superRefine(
|
|
|
28660
29544
|
}
|
|
28661
29545
|
if (input.videoResolution !== void 0) {
|
|
28662
29546
|
ctx.addIssue({
|
|
28663
|
-
code:
|
|
29547
|
+
code: z116.ZodIssueCode.custom,
|
|
28664
29548
|
path: [
|
|
28665
29549
|
"videoResolution"
|
|
28666
29550
|
],
|
|
@@ -28670,7 +29554,7 @@ var EstimateCreativeVariantInput = EstimateCreativeVariantInputBase.superRefine(
|
|
|
28670
29554
|
}
|
|
28671
29555
|
if (input.kind !== "image" && input.nVariants !== void 0) {
|
|
28672
29556
|
ctx.addIssue({
|
|
28673
|
-
code:
|
|
29557
|
+
code: z116.ZodIssueCode.custom,
|
|
28674
29558
|
path: [
|
|
28675
29559
|
"nVariants"
|
|
28676
29560
|
],
|
|
@@ -28680,7 +29564,7 @@ var EstimateCreativeVariantInput = EstimateCreativeVariantInputBase.superRefine(
|
|
|
28680
29564
|
if (input.kind !== "landing") {
|
|
28681
29565
|
if (input.sectionKey !== void 0) {
|
|
28682
29566
|
ctx.addIssue({
|
|
28683
|
-
code:
|
|
29567
|
+
code: z116.ZodIssueCode.custom,
|
|
28684
29568
|
path: [
|
|
28685
29569
|
"sectionKey"
|
|
28686
29570
|
],
|
|
@@ -28689,7 +29573,7 @@ var EstimateCreativeVariantInput = EstimateCreativeVariantInputBase.superRefine(
|
|
|
28689
29573
|
}
|
|
28690
29574
|
if (input.instructions !== void 0) {
|
|
28691
29575
|
ctx.addIssue({
|
|
28692
|
-
code:
|
|
29576
|
+
code: z116.ZodIssueCode.custom,
|
|
28693
29577
|
path: [
|
|
28694
29578
|
"instructions"
|
|
28695
29579
|
],
|
|
@@ -28698,31 +29582,31 @@ var EstimateCreativeVariantInput = EstimateCreativeVariantInputBase.superRefine(
|
|
|
28698
29582
|
}
|
|
28699
29583
|
}
|
|
28700
29584
|
});
|
|
28701
|
-
var CreateCreativeVariantInputBase =
|
|
28702
|
-
sourceTaskId:
|
|
29585
|
+
var CreateCreativeVariantInputBase = z116.object({
|
|
29586
|
+
sourceTaskId: z116.string().uuid().describe("ID de la tarea original completada."),
|
|
28703
29587
|
kind: CreativeVariantKindEnum,
|
|
28704
|
-
confirm:
|
|
28705
|
-
clientRequestId:
|
|
28706
|
-
angle:
|
|
28707
|
-
buyerPersona:
|
|
28708
|
-
scriptNotes:
|
|
28709
|
-
creativeDirection:
|
|
29588
|
+
confirm: z116.literal(true).describe("Confirmaci\xF3n expl\xEDcita del usuario despu\xE9s de mostrarle el estimado de cr\xE9ditos."),
|
|
29589
|
+
clientRequestId: z116.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."),
|
|
29590
|
+
angle: z116.string().trim().min(1).max(500).optional().describe("\xC1ngulo nuevo para video o imagen."),
|
|
29591
|
+
buyerPersona: z116.string().trim().min(1).max(360).optional().describe("Buyer persona coherente con el \xE1ngulo."),
|
|
29592
|
+
scriptNotes: z116.string().trim().min(1).max(2e3).optional().describe("Direcci\xF3n breve del guion para una variante de video."),
|
|
29593
|
+
creativeDirection: z116.string().trim().min(1).max(2e3).optional().describe("Direcci\xF3n creativa para una variante de imagen."),
|
|
28710
29594
|
videoPreset: TaskCreativeVariantVideoPresetEnum.optional().describe("Opcional para video. Preset p\xFAblico; el servidor resuelve modelo/tier desde el task origen."),
|
|
28711
29595
|
videoResolution: VideoResolutionEnum.optional().describe("Opcional para video."),
|
|
28712
|
-
generateAiMusic:
|
|
28713
|
-
nVariants:
|
|
29596
|
+
generateAiMusic: z116.boolean().optional().describe("Opcional para video."),
|
|
29597
|
+
nVariants: z116.number().int().min(1).max(3).optional().describe("Opcional para imagen. N\xFAmero de im\xE1genes adicionales (1-3)."),
|
|
28714
29598
|
imageStyle: ImageCreativeStyleEnum.optional().describe("Opcional para imagen."),
|
|
28715
29599
|
creativeArchetype: ImageCreativeArchetypeEnum.optional().describe("Opcional para imagen."),
|
|
28716
29600
|
visualTreatment: ImageCreativeVisualTreatmentEnum.optional().describe("Opcional para imagen."),
|
|
28717
29601
|
aspectRatio: ImageCreativeAspectRatioEnum.optional().describe("Opcional para imagen."),
|
|
28718
|
-
styleMix:
|
|
29602
|
+
styleMix: z116.boolean().optional().describe("Opcional para imagen."),
|
|
28719
29603
|
sectionKey: LandingSectionKeyEnum.optional().describe("Opcional para landing. Si se omite, se clasifica con IA a partir de instructions."),
|
|
28720
|
-
instructions:
|
|
29604
|
+
instructions: z116.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.")
|
|
28721
29605
|
}).strict();
|
|
28722
29606
|
var CreateCreativeVariantInput = CreateCreativeVariantInputBase.superRefine((input, ctx) => {
|
|
28723
29607
|
if ((input.kind === "video" || input.kind === "image") && !input.angle) {
|
|
28724
29608
|
ctx.addIssue({
|
|
28725
|
-
code:
|
|
29609
|
+
code: z116.ZodIssueCode.custom,
|
|
28726
29610
|
path: [
|
|
28727
29611
|
"angle"
|
|
28728
29612
|
],
|
|
@@ -28731,7 +29615,7 @@ var CreateCreativeVariantInput = CreateCreativeVariantInputBase.superRefine((inp
|
|
|
28731
29615
|
}
|
|
28732
29616
|
if (input.kind === "landing" && !input.sectionKey && !input.instructions) {
|
|
28733
29617
|
ctx.addIssue({
|
|
28734
|
-
code:
|
|
29618
|
+
code: z116.ZodIssueCode.custom,
|
|
28735
29619
|
path: [
|
|
28736
29620
|
"instructions"
|
|
28737
29621
|
],
|
|
@@ -28777,7 +29661,7 @@ var CreateCreativeVariantInput = CreateCreativeVariantInputBase.superRefine((inp
|
|
|
28777
29661
|
for (const field of fieldsByKind[input.kind]) {
|
|
28778
29662
|
if (input[field] !== void 0) {
|
|
28779
29663
|
ctx.addIssue({
|
|
28780
|
-
code:
|
|
29664
|
+
code: z116.ZodIssueCode.custom,
|
|
28781
29665
|
path: [
|
|
28782
29666
|
field
|
|
28783
29667
|
],
|
|
@@ -28910,13 +29794,13 @@ function registerCreativeVariantTools(server, api) {
|
|
|
28910
29794
|
__name(registerCreativeVariantTools, "registerCreativeVariantTools");
|
|
28911
29795
|
|
|
28912
29796
|
// src/tools/script-review.ts
|
|
28913
|
-
import { z as
|
|
29797
|
+
import { z as z117 } from "zod";
|
|
28914
29798
|
var getVideoScriptGuidance = getProductMakerCapabilityModelGuidance("get_video_script");
|
|
28915
29799
|
var regenerateVideoScriptGuidance = getProductMakerCapabilityModelGuidance("regenerate_video_script");
|
|
28916
29800
|
var approveVideoScriptGuidance = getProductMakerCapabilityModelGuidance("approve_video_script");
|
|
28917
|
-
var GetVideoScriptInput =
|
|
28918
|
-
taskId:
|
|
28919
|
-
angleKey:
|
|
29801
|
+
var GetVideoScriptInput = z117.object({
|
|
29802
|
+
taskId: z117.string().min(1).describe("ID de la tarea (la misma que devolvi\xF3 create_product_task / generate_video_creative)."),
|
|
29803
|
+
angleKey: z117.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.')
|
|
28920
29804
|
}).strict();
|
|
28921
29805
|
var ApproveVideoScriptInput = ApproveVideoScriptCapabilityRequest;
|
|
28922
29806
|
var RegenerateVideoScriptInput = RegenerateVideoScriptCapabilityRequest.extend({
|
|
@@ -28999,7 +29883,7 @@ function registerScriptReviewTools(server, api) {
|
|
|
28999
29883
|
__name(registerScriptReviewTools, "registerScriptReviewTools");
|
|
29000
29884
|
|
|
29001
29885
|
// src/tools/connections.ts
|
|
29002
|
-
import { z as
|
|
29886
|
+
import { z as z118 } from "zod";
|
|
29003
29887
|
var listShopifyShopsGuidance = getProductMakerCapabilityModelGuidance("list_shopify_shops");
|
|
29004
29888
|
var listShopifyProductsGuidance = getProductMakerCapabilityModelGuidance("list_shopify_products");
|
|
29005
29889
|
var listMetaAdAccountsGuidance = getProductMakerCapabilityModelGuidance("list_meta_ad_accounts");
|
|
@@ -29008,7 +29892,7 @@ var listTikTokAdvertisersGuidance = getProductMakerCapabilityModelGuidance("list
|
|
|
29008
29892
|
function registerConnectionTools(server, api) {
|
|
29009
29893
|
server.registerTool("list_shopify_shops", {
|
|
29010
29894
|
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.",
|
|
29011
|
-
inputSchema:
|
|
29895
|
+
inputSchema: z118.object({}).shape,
|
|
29012
29896
|
annotations: READS_INTERNAL
|
|
29013
29897
|
}, async () => {
|
|
29014
29898
|
try {
|
|
@@ -29020,10 +29904,10 @@ function registerConnectionTools(server, api) {
|
|
|
29020
29904
|
});
|
|
29021
29905
|
server.registerTool("list_shopify_products", {
|
|
29022
29906
|
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.',
|
|
29023
|
-
inputSchema:
|
|
29024
|
-
shopDomain:
|
|
29025
|
-
query:
|
|
29026
|
-
taskId:
|
|
29907
|
+
inputSchema: z118.object({
|
|
29908
|
+
shopDomain: z118.string().min(3).max(120),
|
|
29909
|
+
query: z118.string().trim().max(120).optional(),
|
|
29910
|
+
taskId: z118.string().uuid().optional()
|
|
29027
29911
|
}).shape,
|
|
29028
29912
|
annotations: READS_EXTERNAL
|
|
29029
29913
|
}, async ({ shopDomain, query, taskId }) => {
|
|
@@ -29041,7 +29925,7 @@ function registerConnectionTools(server, api) {
|
|
|
29041
29925
|
});
|
|
29042
29926
|
server.registerTool("list_meta_ad_accounts", {
|
|
29043
29927
|
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.",
|
|
29044
|
-
inputSchema:
|
|
29928
|
+
inputSchema: z118.object({}).shape,
|
|
29045
29929
|
annotations: READS_EXTERNAL
|
|
29046
29930
|
}, async () => {
|
|
29047
29931
|
try {
|
|
@@ -29053,7 +29937,7 @@ function registerConnectionTools(server, api) {
|
|
|
29053
29937
|
});
|
|
29054
29938
|
server.registerTool("list_meta_destinations", {
|
|
29055
29939
|
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.",
|
|
29056
|
-
inputSchema:
|
|
29940
|
+
inputSchema: z118.object({}).shape,
|
|
29057
29941
|
annotations: READS_EXTERNAL,
|
|
29058
29942
|
_meta: {
|
|
29059
29943
|
"openai/widgetAccessible": true
|
|
@@ -29068,7 +29952,7 @@ function registerConnectionTools(server, api) {
|
|
|
29068
29952
|
});
|
|
29069
29953
|
server.registerTool("list_tiktok_advertisers", {
|
|
29070
29954
|
description: listTikTokAdvertisersGuidance.summary + " Si est\xE1 vac\xEDa, ind\xEDcale al usuario conectar TikTok en productmaker.app/integrations.",
|
|
29071
|
-
inputSchema:
|
|
29955
|
+
inputSchema: z118.object({}).shape,
|
|
29072
29956
|
annotations: READS_EXTERNAL
|
|
29073
29957
|
}, async () => {
|
|
29074
29958
|
try {
|
|
@@ -29082,7 +29966,7 @@ function registerConnectionTools(server, api) {
|
|
|
29082
29966
|
__name(registerConnectionTools, "registerConnectionTools");
|
|
29083
29967
|
|
|
29084
29968
|
// src/tools/actors.ts
|
|
29085
|
-
import { z as
|
|
29969
|
+
import { z as z119 } from "zod";
|
|
29086
29970
|
var listActorsGuidance = getProductMakerCapabilityModelGuidance("list_actors");
|
|
29087
29971
|
var actorVoiceGuidance = {
|
|
29088
29972
|
capabilities: getProductMakerCapabilityModelGuidance("get_actor_voice_capabilities"),
|
|
@@ -29122,7 +30006,7 @@ function sanitizeVoiceResult(value) {
|
|
|
29122
30006
|
__name(sanitizeVoiceResult, "sanitizeVoiceResult");
|
|
29123
30007
|
function registerActorTools(server, api) {
|
|
29124
30008
|
const actor = {
|
|
29125
|
-
actorId:
|
|
30009
|
+
actorId: z119.string().min(1).max(64)
|
|
29126
30010
|
};
|
|
29127
30011
|
const safe = /* @__PURE__ */ __name(async (request) => {
|
|
29128
30012
|
try {
|
|
@@ -29151,8 +30035,8 @@ function registerActorTools(server, api) {
|
|
|
29151
30035
|
inputSchema: {
|
|
29152
30036
|
...actor,
|
|
29153
30037
|
draft: ActorVoiceDraftSchema,
|
|
29154
|
-
requestKey:
|
|
29155
|
-
acceptedQuoteId:
|
|
30038
|
+
requestKey: z119.string().trim().min(1).max(128),
|
|
30039
|
+
acceptedQuoteId: z119.string().trim().min(1).max(128)
|
|
29156
30040
|
},
|
|
29157
30041
|
annotations: CREATES_PAID
|
|
29158
30042
|
}, async ({ actorId, ...body }) => safe(api.postJson(`/v1/actors/${encodeURIComponent(actorId)}/voice/previews`, body)));
|
|
@@ -29160,7 +30044,7 @@ function registerActorTools(server, api) {
|
|
|
29160
30044
|
description: actorVoiceGuidance.get.summary,
|
|
29161
30045
|
inputSchema: {
|
|
29162
30046
|
...actor,
|
|
29163
|
-
candidateId:
|
|
30047
|
+
candidateId: z119.string().min(1).max(64)
|
|
29164
30048
|
},
|
|
29165
30049
|
annotations: READS_INTERNAL
|
|
29166
30050
|
}, async ({ actorId, candidateId }) => safe(api.getJson(`/v1/actors/${encodeURIComponent(actorId)}/voice/previews/${encodeURIComponent(candidateId)}`)));
|
|
@@ -29168,15 +30052,15 @@ function registerActorTools(server, api) {
|
|
|
29168
30052
|
description: actorVoiceGuidance.approve.summary,
|
|
29169
30053
|
inputSchema: {
|
|
29170
30054
|
...actor,
|
|
29171
|
-
candidateId:
|
|
29172
|
-
expectedRevision:
|
|
29173
|
-
expectedApprovedRevision:
|
|
30055
|
+
candidateId: z119.string().min(1).max(64),
|
|
30056
|
+
expectedRevision: z119.number().int().positive(),
|
|
30057
|
+
expectedApprovedRevision: z119.number().int().positive().nullable().optional()
|
|
29174
30058
|
},
|
|
29175
30059
|
annotations: CREATES_INTERNAL
|
|
29176
30060
|
}, async ({ actorId, ...body }) => safe(api.postJson(`/v1/actors/${encodeURIComponent(actorId)}/voice/approve`, body)));
|
|
29177
30061
|
server.registerTool("list_actors", {
|
|
29178
30062
|
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.",
|
|
29179
|
-
inputSchema:
|
|
30063
|
+
inputSchema: z119.object({}).shape,
|
|
29180
30064
|
annotations: READS_INTERNAL
|
|
29181
30065
|
}, async () => {
|
|
29182
30066
|
try {
|
|
@@ -29209,7 +30093,7 @@ function registerActorTools(server, api) {
|
|
|
29209
30093
|
__name(registerActorTools, "registerActorTools");
|
|
29210
30094
|
|
|
29211
30095
|
// src/tools/publish.ts
|
|
29212
|
-
import { z as
|
|
30096
|
+
import { z as z120 } from "zod";
|
|
29213
30097
|
|
|
29214
30098
|
// src/tools/meta-publish-attempt-id.ts
|
|
29215
30099
|
var META_PUBLISH_NAMESPACE = "7b19c9ac-b4eb-4db1-970e-c2f24ce30b98";
|
|
@@ -29248,40 +30132,40 @@ var publishToShopifyGuidance = getProductMakerCapabilityModelGuidance("publish_t
|
|
|
29248
30132
|
var publishToMetaGuidance = getProductMakerCapabilityModelGuidance("publish_to_meta");
|
|
29249
30133
|
var publishCreativesToMetaGuidance = getProductMakerCapabilityModelGuidance("publish_creatives_to_meta");
|
|
29250
30134
|
var publishToTikTokGuidance = getProductMakerCapabilityModelGuidance("publish_to_tiktok");
|
|
29251
|
-
var ShopifyInput =
|
|
29252
|
-
taskId:
|
|
29253
|
-
shopDomain:
|
|
30135
|
+
var ShopifyInput = z120.object({
|
|
30136
|
+
taskId: z120.string().min(1),
|
|
30137
|
+
shopDomain: z120.string().min(1),
|
|
29254
30138
|
/** Override: number of landing images to include. Default = ALL images the
|
|
29255
30139
|
* task generated (the MCP fetches /v1/results first to count). Bounded
|
|
29256
30140
|
* generously here; the backend validates each index against
|
|
29257
30141
|
* landing.images.length and rejects out-of-range. */
|
|
29258
|
-
numLandingImages:
|
|
29259
|
-
variantIndex:
|
|
30142
|
+
numLandingImages: z120.number().int().min(1).max(20).optional(),
|
|
30143
|
+
variantIndex: z120.number().int().min(0).optional(),
|
|
29260
30144
|
/** ASK THE USER before setting: "¿Tu producto tiene variantes (talla, color, etc.)?".
|
|
29261
30145
|
* When true, the landing renders variant-picker pills; when false, it ships
|
|
29262
30146
|
* a single-option product page. Defaults to false. */
|
|
29263
|
-
showVariantPicker:
|
|
30147
|
+
showVariantPicker: z120.boolean().optional(),
|
|
29264
30148
|
/** Default `auto` updates the product previously published for this task and
|
|
29265
30149
|
* shop. Use `existing` with targetProductId to choose another product, or
|
|
29266
30150
|
* `new` only when the user explicitly wants a duplicate/new listing. */
|
|
29267
|
-
publicationMode:
|
|
30151
|
+
publicationMode: z120.enum([
|
|
29268
30152
|
"auto",
|
|
29269
30153
|
"existing",
|
|
29270
30154
|
"new"
|
|
29271
30155
|
]).optional(),
|
|
29272
30156
|
/** Shopify product GID, numeric id, or admin product URL. Required when
|
|
29273
30157
|
* publicationMode is `existing`. */
|
|
29274
|
-
targetProductId:
|
|
30158
|
+
targetProductId: z120.string().trim().min(1).max(300).optional(),
|
|
29275
30159
|
/** Add an announcement bar text above add-to-cart (optional). */
|
|
29276
|
-
announcementBarEnabled:
|
|
29277
|
-
announcementBarText:
|
|
30160
|
+
announcementBarEnabled: z120.boolean().optional(),
|
|
30161
|
+
announcementBarText: z120.string().trim().min(1).max(SHOPIFY_ANNOUNCEMENT_BAR_MAX_LENGTH).optional(),
|
|
29278
30162
|
commerceCorrection: PublishCommerceCorrectionSchema.optional().describe("Complete seller-confirmed commerce values, with the revision and fingerprint returned in recovery. Never invent missing prices or convert currency."),
|
|
29279
|
-
confirmed:
|
|
30163
|
+
confirmed: z120.boolean().optional().describe("Required as true only when submitting commerceCorrection, after showing its values and obtaining explicit seller confirmation.")
|
|
29280
30164
|
});
|
|
29281
30165
|
var ShopifyInputWithAnnouncementBarValidation = ShopifyInput.superRefine((value, ctx) => {
|
|
29282
30166
|
if (value.commerceCorrection && value.confirmed !== true) {
|
|
29283
30167
|
ctx.addIssue({
|
|
29284
|
-
code:
|
|
30168
|
+
code: z120.ZodIssueCode.custom,
|
|
29285
30169
|
path: [
|
|
29286
30170
|
"confirmed"
|
|
29287
30171
|
],
|
|
@@ -29290,7 +30174,7 @@ var ShopifyInputWithAnnouncementBarValidation = ShopifyInput.superRefine((value,
|
|
|
29290
30174
|
}
|
|
29291
30175
|
if (value.announcementBarEnabled && !value.announcementBarText) {
|
|
29292
30176
|
ctx.addIssue({
|
|
29293
|
-
code:
|
|
30177
|
+
code: z120.ZodIssueCode.custom,
|
|
29294
30178
|
path: [
|
|
29295
30179
|
"announcementBarText"
|
|
29296
30180
|
],
|
|
@@ -29299,7 +30183,7 @@ var ShopifyInputWithAnnouncementBarValidation = ShopifyInput.superRefine((value,
|
|
|
29299
30183
|
}
|
|
29300
30184
|
if (!value.announcementBarEnabled && value.announcementBarText) {
|
|
29301
30185
|
ctx.addIssue({
|
|
29302
|
-
code:
|
|
30186
|
+
code: z120.ZodIssueCode.custom,
|
|
29303
30187
|
path: [
|
|
29304
30188
|
"announcementBarText"
|
|
29305
30189
|
],
|
|
@@ -29313,91 +30197,91 @@ function parseShopifyInput(rawInput) {
|
|
|
29313
30197
|
return parsed.data;
|
|
29314
30198
|
}
|
|
29315
30199
|
__name(parseShopifyInput, "parseShopifyInput");
|
|
29316
|
-
var McpCampaignConfig =
|
|
30200
|
+
var McpCampaignConfig = z120.object({
|
|
29317
30201
|
/** ALWAYS in US dollars, whatever the ad account's currency is. ProductMaker
|
|
29318
30202
|
* converts to the account currency server-side. Do NOT pre-convert: on a COP
|
|
29319
30203
|
* account the user's "60.000 al día" is `dailyBudgetUsd: 15`, not 60000. */
|
|
29320
|
-
dailyBudgetUsd:
|
|
30204
|
+
dailyBudgetUsd: z120.number().positive().optional(),
|
|
29321
30205
|
/** ISO-3166-1 alpha-2 country codes or resolvable names (CountryIso2Schema
|
|
29322
30206
|
* normalizes at validation). Meta accepts max 1 (single-country ad set).
|
|
29323
30207
|
* TikTok takes only the first entry. */
|
|
29324
|
-
targetCountries:
|
|
29325
|
-
campaignName:
|
|
30208
|
+
targetCountries: z120.array(CountryIso2Schema).max(1).optional(),
|
|
30209
|
+
campaignName: z120.string().trim().min(1).max(100).optional()
|
|
29326
30210
|
});
|
|
29327
30211
|
var McpAdAccountId = MetaAdAccountId.describe('Meta ad account id. Accepts "act_1234567890" or the bare numeric id "1234567890".');
|
|
29328
|
-
var MetaInput =
|
|
29329
|
-
taskId:
|
|
30212
|
+
var MetaInput = z120.object({
|
|
30213
|
+
taskId: z120.string().min(1),
|
|
29330
30214
|
/** Exact Meta Business portfolio selected from `list_meta_destinations`. */
|
|
29331
|
-
portfolioId:
|
|
30215
|
+
portfolioId: z120.string().trim().min(1),
|
|
29332
30216
|
/** Exact Facebook Page selected from `list_meta_destinations`. */
|
|
29333
|
-
pageId:
|
|
30217
|
+
pageId: z120.string().trim().min(1),
|
|
29334
30218
|
adAccountId: McpAdAccountId,
|
|
29335
|
-
pixelId:
|
|
29336
|
-
landingUrl:
|
|
30219
|
+
pixelId: z120.string().min(1).optional(),
|
|
30220
|
+
landingUrl: z120.string().url().optional(),
|
|
29337
30221
|
/** Select which angles to publish by 0-based index. Omit to publish all
|
|
29338
30222
|
* successful angles. Example: a task with 3 angles → `[0,2]` publishes #0 + #2. */
|
|
29339
|
-
angleIndexes:
|
|
30223
|
+
angleIndexes: z120.array(z120.number().int().nonnegative()).optional(),
|
|
29340
30224
|
campaignConfig: McpCampaignConfig.optional(),
|
|
29341
30225
|
/** Override the ad's primary text (body copy). If omitted, ProductMaker
|
|
29342
30226
|
* uses the auto-generated copy attached to the task. Capped at 500 chars
|
|
29343
30227
|
* (Meta's primary-text limit). */
|
|
29344
|
-
primaryText:
|
|
30228
|
+
primaryText: z120.string().trim().min(1).max(500).optional(),
|
|
29345
30229
|
/** Optional WhatsApp number. When set, the ad button opens a WhatsApp chat
|
|
29346
30230
|
* (wa.me) instead of a landing page. */
|
|
29347
|
-
phoneNumber:
|
|
30231
|
+
phoneNumber: z120.string().min(8).max(32).optional()
|
|
29348
30232
|
});
|
|
29349
|
-
var GroupedTaskSource =
|
|
29350
|
-
type:
|
|
29351
|
-
taskId:
|
|
29352
|
-
angleIndex:
|
|
29353
|
-
videoVariant:
|
|
30233
|
+
var GroupedTaskSource = z120.object({
|
|
30234
|
+
type: z120.literal("task"),
|
|
30235
|
+
taskId: z120.string().min(1),
|
|
30236
|
+
angleIndex: z120.number().int().min(0).optional(),
|
|
30237
|
+
videoVariant: z120.enum([
|
|
29354
30238
|
"with_subtitles",
|
|
29355
30239
|
"without_subtitles"
|
|
29356
30240
|
]).optional(),
|
|
29357
|
-
imageVariantIndex:
|
|
29358
|
-
});
|
|
29359
|
-
var GroupedMediaSource =
|
|
29360
|
-
type:
|
|
29361
|
-
url:
|
|
29362
|
-
path:
|
|
29363
|
-
thumbnailUrl:
|
|
29364
|
-
thumbnailPath:
|
|
30241
|
+
imageVariantIndex: z120.number().int().min(0).optional()
|
|
30242
|
+
});
|
|
30243
|
+
var GroupedMediaSource = z120.object({
|
|
30244
|
+
type: z120.literal("media"),
|
|
30245
|
+
url: z120.string().url().optional().describe("Public HTTPS media URL. For a file on the user laptop, use request_media_upload on the hosted MCP instead."),
|
|
30246
|
+
path: z120.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".'),
|
|
30247
|
+
thumbnailUrl: z120.string().url().optional(),
|
|
30248
|
+
thumbnailPath: z120.string().min(1).optional()
|
|
29365
30249
|
}).refine((value) => Boolean(value.url) !== Boolean(value.path), {
|
|
29366
30250
|
message: "media source needs exactly one of `url` or `path`"
|
|
29367
30251
|
}).refine((value) => !(value.thumbnailUrl && value.thumbnailPath), {
|
|
29368
30252
|
message: "media source needs at most one of `thumbnailUrl` or `thumbnailPath`"
|
|
29369
30253
|
});
|
|
29370
|
-
var GroupedUploadedAssetSource =
|
|
29371
|
-
type:
|
|
30254
|
+
var GroupedUploadedAssetSource = z120.object({
|
|
30255
|
+
type: z120.literal("asset"),
|
|
29372
30256
|
/** Returned by check_media_upload. ProductMaker resolves ownership server-side. */
|
|
29373
|
-
assetId:
|
|
29374
|
-
thumbnailAssetId:
|
|
30257
|
+
assetId: z120.string().uuid(),
|
|
30258
|
+
thumbnailAssetId: z120.string().uuid().optional()
|
|
29375
30259
|
});
|
|
29376
|
-
var GroupedCreative =
|
|
29377
|
-
type:
|
|
30260
|
+
var GroupedCreative = z120.object({
|
|
30261
|
+
type: z120.enum([
|
|
29378
30262
|
"image",
|
|
29379
30263
|
"video"
|
|
29380
30264
|
]),
|
|
29381
|
-
source:
|
|
30265
|
+
source: z120.union([
|
|
29382
30266
|
GroupedTaskSource,
|
|
29383
30267
|
GroupedMediaSource,
|
|
29384
30268
|
GroupedUploadedAssetSource
|
|
29385
30269
|
]),
|
|
29386
|
-
title:
|
|
29387
|
-
description:
|
|
30270
|
+
title: z120.string().max(120).optional(),
|
|
30271
|
+
description: z120.string().max(500).optional(),
|
|
29388
30272
|
cta: MetaCtaType.optional(),
|
|
29389
|
-
creativeId:
|
|
30273
|
+
creativeId: z120.string().min(1).optional()
|
|
29390
30274
|
});
|
|
29391
|
-
var GroupedCampaignConfigBase =
|
|
30275
|
+
var GroupedCampaignConfigBase = z120.object({
|
|
29392
30276
|
campaignType: MetaCampaignType.default("sales"),
|
|
29393
30277
|
optimizationMode: MetaOptimizationMode.default("advantage_plus"),
|
|
29394
30278
|
budgetMode: MetaBudgetMode.optional(),
|
|
29395
|
-
campaignDailyBudget:
|
|
29396
|
-
adSets:
|
|
29397
|
-
tempId:
|
|
29398
|
-
name:
|
|
29399
|
-
dailyBudget:
|
|
29400
|
-
creativeIds:
|
|
30279
|
+
campaignDailyBudget: z120.number().int().positive().optional(),
|
|
30280
|
+
adSets: z120.array(z120.object({
|
|
30281
|
+
tempId: z120.string().min(1).max(80),
|
|
30282
|
+
name: z120.string().min(1).max(100),
|
|
30283
|
+
dailyBudget: z120.number().int().positive().optional(),
|
|
30284
|
+
creativeIds: z120.array(z120.string().min(1)).min(1)
|
|
29401
30285
|
})).min(1).max(CAMPAIGN_LIMITS.MAX_AD_SETS),
|
|
29402
30286
|
/**
|
|
29403
30287
|
* Valid values are 'landing' and 'whatsapp' — there is no 'website'. This
|
|
@@ -29405,17 +30289,17 @@ var GroupedCampaignConfigBase = z117.object({
|
|
|
29405
30289
|
* opaque `invalid_literal` and made the WhatsApp destination — supported by
|
|
29406
30290
|
* the API since #849 — unreachable from this tool.
|
|
29407
30291
|
*/
|
|
29408
|
-
destination:
|
|
29409
|
-
|
|
29410
|
-
type:
|
|
29411
|
-
landingUrl:
|
|
30292
|
+
destination: z120.discriminatedUnion("type", [
|
|
30293
|
+
z120.object({
|
|
30294
|
+
type: z120.literal("landing"),
|
|
30295
|
+
landingUrl: z120.string().url()
|
|
29412
30296
|
}),
|
|
29413
|
-
|
|
29414
|
-
type:
|
|
29415
|
-
phoneNumber:
|
|
30297
|
+
z120.object({
|
|
30298
|
+
type: z120.literal("whatsapp"),
|
|
30299
|
+
phoneNumber: z120.string().min(8).max(32)
|
|
29416
30300
|
})
|
|
29417
30301
|
]),
|
|
29418
|
-
saveAsDefault:
|
|
30302
|
+
saveAsDefault: z120.boolean().default(false),
|
|
29419
30303
|
budgetPolicy: MetaBudgetPolicy.optional()
|
|
29420
30304
|
});
|
|
29421
30305
|
function resolveBudgetMode(value) {
|
|
@@ -29429,7 +30313,7 @@ var GroupedCampaignConfig = GroupedCampaignConfigBase.superRefine((value, ctx) =
|
|
|
29429
30313
|
const budgetMode = resolveBudgetMode(value);
|
|
29430
30314
|
if (budgetMode === "campaign" && value.campaignDailyBudget === void 0) {
|
|
29431
30315
|
ctx.addIssue({
|
|
29432
|
-
code:
|
|
30316
|
+
code: z120.ZodIssueCode.custom,
|
|
29433
30317
|
path: [
|
|
29434
30318
|
"campaignDailyBudget"
|
|
29435
30319
|
],
|
|
@@ -29440,7 +30324,7 @@ var GroupedCampaignConfig = GroupedCampaignConfigBase.superRefine((value, ctx) =
|
|
|
29440
30324
|
value.adSets.forEach((adSet, index) => {
|
|
29441
30325
|
if (adSet.dailyBudget === void 0) {
|
|
29442
30326
|
ctx.addIssue({
|
|
29443
|
-
code:
|
|
30327
|
+
code: z120.ZodIssueCode.custom,
|
|
29444
30328
|
path: [
|
|
29445
30329
|
"adSets",
|
|
29446
30330
|
index,
|
|
@@ -29455,28 +30339,28 @@ var GroupedCampaignConfig = GroupedCampaignConfigBase.superRefine((value, ctx) =
|
|
|
29455
30339
|
...value,
|
|
29456
30340
|
budgetMode: resolveBudgetMode(value)
|
|
29457
30341
|
}));
|
|
29458
|
-
var PublishCreativesToMetaInputBase =
|
|
29459
|
-
creatives:
|
|
30342
|
+
var PublishCreativesToMetaInputBase = z120.object({
|
|
30343
|
+
creatives: z120.array(GroupedCreative).min(1).max(CAMPAIGN_LIMITS.MAX_CREATIVES),
|
|
29460
30344
|
/** Exact Meta Business portfolio selected from `list_meta_destinations`. */
|
|
29461
|
-
portfolioId:
|
|
30345
|
+
portfolioId: z120.string().trim().min(1),
|
|
29462
30346
|
/** Exact Facebook Page selected from `list_meta_destinations`. */
|
|
29463
|
-
pageId:
|
|
30347
|
+
pageId: z120.string().trim().min(1),
|
|
29464
30348
|
adAccountId: McpAdAccountId,
|
|
29465
|
-
pixelId:
|
|
30349
|
+
pixelId: z120.string().min(1).optional(),
|
|
29466
30350
|
/** Required unless `campaignConfig.destination` is whatsapp — see the
|
|
29467
30351
|
* superRefine below. Mirrors the API's `FacebookCreateCampaignFromTasksBodySchema`. */
|
|
29468
|
-
landingUrl:
|
|
30352
|
+
landingUrl: z120.string().url().optional(),
|
|
29469
30353
|
/** ISO-2 code or resolvable country name — mirrors the API contract
|
|
29470
30354
|
* (CountryIso2Schema), which normalizes names to alpha-2 at validation. */
|
|
29471
30355
|
targetCountry: CountryIso2Schema,
|
|
29472
|
-
campaignName:
|
|
30356
|
+
campaignName: z120.string().trim().min(1).max(100),
|
|
29473
30357
|
campaignConfig: GroupedCampaignConfig.optional()
|
|
29474
30358
|
});
|
|
29475
30359
|
var PublishCreativesToMetaInput = PublishCreativesToMetaInputBase.superRefine((value, ctx) => {
|
|
29476
30360
|
const destination = value.campaignConfig?.destination;
|
|
29477
30361
|
if (destination?.type !== "whatsapp" && destination?.type !== "landing" && !value.landingUrl) {
|
|
29478
30362
|
ctx.addIssue({
|
|
29479
|
-
code:
|
|
30363
|
+
code: z120.ZodIssueCode.custom,
|
|
29480
30364
|
path: [
|
|
29481
30365
|
"landingUrl"
|
|
29482
30366
|
],
|
|
@@ -29485,7 +30369,7 @@ var PublishCreativesToMetaInput = PublishCreativesToMetaInputBase.superRefine((v
|
|
|
29485
30369
|
}
|
|
29486
30370
|
if (destination?.type === "landing" && value.landingUrl && destination.landingUrl !== value.landingUrl) {
|
|
29487
30371
|
ctx.addIssue({
|
|
29488
|
-
code:
|
|
30372
|
+
code: z120.ZodIssueCode.custom,
|
|
29489
30373
|
path: [
|
|
29490
30374
|
"campaignConfig",
|
|
29491
30375
|
"destination",
|
|
@@ -29504,16 +30388,16 @@ var PublishCreativesToMetaInput = PublishCreativesToMetaInputBase.superRefine((v
|
|
|
29504
30388
|
}
|
|
29505
30389
|
return value;
|
|
29506
30390
|
});
|
|
29507
|
-
var PresignUploadResponseSchema =
|
|
29508
|
-
uploadUrl:
|
|
30391
|
+
var PresignUploadResponseSchema = z120.object({
|
|
30392
|
+
uploadUrl: z120.string().url().refine((value) => {
|
|
29509
30393
|
const url = new URL(value);
|
|
29510
30394
|
return !url.username && !url.password && url.protocol === "https:";
|
|
29511
30395
|
}, "presigned upload URL must be HTTPS without embedded credentials"),
|
|
29512
|
-
publicUrl:
|
|
30396
|
+
publicUrl: z120.string().url().refine((value) => {
|
|
29513
30397
|
const url = new URL(value);
|
|
29514
30398
|
return !url.username && !url.password;
|
|
29515
30399
|
}, "public media URL must not contain embedded credentials"),
|
|
29516
|
-
stagingAssetId:
|
|
30400
|
+
stagingAssetId: z120.string().uuid()
|
|
29517
30401
|
});
|
|
29518
30402
|
async function uploadResolvedMedia(api, media, assetRole) {
|
|
29519
30403
|
if (!media.buffer) throw new ResolveMediaError("INVALID_INPUT", "resolved media has no bytes");
|
|
@@ -29538,16 +30422,16 @@ async function resolveAndUploadMedia(api, ref, expectedKind, assetRole, transpor
|
|
|
29538
30422
|
return uploadResolvedMedia(api, media, assetRole);
|
|
29539
30423
|
}
|
|
29540
30424
|
__name(resolveAndUploadMedia, "resolveAndUploadMedia");
|
|
29541
|
-
var TikTokInput =
|
|
29542
|
-
taskId:
|
|
29543
|
-
advertiserId:
|
|
29544
|
-
pixelId:
|
|
29545
|
-
landingUrl:
|
|
29546
|
-
angleIndexes:
|
|
30425
|
+
var TikTokInput = z120.object({
|
|
30426
|
+
taskId: z120.string().min(1),
|
|
30427
|
+
advertiserId: z120.string().min(1),
|
|
30428
|
+
pixelId: z120.string().min(1).optional(),
|
|
30429
|
+
landingUrl: z120.string().url().optional(),
|
|
30430
|
+
angleIndexes: z120.array(z120.number().int().nonnegative()).optional(),
|
|
29547
30431
|
campaignConfig: McpCampaignConfig.optional(),
|
|
29548
30432
|
/** Override the ad's description (TikTok's body copy). If omitted,
|
|
29549
30433
|
* ProductMaker uses the auto-generated copy attached to the task. */
|
|
29550
|
-
primaryText:
|
|
30434
|
+
primaryText: z120.string().trim().min(1).max(500).optional()
|
|
29551
30435
|
});
|
|
29552
30436
|
function registerPublishTools(server, api, opts = {}) {
|
|
29553
30437
|
const transport = opts.transport ?? "http";
|
|
@@ -29899,39 +30783,39 @@ __name(toTikTokCampaignConfig, "toTikTokCampaignConfig");
|
|
|
29899
30783
|
|
|
29900
30784
|
// src/tools/meta-upload.ts
|
|
29901
30785
|
import { registerAppTool } from "@modelcontextprotocol/ext-apps/server";
|
|
29902
|
-
import { z as
|
|
30786
|
+
import { z as z121 } from "zod";
|
|
29903
30787
|
var requestMediaUploadGuidance = getProductMakerCapabilityModelGuidance("request_media_upload");
|
|
29904
|
-
var UploadSessionInputBase =
|
|
29905
|
-
mediaKind:
|
|
30788
|
+
var UploadSessionInputBase = z121.object({
|
|
30789
|
+
mediaKind: z121.enum([
|
|
29906
30790
|
"image",
|
|
29907
30791
|
"video",
|
|
29908
30792
|
"mixed"
|
|
29909
30793
|
]).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."),
|
|
29910
|
-
assetRole:
|
|
30794
|
+
assetRole: z121.enum([
|
|
29911
30795
|
"media",
|
|
29912
30796
|
"thumbnail"
|
|
29913
30797
|
]).default("media").describe("Use media for the creative itself; thumbnail only for an image thumbnail."),
|
|
29914
|
-
uploadPurpose:
|
|
30798
|
+
uploadPurpose: z121.enum([
|
|
29915
30799
|
"product_reference",
|
|
29916
30800
|
"creative",
|
|
29917
30801
|
"generic"
|
|
29918
30802
|
]).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."),
|
|
29919
|
-
maxFiles:
|
|
30803
|
+
maxFiles: z121.number().int().min(1).max(10).optional().describe("Optional batch limit: image accepts 1-5, video exactly 1, and mixed 1-10.")
|
|
29920
30804
|
});
|
|
29921
|
-
var OpenAiFileReference =
|
|
29922
|
-
download_url:
|
|
29923
|
-
file_id:
|
|
29924
|
-
mime_type:
|
|
29925
|
-
file_name:
|
|
30805
|
+
var OpenAiFileReference = z121.object({
|
|
30806
|
+
download_url: z121.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."),
|
|
30807
|
+
file_id: z121.string().min(1),
|
|
30808
|
+
mime_type: z121.string().min(1).optional(),
|
|
30809
|
+
file_name: z121.string().min(1).optional()
|
|
29926
30810
|
});
|
|
29927
30811
|
var UploadSessionToolInputShape = {
|
|
29928
30812
|
...UploadSessionInputBase.shape,
|
|
29929
|
-
files:
|
|
30813
|
+
files: z121.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.")
|
|
29930
30814
|
};
|
|
29931
|
-
var UploadSessionToolInput =
|
|
30815
|
+
var UploadSessionToolInput = z121.object(UploadSessionToolInputShape).superRefine((value, ctx) => {
|
|
29932
30816
|
if (value.assetRole === "thumbnail" && value.mediaKind !== "image") {
|
|
29933
30817
|
ctx.addIssue({
|
|
29934
|
-
code:
|
|
30818
|
+
code: z121.ZodIssueCode.custom,
|
|
29935
30819
|
path: [
|
|
29936
30820
|
"mediaKind"
|
|
29937
30821
|
],
|
|
@@ -29940,7 +30824,7 @@ var UploadSessionToolInput = z118.object(UploadSessionToolInputShape).superRefin
|
|
|
29940
30824
|
}
|
|
29941
30825
|
if (value.mediaKind === "video" && value.maxFiles !== void 0 && value.maxFiles !== 1) {
|
|
29942
30826
|
ctx.addIssue({
|
|
29943
|
-
code:
|
|
30827
|
+
code: z121.ZodIssueCode.custom,
|
|
29944
30828
|
path: [
|
|
29945
30829
|
"maxFiles"
|
|
29946
30830
|
],
|
|
@@ -29949,7 +30833,7 @@ var UploadSessionToolInput = z118.object(UploadSessionToolInputShape).superRefin
|
|
|
29949
30833
|
}
|
|
29950
30834
|
if (value.mediaKind === "image" && (value.maxFiles ?? 1) > 5) {
|
|
29951
30835
|
ctx.addIssue({
|
|
29952
|
-
code:
|
|
30836
|
+
code: z121.ZodIssueCode.custom,
|
|
29953
30837
|
path: [
|
|
29954
30838
|
"maxFiles"
|
|
29955
30839
|
],
|
|
@@ -29957,38 +30841,38 @@ var UploadSessionToolInput = z118.object(UploadSessionToolInputShape).superRefin
|
|
|
29957
30841
|
});
|
|
29958
30842
|
}
|
|
29959
30843
|
});
|
|
29960
|
-
var UploadSessionResponse =
|
|
29961
|
-
sessionId:
|
|
29962
|
-
uploadPageUrl:
|
|
29963
|
-
mediaKind:
|
|
30844
|
+
var UploadSessionResponse = z121.object({
|
|
30845
|
+
sessionId: z121.string().uuid(),
|
|
30846
|
+
uploadPageUrl: z121.string().url(),
|
|
30847
|
+
mediaKind: z121.enum([
|
|
29964
30848
|
"image",
|
|
29965
30849
|
"video",
|
|
29966
30850
|
"mixed"
|
|
29967
30851
|
]),
|
|
29968
|
-
assetRole:
|
|
30852
|
+
assetRole: z121.enum([
|
|
29969
30853
|
"media",
|
|
29970
30854
|
"thumbnail"
|
|
29971
30855
|
]),
|
|
29972
|
-
maxFiles:
|
|
29973
|
-
expiresAt:
|
|
30856
|
+
maxFiles: z121.number().int().positive(),
|
|
30857
|
+
expiresAt: z121.string().datetime()
|
|
29974
30858
|
});
|
|
29975
|
-
var UploadStatusInput =
|
|
29976
|
-
sessionId:
|
|
30859
|
+
var UploadStatusInput = z121.object({
|
|
30860
|
+
sessionId: z121.string().uuid().describe("The sessionId returned by request_media_upload.")
|
|
29977
30861
|
});
|
|
29978
|
-
var UniversalUploadStatusResponse =
|
|
29979
|
-
|
|
29980
|
-
status:
|
|
29981
|
-
assets:
|
|
30862
|
+
var UniversalUploadStatusResponse = z121.discriminatedUnion("status", [
|
|
30863
|
+
z121.object({
|
|
30864
|
+
status: z121.literal("pending"),
|
|
30865
|
+
assets: z121.array(z121.never()).length(0)
|
|
29982
30866
|
}),
|
|
29983
|
-
|
|
29984
|
-
status:
|
|
29985
|
-
assets:
|
|
29986
|
-
assetId:
|
|
29987
|
-
mediaKind:
|
|
30867
|
+
z121.object({
|
|
30868
|
+
status: z121.literal("uploaded"),
|
|
30869
|
+
assets: z121.array(z121.object({
|
|
30870
|
+
assetId: z121.string().uuid(),
|
|
30871
|
+
mediaKind: z121.enum([
|
|
29988
30872
|
"image",
|
|
29989
30873
|
"video"
|
|
29990
30874
|
]),
|
|
29991
|
-
assetRole:
|
|
30875
|
+
assetRole: z121.enum([
|
|
29992
30876
|
"media",
|
|
29993
30877
|
"thumbnail"
|
|
29994
30878
|
])
|
|
@@ -29996,13 +30880,13 @@ var UniversalUploadStatusResponse = z118.discriminatedUnion("status", [
|
|
|
29996
30880
|
})
|
|
29997
30881
|
]);
|
|
29998
30882
|
var UniversalUploadStructuredResponse = UploadSessionResponse.extend({
|
|
29999
|
-
uploadPurpose:
|
|
30883
|
+
uploadPurpose: z121.enum([
|
|
30000
30884
|
"product_reference",
|
|
30001
30885
|
"creative",
|
|
30002
30886
|
"generic"
|
|
30003
30887
|
]),
|
|
30004
|
-
checkToolName:
|
|
30005
|
-
resolveEndpoint:
|
|
30888
|
+
checkToolName: z121.literal("check_media_upload"),
|
|
30889
|
+
resolveEndpoint: z121.string().url()
|
|
30006
30890
|
});
|
|
30007
30891
|
function contextualizeUploadPageUrl(uploadPageUrl, uploadPurpose) {
|
|
30008
30892
|
const url = new URL(uploadPageUrl);
|
|
@@ -30093,15 +30977,15 @@ function registerMediaUploadTools(server, api) {
|
|
|
30093
30977
|
__name(registerMediaUploadTools, "registerMediaUploadTools");
|
|
30094
30978
|
|
|
30095
30979
|
// src/tools/campaigns.ts
|
|
30096
|
-
import { z as
|
|
30980
|
+
import { z as z122 } from "zod";
|
|
30097
30981
|
var getMetaCampaignStatusGuidance = getProductMakerCapabilityModelGuidance("get_meta_campaign_status");
|
|
30098
30982
|
var getTikTokCampaignStatusGuidance = getProductMakerCapabilityModelGuidance("get_tiktok_campaign_status");
|
|
30099
|
-
var MetaStatusInput =
|
|
30100
|
-
campaignId:
|
|
30983
|
+
var MetaStatusInput = z122.object({
|
|
30984
|
+
campaignId: z122.string().min(1).describe("Meta campaign ID returned by publish_to_meta.")
|
|
30101
30985
|
});
|
|
30102
|
-
var TikTokStatusInput =
|
|
30103
|
-
campaignId:
|
|
30104
|
-
advertiserId:
|
|
30986
|
+
var TikTokStatusInput = z122.object({
|
|
30987
|
+
campaignId: z122.string().min(1).describe("TikTok campaign ID returned by publish_to_tiktok."),
|
|
30988
|
+
advertiserId: z122.string().min(1).describe("The same advertiserId used to publish the campaign.")
|
|
30105
30989
|
});
|
|
30106
30990
|
function registerCampaignTools(server, api) {
|
|
30107
30991
|
server.registerTool("get_meta_campaign_status", {
|
|
@@ -30206,10 +31090,10 @@ function renderTikTokStatusMarkdown(r) {
|
|
|
30206
31090
|
__name(renderTikTokStatusMarkdown, "renderTikTokStatusMarkdown");
|
|
30207
31091
|
|
|
30208
31092
|
// ../../packages/product-economics/src/product-costing.ts
|
|
30209
|
-
import { z as
|
|
31093
|
+
import { z as z124 } from "zod";
|
|
30210
31094
|
|
|
30211
31095
|
// ../../packages/product-economics/src/product-costing-countries.ts
|
|
30212
|
-
import { z as
|
|
31096
|
+
import { z as z123 } from "zod";
|
|
30213
31097
|
|
|
30214
31098
|
// ../../packages/product-economics/src/product-costing-shipping-references.json
|
|
30215
31099
|
var product_costing_shipping_references_default = {
|
|
@@ -30273,21 +31157,21 @@ var product_costing_shipping_references_default = {
|
|
|
30273
31157
|
};
|
|
30274
31158
|
|
|
30275
31159
|
// ../../packages/product-economics/src/product-costing-countries.ts
|
|
30276
|
-
var ShippingReferenceSchema =
|
|
30277
|
-
reference:
|
|
30278
|
-
min:
|
|
30279
|
-
max:
|
|
30280
|
-
updatedAt:
|
|
30281
|
-
confidence:
|
|
31160
|
+
var ShippingReferenceSchema = z123.object({
|
|
31161
|
+
reference: z123.number().positive(),
|
|
31162
|
+
min: z123.number().nonnegative(),
|
|
31163
|
+
max: z123.number().positive(),
|
|
31164
|
+
updatedAt: z123.string().date(),
|
|
31165
|
+
confidence: z123.enum([
|
|
30282
31166
|
"high",
|
|
30283
31167
|
"medium",
|
|
30284
31168
|
"medium-low"
|
|
30285
31169
|
]),
|
|
30286
|
-
sources:
|
|
31170
|
+
sources: z123.array(z123.string().url().startsWith("https://")).min(1).max(3)
|
|
30287
31171
|
}).superRefine(({ reference, min, max }, context) => {
|
|
30288
31172
|
if (min > reference || reference > max) {
|
|
30289
31173
|
context.addIssue({
|
|
30290
|
-
code:
|
|
31174
|
+
code: z123.ZodIssueCode.custom,
|
|
30291
31175
|
message: "shipping_reference_outside_range",
|
|
30292
31176
|
path: [
|
|
30293
31177
|
"reference"
|
|
@@ -30295,7 +31179,7 @@ var ShippingReferenceSchema = z120.object({
|
|
|
30295
31179
|
});
|
|
30296
31180
|
}
|
|
30297
31181
|
});
|
|
30298
|
-
var SHIPPING_REFERENCES =
|
|
31182
|
+
var SHIPPING_REFERENCES = z123.record(ShippingReferenceSchema).parse(product_costing_shipping_references_default);
|
|
30299
31183
|
var PRODUCT_COSTING_COUNTRY_CODES = Object.freeze(Object.keys(SHIPPING_REFERENCES));
|
|
30300
31184
|
var SPANISH_LOCALE_COUNTRIES = /* @__PURE__ */ new Set([
|
|
30301
31185
|
"MX",
|
|
@@ -30477,18 +31361,18 @@ function getProductCostingCountryName(country) {
|
|
|
30477
31361
|
__name(getProductCostingCountryName, "getProductCostingCountryName");
|
|
30478
31362
|
|
|
30479
31363
|
// ../../packages/product-economics/src/product-costing.ts
|
|
30480
|
-
var ProductCostingInputSchema =
|
|
30481
|
-
country:
|
|
30482
|
-
supplierCost:
|
|
30483
|
-
shippingCost:
|
|
30484
|
-
returnRate:
|
|
30485
|
-
cancellationRate:
|
|
30486
|
-
adsManagerCpa:
|
|
30487
|
-
desiredMargin:
|
|
31364
|
+
var ProductCostingInputSchema = z124.object({
|
|
31365
|
+
country: z124.string().refine(isProductCostingCountryCode, "unsupported_country"),
|
|
31366
|
+
supplierCost: z124.number().finite().max(Number.MAX_SAFE_INTEGER, "amount_too_large").positive("supplier_cost_positive"),
|
|
31367
|
+
shippingCost: z124.number().finite().max(Number.MAX_SAFE_INTEGER, "amount_too_large").nonnegative("shipping_cost_nonnegative"),
|
|
31368
|
+
returnRate: z124.number().finite().min(0, "rate_nonnegative").lt(1, "rate_below_one"),
|
|
31369
|
+
cancellationRate: z124.number().finite().min(0, "rate_nonnegative").lt(1, "rate_below_one"),
|
|
31370
|
+
adsManagerCpa: z124.number().finite().max(Number.MAX_SAFE_INTEGER, "amount_too_large").nonnegative("cpa_nonnegative"),
|
|
31371
|
+
desiredMargin: z124.number().finite().min(0, "margin_nonnegative").lt(1, "margin_below_one")
|
|
30488
31372
|
}).superRefine(({ returnRate, cancellationRate }, context) => {
|
|
30489
31373
|
if (returnRate + cancellationRate >= 1) {
|
|
30490
31374
|
context.addIssue({
|
|
30491
|
-
code:
|
|
31375
|
+
code: z124.ZodIssueCode.custom,
|
|
30492
31376
|
message: "delivery_rate_positive",
|
|
30493
31377
|
path: [
|
|
30494
31378
|
"cancellationRate"
|
|
@@ -30585,15 +31469,15 @@ function calculateProductCosting(rawInput) {
|
|
|
30585
31469
|
__name(calculateProductCosting, "calculateProductCosting");
|
|
30586
31470
|
|
|
30587
31471
|
// src/tools/product-costing.ts
|
|
30588
|
-
import { z as
|
|
31472
|
+
import { z as z125 } from "zod";
|
|
30589
31473
|
var costProductGuidance = getProductMakerCapabilityModelGuidance("cost_product");
|
|
30590
|
-
var optionalAmount = /* @__PURE__ */ __name((description) =>
|
|
30591
|
-
var optionalPercent = /* @__PURE__ */ __name((description) =>
|
|
30592
|
-
var ProductCostingToolInput =
|
|
30593
|
-
country:
|
|
31474
|
+
var optionalAmount = /* @__PURE__ */ __name((description) => z125.number().finite().max(Number.MAX_SAFE_INTEGER).nonnegative().optional().describe(description), "optionalAmount");
|
|
31475
|
+
var optionalPercent = /* @__PURE__ */ __name((description) => z125.number().finite().min(0).lt(100).optional().describe(description), "optionalPercent");
|
|
31476
|
+
var ProductCostingToolInput = z125.object({
|
|
31477
|
+
country: z125.string().trim().toUpperCase().refine(isProductCostingCountryCode, {
|
|
30594
31478
|
message: "Pa\xEDs no soportado por ProductMaker."
|
|
30595
31479
|
}).optional().describe("C\xF3digo ISO-2 de cualquiera de los pa\xEDses disponibles en ProductMaker. Omite para Colombia."),
|
|
30596
|
-
supplierCost:
|
|
31480
|
+
supplierCost: z125.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."),
|
|
30597
31481
|
supplierCostCop: optionalAmount("Alias anterior para costo del proveedor en Colombia. Prefiere supplierCost."),
|
|
30598
31482
|
shippingCost: optionalAmount("Costo de un env\xEDo en la moneda del pa\xEDs. Omite para usar el valor gu\xEDa vigente."),
|
|
30599
31483
|
shippingCostCop: optionalAmount("Alias anterior para flete en Colombia. Prefiere shippingCost."),
|
|
@@ -30756,7 +31640,7 @@ function formatPercent(value) {
|
|
|
30756
31640
|
__name(formatPercent, "formatPercent");
|
|
30757
31641
|
|
|
30758
31642
|
// src/tools/product-research.ts
|
|
30759
|
-
import { z as
|
|
31643
|
+
import { z as z126 } from "zod";
|
|
30760
31644
|
|
|
30761
31645
|
// src/tools/product-research-idempotency.ts
|
|
30762
31646
|
var RESEARCH_IDEMPOTENCY_WINDOW_MS = 2 * 60 * 60 * 1e3;
|
|
@@ -31127,17 +32011,17 @@ var TERMINAL2 = /* @__PURE__ */ new Set([
|
|
|
31127
32011
|
]);
|
|
31128
32012
|
var POLL_INTERVAL_MS = 5e3;
|
|
31129
32013
|
var MAX_WAIT_SECONDS = 120;
|
|
31130
|
-
var FindInput =
|
|
32014
|
+
var FindInput = z126.object({
|
|
31131
32015
|
targetCountry: ResearchTargetCountryEnum.describe("Pa\xEDs objetivo. S\xF3lo CO, MX, PE, EC y CL tienen cobertura de investigaci\xF3n."),
|
|
31132
32016
|
categoryKey: ResearchCategoryEnum.default("auto").describe('Categor\xEDa a investigar. Usa "auto" para que la IA elija seg\xFAn el mercado.'),
|
|
31133
|
-
confirmed:
|
|
31134
|
-
waitSeconds:
|
|
32017
|
+
confirmed: z126.boolean().describe(`Ponlo en true SOLO despu\xE9s de que el usuario haya aceptado el cargo de ${PRICE_CREDITS} cr\xE9ditos. No lo asumas.`),
|
|
32018
|
+
waitSeconds: z126.number().int().min(0).max(MAX_WAIT_SECONDS).optional().describe("Segundos a esperar el reporte antes de devolver el runId. M\xE1ximo 120.")
|
|
31135
32019
|
});
|
|
31136
|
-
var GetInput =
|
|
31137
|
-
runId:
|
|
32020
|
+
var GetInput = z126.object({
|
|
32021
|
+
runId: z126.string().uuid().describe("ID del run devuelto por find_winning_products.")
|
|
31138
32022
|
});
|
|
31139
|
-
var ListInput =
|
|
31140
|
-
limit:
|
|
32023
|
+
var ListInput = z126.object({
|
|
32024
|
+
limit: z126.number().int().min(1).max(20).default(10)
|
|
31141
32025
|
});
|
|
31142
32026
|
function textResult(text2) {
|
|
31143
32027
|
return {
|
|
@@ -31276,17 +32160,17 @@ _Esta llamada no cobr\xF3 cr\xE9ditos: se reutiliz\xF3 una investigaci\xF3n reci
|
|
|
31276
32160
|
__name(registerProductResearchTools, "registerProductResearchTools");
|
|
31277
32161
|
|
|
31278
32162
|
// src/tools/dropshipping-projects.ts
|
|
31279
|
-
import { z as
|
|
32163
|
+
import { z as z127 } from "zod";
|
|
31280
32164
|
var startDropshippingProjectGuidance = getProductMakerCapabilityModelGuidance("start_dropshipping_project");
|
|
31281
32165
|
var costDropshippingProjectGuidance = getProductMakerCapabilityModelGuidance("cost_dropshipping_project");
|
|
31282
32166
|
var guideDropshippingLaunchGuidance = getProductMakerCapabilityModelGuidance("guide_dropshipping_launch");
|
|
31283
|
-
var ProjectId =
|
|
31284
|
-
var DropshippingProjectStatus =
|
|
32167
|
+
var ProjectId = z127.string().uuid();
|
|
32168
|
+
var DropshippingProjectStatus = z127.enum([
|
|
31285
32169
|
"active",
|
|
31286
32170
|
"completed",
|
|
31287
32171
|
"archived"
|
|
31288
32172
|
]);
|
|
31289
|
-
var DropshippingProjectPhase =
|
|
32173
|
+
var DropshippingProjectPhase = z127.enum([
|
|
31290
32174
|
"research",
|
|
31291
32175
|
"costing",
|
|
31292
32176
|
"media",
|
|
@@ -31295,21 +32179,21 @@ var DropshippingProjectPhase = z124.enum([
|
|
|
31295
32179
|
"advertising",
|
|
31296
32180
|
"complete"
|
|
31297
32181
|
]);
|
|
31298
|
-
var DropshippingRewardMilestone =
|
|
32182
|
+
var DropshippingRewardMilestone = z127.enum([
|
|
31299
32183
|
"product_selected",
|
|
31300
32184
|
"costing_saved",
|
|
31301
32185
|
"product_media_ready",
|
|
31302
32186
|
"creatives_completed",
|
|
31303
32187
|
"campaign_created"
|
|
31304
32188
|
]);
|
|
31305
|
-
var TargetCountry =
|
|
32189
|
+
var TargetCountry = z127.enum([
|
|
31306
32190
|
"CO",
|
|
31307
32191
|
"MX",
|
|
31308
32192
|
"PE",
|
|
31309
32193
|
"EC",
|
|
31310
32194
|
"CL"
|
|
31311
32195
|
]);
|
|
31312
|
-
var ResearchCategory =
|
|
32196
|
+
var ResearchCategory = z127.enum([
|
|
31313
32197
|
"auto",
|
|
31314
32198
|
"automotive",
|
|
31315
32199
|
"kitchen",
|
|
@@ -31324,7 +32208,7 @@ var ResearchCategory = z124.enum([
|
|
|
31324
32208
|
"garden",
|
|
31325
32209
|
"cleaning"
|
|
31326
32210
|
]);
|
|
31327
|
-
var HttpUrl3 =
|
|
32211
|
+
var HttpUrl3 = z127.string().url().max(2e3).refine((value) => {
|
|
31328
32212
|
try {
|
|
31329
32213
|
return [
|
|
31330
32214
|
"http:",
|
|
@@ -31334,45 +32218,45 @@ var HttpUrl3 = z124.string().url().max(2e3).refine((value) => {
|
|
|
31334
32218
|
return false;
|
|
31335
32219
|
}
|
|
31336
32220
|
}, "URL must use HTTP(S)");
|
|
31337
|
-
var ResearchCandidateOrigin =
|
|
31338
|
-
type:
|
|
32221
|
+
var ResearchCandidateOrigin = z127.object({
|
|
32222
|
+
type: z127.literal("research_candidate"),
|
|
31339
32223
|
runId: ProjectId,
|
|
31340
|
-
candidateSourceId:
|
|
32224
|
+
candidateSourceId: z127.string().min(1).max(160)
|
|
31341
32225
|
});
|
|
31342
|
-
var TaskOrigin =
|
|
31343
|
-
type:
|
|
32226
|
+
var TaskOrigin = z127.object({
|
|
32227
|
+
type: z127.literal("task"),
|
|
31344
32228
|
taskId: ProjectId
|
|
31345
32229
|
});
|
|
31346
|
-
var ManualOrigin =
|
|
31347
|
-
type:
|
|
32230
|
+
var ManualOrigin = z127.object({
|
|
32231
|
+
type: z127.literal("manual"),
|
|
31348
32232
|
clientRequestId: ProjectId,
|
|
31349
|
-
country:
|
|
32233
|
+
country: z127.string().min(2).max(3).optional()
|
|
31350
32234
|
});
|
|
31351
|
-
var StartProjectInput =
|
|
31352
|
-
origin:
|
|
32235
|
+
var StartProjectInput = z127.object({
|
|
32236
|
+
origin: z127.discriminatedUnion("type", [
|
|
31353
32237
|
ResearchCandidateOrigin,
|
|
31354
32238
|
TaskOrigin,
|
|
31355
32239
|
ManualOrigin
|
|
31356
32240
|
]),
|
|
31357
|
-
productName:
|
|
32241
|
+
productName: z127.string().trim().min(1).max(240).optional(),
|
|
31358
32242
|
productUrl: HttpUrl3.optional(),
|
|
31359
|
-
country:
|
|
31360
|
-
languageCode:
|
|
32243
|
+
country: z127.string().min(2).max(3).optional(),
|
|
32244
|
+
languageCode: z127.string().min(2).max(16).optional()
|
|
31361
32245
|
}).strict();
|
|
31362
|
-
var CostProjectInput =
|
|
32246
|
+
var CostProjectInput = z127.object({
|
|
31363
32247
|
projectId: ProjectId,
|
|
31364
|
-
version:
|
|
31365
|
-
country:
|
|
32248
|
+
version: z127.number().int().positive().describe("Version le\xEDda del proyecto. Copia la \xFAltima que viste; no se la pidas al usuario."),
|
|
32249
|
+
country: z127.string().trim().toUpperCase().refine(isProductCostingCountryCode, {
|
|
31366
32250
|
message: "Pa\xEDs no soportado por ProductMaker."
|
|
31367
32251
|
}).describe("C\xF3digo ISO-2 del pa\xEDs del proyecto. Usa el `targetCountry` que devuelve el proyecto."),
|
|
31368
|
-
supplierCost:
|
|
31369
|
-
shippingCost:
|
|
31370
|
-
returnRate:
|
|
31371
|
-
cancellationRate:
|
|
31372
|
-
adsManagerCpa:
|
|
31373
|
-
desiredMargin:
|
|
31374
|
-
});
|
|
31375
|
-
var GuideInput =
|
|
32252
|
+
supplierCost: z127.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."),
|
|
32253
|
+
shippingCost: z127.number().finite().nonnegative().optional().describe("Costo de un env\xEDo. Omite para usar el valor gu\xEDa del pa\xEDs en vez de inventarlo."),
|
|
32254
|
+
returnRate: z127.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."),
|
|
32255
|
+
cancellationRate: z127.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."),
|
|
32256
|
+
adsManagerCpa: z127.number().finite().nonnegative().optional().describe("Costo por compra de Ads Manager. Omite para usar el valor gu\xEDa del pa\xEDs."),
|
|
32257
|
+
desiredMargin: z127.number().finite().min(0).lt(1).optional().describe("Margen deseado (0.1 = 10%). Omite para usar el valor gu\xEDa del pa\xEDs.")
|
|
32258
|
+
});
|
|
32259
|
+
var GuideInput = z127.object({
|
|
31376
32260
|
projectId: ProjectId.optional()
|
|
31377
32261
|
}).strict();
|
|
31378
32262
|
var NEXT_ACTION_KINDS = [
|
|
@@ -31390,29 +32274,29 @@ var NEXT_ACTION_KINDS = [
|
|
|
31390
32274
|
"repair_meta_campaign",
|
|
31391
32275
|
"review_paused_campaign"
|
|
31392
32276
|
];
|
|
31393
|
-
var DropshippingProjectNextAction =
|
|
31394
|
-
kind:
|
|
31395
|
-
href:
|
|
32277
|
+
var DropshippingProjectNextAction = z127.discriminatedUnion("kind", NEXT_ACTION_KINDS.map((kind) => z127.object({
|
|
32278
|
+
kind: z127.literal(kind),
|
|
32279
|
+
href: z127.string().min(1)
|
|
31396
32280
|
})));
|
|
31397
|
-
var CostingInputSchema =
|
|
31398
|
-
country:
|
|
31399
|
-
supplierCost:
|
|
31400
|
-
shippingCost:
|
|
31401
|
-
returnRate:
|
|
31402
|
-
cancellationRate:
|
|
31403
|
-
adsManagerCpa:
|
|
31404
|
-
desiredMargin:
|
|
32281
|
+
var CostingInputSchema = z127.object({
|
|
32282
|
+
country: z127.string().min(2).max(3),
|
|
32283
|
+
supplierCost: z127.number().finite().positive(),
|
|
32284
|
+
shippingCost: z127.number().finite().nonnegative(),
|
|
32285
|
+
returnRate: z127.number().finite().min(0).lt(1),
|
|
32286
|
+
cancellationRate: z127.number().finite().min(0).lt(1),
|
|
32287
|
+
adsManagerCpa: z127.number().finite().nonnegative(),
|
|
32288
|
+
desiredMargin: z127.number().finite().min(0).lt(1)
|
|
31405
32289
|
}).superRefine(({ returnRate, cancellationRate }, context) => {
|
|
31406
32290
|
if (returnRate + cancellationRate >= 1) context.addIssue({
|
|
31407
|
-
code:
|
|
32291
|
+
code: z127.ZodIssueCode.custom,
|
|
31408
32292
|
path: [
|
|
31409
32293
|
"cancellationRate"
|
|
31410
32294
|
]
|
|
31411
32295
|
});
|
|
31412
32296
|
});
|
|
31413
|
-
var FiniteCostingNumber =
|
|
31414
|
-
var CostingResultSchema =
|
|
31415
|
-
country:
|
|
32297
|
+
var FiniteCostingNumber = z127.number().finite();
|
|
32298
|
+
var CostingResultSchema = z127.object({
|
|
32299
|
+
country: z127.string().min(2).max(3),
|
|
31416
32300
|
deliveryRate: FiniteCostingNumber,
|
|
31417
32301
|
adjustedShippingCost: FiniteCostingNumber,
|
|
31418
32302
|
adjustedCpa: FiniteCostingNumber,
|
|
@@ -31425,45 +32309,45 @@ var CostingResultSchema = z124.object({
|
|
|
31425
32309
|
estimatedProfit: FiniteCostingNumber,
|
|
31426
32310
|
actualMargin: FiniteCostingNumber,
|
|
31427
32311
|
breakEvenAdsManagerCpa: FiniteCostingNumber,
|
|
31428
|
-
wasPriceAutoAdjusted:
|
|
31429
|
-
profitabilityStatus:
|
|
31430
|
-
});
|
|
31431
|
-
var RewardProgressSchema =
|
|
31432
|
-
campaignKey:
|
|
31433
|
-
totalCredits:
|
|
31434
|
-
earnedCredits:
|
|
31435
|
-
milestones:
|
|
32312
|
+
wasPriceAutoAdjusted: z127.boolean(),
|
|
32313
|
+
profitabilityStatus: z127.literal("healthy")
|
|
32314
|
+
});
|
|
32315
|
+
var RewardProgressSchema = z127.object({
|
|
32316
|
+
campaignKey: z127.literal("dropshipping-launch-v1"),
|
|
32317
|
+
totalCredits: z127.number().int().positive(),
|
|
32318
|
+
earnedCredits: z127.number().int().nonnegative(),
|
|
32319
|
+
milestones: z127.array(z127.object({
|
|
31436
32320
|
milestone: DropshippingRewardMilestone,
|
|
31437
|
-
credits:
|
|
31438
|
-
available:
|
|
31439
|
-
earned:
|
|
32321
|
+
credits: z127.number().int().positive(),
|
|
32322
|
+
available: z127.boolean().optional(),
|
|
32323
|
+
earned: z127.boolean(),
|
|
31440
32324
|
grantId: ProjectId.nullable(),
|
|
31441
|
-
grantedAt:
|
|
32325
|
+
grantedAt: z127.string().datetime().nullable()
|
|
31442
32326
|
})).length(DropshippingRewardMilestone.options.length)
|
|
31443
32327
|
});
|
|
31444
|
-
var ProjectResponseSchema =
|
|
32328
|
+
var ProjectResponseSchema = z127.object({
|
|
31445
32329
|
id: ProjectId,
|
|
31446
32330
|
taskId: ProjectId.nullable(),
|
|
31447
32331
|
status: DropshippingProjectStatus,
|
|
31448
|
-
originKey:
|
|
32332
|
+
originKey: z127.string().min(1),
|
|
31449
32333
|
researchRunId: ProjectId.nullable(),
|
|
31450
|
-
candidateSourceId:
|
|
31451
|
-
productName:
|
|
32334
|
+
candidateSourceId: z127.string().nullable(),
|
|
32335
|
+
productName: z127.string().nullable(),
|
|
31452
32336
|
productUrl: HttpUrl3.nullable(),
|
|
31453
32337
|
referenceImageUrl: HttpUrl3.nullable(),
|
|
31454
|
-
targetCountry:
|
|
31455
|
-
languageCode:
|
|
31456
|
-
hasReferenceImage:
|
|
31457
|
-
hasWinnerReferenceImage:
|
|
32338
|
+
targetCountry: z127.string().nullable(),
|
|
32339
|
+
languageCode: z127.string().nullable(),
|
|
32340
|
+
hasReferenceImage: z127.boolean(),
|
|
32341
|
+
hasWinnerReferenceImage: z127.boolean(),
|
|
31458
32342
|
costingInput: CostingInputSchema.nullable(),
|
|
31459
32343
|
costingResult: CostingResultSchema.nullable(),
|
|
31460
|
-
costingVersion:
|
|
31461
|
-
version:
|
|
32344
|
+
costingVersion: z127.number().int().positive().nullable(),
|
|
32345
|
+
version: z127.number().int().positive(),
|
|
31462
32346
|
phase: DropshippingProjectPhase,
|
|
31463
32347
|
nextAction: DropshippingProjectNextAction.nullable(),
|
|
31464
32348
|
rewards: RewardProgressSchema,
|
|
31465
|
-
createdAt:
|
|
31466
|
-
updatedAt:
|
|
32349
|
+
createdAt: z127.string().datetime(),
|
|
32350
|
+
updatedAt: z127.string().datetime()
|
|
31467
32351
|
}).passthrough();
|
|
31468
32352
|
var NEXT_ACTION_GUIDANCE = {
|
|
31469
32353
|
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.",
|
|
@@ -31483,14 +32367,14 @@ var NEXT_ACTION_GUIDANCE = {
|
|
|
31483
32367
|
function projectText(raw, referenceValues = []) {
|
|
31484
32368
|
const project = ProjectResponseSchema.parse(raw);
|
|
31485
32369
|
const kind = project.nextAction?.kind;
|
|
31486
|
-
const
|
|
32370
|
+
const guidance5 = kind ? NEXT_ACTION_GUIDANCE[kind] : void 0;
|
|
31487
32371
|
const winnerReferenceResult = project.hasWinnerReferenceImage ? WinnerReferenceInput.safeParse({
|
|
31488
32372
|
runId: project.researchRunId,
|
|
31489
32373
|
candidateSourceId: project.candidateSourceId
|
|
31490
32374
|
}) : void 0;
|
|
31491
32375
|
const winnerReference = winnerReferenceResult?.success ? winnerReferenceResult.data : void 0;
|
|
31492
32376
|
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;
|
|
31493
|
-
const guidedNextActionGuidance =
|
|
32377
|
+
const guidedNextActionGuidance = guidance5 ? `S\xF3lo si el usuario quiere continuar el lanzamiento guiado completo: ${guidance5}` : void 0;
|
|
31494
32378
|
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;
|
|
31495
32379
|
return {
|
|
31496
32380
|
content: [
|
|
@@ -31583,58 +32467,58 @@ function registerDropshippingProjectTools(server, api) {
|
|
|
31583
32467
|
__name(registerDropshippingProjectTools, "registerDropshippingProjectTools");
|
|
31584
32468
|
|
|
31585
32469
|
// src/tools/daily-control.ts
|
|
31586
|
-
import { z as
|
|
32470
|
+
import { z as z128 } from "zod";
|
|
31587
32471
|
var getDailyControlGuidance = getProductMakerCapabilityModelGuidance("get_daily_control");
|
|
31588
32472
|
var linkCampaignToProjectGuidance = getProductMakerCapabilityModelGuidance("link_campaign_to_project");
|
|
31589
32473
|
var reportDailyReturnsGuidance = getProductMakerCapabilityModelGuidance("report_daily_returns");
|
|
31590
|
-
var ProviderCampaignIdentifier =
|
|
31591
|
-
var DateOnly =
|
|
31592
|
-
var DailyControlInput =
|
|
32474
|
+
var ProviderCampaignIdentifier = z128.string().min(1).max(64).regex(/^[A-Za-z0-9_-]+$/);
|
|
32475
|
+
var DateOnly = z128.string().regex(/^\d{4}-\d{2}-\d{2}$/);
|
|
32476
|
+
var DailyControlInput = z128.object({
|
|
31593
32477
|
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."),
|
|
31594
|
-
days:
|
|
31595
|
-
projectId:
|
|
32478
|
+
days: z128.number().int().min(1).max(14).default(3).describe("Tama\xF1o de la ventana, 1 a 14 d\xEDas."),
|
|
32479
|
+
projectId: z128.string().uuid().optional().describe("Opcional: limita el reporte a un solo proyecto en vez de todos los productos activos.")
|
|
31596
32480
|
}).strict();
|
|
31597
|
-
var LinkCampaignInput =
|
|
31598
|
-
projectId:
|
|
31599
|
-
provider:
|
|
32481
|
+
var LinkCampaignInput = z128.object({
|
|
32482
|
+
projectId: z128.string().uuid(),
|
|
32483
|
+
provider: z128.enum([
|
|
31600
32484
|
"meta",
|
|
31601
32485
|
"tiktok"
|
|
31602
32486
|
]),
|
|
31603
32487
|
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."),
|
|
31604
32488
|
campaignId: ProviderCampaignIdentifier
|
|
31605
32489
|
}).strict();
|
|
31606
|
-
var ReportReturnsInput =
|
|
31607
|
-
projectId:
|
|
32490
|
+
var ReportReturnsInput = z128.object({
|
|
32491
|
+
projectId: z128.string().uuid(),
|
|
31608
32492
|
date: DateOnly,
|
|
31609
|
-
returnedOrders:
|
|
31610
|
-
note:
|
|
32493
|
+
returnedOrders: z128.number().int().min(0).max(1e5),
|
|
32494
|
+
note: z128.string().trim().max(280).optional()
|
|
31611
32495
|
}).strict();
|
|
31612
|
-
var DailyControlStatusMirror = DailyControlStatusEnum.or(
|
|
31613
|
-
var DailyControlLightMirror = DailyControlLightEnum.or(
|
|
31614
|
-
var DailyControlSourceMirror = DailyControlSourceEnum.or(
|
|
31615
|
-
var DailyControlVerdictMirror = DailyControlVerdictEnum.or(
|
|
31616
|
-
var DailyControlDayMirror =
|
|
31617
|
-
date:
|
|
31618
|
-
returnedOrders:
|
|
32496
|
+
var DailyControlStatusMirror = DailyControlStatusEnum.or(z128.string());
|
|
32497
|
+
var DailyControlLightMirror = DailyControlLightEnum.or(z128.string());
|
|
32498
|
+
var DailyControlSourceMirror = DailyControlSourceEnum.or(z128.string());
|
|
32499
|
+
var DailyControlVerdictMirror = DailyControlVerdictEnum.or(z128.string());
|
|
32500
|
+
var DailyControlDayMirror = z128.object({
|
|
32501
|
+
date: z128.string(),
|
|
32502
|
+
returnedOrders: z128.number(),
|
|
31619
32503
|
returnedSource: DailyControlSourceMirror,
|
|
31620
32504
|
cancelledSource: DailyControlSourceMirror,
|
|
31621
|
-
hasEstimates:
|
|
32505
|
+
hasEstimates: z128.boolean(),
|
|
31622
32506
|
light: DailyControlLightMirror
|
|
31623
32507
|
}).passthrough();
|
|
31624
|
-
var DailyControlProductMirror =
|
|
31625
|
-
projectId:
|
|
31626
|
-
productName:
|
|
32508
|
+
var DailyControlProductMirror = z128.object({
|
|
32509
|
+
projectId: z128.string().uuid(),
|
|
32510
|
+
productName: z128.string().nullable(),
|
|
31627
32511
|
status: DailyControlStatusMirror,
|
|
31628
|
-
statusMessage:
|
|
31629
|
-
days:
|
|
32512
|
+
statusMessage: z128.string().nullable(),
|
|
32513
|
+
days: z128.array(DailyControlDayMirror),
|
|
31630
32514
|
verdict: DailyControlVerdictMirror.nullable(),
|
|
31631
|
-
rationale:
|
|
32515
|
+
rationale: z128.string().nullable()
|
|
31632
32516
|
}).passthrough();
|
|
31633
|
-
var DailyControlResponseMirror =
|
|
31634
|
-
since:
|
|
31635
|
-
until:
|
|
31636
|
-
products:
|
|
31637
|
-
archivedProjectSpend:
|
|
32517
|
+
var DailyControlResponseMirror = z128.object({
|
|
32518
|
+
since: z128.string(),
|
|
32519
|
+
until: z128.string(),
|
|
32520
|
+
products: z128.array(DailyControlProductMirror),
|
|
32521
|
+
archivedProjectSpend: z128.array(z128.unknown())
|
|
31638
32522
|
}).passthrough();
|
|
31639
32523
|
var MCP_WARNINGS_KEY = "_mcpDailyControlWarnings";
|
|
31640
32524
|
function withWarnings(response) {
|
|
@@ -31709,7 +32593,7 @@ __name(registerDailyControlTools, "registerDailyControlTools");
|
|
|
31709
32593
|
// src/tools/product-control.ts
|
|
31710
32594
|
import { readFile, stat } from "fs/promises";
|
|
31711
32595
|
import { basename as basename2, isAbsolute, resolve as resolve3 } from "path";
|
|
31712
|
-
import { z as
|
|
32596
|
+
import { z as z129 } from "zod";
|
|
31713
32597
|
var getProductControlGuidance = getProductMakerCapabilityModelGuidance("get_product_control");
|
|
31714
32598
|
var listImportableProductsGuidance = getProductMakerCapabilityModelGuidance("list_importable_products");
|
|
31715
32599
|
var importProductControlsGuidance = getProductMakerCapabilityModelGuidance("import_product_controls");
|
|
@@ -31722,36 +32606,36 @@ var createOperationGuidance = getProductMakerCapabilityModelGuidance("create_ope
|
|
|
31722
32606
|
var mergeProductsGuidance = getProductMakerCapabilityModelGuidance("merge_products");
|
|
31723
32607
|
var syncShopifyOrdersGuidance = getProductMakerCapabilityModelGuidance("sync_shopify_orders");
|
|
31724
32608
|
var importOrdersFileGuidance = getProductMakerCapabilityModelGuidance("import_orders_file");
|
|
31725
|
-
var DateOnly2 =
|
|
32609
|
+
var DateOnly2 = z129.string().regex(/^\d{4}-\d{2}-\d{2}$/);
|
|
31726
32610
|
var IMPORT_BATCH_MAX2 = 50;
|
|
31727
|
-
var ProductControlListInput =
|
|
31728
|
-
days:
|
|
32611
|
+
var ProductControlListInput = z129.object({
|
|
32612
|
+
days: z129.number().int().min(1).max(14).default(3).describe("Tama\xF1o de la ventana, 1 a 14 d\xEDas."),
|
|
31729
32613
|
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."),
|
|
31730
|
-
shopDomain:
|
|
32614
|
+
shopDomain: z129.string().min(1).optional().describe("Tienda, si tiene m\xE1s de una conectada.")
|
|
31731
32615
|
}).strict();
|
|
31732
|
-
var ImportableProductsInput =
|
|
31733
|
-
q:
|
|
31734
|
-
after:
|
|
31735
|
-
shopDomain:
|
|
32616
|
+
var ImportableProductsInput = z129.object({
|
|
32617
|
+
q: z129.string().trim().max(120).optional().describe("B\xFAsqueda por t\xEDtulo. Sin esto un cat\xE1logo grande es inrecorrible."),
|
|
32618
|
+
after: z129.string().max(512).optional().describe("Cursor de la p\xE1gina anterior, tal como vino en la respuesta."),
|
|
32619
|
+
shopDomain: z129.string().min(1).optional()
|
|
31736
32620
|
}).strict();
|
|
31737
|
-
var ImportProductControlsInput =
|
|
31738
|
-
shopifyProductIds:
|
|
32621
|
+
var ImportProductControlsInput = z129.object({
|
|
32622
|
+
shopifyProductIds: z129.array(z129.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."),
|
|
31739
32623
|
// Obligatorio, como en el endpoint: con dos tiendas conectadas, importar
|
|
31740
32624
|
// sin decir a cuál archivaría el producto bajo la tienda equivocada.
|
|
31741
|
-
shopDomain:
|
|
32625
|
+
shopDomain: z129.string().min(1)
|
|
31742
32626
|
}).strict();
|
|
31743
|
-
var SetProductCostingInput =
|
|
31744
|
-
shopDomain:
|
|
31745
|
-
shopifyProductId:
|
|
31746
|
-
targetCountry:
|
|
31747
|
-
supplierCost:
|
|
31748
|
-
shippingCost:
|
|
31749
|
-
returnRate:
|
|
31750
|
-
cancellationRate:
|
|
32627
|
+
var SetProductCostingInput = z129.object({
|
|
32628
|
+
shopDomain: z129.string().min(1),
|
|
32629
|
+
shopifyProductId: z129.string().min(1).max(64),
|
|
32630
|
+
targetCountry: z129.string().length(2).describe("Pa\xEDs de venta en ISO-2, que fija moneda y referencia de flete."),
|
|
32631
|
+
supplierCost: z129.number().finite().positive().describe("Lo que le cuesta el producto al proveedor. Nunca cero."),
|
|
32632
|
+
shippingCost: z129.number().finite().nonnegative(),
|
|
32633
|
+
returnRate: z129.number().finite().min(0).lt(1).describe("Proporci\xF3n 0\u20131, no porcentaje: 0.15 es quince por ciento."),
|
|
32634
|
+
cancellationRate: z129.number().finite().min(0).lt(1)
|
|
31751
32635
|
}).strict();
|
|
31752
|
-
var Count =
|
|
31753
|
-
var ReportProductCountsInput =
|
|
31754
|
-
productControlId:
|
|
32636
|
+
var Count = z129.number().int().min(0).max(1e5).nullable().optional();
|
|
32637
|
+
var ReportProductCountsInput = z129.object({
|
|
32638
|
+
productControlId: z129.string().uuid().describe("El identificador que devuelve get_product_control."),
|
|
31755
32639
|
date: DateOnly2,
|
|
31756
32640
|
orders: Count.describe("Pedidos que entraron ese d\xEDa."),
|
|
31757
32641
|
cancelledOrders: Count,
|
|
@@ -31761,36 +32645,36 @@ var ReportProductCountsInput = z126.object({
|
|
|
31761
32645
|
}).strict();
|
|
31762
32646
|
var MAX_ORDERS_FILE_BYTES = 8 * 1024 * 1024;
|
|
31763
32647
|
var SyncShopifyOrdersInput = SyncShopifyOrdersSchema;
|
|
31764
|
-
var ImportOrdersFileInput =
|
|
32648
|
+
var ImportOrdersFileInput = z129.object({
|
|
31765
32649
|
/** Ruta ABSOLUTA en la máquina de quien usa el MCP. */
|
|
31766
|
-
path:
|
|
31767
|
-
operationId:
|
|
32650
|
+
path: z129.string().min(1),
|
|
32651
|
+
operationId: z129.string().uuid().optional(),
|
|
31768
32652
|
/** Solo mirar: devuelve el resumen y no escribe nada. */
|
|
31769
|
-
dryRun:
|
|
32653
|
+
dryRun: z129.boolean().optional()
|
|
31770
32654
|
}).strict();
|
|
31771
|
-
var CreateOperationInput =
|
|
31772
|
-
countryCode:
|
|
31773
|
-
label:
|
|
32655
|
+
var CreateOperationInput = z129.object({
|
|
32656
|
+
countryCode: z129.string().length(2),
|
|
32657
|
+
label: z129.string().trim().min(1).max(60).optional()
|
|
31774
32658
|
}).strict();
|
|
31775
|
-
var MergeProductsInput =
|
|
31776
|
-
sourceId:
|
|
31777
|
-
targetId:
|
|
32659
|
+
var MergeProductsInput = z129.object({
|
|
32660
|
+
sourceId: z129.string().uuid(),
|
|
32661
|
+
targetId: z129.string().uuid()
|
|
31778
32662
|
}).strict();
|
|
31779
|
-
var SetMonthlyExpenseInput =
|
|
31780
|
-
month:
|
|
32663
|
+
var SetMonthlyExpenseInput = z129.object({
|
|
32664
|
+
month: z129.string().regex(/^\d{4}-(0[1-9]|1[0-2])$/).optional(),
|
|
31781
32665
|
label: SaveMonthlyExpenseSchema.shape.label,
|
|
31782
|
-
amount:
|
|
31783
|
-
operationId:
|
|
32666
|
+
amount: z129.number().finite().min(0),
|
|
32667
|
+
operationId: z129.string().uuid().optional()
|
|
31784
32668
|
}).strict();
|
|
31785
|
-
var SetAdSpendInput =
|
|
31786
|
-
month:
|
|
31787
|
-
operationId:
|
|
32669
|
+
var SetAdSpendInput = z129.object({
|
|
32670
|
+
month: z129.string().regex(/^\d{4}-(0[1-9]|1[0-2])$/).optional(),
|
|
32671
|
+
operationId: z129.string().uuid().optional(),
|
|
31788
32672
|
/** null RETIRA la corrección; cero afirma que no pautó. */
|
|
31789
|
-
amount:
|
|
32673
|
+
amount: z129.number().finite().min(0).nullable()
|
|
31790
32674
|
}).strict();
|
|
31791
|
-
var GetFinancesInput =
|
|
31792
|
-
month:
|
|
31793
|
-
operationId:
|
|
32675
|
+
var GetFinancesInput = z129.object({
|
|
32676
|
+
month: z129.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."),
|
|
32677
|
+
operationId: z129.string().uuid().optional().describe("El negocio, si tiene mas de uno. Con uno solo no hace falta decirlo.")
|
|
31794
32678
|
}).strict();
|
|
31795
32679
|
function registerProductControlTools(server, api, { transport = "http" } = {}) {
|
|
31796
32680
|
server.registerTool("get_product_control", {
|
|
@@ -31980,28 +32864,28 @@ function registerProductControlTools(server, api, { transport = "http" } = {}) {
|
|
|
31980
32864
|
__name(registerProductControlTools, "registerProductControlTools");
|
|
31981
32865
|
|
|
31982
32866
|
// src/tools/market-study-estimate.ts
|
|
31983
|
-
import { z as
|
|
32867
|
+
import { z as z130 } from "zod";
|
|
31984
32868
|
var estimateMarketStudyGuidance = getProductMakerCapabilityModelGuidance("estimate_market_study_cost");
|
|
31985
|
-
var ProductTaskOutputEnum =
|
|
32869
|
+
var ProductTaskOutputEnum = z130.enum([
|
|
31986
32870
|
"landing",
|
|
31987
32871
|
"video",
|
|
31988
32872
|
"image"
|
|
31989
32873
|
]);
|
|
31990
|
-
var EstimateMarketStudyInput =
|
|
31991
|
-
kind:
|
|
32874
|
+
var EstimateMarketStudyInput = z130.object({
|
|
32875
|
+
kind: z130.enum([
|
|
31992
32876
|
"video",
|
|
31993
32877
|
"product_task"
|
|
31994
32878
|
]).describe('"video" para generate_video_creative; "product_task" para create_product_task.'),
|
|
31995
|
-
videoAngleCount:
|
|
31996
|
-
durationSeconds:
|
|
32879
|
+
videoAngleCount: z130.number().int().min(1).max(3).describe("Los mismos 1, 2 o 3 videos que vas a pedir al crear."),
|
|
32880
|
+
durationSeconds: z130.number().int().min(4).max(30).default(15).describe("La misma duraci\xF3n que enviar\xE1s al crear."),
|
|
31997
32881
|
videoStructure: VideoStructureEnum.default("single_take"),
|
|
31998
32882
|
videoQuality: VideoQualityEnum.default("economy"),
|
|
31999
32883
|
videoResolution: VideoResolutionEnum.default("480p"),
|
|
32000
|
-
productImageCount:
|
|
32884
|
+
productImageCount: z130.number().int().min(1).max(5).optional().describe("Cantidad de fotos del producto que adjuntar\xE1s (1-5)."),
|
|
32001
32885
|
outputs: ProductTaskOutputEnum.array().min(1).max(3).optional().describe('Solo para kind="product_task": los mismos outputs de create_product_task. Default: todos.'),
|
|
32002
|
-
generateAiMusic:
|
|
32886
|
+
generateAiMusic: z130.boolean().optional(),
|
|
32003
32887
|
generateAiEdit: AiEditRequestSchema.optional(),
|
|
32004
|
-
replicateWinnerWhenFound:
|
|
32888
|
+
replicateWinnerWhenFound: z130.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.")
|
|
32005
32889
|
}).strict();
|
|
32006
32890
|
function buildMarketStudyEstimateRequests(input) {
|
|
32007
32891
|
const quotesVideo = input.kind === "video" || (input.outputs ?? [
|
|
@@ -32147,6 +33031,9 @@ function createServer(opts) {
|
|
|
32147
33031
|
proxyBaseUrl,
|
|
32148
33032
|
transport
|
|
32149
33033
|
});
|
|
33034
|
+
registerBrandTools(server, api);
|
|
33035
|
+
registerServiceLandingTools(server, api);
|
|
33036
|
+
registerServiceImageTools(server, api);
|
|
32150
33037
|
registerAnimatedVideoTools(server, api);
|
|
32151
33038
|
registerCreativeTools(server, api, {
|
|
32152
33039
|
transport
|