@productmaker/mcp 3.3.0 → 3.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/http.js +1513 -1170
- package/dist/stdio.js +1491 -1157
- package/package.json +2 -2
package/dist/stdio.js
CHANGED
|
@@ -264,7 +264,7 @@ var ApiClient = class {
|
|
|
264
264
|
};
|
|
265
265
|
|
|
266
266
|
// src/index.ts
|
|
267
|
-
var MCP_VERSION = "3.
|
|
267
|
+
var MCP_VERSION = "3.4.1";
|
|
268
268
|
|
|
269
269
|
// src/meta-upload-app.ts
|
|
270
270
|
import { readFileSync } from "fs";
|
|
@@ -336,7 +336,7 @@ function registerMediaUploadAppResource(server, resolveEndpoint) {
|
|
|
336
336
|
__name(registerMediaUploadAppResource, "registerMediaUploadAppResource");
|
|
337
337
|
|
|
338
338
|
// src/tools/tasks.ts
|
|
339
|
-
import { z as
|
|
339
|
+
import { z as z49 } from "zod";
|
|
340
340
|
|
|
341
341
|
// src/resolve-image.ts
|
|
342
342
|
import { lookup as dnsLookup } from "dns/promises";
|
|
@@ -1517,6 +1517,42 @@ var VideoTier = z5.preprocess((v) => v === "grok" ? "single_call" : v, z5.enum([
|
|
|
1517
1517
|
]));
|
|
1518
1518
|
var DEFAULT_VIDEO_TIER = "single_call";
|
|
1519
1519
|
|
|
1520
|
+
// ../../packages/types/src/image-provider.ts
|
|
1521
|
+
var GPT_IMAGE_2_RATIOS_BLOCKED_ABOVE_1K = [
|
|
1522
|
+
"4:5",
|
|
1523
|
+
"5:4"
|
|
1524
|
+
];
|
|
1525
|
+
function gptImage2SupportsTier(aspectRatio, tier) {
|
|
1526
|
+
if (tier === "gpt-image-2:1k") return true;
|
|
1527
|
+
return !GPT_IMAGE_2_RATIOS_BLOCKED_ABOVE_1K.includes(aspectRatio);
|
|
1528
|
+
}
|
|
1529
|
+
__name(gptImage2SupportsTier, "gptImage2SupportsTier");
|
|
1530
|
+
var IMAGE_TIER_UNSUPPORTED_FOR_RATIO_RULE = "image_tier_unsupported_for_ratio";
|
|
1531
|
+
var IMAGE_TIER_LIST = [
|
|
1532
|
+
"gpt-image-2:1k",
|
|
1533
|
+
"gpt-image-2:2k",
|
|
1534
|
+
"gpt-image-2:4k"
|
|
1535
|
+
];
|
|
1536
|
+
function parseImageTier(imageModel) {
|
|
1537
|
+
const normalized = imageModel.trim().toLowerCase();
|
|
1538
|
+
return IMAGE_TIER_LIST.includes(normalized) ? normalized : null;
|
|
1539
|
+
}
|
|
1540
|
+
__name(parseImageTier, "parseImageTier");
|
|
1541
|
+
function validateImageTierForRatio(aspectRatio, imageModel) {
|
|
1542
|
+
if (!aspectRatio || !imageModel) return null;
|
|
1543
|
+
const tier = parseImageTier(imageModel);
|
|
1544
|
+
if (!tier) return null;
|
|
1545
|
+
const ratio = aspectRatio.trim();
|
|
1546
|
+
if (!GPT_IMAGE_2_RATIOS_BLOCKED_ABOVE_1K.includes(ratio)) return null;
|
|
1547
|
+
if (gptImage2SupportsTier(ratio, tier)) return null;
|
|
1548
|
+
return buildImageTierUnsupportedMessage(ratio);
|
|
1549
|
+
}
|
|
1550
|
+
__name(validateImageTierForRatio, "validateImageTierForRatio");
|
|
1551
|
+
function buildImageTierUnsupportedMessage(aspectRatio) {
|
|
1552
|
+
return `El formato ${aspectRatio} solo est\xE1 disponible en calidad est\xE1ndar (1K). Elige 1K, o cambia el formato para usar Premium.`;
|
|
1553
|
+
}
|
|
1554
|
+
__name(buildImageTierUnsupportedMessage, "buildImageTierUnsupportedMessage");
|
|
1555
|
+
|
|
1520
1556
|
// ../../packages/types/src/angle.ts
|
|
1521
1557
|
import { z as z6 } from "zod";
|
|
1522
1558
|
var ImageWinnerSignal = z6.enum([
|
|
@@ -2857,6 +2893,19 @@ var IngestOptions = z15.object({
|
|
|
2857
2893
|
message
|
|
2858
2894
|
});
|
|
2859
2895
|
}
|
|
2896
|
+
const tierMessage = validateImageTierForRatio(data.aspectRatio, data.imageModel);
|
|
2897
|
+
if (tierMessage) {
|
|
2898
|
+
ctx.addIssue({
|
|
2899
|
+
code: z15.ZodIssueCode.custom,
|
|
2900
|
+
path: [
|
|
2901
|
+
"imageModel"
|
|
2902
|
+
],
|
|
2903
|
+
params: {
|
|
2904
|
+
rule: IMAGE_TIER_UNSUPPORTED_FOR_RATIO_RULE
|
|
2905
|
+
},
|
|
2906
|
+
message: tierMessage
|
|
2907
|
+
});
|
|
2908
|
+
}
|
|
2860
2909
|
});
|
|
2861
2910
|
var IngestRequest = z15.object({
|
|
2862
2911
|
url: z15.string().url(),
|
|
@@ -3401,6 +3450,7 @@ var ShopifyPublicationMode = z20.enum([
|
|
|
3401
3450
|
"existing",
|
|
3402
3451
|
"new"
|
|
3403
3452
|
]);
|
|
3453
|
+
var SHOPIFY_ANNOUNCEMENT_BAR_MAX_LENGTH = 160;
|
|
3404
3454
|
var ShopifyPublishRequest = z20.object({
|
|
3405
3455
|
shopDomain: z20.string().min(3).max(120).optional(),
|
|
3406
3456
|
/**
|
|
@@ -3419,7 +3469,9 @@ var ShopifyPublishRequest = z20.object({
|
|
|
3419
3469
|
*/
|
|
3420
3470
|
publicationMode: ShopifyPublicationMode.default("auto"),
|
|
3421
3471
|
/** Shopify product GID, numeric id, or admin product URL. Required for existing mode. */
|
|
3422
|
-
targetProductId: z20.string().trim().min(1).max(300).optional()
|
|
3472
|
+
targetProductId: z20.string().trim().min(1).max(300).optional(),
|
|
3473
|
+
announcementBarEnabled: z20.boolean().optional(),
|
|
3474
|
+
announcementBarText: z20.string().trim().min(1).max(SHOPIFY_ANNOUNCEMENT_BAR_MAX_LENGTH).optional()
|
|
3423
3475
|
}).superRefine((data, ctx) => {
|
|
3424
3476
|
if (data.publicationMode === "existing" && !data.targetProductId) {
|
|
3425
3477
|
ctx.addIssue({
|
|
@@ -3430,6 +3482,24 @@ var ShopifyPublishRequest = z20.object({
|
|
|
3430
3482
|
message: "Selecciona un producto de Shopify."
|
|
3431
3483
|
});
|
|
3432
3484
|
}
|
|
3485
|
+
if (data.announcementBarEnabled && !data.announcementBarText) {
|
|
3486
|
+
ctx.addIssue({
|
|
3487
|
+
code: z20.ZodIssueCode.custom,
|
|
3488
|
+
path: [
|
|
3489
|
+
"announcementBarText"
|
|
3490
|
+
],
|
|
3491
|
+
message: "Escribe el texto del anuncio."
|
|
3492
|
+
});
|
|
3493
|
+
}
|
|
3494
|
+
if (!data.announcementBarEnabled && data.announcementBarText) {
|
|
3495
|
+
ctx.addIssue({
|
|
3496
|
+
code: z20.ZodIssueCode.custom,
|
|
3497
|
+
path: [
|
|
3498
|
+
"announcementBarText"
|
|
3499
|
+
],
|
|
3500
|
+
message: "Activa el anuncio para usar texto."
|
|
3501
|
+
});
|
|
3502
|
+
}
|
|
3433
3503
|
});
|
|
3434
3504
|
var ShopifyPublicationStatus = z20.enum([
|
|
3435
3505
|
"pending",
|
|
@@ -3512,6 +3582,10 @@ var CreateShopifyBillingChargeRequest = z20.object({
|
|
|
3512
3582
|
var CancelShopifySubscriptionRequest = z20.object({
|
|
3513
3583
|
subscriptionId: z20.string().min(1)
|
|
3514
3584
|
});
|
|
3585
|
+
var ShopifyAnnouncementBarState = z20.object({
|
|
3586
|
+
enabled: z20.boolean(),
|
|
3587
|
+
text: z20.string().max(SHOPIFY_ANNOUNCEMENT_BAR_MAX_LENGTH).optional()
|
|
3588
|
+
});
|
|
3515
3589
|
var LandingHealthResponse = z20.object({
|
|
3516
3590
|
taskId: z20.string(),
|
|
3517
3591
|
state: z20.enum([
|
|
@@ -3553,9 +3627,20 @@ var ApproveAnglesRequest = z21.object({
|
|
|
3553
3627
|
"mixed"
|
|
3554
3628
|
]).optional()
|
|
3555
3629
|
});
|
|
3630
|
+
var ApproveAnglesRefusalReason = z21.enum([
|
|
3631
|
+
"photo_review_pending",
|
|
3632
|
+
"photos_not_ready",
|
|
3633
|
+
"reference_out_of_date"
|
|
3634
|
+
]);
|
|
3556
3635
|
var ApproveAnglesResponse = z21.object({
|
|
3557
3636
|
success: z21.boolean(),
|
|
3558
|
-
taskId: z21.string().uuid()
|
|
3637
|
+
taskId: z21.string().uuid(),
|
|
3638
|
+
reason: ApproveAnglesRefusalReason.optional()
|
|
3639
|
+
});
|
|
3640
|
+
var IngestResumeRefusalResponse = z21.object({
|
|
3641
|
+
success: z21.literal(false),
|
|
3642
|
+
taskId: z21.string().uuid(),
|
|
3643
|
+
reason: ApproveAnglesRefusalReason.optional()
|
|
3559
3644
|
});
|
|
3560
3645
|
var GenerateSuggestionsResponse = z21.object({
|
|
3561
3646
|
painPoints: z21.array(z21.string()),
|
|
@@ -3671,7 +3756,8 @@ var ApproveVideoAnglesRequest = z21.object({
|
|
|
3671
3756
|
});
|
|
3672
3757
|
var ApproveVideoAnglesResponse = z21.object({
|
|
3673
3758
|
success: z21.boolean(),
|
|
3674
|
-
taskId: z21.string().uuid()
|
|
3759
|
+
taskId: z21.string().uuid(),
|
|
3760
|
+
reason: ApproveAnglesRefusalReason.optional()
|
|
3675
3761
|
});
|
|
3676
3762
|
var SaveVideoDraftRequest = z21.object({
|
|
3677
3763
|
/** Global creative direction for all videos. Not spoken dialogue. */
|
|
@@ -3703,7 +3789,8 @@ var ApproveImageAnglesRequest = z21.object({
|
|
|
3703
3789
|
});
|
|
3704
3790
|
var ApproveImageAnglesResponse = z21.object({
|
|
3705
3791
|
success: z21.boolean(),
|
|
3706
|
-
taskId: z21.string().uuid()
|
|
3792
|
+
taskId: z21.string().uuid(),
|
|
3793
|
+
reason: ApproveAnglesRefusalReason.optional()
|
|
3707
3794
|
});
|
|
3708
3795
|
var SaveImageDraftRequest = z21.object({
|
|
3709
3796
|
customAngle: z21.string().max(480).optional(),
|
|
@@ -4060,6 +4147,10 @@ var MVPResult = z24.object({
|
|
|
4060
4147
|
videoAngleCount: z24.number().int().min(1).max(3).optional(),
|
|
4061
4148
|
adCreative: AdCreative.optional(),
|
|
4062
4149
|
shopifyPageUrl: z24.string().url().optional(),
|
|
4150
|
+
/** What the announcement bar looks like ON THE PUBLISHED landing right now.
|
|
4151
|
+
* The publish dialog prefills from this: without it the checkbox always opens
|
|
4152
|
+
* unchecked, and republishing anything would read as "remove the bar". */
|
|
4153
|
+
shopifyAnnouncementBar: ShopifyAnnouncementBarState.optional(),
|
|
4063
4154
|
// Pain points and benefits for image wizard pre-filling
|
|
4064
4155
|
painPoints: z24.string().optional(),
|
|
4065
4156
|
benefits: z24.string().optional(),
|
|
@@ -4185,7 +4276,19 @@ var StatusResponse = z24.object({
|
|
|
4185
4276
|
"done",
|
|
4186
4277
|
"error"
|
|
4187
4278
|
]),
|
|
4279
|
+
/** ENGINEERING string (`Task.error`). Never render this to a seller. */
|
|
4188
4280
|
error: z24.string().optional(),
|
|
4281
|
+
/**
|
|
4282
|
+
* SELLER-FACING failure copy, in Spanish, already decided by the backend —
|
|
4283
|
+
* render it VERBATIM. Present only for `status: 'error'`.
|
|
4284
|
+
*
|
|
4285
|
+
* Clients used to receive only `error` and guess at it with a local regex
|
|
4286
|
+
* table; measured 2026-08-18 over 187 failed tasks in 60 days, that guess
|
|
4287
|
+
* produced contentless copy ("Error desconocido." / "Error interno") for 123
|
|
4288
|
+
* of them. Populated from `Task.userMessage`, falling back to the same
|
|
4289
|
+
* resolver for rows written before that column existed.
|
|
4290
|
+
*/
|
|
4291
|
+
userMessage: z24.string().optional(),
|
|
4189
4292
|
startedAt: z24.string().datetime().optional(),
|
|
4190
4293
|
updatedAt: z24.string().datetime().optional(),
|
|
4191
4294
|
stageStatuses: StageStatusSnapshot,
|
|
@@ -4196,6 +4299,10 @@ var StatusResponse = z24.object({
|
|
|
4196
4299
|
* campaign target geo when creating Meta ads from this task. */
|
|
4197
4300
|
targetCountry: z24.string().optional(),
|
|
4198
4301
|
shopifyPageUrl: z24.string().url().optional(),
|
|
4302
|
+
/** What the announcement bar looks like ON THE PUBLISHED landing right now.
|
|
4303
|
+
* The publish dialog prefills from this: without it the checkbox always opens
|
|
4304
|
+
* unchecked, and republishing anything would read as "remove the bar". */
|
|
4305
|
+
shopifyAnnouncementBar: ShopifyAnnouncementBarState.optional(),
|
|
4199
4306
|
shopifyShops: z24.array(z24.object({
|
|
4200
4307
|
shopDomain: z24.string(),
|
|
4201
4308
|
primaryDomain: z24.string().nullable()
|
|
@@ -4206,6 +4313,10 @@ var StatusResponse = z24.object({
|
|
|
4206
4313
|
requiresAction: z24.boolean().optional(),
|
|
4207
4314
|
/** True when the task needs the user's attention rather than passive progress. */
|
|
4208
4315
|
needsReview: z24.boolean().optional(),
|
|
4316
|
+
/** True when an open gate exists that somebody can actually ANSWER. A strictly
|
|
4317
|
+
* narrower fact than `needsReview` — see the identical field on `TaskSummary`
|
|
4318
|
+
* below for why conflating the two produced a call to action that led nowhere. */
|
|
4319
|
+
hasAnswerableGate: z24.boolean().optional(),
|
|
4209
4320
|
/** Which landing pipeline produced this task's landing. 'code' tasks have no
|
|
4210
4321
|
* classic LandingImage rows — the result lives in the inline code editor. */
|
|
4211
4322
|
landingMode: LandingModeSchema.optional(),
|
|
@@ -4276,6 +4387,27 @@ var TaskSummary = z24.object({
|
|
|
4276
4387
|
/** True when user action is ACTUALLY needed (angle review or script review ready).
|
|
4277
4388
|
* Use this for status display to show "Revisar" vs "Progreso". */
|
|
4278
4389
|
needsReview: z24.boolean().optional(),
|
|
4390
|
+
/**
|
|
4391
|
+
* True when an open gate exists that somebody can actually ANSWER.
|
|
4392
|
+
*
|
|
4393
|
+
* DIFFERENT FACT from `needsReview`, and the difference is the bug. `needsReview`
|
|
4394
|
+
* means "this task is waiting on the seller"; the list badge renders «Revisar»
|
|
4395
|
+
* from it. But a task whose dispatch was lost is also "waiting", with no question
|
|
4396
|
+
* open anywhere — and so is the autopilot angle wait, whose resume button is
|
|
4397
|
+
* measurably INERT (`resumeAfterAngles` CASes on a flag that is false there, so
|
|
4398
|
+
* it returns 200 and dispatches nothing). Both render «Revisar» today and both
|
|
4399
|
+
* lead the seller nowhere. The owner spent hours one night convinced the platform
|
|
4400
|
+
* was frozen, chasing a ghost the badge itself invented.
|
|
4401
|
+
*
|
|
4402
|
+
* Derived from the reconciler's gate registry, NOT from a hand-maintained flag:
|
|
4403
|
+
* being a registry entry is what "answerable" means, because every entry declares
|
|
4404
|
+
* a decider and a deadline at compile time. A gate added tomorrow reports itself
|
|
4405
|
+
* here on its own. See `answerableGateIdsFor` in
|
|
4406
|
+
* apps/api/src/modules/cron/gate-reconciler/gate-registry.ts.
|
|
4407
|
+
*
|
|
4408
|
+
* Render «Revisar» on THIS, not on `needsReview`.
|
|
4409
|
+
*/
|
|
4410
|
+
hasAnswerableGate: z24.boolean().optional(),
|
|
4279
4411
|
createdAt: z24.string().datetime().optional(),
|
|
4280
4412
|
startedAt: z24.string().datetime().optional(),
|
|
4281
4413
|
updatedAt: z24.string().datetime().optional(),
|
|
@@ -6067,6 +6199,10 @@ var TikTokCreateCampaignResponseSchema = z35.object({
|
|
|
6067
6199
|
adsManagerUrl: z35.string().optional()
|
|
6068
6200
|
});
|
|
6069
6201
|
|
|
6202
|
+
// ../../packages/types/src/pricing-gemini-omni.ts
|
|
6203
|
+
var GEMINI_OMNI_FLAT_OUR_COST_CENTS = 84;
|
|
6204
|
+
var GEMINI_OMNI_FLAT_USER_PRICE_CENTS = 168;
|
|
6205
|
+
|
|
6070
6206
|
// ../../packages/types/src/pricing.ts
|
|
6071
6207
|
var CREDITS_PER_USD_CENT = 10;
|
|
6072
6208
|
var CREDITS_PER_DOLLAR = 100 * CREDITS_PER_USD_CENT;
|
|
@@ -6475,6 +6611,18 @@ var DEFAULT_PRICING = {
|
|
|
6475
6611
|
ourCostCents: 120,
|
|
6476
6612
|
userPriceCents: 240
|
|
6477
6613
|
},
|
|
6614
|
+
// Gemini Omni Video (gemini-omni-video) — Mix primary generator since
|
|
6615
|
+
// 2026-08-18. FLAT per generation: 168 KIE credits = $0.84, identical at
|
|
6616
|
+
// 720p and 1080p and independent of the 3.5-10s trim window. Do NOT add
|
|
6617
|
+
// per-second overrides for this key — resolveSuccessBilling bills it flat.
|
|
6618
|
+
//
|
|
6619
|
+
// Shares the constants with the pre-submit ESTIMATOR (mix-cost.ts) rather
|
|
6620
|
+
// than repeating the numbers: a literal here that drifted from the estimator
|
|
6621
|
+
// would quote one price and charge another on every mix.
|
|
6622
|
+
"kie:gemini-omni-video": {
|
|
6623
|
+
ourCostCents: GEMINI_OMNI_FLAT_OUR_COST_CENTS,
|
|
6624
|
+
userPriceCents: GEMINI_OMNI_FLAT_USER_PRICE_CENTS
|
|
6625
|
+
},
|
|
6478
6626
|
"kie:video-lipsync-generation": {
|
|
6479
6627
|
ourCostCents: 45,
|
|
6480
6628
|
userPriceCents: 90
|
|
@@ -8438,7 +8586,57 @@ var FeatureSpec = z36.object({
|
|
|
8438
8586
|
});
|
|
8439
8587
|
|
|
8440
8588
|
// ../../packages/types/src/mix.ts
|
|
8589
|
+
import { z as z38 } from "zod";
|
|
8590
|
+
|
|
8591
|
+
// ../../packages/types/src/mix-degradation.ts
|
|
8441
8592
|
import { z as z37 } from "zod";
|
|
8593
|
+
var MIX_DEGRADATION_REASONS = [
|
|
8594
|
+
/**
|
|
8595
|
+
* At least one scene the mix PLANNED is missing from the delivered video, so
|
|
8596
|
+
* the result is shorter than the source. Set at assemble time by comparing
|
|
8597
|
+
* the concat set against the planned scene set — the scene's own code (if it
|
|
8598
|
+
* had one) never reaches the mix-level rollup, which only sees scenes that
|
|
8599
|
+
* SUCCEEDED, and that is exactly how a live 17.00s-of-20.00s delivery
|
|
8600
|
+
* shipped with degradedReason NULL and no banner at all.
|
|
8601
|
+
*
|
|
8602
|
+
* ORDERING: FIRST, above every refusal code. A short delivery must never be
|
|
8603
|
+
* MASKED. It used to sit below the three content refusals on the theory that
|
|
8604
|
+
* a refusal names something the user can change AND explains the missing
|
|
8605
|
+
* scene — but it does neither reliably: in a 3-scene mix where scene 1 fails
|
|
8606
|
+
* outright and scene 2 ships through Wan after a reference refusal, the
|
|
8607
|
+
* refusal copy reads "we used a lower-quality alternative", a claim about
|
|
8608
|
+
* QUALITY that says nothing about the video being shorter than what was paid
|
|
8609
|
+
* for, and that is actively wrong about the dropped scene (for which we used
|
|
8610
|
+
* nothing). Every other code in this list describes a scene that DID ship.
|
|
8611
|
+
*/
|
|
8612
|
+
"scenes_dropped",
|
|
8613
|
+
/** Google's safety filter rejected the user's reference photo. Deterministic. */
|
|
8614
|
+
"reference_rejected",
|
|
8615
|
+
/** Google refused the wording of the change description. */
|
|
8616
|
+
"prompt_rejected",
|
|
8617
|
+
/**
|
|
8618
|
+
* The provider refused the content but its message names neither the
|
|
8619
|
+
* reference nor the wording, so we cannot say which. Distinct from
|
|
8620
|
+
* 'provider_unavailable' on purpose: "unavailable" invites the user to retry
|
|
8621
|
+
* unchanged, which can never succeed against a content refusal and only
|
|
8622
|
+
* burns their credits. This code tells them something must change.
|
|
8623
|
+
*/
|
|
8624
|
+
"content_rejected",
|
|
8625
|
+
/**
|
|
8626
|
+
* The scene's length fell outside Gemini's usable 3.5s-10s trim window —
|
|
8627
|
+
* EITHER bound. Deliberately ONE code for both: the user controls neither
|
|
8628
|
+
* (scene segmentation is entirely ours), so "too short" and "too long" are
|
|
8629
|
+
* not differently actionable, and one honest explanation beats two that
|
|
8630
|
+
* both amount to "our segmentation produced a length the primary generator
|
|
8631
|
+
* cannot take".
|
|
8632
|
+
*/
|
|
8633
|
+
"scene_length_unsupported",
|
|
8634
|
+
/** Transient provider failures exhausted the budget. */
|
|
8635
|
+
"provider_unavailable"
|
|
8636
|
+
];
|
|
8637
|
+
var MixDegradationReasonSchema = z37.enum(MIX_DEGRADATION_REASONS);
|
|
8638
|
+
|
|
8639
|
+
// ../../packages/types/src/mix.ts
|
|
8442
8640
|
var MEDIA_TYPES = [
|
|
8443
8641
|
"video",
|
|
8444
8642
|
"image"
|
|
@@ -8476,38 +8674,38 @@ function isPublicHttpsUrl(raw) {
|
|
|
8476
8674
|
return true;
|
|
8477
8675
|
}
|
|
8478
8676
|
__name(isPublicHttpsUrl, "isPublicHttpsUrl");
|
|
8479
|
-
var PublicHttpsUrl =
|
|
8677
|
+
var PublicHttpsUrl = z38.string().url().refine(isPublicHttpsUrl, {
|
|
8480
8678
|
message: "must be a public HTTPS URL"
|
|
8481
8679
|
});
|
|
8482
|
-
var InternalAssetPath =
|
|
8483
|
-
var MixImageUrl =
|
|
8680
|
+
var InternalAssetPath = z38.string().regex(/^\/(?:api|v1)\/assets\/[A-Za-z0-9._~%/-]+$/, "must be a public HTTPS URL or an internal asset path");
|
|
8681
|
+
var MixImageUrl = z38.union([
|
|
8484
8682
|
PublicHttpsUrl,
|
|
8485
8683
|
InternalAssetPath
|
|
8486
8684
|
]);
|
|
8487
|
-
var MixAxes =
|
|
8488
|
-
product:
|
|
8489
|
-
person:
|
|
8490
|
-
setting:
|
|
8685
|
+
var MixAxes = z38.object({
|
|
8686
|
+
product: z38.boolean(),
|
|
8687
|
+
person: z38.boolean(),
|
|
8688
|
+
setting: z38.boolean()
|
|
8491
8689
|
});
|
|
8492
|
-
var MixReferences =
|
|
8493
|
-
product:
|
|
8494
|
-
person:
|
|
8495
|
-
setting:
|
|
8690
|
+
var MixReferences = z38.object({
|
|
8691
|
+
product: z38.array(PublicHttpsUrl).max(3, "at most 3 product references"),
|
|
8692
|
+
person: z38.array(PublicHttpsUrl).max(3, "at most 3 person references"),
|
|
8693
|
+
setting: z38.array(PublicHttpsUrl).max(3, "at most 3 setting references")
|
|
8496
8694
|
});
|
|
8497
|
-
var MixImageEdit =
|
|
8498
|
-
instruction:
|
|
8499
|
-
imageUrls:
|
|
8695
|
+
var MixImageEdit = z38.object({
|
|
8696
|
+
instruction: z38.string().min(1).max(600),
|
|
8697
|
+
imageUrls: z38.array(MixImageUrl).min(1).max(3)
|
|
8500
8698
|
});
|
|
8501
|
-
var MixRequest =
|
|
8502
|
-
mediaType:
|
|
8503
|
-
originalUrl:
|
|
8699
|
+
var MixRequest = z38.object({
|
|
8700
|
+
mediaType: z38.enum(MEDIA_TYPES),
|
|
8701
|
+
originalUrl: z38.union([
|
|
8504
8702
|
PublicHttpsUrl,
|
|
8505
8703
|
InternalAssetPath
|
|
8506
8704
|
]),
|
|
8507
8705
|
axes: MixAxes,
|
|
8508
8706
|
references: MixReferences,
|
|
8509
|
-
personText:
|
|
8510
|
-
settingText:
|
|
8707
|
+
personText: z38.string().max(500).optional(),
|
|
8708
|
+
settingText: z38.string().max(500).optional(),
|
|
8511
8709
|
/**
|
|
8512
8710
|
* Optional public URL of a user-uploaded audio file (mp3/wav). Wan Video
|
|
8513
8711
|
* Edit has no audio input and the mix never lipsyncs: the Wan output is
|
|
@@ -8516,11 +8714,11 @@ var MixRequest = z37.object({
|
|
|
8516
8714
|
* (mute + overlay). No per-scene slicing, no `reference_audio_urls`.
|
|
8517
8715
|
*/
|
|
8518
8716
|
audioUrl: PublicHttpsUrl.optional(),
|
|
8519
|
-
resolution:
|
|
8520
|
-
originalDurationSec:
|
|
8717
|
+
resolution: z38.enum(RESOLUTIONS).default("720p"),
|
|
8718
|
+
originalDurationSec: z38.number().positive().max(600).optional(),
|
|
8521
8719
|
image: MixImageEdit.optional(),
|
|
8522
|
-
sourceTaskId:
|
|
8523
|
-
sourceCreativeId:
|
|
8720
|
+
sourceTaskId: z38.string().max(64).optional(),
|
|
8721
|
+
sourceCreativeId: z38.string().max(64).optional()
|
|
8524
8722
|
}).superRefine((req, ctx) => {
|
|
8525
8723
|
if (req.mediaType === "image") {
|
|
8526
8724
|
if (!req.image) {
|
|
@@ -8554,18 +8752,18 @@ var MixRequest = z37.object({
|
|
|
8554
8752
|
});
|
|
8555
8753
|
}
|
|
8556
8754
|
});
|
|
8557
|
-
var ReferenceMap =
|
|
8558
|
-
product:
|
|
8559
|
-
start:
|
|
8560
|
-
end:
|
|
8755
|
+
var ReferenceMap = z38.object({
|
|
8756
|
+
product: z38.object({
|
|
8757
|
+
start: z38.number().int(),
|
|
8758
|
+
end: z38.number().int()
|
|
8561
8759
|
}).optional(),
|
|
8562
|
-
person:
|
|
8563
|
-
start:
|
|
8564
|
-
end:
|
|
8760
|
+
person: z38.object({
|
|
8761
|
+
start: z38.number().int(),
|
|
8762
|
+
end: z38.number().int()
|
|
8565
8763
|
}).optional(),
|
|
8566
|
-
setting:
|
|
8567
|
-
start:
|
|
8568
|
-
end:
|
|
8764
|
+
setting: z38.object({
|
|
8765
|
+
start: z38.number().int(),
|
|
8766
|
+
end: z38.number().int()
|
|
8569
8767
|
}).optional()
|
|
8570
8768
|
});
|
|
8571
8769
|
var SCENE_CLIP_TYPES = [
|
|
@@ -8581,39 +8779,39 @@ var INSTANCE_CARDINALITIES = [
|
|
|
8581
8779
|
"multiple_same",
|
|
8582
8780
|
"multiple_distinct"
|
|
8583
8781
|
];
|
|
8584
|
-
var SceneSegment =
|
|
8585
|
-
index:
|
|
8586
|
-
startTs:
|
|
8587
|
-
endTs:
|
|
8588
|
-
seedanceDuration:
|
|
8782
|
+
var SceneSegment = z38.object({
|
|
8783
|
+
index: z38.number().int().positive(),
|
|
8784
|
+
startTs: z38.number().nonnegative(),
|
|
8785
|
+
endTs: z38.number().positive(),
|
|
8786
|
+
seedanceDuration: z38.number().int().min(4).max(15),
|
|
8589
8787
|
// Pass 1 / Pass 2 outputs (all optional — populated when the rich analyzer
|
|
8590
8788
|
// runs and empty on fallback).
|
|
8591
|
-
action:
|
|
8592
|
-
motion:
|
|
8593
|
-
clipType:
|
|
8594
|
-
hasCharacter:
|
|
8595
|
-
faceVisible:
|
|
8789
|
+
action: z38.string().optional(),
|
|
8790
|
+
motion: z38.string().optional(),
|
|
8791
|
+
clipType: z38.enum(SCENE_CLIP_TYPES).optional(),
|
|
8792
|
+
hasCharacter: z38.boolean().optional(),
|
|
8793
|
+
faceVisible: z38.boolean().optional(),
|
|
8596
8794
|
// Pass 2 refinement — rich per-scene metadata used by buildMixPrompt.
|
|
8597
|
-
startState:
|
|
8598
|
-
endState:
|
|
8599
|
-
cameraMovement:
|
|
8600
|
-
emotion:
|
|
8601
|
-
productPlacement:
|
|
8602
|
-
productInteraction:
|
|
8603
|
-
settingDescription:
|
|
8604
|
-
dialogue:
|
|
8795
|
+
startState: z38.string().optional(),
|
|
8796
|
+
endState: z38.string().optional(),
|
|
8797
|
+
cameraMovement: z38.string().optional(),
|
|
8798
|
+
emotion: z38.string().optional(),
|
|
8799
|
+
productPlacement: z38.string().optional(),
|
|
8800
|
+
productInteraction: z38.string().optional(),
|
|
8801
|
+
settingDescription: z38.string().optional(),
|
|
8802
|
+
dialogue: z38.string().optional(),
|
|
8605
8803
|
// Main vs extras / noise (our addition on top of the script's schema).
|
|
8606
|
-
mainPersonDescription:
|
|
8607
|
-
mainProductDescription:
|
|
8608
|
-
personInstances:
|
|
8609
|
-
productInstances:
|
|
8804
|
+
mainPersonDescription: z38.string().optional(),
|
|
8805
|
+
mainProductDescription: z38.string().optional(),
|
|
8806
|
+
personInstances: z38.enum(INSTANCE_CARDINALITIES).optional(),
|
|
8807
|
+
productInstances: z38.enum(INSTANCE_CARDINALITIES).optional(),
|
|
8610
8808
|
/**
|
|
8611
8809
|
* Seconds of prev-clipType content absorbed at the start of this scene by
|
|
8612
8810
|
* Pass 2.7's clipType boundary margin. When > 0 and the scene is a
|
|
8613
8811
|
* pass-through (no Seedance), the concatenator prepends a held-still
|
|
8614
8812
|
* preamble (nano-banana-swapped first frame) to hide the leak.
|
|
8615
8813
|
*/
|
|
8616
|
-
absorbedMarginSec:
|
|
8814
|
+
absorbedMarginSec: z38.number().nonnegative().optional(),
|
|
8617
8815
|
/**
|
|
8618
8816
|
* True when the scene shows post-production graphics baked into the frame:
|
|
8619
8817
|
* caption/subtitle text, infographic overlay cards, or inset
|
|
@@ -8622,33 +8820,39 @@ var SceneSegment = z37.object({
|
|
|
8622
8820
|
* (mentioning nonexistent overlays gives the generator license to
|
|
8623
8821
|
* fabricate them).
|
|
8624
8822
|
*/
|
|
8625
|
-
hasOverlayGraphics:
|
|
8823
|
+
hasOverlayGraphics: z38.boolean().optional()
|
|
8626
8824
|
});
|
|
8627
|
-
var MixSceneResult =
|
|
8628
|
-
index:
|
|
8629
|
-
status:
|
|
8825
|
+
var MixSceneResult = z38.object({
|
|
8826
|
+
index: z38.number().int().positive(),
|
|
8827
|
+
status: z38.enum([
|
|
8630
8828
|
"queued",
|
|
8631
8829
|
"submitted",
|
|
8632
8830
|
"done",
|
|
8633
8831
|
"failed"
|
|
8634
8832
|
]),
|
|
8635
|
-
seedanceTaskId:
|
|
8636
|
-
videoUrl:
|
|
8637
|
-
error:
|
|
8638
|
-
|
|
8639
|
-
|
|
8640
|
-
|
|
8641
|
-
|
|
8833
|
+
seedanceTaskId: z38.string().optional(),
|
|
8834
|
+
videoUrl: z38.string().url().optional(),
|
|
8835
|
+
error: z38.string().optional(),
|
|
8836
|
+
degradedReason: MixDegradationReasonSchema.optional()
|
|
8837
|
+
});
|
|
8838
|
+
var MixResult = z38.object({
|
|
8839
|
+
mixTaskId: z38.string().uuid(),
|
|
8840
|
+
status: z38.enum([
|
|
8642
8841
|
"queued",
|
|
8643
8842
|
"processing",
|
|
8644
8843
|
"done",
|
|
8645
8844
|
"error"
|
|
8646
8845
|
]),
|
|
8647
|
-
resultUrl:
|
|
8648
|
-
scenes:
|
|
8649
|
-
failReason:
|
|
8846
|
+
resultUrl: z38.string().url().optional(),
|
|
8847
|
+
scenes: z38.array(MixSceneResult).default([]),
|
|
8848
|
+
failReason: z38.string().optional(),
|
|
8849
|
+
degradedReason: MixDegradationReasonSchema.optional()
|
|
8650
8850
|
});
|
|
8651
8851
|
|
|
8852
|
+
// ../../packages/types/src/mix-cost.ts
|
|
8853
|
+
var MAX_QA_RETRIES_PER_SCENE_UPPER_BOUND = 1;
|
|
8854
|
+
var MAX_BILLABLE_GENERATIONS_PER_BLOCK = 1 + MAX_QA_RETRIES_PER_SCENE_UPPER_BOUND;
|
|
8855
|
+
|
|
8652
8856
|
// ../../packages/types/src/product-delivery-format.ts
|
|
8653
8857
|
var DELIVERY_FAMILIES = [
|
|
8654
8858
|
"solid_dose",
|
|
@@ -8737,41 +8941,41 @@ var DECLARATION = DELIVERY_FAMILIES.map((family) => [
|
|
|
8737
8941
|
]);
|
|
8738
8942
|
|
|
8739
8943
|
// ../../packages/types/src/task-type.ts
|
|
8740
|
-
import { z as
|
|
8741
|
-
var TaskTypeSchema =
|
|
8944
|
+
import { z as z39 } from "zod";
|
|
8945
|
+
var TaskTypeSchema = z39.enum([
|
|
8742
8946
|
"product",
|
|
8743
8947
|
"service"
|
|
8744
8948
|
]);
|
|
8745
8949
|
|
|
8746
8950
|
// ../../packages/types/src/landing-regeneration.ts
|
|
8747
|
-
import { z as
|
|
8748
|
-
var LandingRegenerationStatus =
|
|
8951
|
+
import { z as z40 } from "zod";
|
|
8952
|
+
var LandingRegenerationStatus = z40.enum([
|
|
8749
8953
|
"queued",
|
|
8750
8954
|
"processing",
|
|
8751
8955
|
"published",
|
|
8752
8956
|
"failed"
|
|
8753
8957
|
]);
|
|
8754
|
-
var RegenerateLandingRoleResponse =
|
|
8755
|
-
ok:
|
|
8958
|
+
var RegenerateLandingRoleResponse = z40.object({
|
|
8959
|
+
ok: z40.literal(true),
|
|
8756
8960
|
role: LandingRole,
|
|
8757
|
-
promptIndex:
|
|
8758
|
-
operationId:
|
|
8961
|
+
promptIndex: z40.number().int().nonnegative(),
|
|
8962
|
+
operationId: z40.string().min(1)
|
|
8759
8963
|
});
|
|
8760
|
-
var LandingRegenerationStatusResponse =
|
|
8761
|
-
ok:
|
|
8762
|
-
operationId:
|
|
8964
|
+
var LandingRegenerationStatusResponse = z40.object({
|
|
8965
|
+
ok: z40.literal(true),
|
|
8966
|
+
operationId: z40.string().min(1),
|
|
8763
8967
|
role: LandingRole,
|
|
8764
8968
|
status: LandingRegenerationStatus,
|
|
8765
|
-
refunded:
|
|
8766
|
-
failureReason:
|
|
8767
|
-
publishedAssetKey:
|
|
8768
|
-
updatedAt:
|
|
8969
|
+
refunded: z40.boolean(),
|
|
8970
|
+
failureReason: z40.string().nullable(),
|
|
8971
|
+
publishedAssetKey: z40.string().nullable(),
|
|
8972
|
+
updatedAt: z40.string().datetime()
|
|
8769
8973
|
});
|
|
8770
8974
|
|
|
8771
8975
|
// ../../packages/types/src/video-edit.ts
|
|
8772
|
-
import { z as
|
|
8976
|
+
import { z as z41 } from "zod";
|
|
8773
8977
|
var MAX_SEGMENT_SECONDS = 3;
|
|
8774
|
-
var VideoEditStatusSchema =
|
|
8978
|
+
var VideoEditStatusSchema = z41.enum([
|
|
8775
8979
|
"pending",
|
|
8776
8980
|
"processing",
|
|
8777
8981
|
"assembling",
|
|
@@ -8780,9 +8984,9 @@ var VideoEditStatusSchema = z40.enum([
|
|
|
8780
8984
|
"discarded",
|
|
8781
8985
|
"failed"
|
|
8782
8986
|
]);
|
|
8783
|
-
var VideoEditTimeSectionSchema =
|
|
8784
|
-
startSec:
|
|
8785
|
-
endSec:
|
|
8987
|
+
var VideoEditTimeSectionSchema = z41.object({
|
|
8988
|
+
startSec: z41.number().min(0),
|
|
8989
|
+
endSec: z41.number().positive()
|
|
8786
8990
|
}).refine((v) => v.endSec > v.startSec, {
|
|
8787
8991
|
message: "endSec must be after startSec",
|
|
8788
8992
|
path: [
|
|
@@ -8794,45 +8998,45 @@ var VideoEditTimeSectionSchema = z40.object({
|
|
|
8794
8998
|
"endSec"
|
|
8795
8999
|
]
|
|
8796
9000
|
});
|
|
8797
|
-
var VideoEditRequestSchema =
|
|
8798
|
-
comment:
|
|
8799
|
-
sections:
|
|
9001
|
+
var VideoEditRequestSchema = z41.object({
|
|
9002
|
+
comment: z41.string().trim().min(1).max(2e3),
|
|
9003
|
+
sections: z41.array(VideoEditTimeSectionSchema).min(1).optional()
|
|
8800
9004
|
});
|
|
8801
|
-
var VideoEditModeSchema =
|
|
9005
|
+
var VideoEditModeSchema = z41.enum([
|
|
8802
9006
|
"wan_edit",
|
|
8803
9007
|
"regenerate"
|
|
8804
9008
|
]);
|
|
8805
|
-
var VideoRegenerationEngineSchema =
|
|
9009
|
+
var VideoRegenerationEngineSchema = z41.enum([
|
|
8806
9010
|
"free_text",
|
|
8807
9011
|
"free_image",
|
|
8808
9012
|
"single_call",
|
|
8809
9013
|
"multi_shot"
|
|
8810
9014
|
]);
|
|
8811
|
-
var VideoRegenerationRouteFingerprintSchema =
|
|
8812
|
-
var RegenerateEditRequestSchema =
|
|
8813
|
-
angleKey:
|
|
8814
|
-
comment:
|
|
9015
|
+
var VideoRegenerationRouteFingerprintSchema = z41.string().trim().min(1).max(128);
|
|
9016
|
+
var RegenerateEditRequestSchema = z41.object({
|
|
9017
|
+
angleKey: z41.string().min(1),
|
|
9018
|
+
comment: z41.string().min(1).max(2e3)
|
|
8815
9019
|
});
|
|
8816
9020
|
|
|
8817
9021
|
// ../../packages/types/src/uploaded-asset.ts
|
|
8818
|
-
import { z as
|
|
8819
|
-
var UploadedAssetMediaKind =
|
|
9022
|
+
import { z as z42 } from "zod";
|
|
9023
|
+
var UploadedAssetMediaKind = z42.enum([
|
|
8820
9024
|
"image",
|
|
8821
9025
|
"video"
|
|
8822
9026
|
]);
|
|
8823
|
-
var UploadedAssetSessionMediaKind =
|
|
9027
|
+
var UploadedAssetSessionMediaKind = z42.enum([
|
|
8824
9028
|
"image",
|
|
8825
9029
|
"video",
|
|
8826
9030
|
"mixed"
|
|
8827
9031
|
]);
|
|
8828
|
-
var UploadedAssetRole =
|
|
9032
|
+
var UploadedAssetRole = z42.enum([
|
|
8829
9033
|
"media",
|
|
8830
9034
|
"thumbnail"
|
|
8831
9035
|
]);
|
|
8832
9036
|
var UPLOADED_ASSET_MAX_FILES = 10;
|
|
8833
9037
|
var UPLOADED_ASSET_IMAGE_MAX_FILES = 5;
|
|
8834
|
-
var UploadedAssetId =
|
|
8835
|
-
var HttpsUrl =
|
|
9038
|
+
var UploadedAssetId = z42.string().uuid();
|
|
9039
|
+
var HttpsUrl = z42.string().url().refine((value) => {
|
|
8836
9040
|
try {
|
|
8837
9041
|
const url = new URL(value);
|
|
8838
9042
|
return url.protocol === "https:" && !url.username && !url.password;
|
|
@@ -8840,23 +9044,23 @@ var HttpsUrl = z41.string().url().refine((value) => {
|
|
|
8840
9044
|
return false;
|
|
8841
9045
|
}
|
|
8842
9046
|
}, "URL must use https");
|
|
8843
|
-
var UtcDateTime =
|
|
9047
|
+
var UtcDateTime = z42.string().datetime().refine((value) => {
|
|
8844
9048
|
const parts = /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})(?:\.\d+)?Z$/.exec(value);
|
|
8845
9049
|
const timestamp = Date.parse(value);
|
|
8846
9050
|
if (!parts || Number.isNaN(timestamp)) return false;
|
|
8847
9051
|
const date = new Date(timestamp);
|
|
8848
9052
|
return date.getUTCFullYear() === Number(parts[1]) && date.getUTCMonth() + 1 === Number(parts[2]) && date.getUTCDate() === Number(parts[3]) && date.getUTCHours() === Number(parts[4]) && date.getUTCMinutes() === Number(parts[5]) && date.getUTCSeconds() === Number(parts[6]);
|
|
8849
9053
|
}, "datetime must be a valid UTC calendar instant");
|
|
8850
|
-
var UploadedAssetRef =
|
|
9054
|
+
var UploadedAssetRef = z42.object({
|
|
8851
9055
|
assetId: UploadedAssetId
|
|
8852
9056
|
}).strict();
|
|
8853
9057
|
var UploadedAssetCompletion = UploadedAssetRef.extend({
|
|
8854
9058
|
mediaKind: UploadedAssetMediaKind,
|
|
8855
9059
|
assetRole: UploadedAssetRole
|
|
8856
9060
|
}).strict();
|
|
8857
|
-
var UploadedAssetMetadata =
|
|
9061
|
+
var UploadedAssetMetadata = z42.object({
|
|
8858
9062
|
assetId: UploadedAssetId,
|
|
8859
|
-
ownerId:
|
|
9063
|
+
ownerId: z42.string().min(1),
|
|
8860
9064
|
mediaKind: UploadedAssetMediaKind,
|
|
8861
9065
|
assetRole: UploadedAssetRole,
|
|
8862
9066
|
createdAt: UtcDateTime,
|
|
@@ -8864,7 +9068,7 @@ var UploadedAssetMetadata = z41.object({
|
|
|
8864
9068
|
}).strict().superRefine((value, ctx) => {
|
|
8865
9069
|
if (Date.parse(value.expiresAt) <= Date.parse(value.createdAt)) {
|
|
8866
9070
|
ctx.addIssue({
|
|
8867
|
-
code:
|
|
9071
|
+
code: z42.ZodIssueCode.custom,
|
|
8868
9072
|
path: [
|
|
8869
9073
|
"expiresAt"
|
|
8870
9074
|
],
|
|
@@ -8872,27 +9076,27 @@ var UploadedAssetMetadata = z41.object({
|
|
|
8872
9076
|
});
|
|
8873
9077
|
}
|
|
8874
9078
|
});
|
|
8875
|
-
var MediaSourceRef =
|
|
9079
|
+
var MediaSourceRef = z42.object({
|
|
8876
9080
|
url: HttpsUrl.optional(),
|
|
8877
|
-
path:
|
|
9081
|
+
path: z42.string().min(1).optional(),
|
|
8878
9082
|
assetId: UploadedAssetId.optional()
|
|
8879
9083
|
}).strict().superRefine((value, ctx) => {
|
|
8880
9084
|
const sourceCount = Number(value.url !== void 0) + Number(value.path !== void 0) + Number(value.assetId !== void 0);
|
|
8881
9085
|
if (sourceCount !== 1) {
|
|
8882
9086
|
ctx.addIssue({
|
|
8883
|
-
code:
|
|
9087
|
+
code: z42.ZodIssueCode.custom,
|
|
8884
9088
|
message: "media source needs exactly one of url, path, or assetId"
|
|
8885
9089
|
});
|
|
8886
9090
|
}
|
|
8887
9091
|
});
|
|
8888
|
-
var UploadedAssetSessionRequest =
|
|
9092
|
+
var UploadedAssetSessionRequest = z42.object({
|
|
8889
9093
|
mediaKind: UploadedAssetSessionMediaKind,
|
|
8890
9094
|
assetRole: UploadedAssetRole.default("media"),
|
|
8891
|
-
maxFiles:
|
|
9095
|
+
maxFiles: z42.number().int().min(1).max(UPLOADED_ASSET_MAX_FILES).optional()
|
|
8892
9096
|
}).strict().superRefine((value, ctx) => {
|
|
8893
9097
|
if (value.mediaKind === "mixed" && value.assetRole === "thumbnail") {
|
|
8894
9098
|
ctx.addIssue({
|
|
8895
|
-
code:
|
|
9099
|
+
code: z42.ZodIssueCode.custom,
|
|
8896
9100
|
path: [
|
|
8897
9101
|
"mediaKind"
|
|
8898
9102
|
],
|
|
@@ -8901,7 +9105,7 @@ var UploadedAssetSessionRequest = z41.object({
|
|
|
8901
9105
|
}
|
|
8902
9106
|
if (value.mediaKind === "video" && value.maxFiles !== void 0 && value.maxFiles !== 1) {
|
|
8903
9107
|
ctx.addIssue({
|
|
8904
|
-
code:
|
|
9108
|
+
code: z42.ZodIssueCode.custom,
|
|
8905
9109
|
path: [
|
|
8906
9110
|
"maxFiles"
|
|
8907
9111
|
],
|
|
@@ -8910,7 +9114,7 @@ var UploadedAssetSessionRequest = z41.object({
|
|
|
8910
9114
|
}
|
|
8911
9115
|
if (value.mediaKind === "image" && (value.maxFiles ?? 1) > UPLOADED_ASSET_IMAGE_MAX_FILES) {
|
|
8912
9116
|
ctx.addIssue({
|
|
8913
|
-
code:
|
|
9117
|
+
code: z42.ZodIssueCode.custom,
|
|
8914
9118
|
path: [
|
|
8915
9119
|
"maxFiles"
|
|
8916
9120
|
],
|
|
@@ -8919,7 +9123,7 @@ var UploadedAssetSessionRequest = z41.object({
|
|
|
8919
9123
|
}
|
|
8920
9124
|
if (value.assetRole === "thumbnail" && value.mediaKind !== "image") {
|
|
8921
9125
|
ctx.addIssue({
|
|
8922
|
-
code:
|
|
9126
|
+
code: z42.ZodIssueCode.custom,
|
|
8923
9127
|
path: [
|
|
8924
9128
|
"mediaKind"
|
|
8925
9129
|
],
|
|
@@ -8927,32 +9131,32 @@ var UploadedAssetSessionRequest = z41.object({
|
|
|
8927
9131
|
});
|
|
8928
9132
|
}
|
|
8929
9133
|
});
|
|
8930
|
-
var UploadedAssetSessionStatus =
|
|
8931
|
-
|
|
8932
|
-
status:
|
|
8933
|
-
assets:
|
|
9134
|
+
var UploadedAssetSessionStatus = z42.discriminatedUnion("status", [
|
|
9135
|
+
z42.object({
|
|
9136
|
+
status: z42.literal("pending"),
|
|
9137
|
+
assets: z42.array(UploadedAssetCompletion).length(0)
|
|
8934
9138
|
}).strict(),
|
|
8935
|
-
|
|
8936
|
-
status:
|
|
8937
|
-
assets:
|
|
9139
|
+
z42.object({
|
|
9140
|
+
status: z42.literal("uploaded"),
|
|
9141
|
+
assets: z42.array(UploadedAssetCompletion).min(1).max(UPLOADED_ASSET_MAX_FILES)
|
|
8938
9142
|
}).strict()
|
|
8939
9143
|
]);
|
|
8940
|
-
var UploadedAssetAdoptionRequest =
|
|
9144
|
+
var UploadedAssetAdoptionRequest = z42.object({
|
|
8941
9145
|
assetId: UploadedAssetId,
|
|
8942
|
-
ownerId:
|
|
8943
|
-
consumer:
|
|
8944
|
-
idempotencyKey:
|
|
9146
|
+
ownerId: z42.string().min(1),
|
|
9147
|
+
consumer: z42.string().min(1).max(100),
|
|
9148
|
+
idempotencyKey: z42.string().min(1).max(255)
|
|
8945
9149
|
}).strict();
|
|
8946
9150
|
|
|
8947
9151
|
// ../../packages/types/src/dropshipping-project.ts
|
|
8948
|
-
import { z as
|
|
8949
|
-
var DropshippingProjectStatusSchema =
|
|
9152
|
+
import { z as z43 } from "zod";
|
|
9153
|
+
var DropshippingProjectStatusSchema = z43.enum([
|
|
8950
9154
|
"active",
|
|
8951
9155
|
"completed",
|
|
8952
9156
|
"archived"
|
|
8953
9157
|
]);
|
|
8954
9158
|
var DROPSHIPPING_LAUNCH_REWARD_CAMPAIGN = "dropshipping-launch-v1";
|
|
8955
|
-
var DropshippingRewardMilestoneSchema =
|
|
9159
|
+
var DropshippingRewardMilestoneSchema = z43.enum([
|
|
8956
9160
|
"product_selected",
|
|
8957
9161
|
"costing_saved",
|
|
8958
9162
|
"product_media_ready",
|
|
@@ -8967,21 +9171,21 @@ var DROPSHIPPING_LAUNCH_REWARD_CREDITS = {
|
|
|
8967
9171
|
campaign_created: 200
|
|
8968
9172
|
};
|
|
8969
9173
|
var DROPSHIPPING_LAUNCH_REWARD_TOTAL_CREDITS = Object.values(DROPSHIPPING_LAUNCH_REWARD_CREDITS).reduce((sum, credits) => sum + credits, 0);
|
|
8970
|
-
var DropshippingProjectRewardMilestoneStateSchema =
|
|
9174
|
+
var DropshippingProjectRewardMilestoneStateSchema = z43.object({
|
|
8971
9175
|
milestone: DropshippingRewardMilestoneSchema,
|
|
8972
|
-
credits:
|
|
8973
|
-
available:
|
|
8974
|
-
earned:
|
|
8975
|
-
grantId:
|
|
8976
|
-
grantedAt:
|
|
8977
|
-
});
|
|
8978
|
-
var DropshippingProjectRewardProgressSchema =
|
|
8979
|
-
campaignKey:
|
|
8980
|
-
totalCredits:
|
|
8981
|
-
earnedCredits:
|
|
8982
|
-
milestones:
|
|
8983
|
-
});
|
|
8984
|
-
var DropshippingProjectPhaseSchema =
|
|
9176
|
+
credits: z43.number().int().positive(),
|
|
9177
|
+
available: z43.boolean().optional(),
|
|
9178
|
+
earned: z43.boolean(),
|
|
9179
|
+
grantId: z43.string().uuid().nullable(),
|
|
9180
|
+
grantedAt: z43.string().datetime().nullable()
|
|
9181
|
+
});
|
|
9182
|
+
var DropshippingProjectRewardProgressSchema = z43.object({
|
|
9183
|
+
campaignKey: z43.literal(DROPSHIPPING_LAUNCH_REWARD_CAMPAIGN),
|
|
9184
|
+
totalCredits: z43.number().int().positive(),
|
|
9185
|
+
earnedCredits: z43.number().int().nonnegative(),
|
|
9186
|
+
milestones: z43.array(DropshippingProjectRewardMilestoneStateSchema).length(DropshippingRewardMilestoneSchema.options.length)
|
|
9187
|
+
});
|
|
9188
|
+
var DropshippingProjectPhaseSchema = z43.enum([
|
|
8985
9189
|
"research",
|
|
8986
9190
|
"costing",
|
|
8987
9191
|
"media",
|
|
@@ -8990,18 +9194,18 @@ var DropshippingProjectPhaseSchema = z42.enum([
|
|
|
8990
9194
|
"advertising",
|
|
8991
9195
|
"complete"
|
|
8992
9196
|
]);
|
|
8993
|
-
var DropshippingProjectCostingInputSchema =
|
|
8994
|
-
country:
|
|
8995
|
-
supplierCost:
|
|
8996
|
-
shippingCost:
|
|
8997
|
-
returnRate:
|
|
8998
|
-
cancellationRate:
|
|
8999
|
-
adsManagerCpa:
|
|
9000
|
-
desiredMargin:
|
|
9197
|
+
var DropshippingProjectCostingInputSchema = z43.object({
|
|
9198
|
+
country: z43.string().min(2).max(3),
|
|
9199
|
+
supplierCost: z43.number().finite().positive(),
|
|
9200
|
+
shippingCost: z43.number().finite().nonnegative(),
|
|
9201
|
+
returnRate: z43.number().finite().min(0).lt(1),
|
|
9202
|
+
cancellationRate: z43.number().finite().min(0).lt(1),
|
|
9203
|
+
adsManagerCpa: z43.number().finite().nonnegative(),
|
|
9204
|
+
desiredMargin: z43.number().finite().min(0).lt(1)
|
|
9001
9205
|
}).superRefine(({ returnRate, cancellationRate }, context) => {
|
|
9002
9206
|
if (returnRate + cancellationRate >= 1) {
|
|
9003
9207
|
context.addIssue({
|
|
9004
|
-
code:
|
|
9208
|
+
code: z43.ZodIssueCode.custom,
|
|
9005
9209
|
path: [
|
|
9006
9210
|
"cancellationRate"
|
|
9007
9211
|
],
|
|
@@ -9009,24 +9213,24 @@ var DropshippingProjectCostingInputSchema = z42.object({
|
|
|
9009
9213
|
});
|
|
9010
9214
|
}
|
|
9011
9215
|
});
|
|
9012
|
-
var DropshippingProjectCostingResultSchema =
|
|
9013
|
-
country:
|
|
9014
|
-
deliveryRate:
|
|
9015
|
-
adjustedShippingCost:
|
|
9016
|
-
adjustedCpa:
|
|
9017
|
-
totalCost:
|
|
9018
|
-
suggestedPrice:
|
|
9019
|
-
baseCommercialPrice:
|
|
9020
|
-
minimumSafePrice:
|
|
9021
|
-
commercialPrice:
|
|
9022
|
-
comparisonPrice:
|
|
9023
|
-
estimatedProfit:
|
|
9024
|
-
actualMargin:
|
|
9025
|
-
breakEvenAdsManagerCpa:
|
|
9026
|
-
wasPriceAutoAdjusted:
|
|
9027
|
-
profitabilityStatus:
|
|
9028
|
-
});
|
|
9029
|
-
var HttpUrlSchema =
|
|
9216
|
+
var DropshippingProjectCostingResultSchema = z43.object({
|
|
9217
|
+
country: z43.string().min(2).max(3),
|
|
9218
|
+
deliveryRate: z43.number().finite(),
|
|
9219
|
+
adjustedShippingCost: z43.number().finite(),
|
|
9220
|
+
adjustedCpa: z43.number().finite(),
|
|
9221
|
+
totalCost: z43.number().finite(),
|
|
9222
|
+
suggestedPrice: z43.number().finite(),
|
|
9223
|
+
baseCommercialPrice: z43.number().finite(),
|
|
9224
|
+
minimumSafePrice: z43.number().finite(),
|
|
9225
|
+
commercialPrice: z43.number().finite(),
|
|
9226
|
+
comparisonPrice: z43.number().finite(),
|
|
9227
|
+
estimatedProfit: z43.number().finite(),
|
|
9228
|
+
actualMargin: z43.number().finite(),
|
|
9229
|
+
breakEvenAdsManagerCpa: z43.number().finite(),
|
|
9230
|
+
wasPriceAutoAdjusted: z43.boolean(),
|
|
9231
|
+
profitabilityStatus: z43.literal("healthy")
|
|
9232
|
+
});
|
|
9233
|
+
var HttpUrlSchema = z43.string().url().max(2e3).refine((value) => {
|
|
9030
9234
|
try {
|
|
9031
9235
|
return [
|
|
9032
9236
|
"http:",
|
|
@@ -9036,124 +9240,124 @@ var HttpUrlSchema = z42.string().url().max(2e3).refine((value) => {
|
|
|
9036
9240
|
return false;
|
|
9037
9241
|
}
|
|
9038
9242
|
}, "Product URL must use HTTP(S)");
|
|
9039
|
-
var ResearchCandidateOriginSchema =
|
|
9040
|
-
type:
|
|
9041
|
-
runId:
|
|
9042
|
-
candidateSourceId:
|
|
9043
|
-
});
|
|
9044
|
-
var TaskOriginSchema =
|
|
9045
|
-
type:
|
|
9046
|
-
taskId:
|
|
9047
|
-
});
|
|
9048
|
-
var ManualOriginSchema =
|
|
9049
|
-
type:
|
|
9050
|
-
clientRequestId:
|
|
9051
|
-
country:
|
|
9052
|
-
});
|
|
9053
|
-
var CreateDropshippingProjectRequestSchema =
|
|
9054
|
-
origin:
|
|
9243
|
+
var ResearchCandidateOriginSchema = z43.object({
|
|
9244
|
+
type: z43.literal("research_candidate"),
|
|
9245
|
+
runId: z43.string().uuid(),
|
|
9246
|
+
candidateSourceId: z43.string().min(1).max(160)
|
|
9247
|
+
});
|
|
9248
|
+
var TaskOriginSchema = z43.object({
|
|
9249
|
+
type: z43.literal("task"),
|
|
9250
|
+
taskId: z43.string().uuid()
|
|
9251
|
+
});
|
|
9252
|
+
var ManualOriginSchema = z43.object({
|
|
9253
|
+
type: z43.literal("manual"),
|
|
9254
|
+
clientRequestId: z43.string().uuid(),
|
|
9255
|
+
country: z43.string().min(2).max(3).optional()
|
|
9256
|
+
});
|
|
9257
|
+
var CreateDropshippingProjectRequestSchema = z43.object({
|
|
9258
|
+
origin: z43.discriminatedUnion("type", [
|
|
9055
9259
|
ResearchCandidateOriginSchema,
|
|
9056
9260
|
TaskOriginSchema,
|
|
9057
9261
|
ManualOriginSchema
|
|
9058
9262
|
]),
|
|
9059
|
-
productName:
|
|
9263
|
+
productName: z43.string().trim().min(1).max(240).optional(),
|
|
9060
9264
|
productUrl: HttpUrlSchema.optional(),
|
|
9061
|
-
country:
|
|
9062
|
-
languageCode:
|
|
9265
|
+
country: z43.string().min(2).max(3).optional(),
|
|
9266
|
+
languageCode: z43.string().min(2).max(16).optional()
|
|
9063
9267
|
}).strict();
|
|
9064
|
-
var SaveDropshippingProjectCostingRequestSchema =
|
|
9268
|
+
var SaveDropshippingProjectCostingRequestSchema = z43.object({
|
|
9065
9269
|
input: DropshippingProjectCostingInputSchema,
|
|
9066
|
-
version:
|
|
9270
|
+
version: z43.number().int().positive()
|
|
9067
9271
|
}).strict();
|
|
9068
|
-
var DropshippingProjectNextActionSchema =
|
|
9069
|
-
|
|
9070
|
-
kind:
|
|
9071
|
-
href:
|
|
9272
|
+
var DropshippingProjectNextActionSchema = z43.discriminatedUnion("kind", [
|
|
9273
|
+
z43.object({
|
|
9274
|
+
kind: z43.literal("cost_product"),
|
|
9275
|
+
href: z43.string().min(1)
|
|
9072
9276
|
}),
|
|
9073
|
-
|
|
9074
|
-
kind:
|
|
9075
|
-
href:
|
|
9277
|
+
z43.object({
|
|
9278
|
+
kind: z43.literal("provide_product_media"),
|
|
9279
|
+
href: z43.string().min(1)
|
|
9076
9280
|
}),
|
|
9077
|
-
|
|
9078
|
-
kind:
|
|
9079
|
-
href:
|
|
9281
|
+
z43.object({
|
|
9282
|
+
kind: z43.literal("generate_creatives"),
|
|
9283
|
+
href: z43.string().min(1)
|
|
9080
9284
|
}),
|
|
9081
|
-
|
|
9082
|
-
kind:
|
|
9083
|
-
href:
|
|
9285
|
+
z43.object({
|
|
9286
|
+
kind: z43.literal("wait_for_creatives"),
|
|
9287
|
+
href: z43.string().min(1)
|
|
9084
9288
|
}),
|
|
9085
|
-
|
|
9086
|
-
kind:
|
|
9087
|
-
href:
|
|
9289
|
+
z43.object({
|
|
9290
|
+
kind: z43.literal("repair_creatives"),
|
|
9291
|
+
href: z43.string().min(1)
|
|
9088
9292
|
}),
|
|
9089
|
-
|
|
9090
|
-
kind:
|
|
9091
|
-
href:
|
|
9293
|
+
z43.object({
|
|
9294
|
+
kind: z43.literal("complete_creative_set"),
|
|
9295
|
+
href: z43.string().min(1)
|
|
9092
9296
|
}),
|
|
9093
|
-
|
|
9094
|
-
kind:
|
|
9095
|
-
href:
|
|
9297
|
+
z43.object({
|
|
9298
|
+
kind: z43.literal("connect_shopify"),
|
|
9299
|
+
href: z43.string().min(1)
|
|
9096
9300
|
}),
|
|
9097
|
-
|
|
9098
|
-
kind:
|
|
9099
|
-
href:
|
|
9301
|
+
z43.object({
|
|
9302
|
+
kind: z43.literal("publish_storefront"),
|
|
9303
|
+
href: z43.string().min(1)
|
|
9100
9304
|
}),
|
|
9101
|
-
|
|
9102
|
-
kind:
|
|
9103
|
-
href:
|
|
9305
|
+
z43.object({
|
|
9306
|
+
kind: z43.literal("wait_for_storefront"),
|
|
9307
|
+
href: z43.string().min(1)
|
|
9104
9308
|
}),
|
|
9105
|
-
|
|
9106
|
-
kind:
|
|
9107
|
-
href:
|
|
9309
|
+
z43.object({
|
|
9310
|
+
kind: z43.literal("connect_meta"),
|
|
9311
|
+
href: z43.string().min(1)
|
|
9108
9312
|
}),
|
|
9109
|
-
|
|
9110
|
-
kind:
|
|
9111
|
-
href:
|
|
9313
|
+
z43.object({
|
|
9314
|
+
kind: z43.literal("create_meta_campaign"),
|
|
9315
|
+
href: z43.string().min(1)
|
|
9112
9316
|
}),
|
|
9113
|
-
|
|
9114
|
-
kind:
|
|
9115
|
-
href:
|
|
9317
|
+
z43.object({
|
|
9318
|
+
kind: z43.literal("repair_meta_campaign"),
|
|
9319
|
+
href: z43.string().min(1)
|
|
9116
9320
|
}),
|
|
9117
|
-
|
|
9118
|
-
kind:
|
|
9119
|
-
href:
|
|
9321
|
+
z43.object({
|
|
9322
|
+
kind: z43.literal("review_paused_campaign"),
|
|
9323
|
+
href: z43.string().min(1)
|
|
9120
9324
|
})
|
|
9121
9325
|
]);
|
|
9122
|
-
var DropshippingProjectResponseSchema =
|
|
9123
|
-
id:
|
|
9124
|
-
taskId:
|
|
9326
|
+
var DropshippingProjectResponseSchema = z43.object({
|
|
9327
|
+
id: z43.string().uuid(),
|
|
9328
|
+
taskId: z43.string().uuid().nullable(),
|
|
9125
9329
|
status: DropshippingProjectStatusSchema,
|
|
9126
|
-
originKey:
|
|
9127
|
-
researchRunId:
|
|
9128
|
-
candidateSourceId:
|
|
9129
|
-
productName:
|
|
9330
|
+
originKey: z43.string().min(1),
|
|
9331
|
+
researchRunId: z43.string().uuid().nullable(),
|
|
9332
|
+
candidateSourceId: z43.string().nullable(),
|
|
9333
|
+
productName: z43.string().nullable(),
|
|
9130
9334
|
productUrl: HttpUrlSchema.nullable(),
|
|
9131
|
-
referenceImageUrl:
|
|
9132
|
-
hasReferenceImage:
|
|
9133
|
-
targetCountry:
|
|
9134
|
-
languageCode:
|
|
9335
|
+
referenceImageUrl: z43.string().url().nullable(),
|
|
9336
|
+
hasReferenceImage: z43.boolean(),
|
|
9337
|
+
targetCountry: z43.string().nullable(),
|
|
9338
|
+
languageCode: z43.string().nullable(),
|
|
9135
9339
|
costingInput: DropshippingProjectCostingInputSchema.nullable(),
|
|
9136
9340
|
costingResult: DropshippingProjectCostingResultSchema.nullable(),
|
|
9137
|
-
costingVersion:
|
|
9138
|
-
version:
|
|
9341
|
+
costingVersion: z43.number().int().positive().nullable(),
|
|
9342
|
+
version: z43.number().int().positive(),
|
|
9139
9343
|
phase: DropshippingProjectPhaseSchema,
|
|
9140
9344
|
nextAction: DropshippingProjectNextActionSchema.nullable(),
|
|
9141
9345
|
rewards: DropshippingProjectRewardProgressSchema,
|
|
9142
|
-
createdAt:
|
|
9143
|
-
updatedAt:
|
|
9346
|
+
createdAt: z43.string().datetime(),
|
|
9347
|
+
updatedAt: z43.string().datetime()
|
|
9144
9348
|
});
|
|
9145
9349
|
|
|
9146
9350
|
// ../../packages/types/src/scene-subject-contract.ts
|
|
9147
|
-
import { z as
|
|
9148
|
-
var SceneSubjectContractSchema =
|
|
9149
|
-
maxPeople:
|
|
9150
|
-
minPeople:
|
|
9151
|
-
minDistinctPeople:
|
|
9152
|
-
maxHands:
|
|
9351
|
+
import { z as z44 } from "zod";
|
|
9352
|
+
var SceneSubjectContractSchema = z44.object({
|
|
9353
|
+
maxPeople: z44.number().int().min(1).max(10),
|
|
9354
|
+
minPeople: z44.number().int().min(0).max(10),
|
|
9355
|
+
minDistinctPeople: z44.number().int().min(0).max(10),
|
|
9356
|
+
maxHands: z44.number().int().min(0).max(20)
|
|
9153
9357
|
}).superRefine((value, ctx) => {
|
|
9154
9358
|
if (value.minPeople > value.maxPeople) {
|
|
9155
9359
|
ctx.addIssue({
|
|
9156
|
-
code:
|
|
9360
|
+
code: z44.ZodIssueCode.custom,
|
|
9157
9361
|
path: [
|
|
9158
9362
|
"minPeople"
|
|
9159
9363
|
],
|
|
@@ -9162,7 +9366,7 @@ var SceneSubjectContractSchema = z43.object({
|
|
|
9162
9366
|
}
|
|
9163
9367
|
if (value.minDistinctPeople > value.maxPeople) {
|
|
9164
9368
|
ctx.addIssue({
|
|
9165
|
-
code:
|
|
9369
|
+
code: z44.ZodIssueCode.custom,
|
|
9166
9370
|
path: [
|
|
9167
9371
|
"minDistinctPeople"
|
|
9168
9372
|
],
|
|
@@ -9171,7 +9375,7 @@ var SceneSubjectContractSchema = z43.object({
|
|
|
9171
9375
|
}
|
|
9172
9376
|
if (value.minDistinctPeople > value.minPeople) {
|
|
9173
9377
|
ctx.addIssue({
|
|
9174
|
-
code:
|
|
9378
|
+
code: z44.ZodIssueCode.custom,
|
|
9175
9379
|
path: [
|
|
9176
9380
|
"minDistinctPeople"
|
|
9177
9381
|
],
|
|
@@ -9181,7 +9385,7 @@ var SceneSubjectContractSchema = z43.object({
|
|
|
9181
9385
|
});
|
|
9182
9386
|
|
|
9183
9387
|
// ../../packages/types/src/service-creative.ts
|
|
9184
|
-
import { z as
|
|
9388
|
+
import { z as z45 } from "zod";
|
|
9185
9389
|
var SERVICE_CREATIVE_PUBLIC_PRESETS = [
|
|
9186
9390
|
"auto",
|
|
9187
9391
|
"ugc_dynamic",
|
|
@@ -9189,19 +9393,19 @@ var SERVICE_CREATIVE_PUBLIC_PRESETS = [
|
|
|
9189
9393
|
"cinematic",
|
|
9190
9394
|
"wild_card"
|
|
9191
9395
|
];
|
|
9192
|
-
var ServiceCreativePresetSchema =
|
|
9396
|
+
var ServiceCreativePresetSchema = z45.enum(SERVICE_CREATIVE_PUBLIC_PRESETS);
|
|
9193
9397
|
var SERVICE_REVIEW_MODES = [
|
|
9194
9398
|
"automatic",
|
|
9195
9399
|
"guided"
|
|
9196
9400
|
];
|
|
9197
|
-
var ServiceReviewModeSchema =
|
|
9401
|
+
var ServiceReviewModeSchema = z45.enum(SERVICE_REVIEW_MODES);
|
|
9198
9402
|
var SERVICE_HIGGSFIELD_MODES = [
|
|
9199
9403
|
"product_review",
|
|
9200
9404
|
"ugc_how_to",
|
|
9201
9405
|
"tv_spot",
|
|
9202
9406
|
"wild_card"
|
|
9203
9407
|
];
|
|
9204
|
-
var ServiceHiggsfieldModeSchema =
|
|
9408
|
+
var ServiceHiggsfieldModeSchema = z45.enum(SERVICE_HIGGSFIELD_MODES);
|
|
9205
9409
|
var SERVICE_MEDIA_ROLES = [
|
|
9206
9410
|
"screen_demo",
|
|
9207
9411
|
"ad_reference",
|
|
@@ -9209,20 +9413,20 @@ var SERVICE_MEDIA_ROLES = [
|
|
|
9209
9413
|
"presenter_reference",
|
|
9210
9414
|
"brand_asset"
|
|
9211
9415
|
];
|
|
9212
|
-
var ServiceMediaRoleSchema =
|
|
9213
|
-
var ServiceMediaAssetSchema =
|
|
9416
|
+
var ServiceMediaRoleSchema = z45.enum(SERVICE_MEDIA_ROLES);
|
|
9417
|
+
var ServiceMediaAssetSchema = z45.object({
|
|
9214
9418
|
role: ServiceMediaRoleSchema,
|
|
9215
|
-
s3Key:
|
|
9419
|
+
s3Key: z45.string().min(1),
|
|
9216
9420
|
/** HMAC ticket returned by the authenticated service-media presign route. */
|
|
9217
|
-
uploadToken:
|
|
9218
|
-
mimeType:
|
|
9219
|
-
durationSec:
|
|
9220
|
-
sourceName:
|
|
9421
|
+
uploadToken: z45.string().min(1).optional(),
|
|
9422
|
+
mimeType: z45.string().min(1),
|
|
9423
|
+
durationSec: z45.number().positive().max(30).optional(),
|
|
9424
|
+
sourceName: z45.string().max(255).optional()
|
|
9221
9425
|
}).superRefine((asset, ctx) => {
|
|
9222
9426
|
const isVideoRole = asset.role === "screen_demo" || asset.role === "ad_reference";
|
|
9223
9427
|
if (isVideoRole && !asset.mimeType.startsWith("video/")) {
|
|
9224
9428
|
ctx.addIssue({
|
|
9225
|
-
code:
|
|
9429
|
+
code: z45.ZodIssueCode.custom,
|
|
9226
9430
|
message: `${asset.role} requires video media`,
|
|
9227
9431
|
path: [
|
|
9228
9432
|
"mimeType"
|
|
@@ -9231,7 +9435,7 @@ var ServiceMediaAssetSchema = z44.object({
|
|
|
9231
9435
|
}
|
|
9232
9436
|
if (isVideoRole && asset.durationSec === void 0) {
|
|
9233
9437
|
ctx.addIssue({
|
|
9234
|
-
code:
|
|
9438
|
+
code: z45.ZodIssueCode.custom,
|
|
9235
9439
|
message: `${asset.role} requires durationSec`,
|
|
9236
9440
|
path: [
|
|
9237
9441
|
"durationSec"
|
|
@@ -9240,7 +9444,7 @@ var ServiceMediaAssetSchema = z44.object({
|
|
|
9240
9444
|
}
|
|
9241
9445
|
if (!isVideoRole && !asset.mimeType.startsWith("image/")) {
|
|
9242
9446
|
ctx.addIssue({
|
|
9243
|
-
code:
|
|
9447
|
+
code: z45.ZodIssueCode.custom,
|
|
9244
9448
|
message: `${asset.role} requires image media`,
|
|
9245
9449
|
path: [
|
|
9246
9450
|
"mimeType"
|
|
@@ -9248,13 +9452,13 @@ var ServiceMediaAssetSchema = z44.object({
|
|
|
9248
9452
|
});
|
|
9249
9453
|
}
|
|
9250
9454
|
});
|
|
9251
|
-
var ServiceMediaBundleSchema =
|
|
9252
|
-
assets:
|
|
9455
|
+
var ServiceMediaBundleSchema = z45.object({
|
|
9456
|
+
assets: z45.array(ServiceMediaAssetSchema).max(15).default([])
|
|
9253
9457
|
}).superRefine((bundle, ctx) => {
|
|
9254
9458
|
const adReferences = bundle.assets.filter((asset) => asset.role === "ad_reference");
|
|
9255
9459
|
if (adReferences.length > 5) {
|
|
9256
9460
|
ctx.addIssue({
|
|
9257
|
-
code:
|
|
9461
|
+
code: z45.ZodIssueCode.custom,
|
|
9258
9462
|
message: "A maximum of five ad_reference videos is allowed",
|
|
9259
9463
|
path: [
|
|
9260
9464
|
"assets"
|
|
@@ -9264,7 +9468,7 @@ var ServiceMediaBundleSchema = z44.object({
|
|
|
9264
9468
|
const screenDemos = bundle.assets.filter((asset) => asset.role === "screen_demo");
|
|
9265
9469
|
if (screenDemos.length > 3) {
|
|
9266
9470
|
ctx.addIssue({
|
|
9267
|
-
code:
|
|
9471
|
+
code: z45.ZodIssueCode.custom,
|
|
9268
9472
|
message: "A maximum of three screen_demo videos is allowed",
|
|
9269
9473
|
path: [
|
|
9270
9474
|
"assets"
|
|
@@ -9281,33 +9485,33 @@ var SERVICE_MEDIA_ANALYSIS_KINDS = [
|
|
|
9281
9485
|
"product",
|
|
9282
9486
|
"other"
|
|
9283
9487
|
];
|
|
9284
|
-
var ServiceMediaAnalysisKindSchema =
|
|
9285
|
-
var ServiceMediaAnalysisItemSchema =
|
|
9286
|
-
assetIndex:
|
|
9287
|
-
mediaType:
|
|
9488
|
+
var ServiceMediaAnalysisKindSchema = z45.enum(SERVICE_MEDIA_ANALYSIS_KINDS);
|
|
9489
|
+
var ServiceMediaAnalysisItemSchema = z45.object({
|
|
9490
|
+
assetIndex: z45.number().int().nonnegative(),
|
|
9491
|
+
mediaType: z45.enum([
|
|
9288
9492
|
"image",
|
|
9289
9493
|
"video"
|
|
9290
9494
|
]),
|
|
9291
9495
|
kind: ServiceMediaAnalysisKindSchema,
|
|
9292
|
-
description:
|
|
9293
|
-
tags:
|
|
9294
|
-
suggestedRoles:
|
|
9295
|
-
visiblePeople:
|
|
9296
|
-
ocrText:
|
|
9297
|
-
speechSummary:
|
|
9298
|
-
});
|
|
9299
|
-
var ServiceMediaAnalysisSchema =
|
|
9300
|
-
version:
|
|
9301
|
-
analyzedAt:
|
|
9302
|
-
pricingAction:
|
|
9303
|
-
assetFingerprint:
|
|
9496
|
+
description: z45.string().min(1).max(1200),
|
|
9497
|
+
tags: z45.array(z45.string().min(1).max(80)).max(12),
|
|
9498
|
+
suggestedRoles: z45.array(ServiceMediaRoleSchema).max(4),
|
|
9499
|
+
visiblePeople: z45.number().int().nonnegative().max(20).optional(),
|
|
9500
|
+
ocrText: z45.string().max(2e3).optional(),
|
|
9501
|
+
speechSummary: z45.string().max(2e3).optional()
|
|
9502
|
+
});
|
|
9503
|
+
var ServiceMediaAnalysisSchema = z45.object({
|
|
9504
|
+
version: z45.literal(1),
|
|
9505
|
+
analyzedAt: z45.string().datetime(),
|
|
9506
|
+
pricingAction: z45.literal("service-media-analysis"),
|
|
9507
|
+
assetFingerprint: z45.string().regex(/^[a-f0-9]{64}$/),
|
|
9304
9508
|
/** Language used for human-readable descriptions and tags. Optional for old checkpoints. */
|
|
9305
|
-
language:
|
|
9509
|
+
language: z45.enum([
|
|
9306
9510
|
"es",
|
|
9307
9511
|
"en",
|
|
9308
9512
|
"pt"
|
|
9309
9513
|
]).optional(),
|
|
9310
|
-
items:
|
|
9514
|
+
items: z45.array(ServiceMediaAnalysisItemSchema).max(15)
|
|
9311
9515
|
});
|
|
9312
9516
|
|
|
9313
9517
|
// ../../packages/types/src/productmaker-capability-registry.ts
|
|
@@ -9365,6 +9569,7 @@ function slot(name, required, mcp2, whatsapp = mcp2, source = "user") {
|
|
|
9365
9569
|
}
|
|
9366
9570
|
__name(slot, "slot");
|
|
9367
9571
|
var SERVER_SELECTED_OR_ARTIFACT_FIELDS = /* @__PURE__ */ new Set([
|
|
9572
|
+
"actionKind",
|
|
9368
9573
|
"actorId",
|
|
9369
9574
|
"adAccountId",
|
|
9370
9575
|
"advertiserId",
|
|
@@ -9823,7 +10028,7 @@ var PRODUCTMAKER_CAPABILITY_REGISTRY = deepFreeze([
|
|
|
9823
10028
|
], [
|
|
9824
10029
|
"creative"
|
|
9825
10030
|
]),
|
|
9826
|
-
defineCapability("generate_landing", "
|
|
10031
|
+
defineCapability("generate_landing", "67520417176396516cd5ad358dca39f70a5c4a74169eecd85987217226c12251", "credit_consuming", "creative", [
|
|
9827
10032
|
"credit_consuming",
|
|
9828
10033
|
"state_change"
|
|
9829
10034
|
], [
|
|
@@ -9868,12 +10073,14 @@ var PRODUCTMAKER_CAPABILITY_REGISTRY = deepFreeze([
|
|
|
9868
10073
|
"competitorScanEnabled",
|
|
9869
10074
|
"landingStrategy",
|
|
9870
10075
|
"landingImageFormat",
|
|
10076
|
+
"pauseForAngleReview",
|
|
9871
10077
|
// MCP-only, like create_product_task's aiDecidesAll.
|
|
9872
10078
|
"aiDecidesAll"
|
|
9873
10079
|
], [
|
|
9874
10080
|
"competitorScanEnabled",
|
|
9875
10081
|
"landingStrategy",
|
|
9876
|
-
"landingImageFormat"
|
|
10082
|
+
"landingImageFormat",
|
|
10083
|
+
"pauseForAngleReview"
|
|
9877
10084
|
])
|
|
9878
10085
|
], [
|
|
9879
10086
|
"product_reference"
|
|
@@ -10189,7 +10396,7 @@ var PRODUCTMAKER_CAPABILITY_REGISTRY = deepFreeze([
|
|
|
10189
10396
|
], [
|
|
10190
10397
|
"meta_publication"
|
|
10191
10398
|
]),
|
|
10192
|
-
defineCapability("publish_to_shopify", "
|
|
10399
|
+
defineCapability("publish_to_shopify", "6a75ad9bfe04aed24ed41564011ac785657bab12caccd4d78c1de846ce798e82", "external_mutation", "publishing", [
|
|
10193
10400
|
"external_mutation",
|
|
10194
10401
|
"state_change"
|
|
10195
10402
|
], [
|
|
@@ -10209,7 +10416,13 @@ var PRODUCTMAKER_CAPABILITY_REGISTRY = deepFreeze([
|
|
|
10209
10416
|
slot("publication_target", false, [
|
|
10210
10417
|
"publicationMode",
|
|
10211
10418
|
"targetProductId"
|
|
10212
|
-
])
|
|
10419
|
+
]),
|
|
10420
|
+
// MCP + web only: the WhatsApp publish tool exposes no announcement fields,
|
|
10421
|
+
// and claiming them here breaks the schema-parity invariant.
|
|
10422
|
+
slot("announcement_bar", false, [
|
|
10423
|
+
"announcementBarEnabled",
|
|
10424
|
+
"announcementBarText"
|
|
10425
|
+
], [])
|
|
10213
10426
|
], [
|
|
10214
10427
|
"product",
|
|
10215
10428
|
"shop"
|
|
@@ -10383,8 +10596,8 @@ var PRODUCTMAKER_MCP_CAPABILITIES = toLegacyCapabilityCatalog(PRODUCTMAKER_MCP_C
|
|
|
10383
10596
|
var PRODUCTMAKER_MCP_CAPABILITY_NAMES = PRODUCTMAKER_MCP_CAPABILITY_REGISTRY.map((capability) => capability.name);
|
|
10384
10597
|
|
|
10385
10598
|
// ../../packages/types/src/actor.ts
|
|
10386
|
-
import { z as
|
|
10387
|
-
var ActorStatusSchema =
|
|
10599
|
+
import { z as z46 } from "zod";
|
|
10600
|
+
var ActorStatusSchema = z46.enum([
|
|
10388
10601
|
"draft",
|
|
10389
10602
|
"generating",
|
|
10390
10603
|
"ready",
|
|
@@ -10393,18 +10606,18 @@ var ActorStatusSchema = z45.enum([
|
|
|
10393
10606
|
var ACTOR_NAME_MAX_CHARS = 60;
|
|
10394
10607
|
var ACTOR_DESCRIPTION_MAX_CHARS = 600;
|
|
10395
10608
|
var ACTOR_CANDIDATE_COUNT = 4;
|
|
10396
|
-
var ActorGenderSchema =
|
|
10609
|
+
var ActorGenderSchema = z46.enum([
|
|
10397
10610
|
"female",
|
|
10398
10611
|
"male",
|
|
10399
10612
|
"non_binary"
|
|
10400
10613
|
]);
|
|
10401
|
-
var ActorSchema =
|
|
10402
|
-
id:
|
|
10403
|
-
name:
|
|
10404
|
-
description:
|
|
10614
|
+
var ActorSchema = z46.object({
|
|
10615
|
+
id: z46.string(),
|
|
10616
|
+
name: z46.string().min(1).max(ACTOR_NAME_MAX_CHARS),
|
|
10617
|
+
description: z46.string().max(ACTOR_DESCRIPTION_MAX_CHARS),
|
|
10405
10618
|
gender: ActorGenderSchema,
|
|
10406
|
-
voiceId:
|
|
10407
|
-
brandId:
|
|
10619
|
+
voiceId: z46.string().nullable().optional(),
|
|
10620
|
+
brandId: z46.string().nullable().optional(),
|
|
10408
10621
|
status: ActorStatusSchema,
|
|
10409
10622
|
/**
|
|
10410
10623
|
* A candidate-grid run is in flight RIGHT NOW.
|
|
@@ -10416,36 +10629,36 @@ var ActorSchema = z45.object({
|
|
|
10416
10629
|
* timestamp with a TTL, so an orphaned run reads as `false` instead of
|
|
10417
10630
|
* pinning the personaje forever.
|
|
10418
10631
|
*/
|
|
10419
|
-
generating:
|
|
10420
|
-
photoUrl:
|
|
10421
|
-
avatarUrl:
|
|
10422
|
-
candidateUrls:
|
|
10632
|
+
generating: z46.boolean().optional(),
|
|
10633
|
+
photoUrl: z46.string().url().nullable().optional(),
|
|
10634
|
+
avatarUrl: z46.string().url().nullable().optional(),
|
|
10635
|
+
candidateUrls: z46.array(z46.string().url()).optional(),
|
|
10423
10636
|
/** Indices of candidateUrls that failed the anatomy check (Task 6). */
|
|
10424
|
-
flaggedCandidateIndices:
|
|
10637
|
+
flaggedCandidateIndices: z46.array(z46.number().int()).optional()
|
|
10425
10638
|
});
|
|
10426
|
-
var CreateActorRequest =
|
|
10427
|
-
name:
|
|
10428
|
-
description:
|
|
10639
|
+
var CreateActorRequest = z46.object({
|
|
10640
|
+
name: z46.string().min(1).max(ACTOR_NAME_MAX_CHARS),
|
|
10641
|
+
description: z46.string().max(ACTOR_DESCRIPTION_MAX_CHARS).default(""),
|
|
10429
10642
|
gender: ActorGenderSchema,
|
|
10430
|
-
voiceId:
|
|
10431
|
-
brandId:
|
|
10432
|
-
sourcePhotoKey:
|
|
10643
|
+
voiceId: z46.string().optional(),
|
|
10644
|
+
brandId: z46.string().optional(),
|
|
10645
|
+
sourcePhotoKey: z46.string().optional()
|
|
10433
10646
|
}).refine((v) => v.description.trim().length > 0 || !!v.sourcePhotoKey, {
|
|
10434
10647
|
message: "Se necesita una descripci\xF3n o una foto"
|
|
10435
10648
|
});
|
|
10436
|
-
var UpdateActorRequest =
|
|
10437
|
-
name:
|
|
10438
|
-
description:
|
|
10649
|
+
var UpdateActorRequest = z46.object({
|
|
10650
|
+
name: z46.string().min(1).max(ACTOR_NAME_MAX_CHARS).optional(),
|
|
10651
|
+
description: z46.string().max(ACTOR_DESCRIPTION_MAX_CHARS).optional(),
|
|
10439
10652
|
gender: ActorGenderSchema.optional(),
|
|
10440
|
-
voiceId:
|
|
10441
|
-
brandId:
|
|
10653
|
+
voiceId: z46.string().nullable().optional(),
|
|
10654
|
+
brandId: z46.string().nullable().optional()
|
|
10442
10655
|
});
|
|
10443
|
-
var ApproveActorRequest =
|
|
10444
|
-
candidateIndex:
|
|
10656
|
+
var ApproveActorRequest = z46.object({
|
|
10657
|
+
candidateIndex: z46.number().int().min(0).max(ACTOR_CANDIDATE_COUNT - 1)
|
|
10445
10658
|
});
|
|
10446
10659
|
|
|
10447
10660
|
// ../../packages/types/src/referral-dashboard.ts
|
|
10448
|
-
import { z as
|
|
10661
|
+
import { z as z47 } from "zod";
|
|
10449
10662
|
var REFERRAL_DASHBOARD_RAW_STATUSES = [
|
|
10450
10663
|
"ACTIVE",
|
|
10451
10664
|
"PENDING",
|
|
@@ -10464,66 +10677,66 @@ var REFERRAL_DASHBOARD_PLAN_KINDS = [
|
|
|
10464
10677
|
"last",
|
|
10465
10678
|
"none"
|
|
10466
10679
|
];
|
|
10467
|
-
var ReferralDashboardRawStatusSchema =
|
|
10468
|
-
var ReferralDashboardStateSchema =
|
|
10469
|
-
var ReferralDashboardPlanKindSchema =
|
|
10470
|
-
var ReferralDashboardStatusSchema =
|
|
10680
|
+
var ReferralDashboardRawStatusSchema = z47.enum(REFERRAL_DASHBOARD_RAW_STATUSES);
|
|
10681
|
+
var ReferralDashboardStateSchema = z47.enum(REFERRAL_DASHBOARD_STATES);
|
|
10682
|
+
var ReferralDashboardPlanKindSchema = z47.enum(REFERRAL_DASHBOARD_PLAN_KINDS);
|
|
10683
|
+
var ReferralDashboardStatusSchema = z47.enum([
|
|
10471
10684
|
"all",
|
|
10472
10685
|
"active",
|
|
10473
10686
|
"pending",
|
|
10474
10687
|
"inactive"
|
|
10475
10688
|
]).default("all");
|
|
10476
|
-
var ReferralDashboardPlanSchema =
|
|
10477
|
-
var ReferralDashboardFiltersSchema =
|
|
10689
|
+
var ReferralDashboardPlanSchema = z47.string().trim().min(1).max(100).optional();
|
|
10690
|
+
var ReferralDashboardFiltersSchema = z47.object({
|
|
10478
10691
|
status: ReferralDashboardStatusSchema,
|
|
10479
10692
|
plan: ReferralDashboardPlanSchema
|
|
10480
10693
|
});
|
|
10481
|
-
var ReferralDashboardSubscriptionSchema =
|
|
10482
|
-
planName:
|
|
10694
|
+
var ReferralDashboardSubscriptionSchema = z47.object({
|
|
10695
|
+
planName: z47.string().nullable(),
|
|
10483
10696
|
planKind: ReferralDashboardPlanKindSchema,
|
|
10484
10697
|
rawStatus: ReferralDashboardRawStatusSchema.nullable(),
|
|
10485
10698
|
state: ReferralDashboardStateSchema,
|
|
10486
|
-
currentPeriodEnd:
|
|
10699
|
+
currentPeriodEnd: z47.string().datetime({
|
|
10487
10700
|
offset: true
|
|
10488
10701
|
}).nullable(),
|
|
10489
|
-
cancelAtPeriodEnd:
|
|
10702
|
+
cancelAtPeriodEnd: z47.boolean()
|
|
10490
10703
|
});
|
|
10491
|
-
var ReferralDashboardReferralSchema =
|
|
10492
|
-
id:
|
|
10493
|
-
registeredAt:
|
|
10704
|
+
var ReferralDashboardReferralSchema = z47.object({
|
|
10705
|
+
id: z47.string().min(1),
|
|
10706
|
+
registeredAt: z47.string().datetime({
|
|
10494
10707
|
offset: true
|
|
10495
10708
|
}),
|
|
10496
10709
|
// Kept in the response for compatibility with existing referral API clients.
|
|
10497
10710
|
// The referrer dashboard intentionally does not render these legacy fields.
|
|
10498
|
-
expiresAt:
|
|
10711
|
+
expiresAt: z47.string().datetime({
|
|
10499
10712
|
offset: true
|
|
10500
10713
|
}).nullable().optional(),
|
|
10501
|
-
status:
|
|
10502
|
-
referredUser:
|
|
10503
|
-
email:
|
|
10714
|
+
status: z47.string().min(1).optional(),
|
|
10715
|
+
referredUser: z47.object({
|
|
10716
|
+
email: z47.string().nullable()
|
|
10504
10717
|
}),
|
|
10505
10718
|
subscription: ReferralDashboardSubscriptionSchema
|
|
10506
10719
|
});
|
|
10507
|
-
var ReferralDashboardReferralsPageSchema =
|
|
10508
|
-
referrals:
|
|
10509
|
-
total:
|
|
10720
|
+
var ReferralDashboardReferralsPageSchema = z47.object({
|
|
10721
|
+
referrals: z47.array(ReferralDashboardReferralSchema),
|
|
10722
|
+
total: z47.number().int().nonnegative()
|
|
10510
10723
|
});
|
|
10511
|
-
var ReferralDashboardPlanBreakdownSchema =
|
|
10512
|
-
planName:
|
|
10513
|
-
count:
|
|
10724
|
+
var ReferralDashboardPlanBreakdownSchema = z47.object({
|
|
10725
|
+
planName: z47.string().nullable(),
|
|
10726
|
+
count: z47.number().int().nonnegative()
|
|
10514
10727
|
});
|
|
10515
|
-
var ReferralDashboardMonthlyRegistrationSchema =
|
|
10516
|
-
month:
|
|
10517
|
-
count:
|
|
10728
|
+
var ReferralDashboardMonthlyRegistrationSchema = z47.object({
|
|
10729
|
+
month: z47.string().regex(/^\d{4}-(0[1-9]|1[0-2])$/),
|
|
10730
|
+
count: z47.number().int().nonnegative()
|
|
10518
10731
|
});
|
|
10519
|
-
var ReferralDashboardOverviewSchema =
|
|
10520
|
-
totalReferredUsers:
|
|
10521
|
-
activeUsers:
|
|
10522
|
-
pendingUsers:
|
|
10523
|
-
inactiveUsers:
|
|
10524
|
-
noPlanUsers:
|
|
10525
|
-
planBreakdown:
|
|
10526
|
-
monthlyRegistrations:
|
|
10732
|
+
var ReferralDashboardOverviewSchema = z47.object({
|
|
10733
|
+
totalReferredUsers: z47.number().int().nonnegative(),
|
|
10734
|
+
activeUsers: z47.number().int().nonnegative(),
|
|
10735
|
+
pendingUsers: z47.number().int().nonnegative(),
|
|
10736
|
+
inactiveUsers: z47.number().int().nonnegative(),
|
|
10737
|
+
noPlanUsers: z47.number().int().nonnegative(),
|
|
10738
|
+
planBreakdown: z47.array(ReferralDashboardPlanBreakdownSchema),
|
|
10739
|
+
monthlyRegistrations: z47.array(ReferralDashboardMonthlyRegistrationSchema).refine((months) => months.length === 6, "Expected six monthly registration buckets")
|
|
10527
10740
|
});
|
|
10528
10741
|
|
|
10529
10742
|
// src/resolve-media.ts
|
|
@@ -10766,10 +10979,10 @@ async function resolveMediaRef(ref, opts) {
|
|
|
10766
10979
|
__name(resolveMediaRef, "resolveMediaRef");
|
|
10767
10980
|
|
|
10768
10981
|
// src/tools/shared.ts
|
|
10769
|
-
import { z as
|
|
10770
|
-
var Lang =
|
|
10771
|
-
var Country =
|
|
10772
|
-
var LandingStrategyEnum =
|
|
10982
|
+
import { z as z48 } from "zod";
|
|
10983
|
+
var Lang = z48.string().min(2).max(10);
|
|
10984
|
+
var Country = z48.string().min(2).max(3);
|
|
10985
|
+
var LandingStrategyEnum = z48.enum([
|
|
10773
10986
|
"auto",
|
|
10774
10987
|
"classic",
|
|
10775
10988
|
"pain_solution",
|
|
@@ -10778,12 +10991,12 @@ var LandingStrategyEnum = z47.enum([
|
|
|
10778
10991
|
"offer_value",
|
|
10779
10992
|
"lifestyle_identity"
|
|
10780
10993
|
]);
|
|
10781
|
-
var LandingImageFormatEnum =
|
|
10994
|
+
var LandingImageFormatEnum = z48.enum([
|
|
10782
10995
|
"9:16",
|
|
10783
10996
|
"4:5",
|
|
10784
10997
|
"1:1"
|
|
10785
10998
|
]);
|
|
10786
|
-
var VideoPresetEnum =
|
|
10999
|
+
var VideoPresetEnum = z48.enum([
|
|
10787
11000
|
"auto",
|
|
10788
11001
|
"ugc_dynamic",
|
|
10789
11002
|
"ugc_how_to",
|
|
@@ -10796,7 +11009,7 @@ var VideoPresetEnum = z47.enum([
|
|
|
10796
11009
|
"pixar_problem",
|
|
10797
11010
|
"pixar_sassy"
|
|
10798
11011
|
]);
|
|
10799
|
-
var TaskCreativeVariantVideoPresetEnum =
|
|
11012
|
+
var TaskCreativeVariantVideoPresetEnum = z48.enum([
|
|
10800
11013
|
"auto",
|
|
10801
11014
|
"ugc_dynamic",
|
|
10802
11015
|
"ugc_how_to",
|
|
@@ -10867,7 +11080,7 @@ function resolveVideoPreset(preset) {
|
|
|
10867
11080
|
return VIDEO_PRESET_RESOLUTION[preset] ?? null;
|
|
10868
11081
|
}
|
|
10869
11082
|
__name(resolveVideoPreset, "resolveVideoPreset");
|
|
10870
|
-
var VideoTierEnum =
|
|
11083
|
+
var VideoTierEnum = z48.enum([
|
|
10871
11084
|
"basic"
|
|
10872
11085
|
]);
|
|
10873
11086
|
function resolveVideoTier(_tier) {
|
|
@@ -10882,17 +11095,17 @@ function resolveVideoFormatForDuration(durationSeconds) {
|
|
|
10882
11095
|
return durationSeconds === 30 ? "double_30s" : "single_15s";
|
|
10883
11096
|
}
|
|
10884
11097
|
__name(resolveVideoFormatForDuration, "resolveVideoFormatForDuration");
|
|
10885
|
-
var VideoResolutionEnum =
|
|
11098
|
+
var VideoResolutionEnum = z48.enum([
|
|
10886
11099
|
"480p",
|
|
10887
11100
|
"720p"
|
|
10888
11101
|
]);
|
|
10889
|
-
var VideoStyleEnum =
|
|
11102
|
+
var VideoStyleEnum = z48.enum([
|
|
10890
11103
|
"ugc",
|
|
10891
11104
|
"cartoon_3d",
|
|
10892
11105
|
"product_only",
|
|
10893
11106
|
"ai_dynamic"
|
|
10894
11107
|
]);
|
|
10895
|
-
var NarrativeStyleEnum =
|
|
11108
|
+
var NarrativeStyleEnum = z48.enum([
|
|
10896
11109
|
"review",
|
|
10897
11110
|
"ugc_how_to",
|
|
10898
11111
|
"problem_product_cta",
|
|
@@ -10909,7 +11122,7 @@ var NarrativeStyleEnum = z47.enum([
|
|
|
10909
11122
|
"ugc_skeptic_confession",
|
|
10910
11123
|
"ugc_demo_review"
|
|
10911
11124
|
]);
|
|
10912
|
-
var PublicNarrativeStyleEnum =
|
|
11125
|
+
var PublicNarrativeStyleEnum = z48.enum([
|
|
10913
11126
|
"review",
|
|
10914
11127
|
"ugc_how_to",
|
|
10915
11128
|
"ugc_unboxing_chaos",
|
|
@@ -10921,7 +11134,7 @@ var PublicNarrativeStyleEnum = z47.enum([
|
|
|
10921
11134
|
"animated_problem_pixar",
|
|
10922
11135
|
"sassy_product_pixar"
|
|
10923
11136
|
]);
|
|
10924
|
-
var VideoModelEnum =
|
|
11137
|
+
var VideoModelEnum = z48.enum([
|
|
10925
11138
|
"veo",
|
|
10926
11139
|
"wan",
|
|
10927
11140
|
"bytedance",
|
|
@@ -10929,29 +11142,29 @@ var VideoModelEnum = z47.enum([
|
|
|
10929
11142
|
"grok",
|
|
10930
11143
|
"kling"
|
|
10931
11144
|
]);
|
|
10932
|
-
var ProductReferenceUserChoiceEnum =
|
|
11145
|
+
var ProductReferenceUserChoiceEnum = z48.enum([
|
|
10933
11146
|
"same_product",
|
|
10934
11147
|
"variants",
|
|
10935
11148
|
"different_products",
|
|
10936
11149
|
"combo_distinct"
|
|
10937
11150
|
]);
|
|
10938
|
-
var ProductReferenceCandidateGroupingEnum =
|
|
11151
|
+
var ProductReferenceCandidateGroupingEnum = z48.enum([
|
|
10939
11152
|
"same_product_same_variant",
|
|
10940
11153
|
"same_product_different_variants",
|
|
10941
11154
|
"different_products",
|
|
10942
11155
|
"unclear"
|
|
10943
11156
|
]);
|
|
10944
|
-
var ProductReferenceScopeEnum =
|
|
11157
|
+
var ProductReferenceScopeEnum = z48.enum([
|
|
10945
11158
|
"base",
|
|
10946
11159
|
"video_view_pack"
|
|
10947
11160
|
]);
|
|
10948
|
-
var ProductReferenceResolutionDecisionEnum =
|
|
11161
|
+
var ProductReferenceResolutionDecisionEnum = z48.enum([
|
|
10949
11162
|
"approve",
|
|
10950
11163
|
"reject",
|
|
10951
11164
|
"observed_only",
|
|
10952
11165
|
"correct_view"
|
|
10953
11166
|
]);
|
|
10954
|
-
var ProductReferenceRejectionReasonEnum =
|
|
11167
|
+
var ProductReferenceRejectionReasonEnum = z48.enum([
|
|
10955
11168
|
"replacement_requested",
|
|
10956
11169
|
"wrong_product_identity",
|
|
10957
11170
|
"wrong_variant",
|
|
@@ -10962,13 +11175,13 @@ var ProductReferenceRejectionReasonEnum = z47.enum([
|
|
|
10962
11175
|
"not_a_product_photo",
|
|
10963
11176
|
"other"
|
|
10964
11177
|
]);
|
|
10965
|
-
var GapDurationChoiceMirror =
|
|
10966
|
-
angleIndex:
|
|
10967
|
-
recommendedSec:
|
|
10968
|
-
|
|
10969
|
-
|
|
11178
|
+
var GapDurationChoiceMirror = z48.object({
|
|
11179
|
+
angleIndex: z48.number().int().nonnegative(),
|
|
11180
|
+
recommendedSec: z48.union([
|
|
11181
|
+
z48.literal(15),
|
|
11182
|
+
z48.literal(30)
|
|
10970
11183
|
]),
|
|
10971
|
-
estimatedDeltaCredits:
|
|
11184
|
+
estimatedDeltaCredits: z48.number().int().nonnegative().optional()
|
|
10972
11185
|
});
|
|
10973
11186
|
function buildSelectedImageUrlsHash(urls) {
|
|
10974
11187
|
const input = [
|
|
@@ -10978,13 +11191,13 @@ function buildSelectedImageUrlsHash(urls) {
|
|
|
10978
11191
|
return `fnv1a:${hash.toString(16).padStart(8, "0")}:${urls.length}`;
|
|
10979
11192
|
}
|
|
10980
11193
|
__name(buildSelectedImageUrlsHash, "buildSelectedImageUrlsHash");
|
|
10981
|
-
var ImageCreativeStyleEnum =
|
|
11194
|
+
var ImageCreativeStyleEnum = z48.enum([
|
|
10982
11195
|
"studio",
|
|
10983
11196
|
"floating",
|
|
10984
11197
|
"ingredients",
|
|
10985
11198
|
"in_use"
|
|
10986
11199
|
]);
|
|
10987
|
-
var ImageCreativeArchetypeEnum =
|
|
11200
|
+
var ImageCreativeArchetypeEnum = z48.enum([
|
|
10988
11201
|
"direct_response_product_ad",
|
|
10989
11202
|
"hero_outcome",
|
|
10990
11203
|
"offer_value_prop",
|
|
@@ -10993,20 +11206,20 @@ var ImageCreativeArchetypeEnum = z47.enum([
|
|
|
10993
11206
|
"problem_solution",
|
|
10994
11207
|
"lifestyle_identity"
|
|
10995
11208
|
]);
|
|
10996
|
-
var ImageCreativeVisualTreatmentEnum =
|
|
11209
|
+
var ImageCreativeVisualTreatmentEnum = z48.enum([
|
|
10997
11210
|
"performance_product_ad",
|
|
10998
11211
|
"premium_dtc_carousel"
|
|
10999
11212
|
]);
|
|
11000
|
-
var ImageCreativeAspectRatioEnum =
|
|
11213
|
+
var ImageCreativeAspectRatioEnum = z48.enum([
|
|
11001
11214
|
"1:1",
|
|
11002
11215
|
"4:5",
|
|
11003
11216
|
"9:16",
|
|
11004
11217
|
"16:9"
|
|
11005
11218
|
]);
|
|
11006
|
-
var ImageRefSchema =
|
|
11007
|
-
url:
|
|
11008
|
-
path:
|
|
11009
|
-
assetId:
|
|
11219
|
+
var ImageRefSchema = z48.object({
|
|
11220
|
+
url: z48.string().url().optional().describe('URL p\xFAblica HTTPS de la foto. Ej: una URL de Shopify CDN, Cloudinary, Imgur. \xDAsala cuando el usuario te diga "est\xE1 en X.com/foto.jpg" o cuando la foto ya est\xE9 hospedada.'),
|
|
11221
|
+
path: z48.string().min(1).optional().describe('Ruta del archivo en el computador del usuario. Ej: "/Users/maria/Desktop/foto.jpg", "~/Pictures/camiseta.png", "C:\\Users\\Maria\\Desktop\\foto.jpg". El MCP local la lee del disco y la sube. SOLO funciona cuando el MCP corre local (stdio). NO funciona en mcp.productmaker.app (HTTP) \u2014 ah\xED usa `url`.'),
|
|
11222
|
+
assetId: z48.string().uuid().optional().describe("ID opaco devuelto por check_media_upload. \xDAsalo para una imagen subida desde el MCP App; ProductMaker adopta el archivo directamente y el MCP nunca descarga sus bytes.")
|
|
11010
11223
|
}).refine((v) => [
|
|
11011
11224
|
v.url,
|
|
11012
11225
|
v.path,
|
|
@@ -11014,11 +11227,11 @@ var ImageRefSchema = z47.object({
|
|
|
11014
11227
|
].filter(Boolean).length === 1, {
|
|
11015
11228
|
message: "each image entry must have exactly one of `url`, `path`, or `assetId`"
|
|
11016
11229
|
});
|
|
11017
|
-
var ImagesInput =
|
|
11230
|
+
var ImagesInput = z48.array(ImageRefSchema).min(1).max(5).superRefine((images, ctx) => {
|
|
11018
11231
|
const assetCount = images.filter((image) => Boolean(image.assetId)).length;
|
|
11019
11232
|
if (assetCount > 0 && assetCount !== images.length) {
|
|
11020
11233
|
ctx.addIssue({
|
|
11021
|
-
code:
|
|
11234
|
+
code: z48.ZodIssueCode.custom,
|
|
11022
11235
|
message: "all image entries must use assetId, or all must use URL/path sources"
|
|
11023
11236
|
});
|
|
11024
11237
|
}
|
|
@@ -11088,14 +11301,14 @@ async function submitCreative(api, opts) {
|
|
|
11088
11301
|
});
|
|
11089
11302
|
}
|
|
11090
11303
|
__name(submitCreative, "submitCreative");
|
|
11091
|
-
var ResearchTargetCountryEnum =
|
|
11304
|
+
var ResearchTargetCountryEnum = z48.enum([
|
|
11092
11305
|
"CO",
|
|
11093
11306
|
"MX",
|
|
11094
11307
|
"PE",
|
|
11095
11308
|
"EC",
|
|
11096
11309
|
"CL"
|
|
11097
11310
|
]);
|
|
11098
|
-
var ResearchCategoryEnum =
|
|
11311
|
+
var ResearchCategoryEnum = z48.enum([
|
|
11099
11312
|
"auto",
|
|
11100
11313
|
"automotive",
|
|
11101
11314
|
"kitchen",
|
|
@@ -11110,6 +11323,42 @@ var ResearchCategoryEnum = z47.enum([
|
|
|
11110
11323
|
"garden",
|
|
11111
11324
|
"cleaning"
|
|
11112
11325
|
]);
|
|
11326
|
+
var READS_INTERNAL = {
|
|
11327
|
+
readOnlyHint: true,
|
|
11328
|
+
destructiveHint: false,
|
|
11329
|
+
idempotentHint: true,
|
|
11330
|
+
openWorldHint: false
|
|
11331
|
+
};
|
|
11332
|
+
var READS_EXTERNAL = {
|
|
11333
|
+
readOnlyHint: true,
|
|
11334
|
+
destructiveHint: false,
|
|
11335
|
+
idempotentHint: true,
|
|
11336
|
+
openWorldHint: true
|
|
11337
|
+
};
|
|
11338
|
+
var CREATES_INTERNAL = {
|
|
11339
|
+
readOnlyHint: false,
|
|
11340
|
+
destructiveHint: false,
|
|
11341
|
+
idempotentHint: false,
|
|
11342
|
+
openWorldHint: false
|
|
11343
|
+
};
|
|
11344
|
+
var CREATES_EXTERNAL = {
|
|
11345
|
+
readOnlyHint: false,
|
|
11346
|
+
destructiveHint: false,
|
|
11347
|
+
idempotentHint: false,
|
|
11348
|
+
openWorldHint: true
|
|
11349
|
+
};
|
|
11350
|
+
var OVERWRITES_INTERNAL = {
|
|
11351
|
+
readOnlyHint: false,
|
|
11352
|
+
destructiveHint: true,
|
|
11353
|
+
idempotentHint: false,
|
|
11354
|
+
openWorldHint: false
|
|
11355
|
+
};
|
|
11356
|
+
var OVERWRITES_EXTERNAL = {
|
|
11357
|
+
readOnlyHint: false,
|
|
11358
|
+
destructiveHint: true,
|
|
11359
|
+
idempotentHint: false,
|
|
11360
|
+
openWorldHint: true
|
|
11361
|
+
};
|
|
11113
11362
|
|
|
11114
11363
|
// src/errors.ts
|
|
11115
11364
|
var McpErrorCode = {
|
|
@@ -11567,6 +11816,18 @@ function translateError(err) {
|
|
|
11567
11816
|
}
|
|
11568
11817
|
__name(translateError, "translateError");
|
|
11569
11818
|
|
|
11819
|
+
// src/tools/approve-refusal-message.ts
|
|
11820
|
+
function approveRefusalMessage(reason) {
|
|
11821
|
+
if (reason === "photo_review_pending") {
|
|
11822
|
+
return "No se inici\xF3 la generaci\xF3n: faltan por revisar las fotos del producto. Abre la tarea, aprueba las fotos y vuelve a intentar. No se cobr\xF3 nada.";
|
|
11823
|
+
}
|
|
11824
|
+
if (reason === "photos_not_ready") {
|
|
11825
|
+
return "No se inici\xF3 la generaci\xF3n: todav\xEDa estamos preparando las fotos del producto. Intenta de nuevo en un momento. No se cobr\xF3 nada.";
|
|
11826
|
+
}
|
|
11827
|
+
return "No se inici\xF3 la generaci\xF3n y no se cobr\xF3 nada. Los cambios siguen guardados: vuelve a intentar.";
|
|
11828
|
+
}
|
|
11829
|
+
__name(approveRefusalMessage, "approveRefusalMessage");
|
|
11830
|
+
|
|
11570
11831
|
// src/asset-proxy.ts
|
|
11571
11832
|
import { createHmac, timingSafeEqual } from "crypto";
|
|
11572
11833
|
var TOKEN_TTL_SECONDS = 3600;
|
|
@@ -11748,54 +12009,54 @@ function renderOutputsMarkdown(outputs) {
|
|
|
11748
12009
|
__name(renderOutputsMarkdown, "renderOutputsMarkdown");
|
|
11749
12010
|
|
|
11750
12011
|
// src/tools/tasks.ts
|
|
11751
|
-
var CreateProductTaskInput =
|
|
12012
|
+
var CreateProductTaskInput = z49.object({
|
|
11752
12013
|
images: ImagesInput.optional().describe("1\u20135 fotos del mismo producto. Omitir \xFAnicamente cuando projectId apunta a un proyecto propio con imagen guardada."),
|
|
11753
|
-
projectId:
|
|
11754
|
-
productUrl:
|
|
12014
|
+
projectId: z49.string().uuid().optional().describe("Opcional. Proyecto guiado propio. Permite continuar sin volver a subir la imagen guardada; nunca evita cr\xE9ditos, cuotas ni confirmaciones."),
|
|
12015
|
+
productUrl: z49.string().url().optional().describe("Opcional. URL de la p\xE1gina del producto (Shopify, AliExpress, Amazon, etc.) para enriquecer el contexto. Si se omite, la IA infiere todo desde la imagen."),
|
|
11755
12016
|
language: Lang.describe('C\xF3digo de idioma para el contenido generado. Ejemplos: "es", "en", "pt". REQUERIDO \u2014 si el usuario no lo especific\xF3, PREG\xDANTALE antes de invocar. NUNCA infieras desde el contexto del chat ni asumas "es" por default.'),
|
|
11756
12017
|
country: Country.describe('C\xF3digo ISO del pa\xEDs objetivo. Ejemplos: "CO", "MX", "US", "AR". Define moneda, m\xE9todos de pago locales y tono. REQUERIDO \u2014 si el usuario no lo especific\xF3, PREG\xDANTALE antes de invocar. NUNCA infieras desde el contexto del chat.'),
|
|
11757
|
-
price:
|
|
11758
|
-
compareAtPrice:
|
|
11759
|
-
offer:
|
|
11760
|
-
paymentMethods:
|
|
11761
|
-
productContext:
|
|
11762
|
-
productLabel:
|
|
12018
|
+
price: z49.string().min(1).max(120).optional().describe('Precio que se mostrar\xE1 en la landing y video. Formato libre \u2014 ej. "$89.900", "USD 29.99", "MXN 599". Opcional pero MUY IMPACTANTE: si el usuario no lo mencion\xF3, PREG\xDANTALE antes de invocar. Si te dice "t\xFA elige", om\xEDtelo y el backend sugiere uno.'),
|
|
12019
|
+
compareAtPrice: z49.string().min(1).max(120).optional().describe('Opcional. Precio anterior para comunicar un descuento real en landing e im\xE1genes. Ej. "$129.000" cuando price="$89.900".'),
|
|
12020
|
+
offer: z49.string().min(1).max(240).optional().describe('Oferta o promoci\xF3n a destacar. Ej. "2x1", "Env\xEDo gratis hoy", "50% OFF". Opcional pero MUY IMPACTANTE: si el usuario no lo mencion\xF3, PREG\xDANTALE antes de invocar (es v\xE1lido que diga "sin oferta"). Si te dice "t\xFA elige", om\xEDtelo.'),
|
|
12021
|
+
paymentMethods: z49.string().min(1).max(240).optional().describe('Opcional. M\xE9todos de pago que puede mostrar la landing. Ej. "Tarjeta, PSE, Nequi, Contraentrega". Si se omite, la landing no muestra m\xE9todos de pago.'),
|
|
12022
|
+
productContext: z49.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."),
|
|
12023
|
+
productLabel: z49.string().trim().max(80).optional().describe('Opcional. Qu\xE9 es el producto, en pocas palabras (ej. "quita pelusas para ropa"). Es AUTORITATIVO: si lo env\xEDas, la IA usa esta identidad y no la reemplaza por otra categor\xEDa. Si lo omites, la IA detecta el producto desde la imagen.'),
|
|
11763
12024
|
videoPreset: VideoPresetEnum.default("auto").describe('Opcional. Tipo/narrativa del video \u2014 \xFAnico campo de "estilo" que el usuario controla. Default "auto" (la IA escoge tras analizar el producto). Todos los presets funcionan a 15s (single_call) y 30s (Multiescena). Opciones: "auto", "ugc_dynamic" (review con experiencia y prueba), "ugc_how_to" (tutorial con pasos y resultado visible), "ugc_unboxing_chaos" (apertura y primera prueba), "problem_solution" (problema concreto, producto como soluci\xF3n y CTA), "product_showcase" (producto ejecutando una funci\xF3n real), "wearable_showcase" (prenda o accesorio modelado), "cinematic" (TV spot con receta cinematogr\xE1fica), "wild_card" (una regla visual experimental original), "pixar_problem" (problema animado 3D), "pixar_sassy" (producto que habla). Si el usuario pide algo fuera de esta lista, p\xEDdele una opci\xF3n v\xE1lida.'),
|
|
11764
|
-
durationSeconds:
|
|
11765
|
-
|
|
11766
|
-
|
|
12025
|
+
durationSeconds: z49.union([
|
|
12026
|
+
z49.literal(15),
|
|
12027
|
+
z49.literal(30)
|
|
11767
12028
|
]).default(15).describe("Opcional. Duraci\xF3n objetivo del video: 15s (single_call, default) o 30s (Multiescena, aproximadamente 30s y facturado por escena)."),
|
|
11768
|
-
pauseForAngleReview:
|
|
11769
|
-
aiDecidesAll:
|
|
12029
|
+
pauseForAngleReview: z49.boolean().optional().describe("Opcional. Si true, pausa despu\xE9s de generar el \xE1ngulo para que el usuario lo confirme o edite antes del render. \xDAsalo si quiere decidir 15/30s cuando aparezca un winner. Default false."),
|
|
12030
|
+
aiDecidesAll: z49.boolean().optional().describe("Opcional. Si true, ProductMaker decide TODO y no vuelve a interrumpir al usuario: resuelve solo las confirmaciones de referencias del producto y no abre gates de aprobaci\xF3n, as\xED que `pendingUserAction` no deber\xEDa aparecer. Act\xEDvalo \xFAnicamente cuando el usuario pida expl\xEDcitamente que la IA decida todo o que no le pregunten m\xE1s. Default false."),
|
|
11770
12031
|
videoTier: VideoTierEnum.optional().describe('Opcional. Estilo del video. "basic" = "Realista" para 15s; al pedir durationSeconds=30 el sistema selecciona internamente Multiescena. No se expone el tier interno directamente.'),
|
|
11771
12032
|
videoResolution: VideoResolutionEnum.optional().describe('Opcional. Resoluci\xF3n del render. En la web se pregunta "\xBFResoluci\xF3n del video?". "480p" = excelente calidad y m\xE1s econ\xF3mico (RECOMENDADO, default si se omite); "720p" = m\xE1xima nitidez, entre 3% y 15% m\xE1s caro seg\xFAn formato y tier. Si el usuario no menciona resoluci\xF3n, OMITE este campo (el backend usa 480p).'),
|
|
11772
|
-
multiAngleVideos:
|
|
12033
|
+
multiAngleVideos: z49.boolean().optional().describe("Opcional. Si true, genera m\xFAltiples \xE1ngulos de venta en paralelo (3-5 videos). Si false/omitido, genera 1 video del \xE1ngulo principal. Default false."),
|
|
11773
12034
|
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". angleNarratives[i] fuerza la narrativa del \xE1ngulo i; las posiciones restantes las elige la IA. Requiere multiAngleVideos:true para varios \xE1ngulos.'),
|
|
11774
|
-
outputs:
|
|
12035
|
+
outputs: z49.array(z49.enum([
|
|
11775
12036
|
"landing",
|
|
11776
12037
|
"video",
|
|
11777
12038
|
"image"
|
|
11778
12039
|
])).min(2).max(3).optional().describe('Opcional. Qu\xE9 outputs generar. Default: todos ["landing","video","image"]. Para 2 outputs: ["landing","image"] (sin video), ["video","image"] (sin landing), ["landing","video"] (sin imagen). Para 1 solo output, usa generate_landing / generate_video_creative / generate_image_creatives.'),
|
|
11779
|
-
competitorScanEnabled:
|
|
11780
|
-
brandId:
|
|
11781
|
-
actorId:
|
|
11782
|
-
creativeDirection:
|
|
11783
|
-
headline:
|
|
11784
|
-
subheadline:
|
|
12040
|
+
competitorScanEnabled: z49.boolean().optional().describe("Opcional. Si true (default), el sistema analiza ads de competencia en Meta Ad Library y enriquece los \xE1ngulos generados con hooks y painPoints validados por el mercado (\u2248$0.20-0.48 extra por task \u2014 var\xEDa con cu\xE1ntas transcripciones de video se capturen). Si false, el pipeline genera \xE1ngulos sin esa se\xF1al \u2014 usa esto SOLO si el usuario ya tiene su propia investigaci\xF3n de mercado o quiere ahorrar costos en productos muy nicho sin presencia en Meta. Default true."),
|
|
12041
|
+
brandId: z49.string().uuid().optional().describe("Opcional. ID de una marca del usuario; si se env\xEDa, el sistema usa su voz, colores y persona para enriquecer los outputs generados."),
|
|
12042
|
+
actorId: z49.string().trim().min(1).optional().describe("Opcional. ID de un personaje guardado (usa list_actors para verlos). Si lo env\xEDas, su foto y su voz mandan sobre la descripci\xF3n de texto."),
|
|
12043
|
+
creativeDirection: z49.string().max(500).optional().describe('Opcional. Direcci\xF3n creativa en texto libre (colores, mood, estilo) que aplica a los outputs de IMAGEN y LANDING (no al video \u2014 el estilo/ambiente del video se dirige con scriptNotes). M\xC1XIMA PRIORIDAD: anula marca, colores y estilo por defecto. Ej. "amarillo y azul, minimalista". M\xE1x 500 caracteres.'),
|
|
12044
|
+
headline: z49.string().max(120).optional().describe("Opcional. Titular EXACTO a renderizar. Se imprime tal cual: la IA no lo reescribe, no lo acorta y no lo traduce. Se aplica a las IM\xC1GENES y a la portada de la landing de esta task (el video no lo usa; para el video usa scriptNotes). \xDAsalo cuando el copy es de marca y no es negociable. M\xE1x 120 caracteres. Si lo omites, la IA escribe el titular."),
|
|
12045
|
+
subheadline: z49.string().max(160).optional().describe("Opcional. Subt\xEDtulo EXACTO a renderizar, misma regla que headline. Independiente: puedes fijar solo el subt\xEDtulo. M\xE1x 160 caracteres."),
|
|
11785
12046
|
landingStrategy: LandingStrategyEnum.default("classic").describe('Opcional. Estructura de la landing cl\xE1sica cuando landing est\xE1 entre outputs: "classic" (estructura original probada y recomendada; default del primer rollout), "auto" (la IA decide; experimental), "pain_solution", "social_proof" (Landing Viral: UGC, WhatsApp, Instagram y evidencia social), "benefits_clarity", "offer_value" o "lifestyle_identity". Default "classic".'),
|
|
11786
12047
|
landingImageFormat: LandingImageFormatEnum.default("9:16").describe('Opcional. Formato/relaci\xF3n de aspecto de las im\xE1genes de la landing cl\xE1sica cuando landing est\xE1 entre outputs: "9:16" (vertical, por defecto), "4:5" (vertical compacto para feed) o "1:1" (cuadrado). Default "9:16".')
|
|
11787
12048
|
}).strict();
|
|
11788
|
-
var CreateServiceVideoInput =
|
|
11789
|
-
landingUrl:
|
|
11790
|
-
additionalContext:
|
|
12049
|
+
var CreateServiceVideoInput = z49.object({
|
|
12050
|
+
landingUrl: z49.string().url().describe("URL p\xFAblica de la landing del servicio. Es suficiente para generar el video; los medios son opcionales en la web."),
|
|
12051
|
+
additionalContext: z49.string().max(3e3).optional().describe('Instrucci\xF3n natural prioritaria. Ej.: "adapta el ritmo de estos competidores", "enfatiza reservas de demos" o restricciones de claims.'),
|
|
11791
12052
|
language: Lang.describe("Idioma del video: es, en o pt."),
|
|
11792
|
-
voiceId:
|
|
11793
|
-
brandId:
|
|
11794
|
-
reviewMode:
|
|
12053
|
+
voiceId: z49.string().trim().min(1).optional().describe('Opcional. Voz de ElevenLabs para toda la narraci\xF3n. Usa "auto" para que la IA la alinee con el presentador o env\xEDa un ID concreto de la lista de voces de la web.'),
|
|
12054
|
+
brandId: z49.string().uuid().optional().describe("Marca guardada del usuario. Aplica voz, mensajes, paleta y persona predeterminada."),
|
|
12055
|
+
reviewMode: z49.enum([
|
|
11795
12056
|
"automatic",
|
|
11796
12057
|
"guided"
|
|
11797
12058
|
]).default("automatic").describe("automatic = la IA elige concepto y guion sin pausas; guided = el usuario aprueba concepto y storyboard."),
|
|
11798
|
-
videoPreset:
|
|
12059
|
+
videoPreset: z49.enum([
|
|
11799
12060
|
"auto",
|
|
11800
12061
|
"ugc_dynamic",
|
|
11801
12062
|
"ugc_how_to",
|
|
@@ -11803,51 +12064,51 @@ var CreateServiceVideoInput = z48.object({
|
|
|
11803
12064
|
"wild_card"
|
|
11804
12065
|
]).default("auto")
|
|
11805
12066
|
}).strict();
|
|
11806
|
-
var GetTaskStatusInput =
|
|
11807
|
-
taskId:
|
|
11808
|
-
waitSeconds:
|
|
11809
|
-
});
|
|
11810
|
-
var ListTasksInput =
|
|
11811
|
-
limit:
|
|
11812
|
-
offset:
|
|
11813
|
-
search:
|
|
11814
|
-
});
|
|
11815
|
-
var EditTaskDraftInput =
|
|
11816
|
-
taskId:
|
|
11817
|
-
angleIndex:
|
|
11818
|
-
angle:
|
|
11819
|
-
angle:
|
|
11820
|
-
buyerPersona:
|
|
11821
|
-
actor:
|
|
11822
|
-
actorId:
|
|
11823
|
-
gender:
|
|
12067
|
+
var GetTaskStatusInput = z49.object({
|
|
12068
|
+
taskId: z49.string().min(1),
|
|
12069
|
+
waitSeconds: z49.number().int().min(0).max(120).optional()
|
|
12070
|
+
});
|
|
12071
|
+
var ListTasksInput = z49.object({
|
|
12072
|
+
limit: z49.number().int().min(1).max(50).optional(),
|
|
12073
|
+
offset: z49.number().int().min(0).optional(),
|
|
12074
|
+
search: z49.string().optional()
|
|
12075
|
+
});
|
|
12076
|
+
var EditTaskDraftInput = z49.object({
|
|
12077
|
+
taskId: z49.string().min(1),
|
|
12078
|
+
angleIndex: z49.number().int().min(0),
|
|
12079
|
+
angle: z49.object({
|
|
12080
|
+
angle: z49.string().min(1).max(480),
|
|
12081
|
+
buyerPersona: z49.string().min(1).max(360),
|
|
12082
|
+
actor: z49.string().min(1).max(360).optional(),
|
|
12083
|
+
actorId: z49.string().trim().min(1).optional().describe("Opcional. ID de un personaje guardado (usa list_actors para verlos). Si lo env\xEDas, su foto y su voz mandan sobre la descripci\xF3n de texto."),
|
|
12084
|
+
gender: z49.enum([
|
|
11824
12085
|
"female",
|
|
11825
12086
|
"male"
|
|
11826
12087
|
]).optional(),
|
|
11827
|
-
scriptNotes:
|
|
12088
|
+
scriptNotes: z49.string().max(2e3).optional()
|
|
11828
12089
|
})
|
|
11829
12090
|
});
|
|
11830
|
-
var PendingActionPayload =
|
|
11831
|
-
|
|
11832
|
-
kind:
|
|
12091
|
+
var PendingActionPayload = z49.discriminatedUnion("kind", [
|
|
12092
|
+
z49.object({
|
|
12093
|
+
kind: z49.literal("product_reference_set_approval"),
|
|
11833
12094
|
scope: ProductReferenceScopeEnum,
|
|
11834
|
-
scopeKey:
|
|
11835
|
-
scopeSnapshotId:
|
|
11836
|
-
expectedScopeHash:
|
|
12095
|
+
scopeKey: z49.string().min(1).max(120),
|
|
12096
|
+
scopeSnapshotId: z49.string().uuid(),
|
|
12097
|
+
expectedScopeHash: z49.string().min(1).max(128),
|
|
11837
12098
|
decision: ProductReferenceResolutionDecisionEnum,
|
|
11838
12099
|
reason: ProductReferenceRejectionReasonEnum.optional(),
|
|
11839
|
-
affectedRefKeys:
|
|
11840
|
-
recoveryAction:
|
|
12100
|
+
affectedRefKeys: z49.array(z49.string().min(1).max(200)).min(1).optional(),
|
|
12101
|
+
recoveryAction: z49.enum([
|
|
11841
12102
|
"remove",
|
|
11842
12103
|
"replace",
|
|
11843
12104
|
"upload",
|
|
11844
12105
|
"return_to_selection"
|
|
11845
12106
|
]).optional()
|
|
11846
12107
|
}),
|
|
11847
|
-
|
|
11848
|
-
kind:
|
|
12108
|
+
z49.object({
|
|
12109
|
+
kind: z49.literal("product_reference_confirmation"),
|
|
11849
12110
|
userChoice: ProductReferenceUserChoiceEnum.optional(),
|
|
11850
|
-
confirmedProductNames:
|
|
12111
|
+
confirmedProductNames: z49.array(z49.string().min(1).max(120)).min(2).max(8).optional(),
|
|
11851
12112
|
// SCOPED multi-scene opt-in — the ONLY way STANDARD ("Multiescena") is
|
|
11852
12113
|
// reachable via MCP. Valid ONLY together with userChoice='variants' (same
|
|
11853
12114
|
// product in several colors): renders one shot per color and stitches them,
|
|
@@ -11858,50 +12119,50 @@ var PendingActionPayload = z48.discriminatedUnion("kind", [
|
|
|
11858
12119
|
// safe because multi-scene IS the intended render for a color variant set,
|
|
11859
12120
|
// not a blind quality upsell. Maps to `videoTier: 'standard'` on the
|
|
11860
12121
|
// confirm call; the backend re-gates it (variants + variant_set only).
|
|
11861
|
-
useMultiSceneShowcase:
|
|
12122
|
+
useMultiSceneShowcase: z49.boolean().optional().describe('Opcional (SOLO con userChoice="variants"). true = render Multiescena: una toma por color, mostrando todas las variantes, hasta 30s. Se factura POR ESCENA (m\xE1s caro que el render normal de una sola toma) \u2014 AV\xCDSALE del mayor costo al usuario antes de activarlo. Omitir = render est\xE1ndar de una sola toma. Se ignora si userChoice no es "variants".')
|
|
11862
12123
|
}),
|
|
11863
|
-
|
|
11864
|
-
kind:
|
|
12124
|
+
z49.object({
|
|
12125
|
+
kind: z49.literal("angle_review_approval"),
|
|
11865
12126
|
// All edits are optional — empty action body just approves the
|
|
11866
12127
|
// backend-generated draft as-is. Field shapes mirror the angle-review
|
|
11867
12128
|
// request contracts in @packages/types/wizard.ts; the handler routes to
|
|
11868
12129
|
// the dedicated video/image/autopilot endpoint based on StatusResponse.kind.
|
|
11869
|
-
customAngle:
|
|
12130
|
+
customAngle: z49.string().min(1).max(480).optional(),
|
|
11870
12131
|
// Keep the shared MCP action within the strictest downstream contract:
|
|
11871
12132
|
// autopilot/image resume endpoints cap these fields at 1500 characters.
|
|
11872
|
-
painPoints:
|
|
11873
|
-
benefits:
|
|
11874
|
-
features:
|
|
11875
|
-
gapDurationSeconds:
|
|
11876
|
-
|
|
11877
|
-
|
|
12133
|
+
painPoints: z49.string().max(1500).optional(),
|
|
12134
|
+
benefits: z49.string().max(1500).optional(),
|
|
12135
|
+
features: z49.array(z49.string().min(1).max(300)).max(10).optional(),
|
|
12136
|
+
gapDurationSeconds: z49.union([
|
|
12137
|
+
z49.literal(15),
|
|
12138
|
+
z49.literal(30)
|
|
11878
12139
|
]).optional().describe("Opcional. Solo para aceptar expl\xEDcitamente la recomendaci\xF3n gapDurationChoice del status. 30 env\xEDa el override double_30s para el \xE1ngulo recomendado; 15 u omitido conserva la duraci\xF3n cotizada.")
|
|
11879
12140
|
}),
|
|
11880
|
-
|
|
11881
|
-
kind:
|
|
11882
|
-
angleId:
|
|
11883
|
-
content:
|
|
11884
|
-
creativeDirection:
|
|
11885
|
-
actor:
|
|
12141
|
+
z49.object({
|
|
12142
|
+
kind: z49.literal("service_angle_approval"),
|
|
12143
|
+
angleId: z49.string().uuid(),
|
|
12144
|
+
content: z49.string().min(1).max(480).optional(),
|
|
12145
|
+
creativeDirection: z49.string().min(1).max(1500).optional(),
|
|
12146
|
+
actor: z49.string().min(1).max(360).optional()
|
|
11886
12147
|
}),
|
|
11887
|
-
|
|
11888
|
-
kind:
|
|
11889
|
-
voiceovers:
|
|
11890
|
-
visualDirections:
|
|
12148
|
+
z49.object({
|
|
12149
|
+
kind: z49.literal("service_script_approval"),
|
|
12150
|
+
voiceovers: z49.record(z49.string(), z49.string().min(1).max(600)).optional(),
|
|
12151
|
+
visualDirections: z49.record(z49.string(), z49.string().min(1).max(1200)).optional()
|
|
11891
12152
|
})
|
|
11892
12153
|
]);
|
|
11893
|
-
var ResolvePendingActionInput =
|
|
11894
|
-
taskId:
|
|
12154
|
+
var ResolvePendingActionInput = z49.object({
|
|
12155
|
+
taskId: z49.string().min(1),
|
|
11895
12156
|
action: PendingActionPayload
|
|
11896
12157
|
}).strict();
|
|
11897
12158
|
function resolveVariantConfirmVideoTier(action) {
|
|
11898
12159
|
return action.useMultiSceneShowcase && action.userChoice === "variants" ? "standard" : void 0;
|
|
11899
12160
|
}
|
|
11900
12161
|
__name(resolveVariantConfirmVideoTier, "resolveVariantConfirmVideoTier");
|
|
11901
|
-
var StatusForResolveSchema =
|
|
12162
|
+
var StatusForResolveSchema = z49.object({
|
|
11902
12163
|
// Metadata used to select the backend resume contract. Older API responses
|
|
11903
12164
|
// may omit these fields, so the resolver keeps the generic fallback.
|
|
11904
|
-
kind:
|
|
12165
|
+
kind: z49.enum([
|
|
11905
12166
|
"autopilot",
|
|
11906
12167
|
"video",
|
|
11907
12168
|
"image",
|
|
@@ -11909,24 +12170,24 @@ var StatusForResolveSchema = z48.object({
|
|
|
11909
12170
|
"mix",
|
|
11910
12171
|
"service"
|
|
11911
12172
|
]).optional(),
|
|
11912
|
-
selectedOutputs:
|
|
12173
|
+
selectedOutputs: z49.array(z49.enum([
|
|
11913
12174
|
"landing",
|
|
11914
12175
|
"video",
|
|
11915
12176
|
"image"
|
|
11916
12177
|
])).optional(),
|
|
11917
|
-
videoAngleCount:
|
|
11918
|
-
ugcFormat:
|
|
12178
|
+
videoAngleCount: z49.number().int().min(1).max(3).optional(),
|
|
12179
|
+
ugcFormat: z49.enum([
|
|
11919
12180
|
"single_8s",
|
|
11920
12181
|
"single_15s",
|
|
11921
12182
|
"double_30s"
|
|
11922
12183
|
]).optional(),
|
|
11923
|
-
videoStyle:
|
|
12184
|
+
videoStyle: z49.enum([
|
|
11924
12185
|
"ugc",
|
|
11925
12186
|
"cartoon_3d",
|
|
11926
12187
|
"product_only",
|
|
11927
12188
|
"ai_dynamic"
|
|
11928
12189
|
]).optional(),
|
|
11929
|
-
videoModel:
|
|
12190
|
+
videoModel: z49.enum([
|
|
11930
12191
|
"veo",
|
|
11931
12192
|
"wan",
|
|
11932
12193
|
"bytedance",
|
|
@@ -11934,62 +12195,62 @@ var StatusForResolveSchema = z48.object({
|
|
|
11934
12195
|
"grok",
|
|
11935
12196
|
"kling"
|
|
11936
12197
|
]).optional(),
|
|
11937
|
-
videoTier:
|
|
12198
|
+
videoTier: z49.enum([
|
|
11938
12199
|
"legacy",
|
|
11939
12200
|
"standard",
|
|
11940
12201
|
"pro",
|
|
11941
12202
|
"single_call"
|
|
11942
12203
|
]).optional(),
|
|
11943
|
-
videoResolution:
|
|
12204
|
+
videoResolution: z49.enum([
|
|
11944
12205
|
"480p",
|
|
11945
12206
|
"720p"
|
|
11946
12207
|
]).optional(),
|
|
11947
12208
|
narrativeStyle: NarrativeStyleEnum.optional(),
|
|
11948
|
-
pendingUserAction:
|
|
11949
|
-
kind:
|
|
11950
|
-
candidateGrouping:
|
|
11951
|
-
selectedImageUrls:
|
|
11952
|
-
proposedProductNames:
|
|
12209
|
+
pendingUserAction: z49.object({
|
|
12210
|
+
kind: z49.string(),
|
|
12211
|
+
candidateGrouping: z49.string().optional(),
|
|
12212
|
+
selectedImageUrls: z49.array(z49.string()).optional(),
|
|
12213
|
+
proposedProductNames: z49.array(z49.string()).optional(),
|
|
11953
12214
|
// Adaptive reference questions are server-owned. Echo only the evidence
|
|
11954
12215
|
// identity and the choices shown by the current snapshot; never accept a
|
|
11955
12216
|
// client-supplied reference set as authorization.
|
|
11956
|
-
options:
|
|
11957
|
-
mode:
|
|
12217
|
+
options: z49.array(ProductReferenceUserChoiceEnum).max(4).optional(),
|
|
12218
|
+
mode: z49.enum([
|
|
11958
12219
|
"question",
|
|
11959
12220
|
"visual_recovery"
|
|
11960
12221
|
]).optional(),
|
|
11961
|
-
decisionKey:
|
|
11962
|
-
questionOrdinal:
|
|
11963
|
-
questionCeiling:
|
|
11964
|
-
|
|
11965
|
-
|
|
12222
|
+
decisionKey: z49.string().optional(),
|
|
12223
|
+
questionOrdinal: z49.number().int().min(1).max(5).optional(),
|
|
12224
|
+
questionCeiling: z49.union([
|
|
12225
|
+
z49.literal(3),
|
|
12226
|
+
z49.literal(5)
|
|
11966
12227
|
]).optional(),
|
|
11967
|
-
referenceEvidenceVersion:
|
|
11968
|
-
expectedEvidenceHash:
|
|
11969
|
-
question:
|
|
11970
|
-
prompt:
|
|
11971
|
-
helpText:
|
|
11972
|
-
options:
|
|
12228
|
+
referenceEvidenceVersion: z49.number().int().positive().optional(),
|
|
12229
|
+
expectedEvidenceHash: z49.string().optional(),
|
|
12230
|
+
question: z49.object({
|
|
12231
|
+
prompt: z49.string().min(1).max(240),
|
|
12232
|
+
helpText: z49.string().min(1).max(300).optional(),
|
|
12233
|
+
options: z49.array(z49.object({
|
|
11973
12234
|
value: ProductReferenceUserChoiceEnum,
|
|
11974
|
-
label:
|
|
11975
|
-
description:
|
|
12235
|
+
label: z49.string().min(1).max(120),
|
|
12236
|
+
description: z49.string().min(1).max(240).optional()
|
|
11976
12237
|
})).min(2).max(4)
|
|
11977
12238
|
}).optional(),
|
|
11978
12239
|
scope: ProductReferenceScopeEnum.optional(),
|
|
11979
|
-
scopeKey:
|
|
11980
|
-
scopeSnapshotId:
|
|
11981
|
-
scopeHash:
|
|
11982
|
-
baseReferenceSetHash:
|
|
11983
|
-
phase:
|
|
12240
|
+
scopeKey: z49.string().optional(),
|
|
12241
|
+
scopeSnapshotId: z49.string().uuid().optional(),
|
|
12242
|
+
scopeHash: z49.string().optional(),
|
|
12243
|
+
baseReferenceSetHash: z49.string().optional(),
|
|
12244
|
+
phase: z49.enum([
|
|
11984
12245
|
"preparation",
|
|
11985
12246
|
"final"
|
|
11986
12247
|
]).optional(),
|
|
11987
|
-
selectedRefKeys:
|
|
11988
|
-
allowedDecisions:
|
|
11989
|
-
customAngleDraft:
|
|
11990
|
-
painPointsDraft:
|
|
11991
|
-
benefitsDraft:
|
|
11992
|
-
featuresDraft:
|
|
12248
|
+
selectedRefKeys: z49.array(z49.string().min(1).max(200)).optional(),
|
|
12249
|
+
allowedDecisions: z49.array(ProductReferenceResolutionDecisionEnum).optional(),
|
|
12250
|
+
customAngleDraft: z49.string().optional(),
|
|
12251
|
+
painPointsDraft: z49.string().optional(),
|
|
12252
|
+
benefitsDraft: z49.string().optional(),
|
|
12253
|
+
featuresDraft: z49.array(z49.string()).optional(),
|
|
11993
12254
|
// Recommendation consumed only when the caller explicitly sends
|
|
11994
12255
|
// gapDurationSeconds: 30 in the angle approval action.
|
|
11995
12256
|
gapDurationChoice: GapDurationChoiceMirror.optional(),
|
|
@@ -11998,17 +12259,17 @@ var StatusForResolveSchema = z48.object({
|
|
|
11998
12259
|
// keys. `actorId` (the saved custom actor the picker stored) was silently
|
|
11999
12260
|
// dropped here, so the approval handler below could not echo it back on the
|
|
12000
12261
|
// PATCH — the selection looked accepted and reached no render.
|
|
12001
|
-
angles:
|
|
12002
|
-
id:
|
|
12003
|
-
content:
|
|
12004
|
-
creativeDirection:
|
|
12005
|
-
actor:
|
|
12006
|
-
actorId:
|
|
12262
|
+
angles: z49.array(z49.object({
|
|
12263
|
+
id: z49.string().uuid(),
|
|
12264
|
+
content: z49.string(),
|
|
12265
|
+
creativeDirection: z49.string(),
|
|
12266
|
+
actor: z49.string(),
|
|
12267
|
+
actorId: z49.string().nullable().optional()
|
|
12007
12268
|
}).passthrough()).optional(),
|
|
12008
|
-
panels:
|
|
12009
|
-
beatId:
|
|
12010
|
-
voiceover:
|
|
12011
|
-
visualDirection:
|
|
12269
|
+
panels: z49.array(z49.object({
|
|
12270
|
+
beatId: z49.string(),
|
|
12271
|
+
voiceover: z49.string(),
|
|
12272
|
+
visualDirection: z49.string()
|
|
12012
12273
|
}).passthrough()).optional()
|
|
12013
12274
|
}).optional()
|
|
12014
12275
|
}).passthrough();
|
|
@@ -12047,7 +12308,7 @@ function resolveStatusTaskKind(status) {
|
|
|
12047
12308
|
}
|
|
12048
12309
|
__name(resolveStatusTaskKind, "resolveStatusTaskKind");
|
|
12049
12310
|
function registerTaskTools(server, api, { proxyBaseUrl = null, transport = "http" } = {}) {
|
|
12050
|
-
server.tool("create_product_task", "Crea una tarea de producto desde fotos o desde un proyecto guiado propio. Por defecto genera los 3 outputs (landing + video + image creatives) EN UN MISMO TASK. Devuelve un taskId.\n\nINPUTS REQUERIDOS: images (1-5 fotos del MISMO producto; la primera es la hero) O projectId si el proyecto ya tiene una imagen guardada; adem\xE1s language y country. Para fotos en una laptop con MCP remoto llama `request_media_upload` con mediaKind=image y luego usa los assetId devueltos dentro de images; no pidas path ni URL cuando el App est\xE9 disponible.\n\nANTES de invocar, PREG\xDANTALE al usuario por price, offer y paymentMethods si no los mencion\xF3 \u2014 son inputs de alto impacto en la landing y nunca debes asumirlos. Si hay descuento, pregunta tambi\xE9n compareAtPrice. NO defaultes country/language desde el contexto del chat: preg\xFAntale expl\xEDcitamente, incluso si est\xE1 en LATAM.\n\nSi env\xEDas projectId, debe ser el proyecto propio que el usuario acaba de costear; el backend resolver\xE1 la imagen privada y conservar\xE1 todos los controles de cr\xE9ditos/confirmaci\xF3n. Si el usuario adjunta images junto con projectId, esas fotos reemplazan la referencia solo para este task.\n\nANTES de invocar, DILE CLARAMENTE qu\xE9 outputs vas a generar y que `create_product_task` los genera juntos en un mismo task.\n\nUsa SOLO valores declarados en los enums.", CreateProductTaskInput.shape, async (input) => {
|
|
12311
|
+
server.tool("create_product_task", "Crea una tarea de producto desde fotos o desde un proyecto guiado propio. Por defecto genera los 3 outputs (landing + video + image creatives) EN UN MISMO TASK. Devuelve un taskId.\n\nINPUTS REQUERIDOS: images (1-5 fotos del MISMO producto; la primera es la hero) O projectId si el proyecto ya tiene una imagen guardada; adem\xE1s language y country. Para fotos en una laptop con MCP remoto llama `request_media_upload` con mediaKind=image y luego usa los assetId devueltos dentro de images; no pidas path ni URL cuando el App est\xE9 disponible.\n\nANTES de invocar, PREG\xDANTALE al usuario por price, offer y paymentMethods si no los mencion\xF3 \u2014 son inputs de alto impacto en la landing y nunca debes asumirlos. Si hay descuento, pregunta tambi\xE9n compareAtPrice. NO defaultes country/language desde el contexto del chat: preg\xFAntale expl\xEDcitamente, incluso si est\xE1 en LATAM.\n\nSi env\xEDas projectId, debe ser el proyecto propio que el usuario acaba de costear; el backend resolver\xE1 la imagen privada y conservar\xE1 todos los controles de cr\xE9ditos/confirmaci\xF3n. Si el usuario adjunta images junto con projectId, esas fotos reemplazan la referencia solo para este task.\n\nANTES de invocar, DILE CLARAMENTE qu\xE9 outputs vas a generar y que `create_product_task` los genera juntos en un mismo task.\n\nUsa SOLO valores declarados en los enums.", CreateProductTaskInput.shape, CREATES_EXTERNAL, async (input) => {
|
|
12051
12312
|
try {
|
|
12052
12313
|
if (!input.images && !input.projectId) {
|
|
12053
12314
|
throw new Error("images es requerido salvo que env\xEDes projectId.");
|
|
@@ -12115,7 +12376,7 @@ function registerTaskTools(server, api, { proxyBaseUrl = null, transport = "http
|
|
|
12115
12376
|
throw translateError(e);
|
|
12116
12377
|
}
|
|
12117
12378
|
});
|
|
12118
|
-
server.tool("create_service_video", "Crea un video para un servicio desde su landing. Esta acci\xF3n consume cr\xE9ditos: confirma la intenci\xF3n del usuario antes de llamarla. `automatic` genera directamente; `guided` devuelve conceptos para que el usuario apruebe uno mediante `resolve_pending_action`. La web tambi\xE9n acepta demos y anuncios de referencia; el transporte MCP de video adjunto todav\xEDa falla cerrado hasta poder subir bytes de forma segura.", CreateServiceVideoInput.shape, async (input) => {
|
|
12379
|
+
server.tool("create_service_video", "Crea un video para un servicio desde su landing. Esta acci\xF3n consume cr\xE9ditos: confirma la intenci\xF3n del usuario antes de llamarla. `automatic` genera directamente; `guided` devuelve conceptos para que el usuario apruebe uno mediante `resolve_pending_action`. La web tambi\xE9n acepta demos y anuncios de referencia; el transporte MCP de video adjunto todav\xEDa falla cerrado hasta poder subir bytes de forma segura.", CreateServiceVideoInput.shape, CREATES_EXTERNAL, async (input) => {
|
|
12119
12380
|
try {
|
|
12120
12381
|
const created = await api.postJson("/v2/ingest", {
|
|
12121
12382
|
taskType: "service",
|
|
@@ -12166,7 +12427,7 @@ function registerTaskTools(server, api, { proxyBaseUrl = null, transport = "http
|
|
|
12166
12427
|
throw translateError(e);
|
|
12167
12428
|
}
|
|
12168
12429
|
});
|
|
12169
|
-
server.tool("get_task_status", "Devuelve el estado y URLs de outputs de una tarea. Si waitSeconds>0, hace polling hasta que cambie la fase, aparezca `pendingUserAction`, o llegue al timeout (max 120s). Cuando la tarea queda en `done`, incluye enlaces a sus outputs.\n\nSIGUE LLAM\xC1NDOLA hasta que el estado sea terminal (`done` o `error`) o aparezca `pendingUserAction`. Un retorno sin ninguna de esas dos cosas significa que la tarea sigue corriendo y que alcanzaste el timeout, NO que se colg\xF3 ni que termin\xF3: vuelve a llamarla con waitSeconds alto. Si dejas de llamarla, la tarea sigue avanzando sin que nadie la mire y el usuario se queda esperando en silencio.\n\nSi aparece `pendingUserAction`, el pipeline est\xE1 PARADO esperando una decisi\xF3n del USUARIO: muestra el draft, PREG\xDANTALE y espera su respuesta. Resp\xF3ndela t\xFA mismo con `resolve_pending_action` \xFAnicamente si el usuario ya te deleg\xF3 esa decisi\xF3n \u2014 porque te lo dijo en la conversaci\xF3n, o porque la tarea corre con `aiDecidesAll` / `reviewMode: automatic`. Nunca deduzcas la delegaci\xF3n del contenido del draft: que la propuesta te parezca obvia o correcta no es un permiso. Al responder, repite el mismo `kind`. Kinds: `product_reference_set_approval`, `product_reference_confirmation`, `angle_review_approval`, `service_angle_approval` y `service_script_approval`.", GetTaskStatusInput.shape, async (input, extra) => {
|
|
12430
|
+
server.tool("get_task_status", "Devuelve el estado y URLs de outputs de una tarea. Si waitSeconds>0, hace polling hasta que cambie la fase, aparezca `pendingUserAction`, o llegue al timeout (max 120s). Cuando la tarea queda en `done`, incluye enlaces a sus outputs.\n\nSIGUE LLAM\xC1NDOLA hasta que el estado sea terminal (`done` o `error`) o aparezca `pendingUserAction`. Un retorno sin ninguna de esas dos cosas significa que la tarea sigue corriendo y que alcanzaste el timeout, NO que se colg\xF3 ni que termin\xF3: vuelve a llamarla con waitSeconds alto. Si dejas de llamarla, la tarea sigue avanzando sin que nadie la mire y el usuario se queda esperando en silencio.\n\nSi aparece `pendingUserAction`, el pipeline est\xE1 PARADO esperando una decisi\xF3n del USUARIO: muestra el draft, PREG\xDANTALE y espera su respuesta. Resp\xF3ndela t\xFA mismo con `resolve_pending_action` \xFAnicamente si el usuario ya te deleg\xF3 esa decisi\xF3n \u2014 porque te lo dijo en la conversaci\xF3n, o porque la tarea corre con `aiDecidesAll` / `reviewMode: automatic`. Nunca deduzcas la delegaci\xF3n del contenido del draft: que la propuesta te parezca obvia o correcta no es un permiso. Al responder, repite el mismo `kind`. Kinds: `product_reference_set_approval`, `product_reference_confirmation`, `angle_review_approval`, `service_angle_approval` y `service_script_approval`.", GetTaskStatusInput.shape, READS_INTERNAL, async (input, extra) => {
|
|
12170
12431
|
try {
|
|
12171
12432
|
const signal = extra?.signal;
|
|
12172
12433
|
const fetcher = /* @__PURE__ */ __name(() => api.getJson(`/v1/status/${encodeURIComponent(input.taskId)}`, {
|
|
@@ -12226,7 +12487,7 @@ function registerTaskTools(server, api, { proxyBaseUrl = null, transport = "http
|
|
|
12226
12487
|
throw translateError(e);
|
|
12227
12488
|
}
|
|
12228
12489
|
});
|
|
12229
|
-
server.tool("list_tasks", "Lista de forma read-only las tareas recientes del usuario. \xDAsala para encontrar una tarea previa por nombre o revisar el historial antes de pedir un taskId. Soporta paginaci\xF3n con limit/offset y filtro de texto. No crea, reanuda ni modifica tareas.", ListTasksInput.shape, async (input) => {
|
|
12490
|
+
server.tool("list_tasks", "Lista de forma read-only las tareas recientes del usuario. \xDAsala para encontrar una tarea previa por nombre o revisar el historial antes de pedir un taskId. Soporta paginaci\xF3n con limit/offset y filtro de texto. No crea, reanuda ni modifica tareas.", ListTasksInput.shape, READS_INTERNAL, async (input) => {
|
|
12230
12491
|
try {
|
|
12231
12492
|
const qs = new URLSearchParams();
|
|
12232
12493
|
if (input.limit != null) qs.set("limit", String(input.limit));
|
|
@@ -12239,7 +12500,7 @@ function registerTaskTools(server, api, { proxyBaseUrl = null, transport = "http
|
|
|
12239
12500
|
throw translateError(e);
|
|
12240
12501
|
}
|
|
12241
12502
|
});
|
|
12242
|
-
server.tool("edit_task_draft", "Edita el draft de un angle de marketing antes de la generaci\xF3n. Campos editables: angle (texto del angle), buyerPersona, actor (descripci\xF3n del personaje), actorId (ID de un personaje guardado; usa list_actors), gender (female|male), scriptNotes. Use this only before generation completes. Editing already-generated images or videos is not supported.", EditTaskDraftInput.shape, async (input) => {
|
|
12503
|
+
server.tool("edit_task_draft", "Edita el draft de un angle de marketing antes de la generaci\xF3n. Campos editables: angle (texto del angle), buyerPersona, actor (descripci\xF3n del personaje), actorId (ID de un personaje guardado; usa list_actors), gender (female|male), scriptNotes. Use this only before generation completes. Editing already-generated images or videos is not supported.", EditTaskDraftInput.shape, OVERWRITES_INTERNAL, async (input) => {
|
|
12243
12504
|
try {
|
|
12244
12505
|
const angle = await api.patchJson(`/v1/tasks/${encodeURIComponent(input.taskId)}/angles/${input.angleIndex}`, input.angle ?? {});
|
|
12245
12506
|
return asText({
|
|
@@ -12249,315 +12510,331 @@ function registerTaskTools(server, api, { proxyBaseUrl = null, transport = "http
|
|
|
12249
12510
|
throw translateError(e);
|
|
12250
12511
|
}
|
|
12251
12512
|
});
|
|
12252
|
-
server.tool(
|
|
12253
|
-
|
|
12254
|
-
|
|
12255
|
-
|
|
12256
|
-
|
|
12257
|
-
|
|
12258
|
-
|
|
12259
|
-
|
|
12260
|
-
|
|
12261
|
-
|
|
12262
|
-
|
|
12263
|
-
|
|
12264
|
-
|
|
12265
|
-
|
|
12266
|
-
|
|
12267
|
-
|
|
12268
|
-
if (input.action.decision === "reject" && (!input.action.reason || !input.action.affectedRefKeys?.length || !input.action.recoveryAction)) {
|
|
12269
|
-
throw new Error("Una referencia rechazada necesita motivo, referencias afectadas y acci\xF3n de recuperaci\xF3n.");
|
|
12270
|
-
}
|
|
12271
|
-
if (input.action.decision === "correct_view" && (!input.action.reason || !input.action.affectedRefKeys?.length)) {
|
|
12272
|
-
throw new Error("Una correcci\xF3n de \xE1ngulo necesita motivo y referencias afectadas.");
|
|
12273
|
-
}
|
|
12274
|
-
if (input.action.decision === "correct_view" && input.action.affectedRefKeys?.length !== 1) {
|
|
12275
|
-
throw new Error("Corrige una sola vista por intento.");
|
|
12276
|
-
}
|
|
12277
|
-
if (pending.phase === "preparation" && input.action.decision === "approve" && (!pending.selectedRefKeys || pending.selectedRefKeys.length === 0)) {
|
|
12278
|
-
throw new Error("No hay fotos seleccionadas para preparar. Abre la tarea en ProductMaker Web y elige al menos una.");
|
|
12279
|
-
}
|
|
12280
|
-
const result = await api.postJson(`/v1/tasks/${encodeURIComponent(input.taskId)}/resolve-product-reference-set`, {
|
|
12281
|
-
...input.action,
|
|
12282
|
-
...pending.phase === "preparation" && input.action.decision === "approve" && pending.selectedRefKeys ? {
|
|
12283
|
-
selectedRefKeys: pending.selectedRefKeys
|
|
12284
|
-
} : {}
|
|
12285
|
-
});
|
|
12286
|
-
return asText(result);
|
|
12513
|
+
server.tool(
|
|
12514
|
+
"resolve_pending_action",
|
|
12515
|
+
"Resuelve la acci\xF3n pendiente devuelta por `get_task_status`; `action.kind` debe coincidir exactamente. Esta llamada habla EN NOMBRE DEL USUARIO: \xFAsala solo con una respuesta que \xE9l te haya dado, o cuando te haya delegado la decisi\xF3n de forma expl\xEDcita o v\xEDa `aiDecidesAll` / `reviewMode: automatic`. Si nadie te deleg\xF3 nada, pregunta y espera en vez de llamarla. Puede aprobar el conjunto exacto de referencias del producto, confirmar agrupaci\xF3n, o resolver revisiones de \xE1ngulos/servicios. Nunca env\xEDes una lista de referencias: el servidor conserva el conjunto mostrado y rechaza snapshots obsoletos. La recuperaci\xF3n que necesita fotos nuevas se completa en ProductMaker Web porque MCP no acepta URLs arbitrarias sin ticket de upload ligado al usuario.",
|
|
12516
|
+
ResolvePendingActionInput.shape,
|
|
12517
|
+
// Approving the gate RELEASES the pipeline into paid generation at the video
|
|
12518
|
+
// and image providers (registry effects: credit_consuming + state_change),
|
|
12519
|
+
// and decision=reject discards the approved reference set. Both halves, hence
|
|
12520
|
+
// destructive AND open-world.
|
|
12521
|
+
OVERWRITES_EXTERNAL,
|
|
12522
|
+
async (input) => {
|
|
12523
|
+
try {
|
|
12524
|
+
const raw = await api.getJson(`/v1/status/${encodeURIComponent(input.taskId)}`);
|
|
12525
|
+
const status = StatusForResolveSchema.parse(raw);
|
|
12526
|
+
const pending = status.pendingUserAction;
|
|
12527
|
+
if (!pending) {
|
|
12528
|
+
throw new Error("No hay acci\xF3n pendiente para esta tarea \u2014 llama get_task_status primero para confirmar el estado.");
|
|
12287
12529
|
}
|
|
12288
|
-
|
|
12289
|
-
|
|
12290
|
-
const selectedImageUrls = pending.selectedImageUrls ?? [];
|
|
12291
|
-
if (pending.mode === "visual_recovery") {
|
|
12292
|
-
throw new Error("Esta recuperaci\xF3n necesita subir fotos con un ticket ligado al usuario. \xC1brela en ProductMaker Web; MCP no acepta URLs arbitrarias.");
|
|
12293
|
-
}
|
|
12294
|
-
if (!candidateGrouping || selectedImageUrls.length === 0) {
|
|
12295
|
-
throw new Error("Estado inconsistente: la tarea est\xE1 pausada pero falta candidateGrouping o selectedImageUrls.");
|
|
12296
|
-
}
|
|
12297
|
-
const userChoice = input.action.userChoice;
|
|
12298
|
-
if (!userChoice) {
|
|
12299
|
-
throw new Error("La pregunta adaptativa necesita userChoice.");
|
|
12300
|
-
}
|
|
12301
|
-
const offeredChoices = pending.question?.options.map((option) => option.value) ?? pending.options;
|
|
12302
|
-
if (!offeredChoices) {
|
|
12303
|
-
throw new Error("Estado inconsistente: falta la pregunta adaptativa con sus opciones verificables. Vuelve a llamar get_task_status.");
|
|
12304
|
-
}
|
|
12305
|
-
if (!offeredChoices.includes(userChoice)) {
|
|
12306
|
-
throw new Error(`La opci\xF3n "${userChoice}" no fue ofrecida en la pregunta pendiente. Elige una opci\xF3n mostrada por get_task_status.`);
|
|
12307
|
-
}
|
|
12308
|
-
const r = await api.postJson(`/v1/tasks/${encodeURIComponent(input.taskId)}/confirm-product-references`, {
|
|
12309
|
-
productReferenceConfirmation: {
|
|
12310
|
-
candidateGrouping,
|
|
12311
|
-
userChoice,
|
|
12312
|
-
// Server overwrites with its own server-side computation;
|
|
12313
|
-
// we send the matching MCP-mirror value so the wire body
|
|
12314
|
-
// is internally coherent if the server skips overwrite.
|
|
12315
|
-
selectedImageUrlsHash: buildSelectedImageUrlsHash(selectedImageUrls)
|
|
12316
|
-
},
|
|
12317
|
-
...input.action.confirmedProductNames ? {
|
|
12318
|
-
confirmedProductNames: input.action.confirmedProductNames
|
|
12319
|
-
} : {},
|
|
12320
|
-
...pending.decisionKey && pending.referenceEvidenceVersion && pending.expectedEvidenceHash ? {
|
|
12321
|
-
decisionKey: pending.decisionKey,
|
|
12322
|
-
referenceEvidenceVersion: pending.referenceEvidenceVersion,
|
|
12323
|
-
expectedEvidenceHash: pending.expectedEvidenceHash
|
|
12324
|
-
} : {},
|
|
12325
|
-
// Multi-scene color showcase → upgrade to STANDARD. Gated to the
|
|
12326
|
-
// variants choice client-side so the wire body stays coherent; the
|
|
12327
|
-
// backend re-gates on variant_set grouping.
|
|
12328
|
-
...resolveVariantConfirmVideoTier(input.action) === "standard" ? {
|
|
12329
|
-
videoTier: "standard"
|
|
12330
|
-
} : {}
|
|
12331
|
-
});
|
|
12332
|
-
return asText(r);
|
|
12530
|
+
if (pending.kind !== input.action.kind) {
|
|
12531
|
+
throw new Error(`La tarea espera una acci\xF3n de tipo "${pending.kind}" pero mandaste "${input.action.kind}". Reusa el mismo kind que viste en pendingUserAction.`);
|
|
12333
12532
|
}
|
|
12334
|
-
|
|
12335
|
-
|
|
12336
|
-
|
|
12337
|
-
|
|
12533
|
+
switch (input.action.kind) {
|
|
12534
|
+
case "product_reference_set_approval": {
|
|
12535
|
+
if (pending.scope !== input.action.scope || pending.scopeKey !== input.action.scopeKey || pending.scopeSnapshotId !== input.action.scopeSnapshotId || pending.scopeHash !== input.action.expectedScopeHash || !pending.allowedDecisions?.includes(input.action.decision)) {
|
|
12536
|
+
throw new Error("La aprobaci\xF3n de referencias no coincide con el snapshot mostrado; vuelve a llamar get_task_status.");
|
|
12537
|
+
}
|
|
12538
|
+
if (input.action.decision === "reject" && (!input.action.reason || !input.action.affectedRefKeys?.length || !input.action.recoveryAction)) {
|
|
12539
|
+
throw new Error("Una referencia rechazada necesita motivo, referencias afectadas y acci\xF3n de recuperaci\xF3n.");
|
|
12540
|
+
}
|
|
12541
|
+
if (input.action.decision === "correct_view" && (!input.action.reason || !input.action.affectedRefKeys?.length)) {
|
|
12542
|
+
throw new Error("Una correcci\xF3n de \xE1ngulo necesita motivo y referencias afectadas.");
|
|
12543
|
+
}
|
|
12544
|
+
if (input.action.decision === "correct_view" && input.action.affectedRefKeys?.length !== 1) {
|
|
12545
|
+
throw new Error("Corrige una sola vista por intento.");
|
|
12546
|
+
}
|
|
12547
|
+
if (pending.phase === "preparation" && input.action.decision === "approve" && (!pending.selectedRefKeys || pending.selectedRefKeys.length === 0)) {
|
|
12548
|
+
throw new Error("No hay fotos seleccionadas para preparar. Abre la tarea en ProductMaker Web y elige al menos una.");
|
|
12549
|
+
}
|
|
12550
|
+
const result = await api.postJson(`/v1/tasks/${encodeURIComponent(input.taskId)}/resolve-product-reference-set`, {
|
|
12551
|
+
...input.action,
|
|
12552
|
+
...pending.phase === "preparation" && input.action.decision === "approve" && pending.selectedRefKeys ? {
|
|
12553
|
+
selectedRefKeys: pending.selectedRefKeys
|
|
12554
|
+
} : {}
|
|
12555
|
+
});
|
|
12556
|
+
return asText(result);
|
|
12338
12557
|
}
|
|
12339
|
-
|
|
12340
|
-
|
|
12341
|
-
|
|
12342
|
-
|
|
12343
|
-
|
|
12344
|
-
|
|
12345
|
-
|
|
12346
|
-
|
|
12347
|
-
|
|
12348
|
-
|
|
12349
|
-
|
|
12558
|
+
case "product_reference_confirmation": {
|
|
12559
|
+
const candidateGrouping = pending.candidateGrouping;
|
|
12560
|
+
const selectedImageUrls = pending.selectedImageUrls ?? [];
|
|
12561
|
+
if (pending.mode === "visual_recovery") {
|
|
12562
|
+
throw new Error("Esta recuperaci\xF3n necesita subir fotos con un ticket ligado al usuario. \xC1brela en ProductMaker Web; MCP no acepta URLs arbitrarias.");
|
|
12563
|
+
}
|
|
12564
|
+
if (!candidateGrouping || selectedImageUrls.length === 0) {
|
|
12565
|
+
throw new Error("Estado inconsistente: la tarea est\xE1 pausada pero falta candidateGrouping o selectedImageUrls.");
|
|
12566
|
+
}
|
|
12567
|
+
const userChoice = input.action.userChoice;
|
|
12568
|
+
if (!userChoice) {
|
|
12569
|
+
throw new Error("La pregunta adaptativa necesita userChoice.");
|
|
12570
|
+
}
|
|
12571
|
+
const offeredChoices = pending.question?.options.map((option) => option.value) ?? pending.options;
|
|
12572
|
+
if (!offeredChoices) {
|
|
12573
|
+
throw new Error("Estado inconsistente: falta la pregunta adaptativa con sus opciones verificables. Vuelve a llamar get_task_status.");
|
|
12574
|
+
}
|
|
12575
|
+
if (!offeredChoices.includes(userChoice)) {
|
|
12576
|
+
throw new Error(`La opci\xF3n "${userChoice}" no fue ofrecida en la pregunta pendiente. Elige una opci\xF3n mostrada por get_task_status.`);
|
|
12577
|
+
}
|
|
12578
|
+
const r = await api.postJson(`/v1/tasks/${encodeURIComponent(input.taskId)}/confirm-product-references`, {
|
|
12579
|
+
productReferenceConfirmation: {
|
|
12580
|
+
candidateGrouping,
|
|
12581
|
+
userChoice,
|
|
12582
|
+
// Server overwrites with its own server-side computation;
|
|
12583
|
+
// we send the matching MCP-mirror value so the wire body
|
|
12584
|
+
// is internally coherent if the server skips overwrite.
|
|
12585
|
+
selectedImageUrlsHash: buildSelectedImageUrlsHash(selectedImageUrls)
|
|
12586
|
+
},
|
|
12587
|
+
...input.action.confirmedProductNames ? {
|
|
12588
|
+
confirmedProductNames: input.action.confirmedProductNames
|
|
12589
|
+
} : {},
|
|
12590
|
+
...pending.decisionKey && pending.referenceEvidenceVersion && pending.expectedEvidenceHash ? {
|
|
12591
|
+
decisionKey: pending.decisionKey,
|
|
12592
|
+
referenceEvidenceVersion: pending.referenceEvidenceVersion,
|
|
12593
|
+
expectedEvidenceHash: pending.expectedEvidenceHash
|
|
12594
|
+
} : {},
|
|
12595
|
+
// Multi-scene color showcase → upgrade to STANDARD. Gated to the
|
|
12596
|
+
// variants choice client-side so the wire body stays coherent; the
|
|
12597
|
+
// backend re-gates on variant_set grouping.
|
|
12598
|
+
...resolveVariantConfirmVideoTier(input.action) === "standard" ? {
|
|
12599
|
+
videoTier: "standard"
|
|
12600
|
+
} : {}
|
|
12601
|
+
});
|
|
12602
|
+
return asText(r);
|
|
12350
12603
|
}
|
|
12351
|
-
|
|
12352
|
-
const
|
|
12353
|
-
if (
|
|
12354
|
-
|
|
12355
|
-
throw new Error(`La recomendaci\xF3n de duraci\xF3n para este winner es ${choice.recommendedSec}s; no se puede solicitar 30s con este estado.`);
|
|
12604
|
+
case "angle_review_approval": {
|
|
12605
|
+
const body = {};
|
|
12606
|
+
if (pending.customAngleDraft && pending.customAngleDraft.length > 0) {
|
|
12607
|
+
body.customAngle = pending.customAngleDraft;
|
|
12356
12608
|
}
|
|
12357
|
-
body.
|
|
12358
|
-
|
|
12359
|
-
|
|
12360
|
-
|
|
12361
|
-
}
|
|
12609
|
+
if (pending.painPointsDraft != null) body.painPoints = pending.painPointsDraft;
|
|
12610
|
+
if (pending.benefitsDraft != null) body.benefits = pending.benefitsDraft;
|
|
12611
|
+
if (pending.featuresDraft != null) body.features = [
|
|
12612
|
+
...pending.featuresDraft
|
|
12362
12613
|
];
|
|
12363
|
-
|
|
12364
|
-
|
|
12365
|
-
|
|
12366
|
-
|
|
12367
|
-
|
|
12368
|
-
|
|
12369
|
-
|
|
12370
|
-
|
|
12371
|
-
|
|
12372
|
-
|
|
12373
|
-
|
|
12614
|
+
if (input.action.customAngle != null) body.customAngle = input.action.customAngle;
|
|
12615
|
+
if (input.action.painPoints != null) body.painPoints = input.action.painPoints;
|
|
12616
|
+
if (input.action.benefits != null) body.benefits = input.action.benefits;
|
|
12617
|
+
if (input.action.features != null) body.features = input.action.features;
|
|
12618
|
+
if (input.action.gapDurationSeconds != null && !pending.gapDurationChoice) {
|
|
12619
|
+
throw new Error("No hay una recomendaci\xF3n gapDurationChoice pendiente para elegir una duraci\xF3n. Consulta get_task_status y confirma el winner primero.");
|
|
12620
|
+
}
|
|
12621
|
+
if (input.action.gapDurationSeconds === 30) {
|
|
12622
|
+
const choice = pending.gapDurationChoice;
|
|
12623
|
+
if (!choice) throw new Error("No hay una recomendaci\xF3n gapDurationChoice pendiente para aceptar 30s.");
|
|
12624
|
+
if (choice.recommendedSec !== 30) {
|
|
12625
|
+
throw new Error(`La recomendaci\xF3n de duraci\xF3n para este winner es ${choice.recommendedSec}s; no se puede solicitar 30s con este estado.`);
|
|
12374
12626
|
}
|
|
12375
|
-
|
|
12376
|
-
|
|
12377
|
-
|
|
12378
|
-
|
|
12379
|
-
|
|
12627
|
+
body.angleVideoOptions = [
|
|
12628
|
+
{
|
|
12629
|
+
angleIndex: choice.angleIndex,
|
|
12630
|
+
ugcFormat: "double_30s"
|
|
12631
|
+
}
|
|
12632
|
+
];
|
|
12633
|
+
}
|
|
12634
|
+
const selectedAngleIndices = resolveSelectedAngleIndices(status, pending.gapDurationChoice);
|
|
12635
|
+
const resolvedTaskKind = resolveStatusTaskKind(status);
|
|
12636
|
+
const isVideoTask = resolvedTaskKind === "video";
|
|
12637
|
+
const isAutopilotTask = resolvedTaskKind === "autopilot";
|
|
12638
|
+
if ((isVideoTask || isAutopilotTask) && body.customAngle != null) {
|
|
12639
|
+
const existingOptions = Array.isArray(body.angleVideoOptions) ? body.angleVideoOptions : [];
|
|
12640
|
+
const optionsByIndex = new Map(existingOptions.map((option) => [
|
|
12641
|
+
option.angleIndex,
|
|
12642
|
+
{
|
|
12643
|
+
...option
|
|
12644
|
+
}
|
|
12645
|
+
]));
|
|
12646
|
+
for (const angleIndex of selectedAngleIndices) {
|
|
12647
|
+
optionsByIndex.set(angleIndex, {
|
|
12648
|
+
...optionsByIndex.get(angleIndex) ?? {
|
|
12649
|
+
angleIndex
|
|
12650
|
+
},
|
|
12651
|
+
angle: body.customAngle
|
|
12652
|
+
});
|
|
12653
|
+
}
|
|
12654
|
+
body.angleVideoOptions = [
|
|
12655
|
+
...optionsByIndex.values()
|
|
12656
|
+
].sort((left, right) => Number(left.angleIndex) - Number(right.angleIndex));
|
|
12657
|
+
}
|
|
12658
|
+
if (isVideoTask) {
|
|
12659
|
+
body.selectedAngleIndices = selectedAngleIndices;
|
|
12660
|
+
const r2 = await api.postJson(`/v1/tasks/${encodeURIComponent(input.taskId)}/approve-video-angles`, body);
|
|
12661
|
+
return asText(r2);
|
|
12662
|
+
}
|
|
12663
|
+
if (isAutopilotTask) {
|
|
12664
|
+
const resumeBody = {
|
|
12665
|
+
selectedAngleIndices,
|
|
12666
|
+
...status.ugcFormat ? {
|
|
12667
|
+
ugcFormat: status.ugcFormat
|
|
12668
|
+
} : {
|
|
12669
|
+
ugcFormat: "single_15s"
|
|
12380
12670
|
},
|
|
12381
|
-
|
|
12382
|
-
|
|
12671
|
+
...status.videoStyle ? {
|
|
12672
|
+
videoStyle: status.videoStyle
|
|
12673
|
+
} : {},
|
|
12674
|
+
...status.videoModel ? {
|
|
12675
|
+
videoModel: status.videoModel
|
|
12676
|
+
} : {},
|
|
12677
|
+
...status.videoTier ? {
|
|
12678
|
+
videoTier: status.videoTier
|
|
12679
|
+
} : {},
|
|
12680
|
+
...status.videoResolution ? {
|
|
12681
|
+
videoResolution: status.videoResolution
|
|
12682
|
+
} : {},
|
|
12683
|
+
...status.narrativeStyle ? {
|
|
12684
|
+
narrativeStyle: status.narrativeStyle
|
|
12685
|
+
} : {},
|
|
12686
|
+
...body.customAngle != null ? {
|
|
12687
|
+
customAngleOverride: body.customAngle
|
|
12688
|
+
} : {},
|
|
12689
|
+
...body.painPoints != null ? {
|
|
12690
|
+
painPoints: body.painPoints
|
|
12691
|
+
} : {},
|
|
12692
|
+
...body.benefits != null ? {
|
|
12693
|
+
benefits: body.benefits
|
|
12694
|
+
} : {},
|
|
12695
|
+
...body.angleVideoOptions ? {
|
|
12696
|
+
angleVideoOptions: body.angleVideoOptions
|
|
12697
|
+
} : {}
|
|
12698
|
+
};
|
|
12699
|
+
if (body.features !== void 0) {
|
|
12700
|
+
await api.patchJson(`/v1/tasks/${encodeURIComponent(input.taskId)}/update-product-features`, {
|
|
12701
|
+
features: body.features
|
|
12702
|
+
});
|
|
12703
|
+
}
|
|
12704
|
+
const r2 = await api.postJson(`/v1/ingest/${encodeURIComponent(input.taskId)}/resume`, resumeBody);
|
|
12705
|
+
if (r2?.success === false) {
|
|
12706
|
+
throw new Error(approveRefusalMessage(r2.reason));
|
|
12707
|
+
}
|
|
12708
|
+
return asText(r2);
|
|
12383
12709
|
}
|
|
12384
|
-
|
|
12385
|
-
|
|
12386
|
-
].sort((left, right) => Number(left.angleIndex) - Number(right.angleIndex));
|
|
12387
|
-
}
|
|
12388
|
-
if (isVideoTask) {
|
|
12389
|
-
body.selectedAngleIndices = selectedAngleIndices;
|
|
12390
|
-
const r2 = await api.postJson(`/v1/tasks/${encodeURIComponent(input.taskId)}/approve-video-angles`, body);
|
|
12391
|
-
return asText(r2);
|
|
12392
|
-
}
|
|
12393
|
-
if (isAutopilotTask) {
|
|
12394
|
-
const resumeBody = {
|
|
12395
|
-
selectedAngleIndices,
|
|
12396
|
-
...status.ugcFormat ? {
|
|
12397
|
-
ugcFormat: status.ugcFormat
|
|
12398
|
-
} : {
|
|
12399
|
-
ugcFormat: "single_15s"
|
|
12400
|
-
},
|
|
12401
|
-
...status.videoStyle ? {
|
|
12402
|
-
videoStyle: status.videoStyle
|
|
12403
|
-
} : {},
|
|
12404
|
-
...status.videoModel ? {
|
|
12405
|
-
videoModel: status.videoModel
|
|
12406
|
-
} : {},
|
|
12407
|
-
...status.videoTier ? {
|
|
12408
|
-
videoTier: status.videoTier
|
|
12409
|
-
} : {},
|
|
12410
|
-
...status.videoResolution ? {
|
|
12411
|
-
videoResolution: status.videoResolution
|
|
12412
|
-
} : {},
|
|
12413
|
-
...status.narrativeStyle ? {
|
|
12414
|
-
narrativeStyle: status.narrativeStyle
|
|
12415
|
-
} : {},
|
|
12710
|
+
const route = resolvedTaskKind === "image" ? `/v1/tasks/${encodeURIComponent(input.taskId)}/approve-image-angles` : `/v1/tasks/${encodeURIComponent(input.taskId)}/approve-angles`;
|
|
12711
|
+
const imageBody = status.kind === "image" ? {
|
|
12416
12712
|
...body.customAngle != null ? {
|
|
12417
|
-
|
|
12713
|
+
customAngle: body.customAngle
|
|
12418
12714
|
} : {},
|
|
12419
12715
|
...body.painPoints != null ? {
|
|
12420
12716
|
painPoints: body.painPoints
|
|
12421
12717
|
} : {},
|
|
12422
|
-
...body.
|
|
12423
|
-
benefits: body.benefits
|
|
12424
|
-
} : {},
|
|
12425
|
-
...body.angleVideoOptions ? {
|
|
12426
|
-
angleVideoOptions: body.angleVideoOptions
|
|
12427
|
-
} : {}
|
|
12428
|
-
};
|
|
12429
|
-
if (body.features !== void 0) {
|
|
12430
|
-
await api.patchJson(`/v1/tasks/${encodeURIComponent(input.taskId)}/update-product-features`, {
|
|
12718
|
+
...body.features != null ? {
|
|
12431
12719
|
features: body.features
|
|
12432
|
-
}
|
|
12720
|
+
} : {}
|
|
12721
|
+
} : body;
|
|
12722
|
+
const r = await api.postJson(route, imageBody);
|
|
12723
|
+
if (r?.success === false) {
|
|
12724
|
+
throw new Error(approveRefusalMessage(r.reason));
|
|
12433
12725
|
}
|
|
12434
|
-
|
|
12435
|
-
return asText(r2);
|
|
12726
|
+
return asText(r);
|
|
12436
12727
|
}
|
|
12437
|
-
|
|
12438
|
-
|
|
12439
|
-
|
|
12440
|
-
|
|
12441
|
-
|
|
12442
|
-
|
|
12443
|
-
|
|
12444
|
-
|
|
12445
|
-
|
|
12446
|
-
|
|
12447
|
-
|
|
12448
|
-
|
|
12449
|
-
|
|
12450
|
-
|
|
12451
|
-
|
|
12452
|
-
|
|
12453
|
-
|
|
12454
|
-
|
|
12455
|
-
|
|
12456
|
-
|
|
12728
|
+
case "service_angle_approval": {
|
|
12729
|
+
const action = input.action;
|
|
12730
|
+
const candidate = pending.angles?.find((angle) => angle.id === action.angleId);
|
|
12731
|
+
if (!candidate) {
|
|
12732
|
+
throw new Error("El angleId no pertenece a los conceptos pendientes de esta tarea.");
|
|
12733
|
+
}
|
|
12734
|
+
const edits = {
|
|
12735
|
+
content: action.content ?? candidate.content,
|
|
12736
|
+
creativeDirection: action.creativeDirection ?? candidate.creativeDirection,
|
|
12737
|
+
actor: action.actor ?? candidate.actor,
|
|
12738
|
+
// Echo the persisted saved actor. Omitted when absent so the PATCH
|
|
12739
|
+
// never writes a null over a selection made elsewhere — the
|
|
12740
|
+
// backend only updates the keys it receives.
|
|
12741
|
+
...candidate.actorId ? {
|
|
12742
|
+
actorId: candidate.actorId
|
|
12743
|
+
} : {}
|
|
12744
|
+
};
|
|
12745
|
+
await api.patchJson(`/v1/tasks/${encodeURIComponent(input.taskId)}/service-angles/${encodeURIComponent(candidate.id)}`, edits);
|
|
12746
|
+
const result = await api.postJson(`/v1/tasks/${encodeURIComponent(input.taskId)}/service-angles/select`, {
|
|
12747
|
+
angleIds: [
|
|
12748
|
+
candidate.id
|
|
12749
|
+
]
|
|
12750
|
+
});
|
|
12751
|
+
return asText(result);
|
|
12457
12752
|
}
|
|
12458
|
-
|
|
12459
|
-
|
|
12460
|
-
|
|
12461
|
-
|
|
12462
|
-
|
|
12463
|
-
|
|
12464
|
-
|
|
12465
|
-
|
|
12466
|
-
|
|
12467
|
-
|
|
12468
|
-
|
|
12469
|
-
|
|
12470
|
-
|
|
12471
|
-
|
|
12472
|
-
|
|
12473
|
-
|
|
12474
|
-
});
|
|
12475
|
-
return asText(result);
|
|
12476
|
-
}
|
|
12477
|
-
case "service_script_approval": {
|
|
12478
|
-
const panels = pending.panels ?? [];
|
|
12479
|
-
if (panels.length === 0) {
|
|
12480
|
-
throw new Error("El storyboard pendiente no contiene paneles aprobables.");
|
|
12753
|
+
case "service_script_approval": {
|
|
12754
|
+
const panels = pending.panels ?? [];
|
|
12755
|
+
if (panels.length === 0) {
|
|
12756
|
+
throw new Error("El storyboard pendiente no contiene paneles aprobables.");
|
|
12757
|
+
}
|
|
12758
|
+
const voiceovers = {};
|
|
12759
|
+
const visualDirections = {};
|
|
12760
|
+
for (const panel of panels) {
|
|
12761
|
+
voiceovers[panel.beatId] = input.action.voiceovers?.[panel.beatId] ?? panel.voiceover;
|
|
12762
|
+
visualDirections[panel.beatId] = input.action.visualDirections?.[panel.beatId] ?? panel.visualDirection;
|
|
12763
|
+
}
|
|
12764
|
+
const result = await api.postJson(`/v1/tasks/${encodeURIComponent(input.taskId)}/service-script/approve`, {
|
|
12765
|
+
voiceovers,
|
|
12766
|
+
visualDirections
|
|
12767
|
+
});
|
|
12768
|
+
return asText(result);
|
|
12481
12769
|
}
|
|
12482
|
-
|
|
12483
|
-
|
|
12484
|
-
|
|
12485
|
-
voiceovers[panel.beatId] = input.action.voiceovers?.[panel.beatId] ?? panel.voiceover;
|
|
12486
|
-
visualDirections[panel.beatId] = input.action.visualDirections?.[panel.beatId] ?? panel.visualDirection;
|
|
12770
|
+
default: {
|
|
12771
|
+
const _exhaustive = input.action;
|
|
12772
|
+
throw new Error(`Unsupported pending action kind: ${String(_exhaustive.kind)}`);
|
|
12487
12773
|
}
|
|
12488
|
-
const result = await api.postJson(`/v1/tasks/${encodeURIComponent(input.taskId)}/service-script/approve`, {
|
|
12489
|
-
voiceovers,
|
|
12490
|
-
visualDirections
|
|
12491
|
-
});
|
|
12492
|
-
return asText(result);
|
|
12493
|
-
}
|
|
12494
|
-
default: {
|
|
12495
|
-
const _exhaustive = input.action;
|
|
12496
|
-
throw new Error(`Unsupported pending action kind: ${String(_exhaustive.kind)}`);
|
|
12497
12774
|
}
|
|
12775
|
+
} catch (e) {
|
|
12776
|
+
throw translateError(e);
|
|
12498
12777
|
}
|
|
12499
|
-
} catch (e) {
|
|
12500
|
-
throw translateError(e);
|
|
12501
12778
|
}
|
|
12502
|
-
|
|
12779
|
+
);
|
|
12503
12780
|
}
|
|
12504
12781
|
__name(registerTaskTools, "registerTaskTools");
|
|
12505
12782
|
|
|
12506
12783
|
// src/tools/creatives.ts
|
|
12507
12784
|
import { randomUUID } from "crypto";
|
|
12508
|
-
import { z as
|
|
12785
|
+
import { z as z50 } from "zod";
|
|
12509
12786
|
var CreativeRefs = {
|
|
12510
12787
|
images: ImagesInput,
|
|
12511
12788
|
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.'),
|
|
12512
12789
|
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.'),
|
|
12513
|
-
price:
|
|
12514
|
-
offer:
|
|
12515
|
-
compareAtPrice:
|
|
12516
|
-
paymentMethods:
|
|
12517
|
-
productContext:
|
|
12518
|
-
productLabel:
|
|
12519
|
-
buyerPersona:
|
|
12520
|
-
isCombo:
|
|
12521
|
-
competitorScanEnabled:
|
|
12522
|
-
brandId:
|
|
12523
|
-
projectId:
|
|
12524
|
-
creativeDirection:
|
|
12525
|
-
headline:
|
|
12526
|
-
subheadline:
|
|
12790
|
+
price: z50.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.'),
|
|
12791
|
+
offer: z50.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.'),
|
|
12792
|
+
compareAtPrice: z50.string().min(1).max(120).optional().describe('Opcional. Precio anterior o de referencia para mostrar descuento. Ej. "$129.000" cuando price="$89.900".'),
|
|
12793
|
+
paymentMethods: z50.string().min(1).max(240).optional().describe('Opcional. M\xE9todos de pago a mostrar. Ej. "Tarjeta, PSE, Nequi, Contraentrega". Si se omite, la landing no muestra m\xE9todos de pago.'),
|
|
12794
|
+
productContext: z50.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."),
|
|
12795
|
+
productLabel: z50.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.'),
|
|
12796
|
+
buyerPersona: z50.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.'),
|
|
12797
|
+
isCombo: z50.boolean().optional().describe("Opcional. Si true, el creativo presenta el producto como combo/pack. Default false."),
|
|
12798
|
+
competitorScanEnabled: z50.boolean().optional().describe("Opcional. Si true (default), el sistema analiza ads de competencia en Meta Ad Library y enriquece los \xE1ngulos generados con hooks y painPoints validados por el mercado (\u2248$0.20-0.48 extra por task \u2014 var\xEDa con cu\xE1ntas transcripciones de video se capturen). Si false, el pipeline genera \xE1ngulos sin esa se\xF1al \u2014 usa esto SOLO si el usuario ya tiene su propia investigaci\xF3n de mercado o quiere ahorrar costos en productos muy nicho sin presencia en Meta. Default true."),
|
|
12799
|
+
brandId: z50.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."),
|
|
12800
|
+
projectId: z50.string().uuid().optional().describe("Opcional. ID de un proyecto guiado de dropshipping activo del usuario. Enlaza esta generaci\xF3n con su progreso y recompensas."),
|
|
12801
|
+
creativeDirection: z50.string().max(500).optional().describe('Opcional. Direcci\xF3n creativa en texto libre (colores, mood, estilo). M\xC1XIMA PRIORIDAD: anula marca, colores y estilo por defecto. Aplica SOLO a im\xE1genes y landing. Ej. im\xE1genes: "amarillo, azul y rojo, minimalista"; landing: "elegante, paleta fr\xEDa". Para video usa scriptNotes (ambiente/escenario). M\xE1x 500 caracteres.'),
|
|
12802
|
+
headline: z50.string().max(120).optional().describe("Opcional. Titular EXACTO a renderizar en la imagen (o en la portada de la landing). Se imprime tal cual: la IA no lo reescribe, no lo acorta y no lo traduce. \xDAsalo cuando el copy es de marca y no es negociable. M\xE1x 120 caracteres. Si lo omites, la IA escribe el titular."),
|
|
12803
|
+
subheadline: z50.string().max(160).optional().describe("Opcional. Subt\xEDtulo EXACTO a renderizar, misma regla que headline. Independiente: puedes fijar solo el subt\xEDtulo. M\xE1x 160 caracteres.")
|
|
12527
12804
|
};
|
|
12528
|
-
var VideoInput =
|
|
12805
|
+
var VideoInput = z50.object({
|
|
12529
12806
|
images: CreativeRefs.images,
|
|
12530
|
-
title:
|
|
12531
|
-
primaryAngle:
|
|
12807
|
+
title: z50.string().min(1).describe('T\xEDtulo corto del producto. Aparece en la primera vista del video. Ej. "Crema Anti-Edad Premium".'),
|
|
12808
|
+
primaryAngle: z50.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".'),
|
|
12532
12809
|
language: CreativeRefs.language,
|
|
12533
12810
|
country: CreativeRefs.country,
|
|
12534
|
-
aspectRatio:
|
|
12811
|
+
aspectRatio: z50.enum([
|
|
12535
12812
|
"9:16",
|
|
12536
12813
|
"16:9",
|
|
12537
12814
|
"Auto"
|
|
12538
12815
|
]).describe('Relaci\xF3n de aspecto. "9:16" = vertical (TikTok/Reels \u2014 default recomendado para ads). "16:9" = horizontal (YouTube). "Auto" = decide la IA.'),
|
|
12539
|
-
durationSeconds:
|
|
12540
|
-
|
|
12541
|
-
|
|
12816
|
+
durationSeconds: z50.union([
|
|
12817
|
+
z50.literal(15),
|
|
12818
|
+
z50.literal(30)
|
|
12542
12819
|
]).default(15).describe("Opcional. Duraci\xF3n objetivo: 15s (single_call, default) o 30s (Multiescena, aproximadamente 30s y facturado por escena)."),
|
|
12543
12820
|
videoPreset: VideoPresetEnum.default("auto").describe('Opcional. Tipo/narrativa del video \u2014 \xFAnico campo de "estilo" que el usuario controla. Default "auto" (la IA escoge tras analizar el producto). Todos los presets funcionan a 15s (single_call) y 30s (Multiescena). Opciones: "auto", "ugc_dynamic" (review con experiencia y prueba), "ugc_how_to" (tutorial con pasos y resultado visible), "ugc_unboxing_chaos" (apertura y primera prueba), "problem_solution" (problema concreto, producto como soluci\xF3n y CTA), "product_showcase" (producto ejecutando una funci\xF3n real), "wearable_showcase" (prenda o accesorio modelado), "cinematic" (TV spot con receta cinematogr\xE1fica), "wild_card" (una regla visual experimental original), "pixar_problem" (problema animado 3D), "pixar_sassy" (producto que habla).'),
|
|
12544
12821
|
videoTier: VideoTierEnum.optional().describe('Opcional. Estilo del video. "basic" = "Realista"; para durationSeconds=30 el sistema selecciona internamente Multiescena. No se expone el tier interno directamente.'),
|
|
12545
12822
|
videoResolution: VideoResolutionEnum.optional().describe('Opcional. Resoluci\xF3n del render. En la web se pregunta "\xBFResoluci\xF3n del video?". "480p" = excelente calidad y m\xE1s econ\xF3mico (RECOMENDADO, default si se omite); "720p" = m\xE1xima nitidez, entre 3% y 15% m\xE1s caro seg\xFAn formato y tier. Si el usuario no menciona resoluci\xF3n, OMITE este campo (el backend usa 480p).'),
|
|
12546
|
-
features:
|
|
12823
|
+
features: z50.array(z50.string()).optional().describe('Opcional. Lista de features/beneficios clave a mencionar. Ej. ["resistente al agua", "bater\xEDa 24h", "carga inal\xE1mbrica"].'),
|
|
12547
12824
|
price: CreativeRefs.price,
|
|
12548
12825
|
offer: CreativeRefs.offer,
|
|
12549
12826
|
productContext: CreativeRefs.productContext,
|
|
12550
12827
|
productLabel: CreativeRefs.productLabel,
|
|
12551
|
-
cta:
|
|
12552
|
-
scriptNotes:
|
|
12553
|
-
pauseForVideoScriptReview:
|
|
12554
|
-
pauseForAngleReview:
|
|
12555
|
-
aiDecidesAll:
|
|
12828
|
+
cta: z50.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.'),
|
|
12829
|
+
scriptNotes: z50.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 \u2014 para editar el guion palabra-por-palabra, pasa `pauseForVideoScriptReview: true` y usa get_video_script / approve_video_script tras la pausa.'),
|
|
12830
|
+
pauseForVideoScriptReview: z50.boolean().optional().describe("Opcional. Si true, la tarea pausa despu\xE9s de generar el guion y antes de renderizar. \xDAsalo cuando el usuario quiera editar el guion antes de gastar cr\xE9ditos en el video. Tras la pausa: get_video_script para leerlo, approve_video_script para aprobar (con o sin ediciones). Default false."),
|
|
12831
|
+
pauseForAngleReview: z50.boolean().optional().describe("Opcional. Si true, pausa despu\xE9s de generar el \xE1ngulo para que el usuario lo confirme o edite antes del render. Default false."),
|
|
12832
|
+
aiDecidesAll: z50.boolean().optional().describe("Opcional. Si true, ProductMaker decide TODO y no vuelve a interrumpir al usuario: resuelve solo las confirmaciones de referencias del producto y no abre gates de aprobaci\xF3n. Act\xEDvalo \xFAnicamente cuando el usuario pida expl\xEDcitamente que la IA decida todo o que no le pregunten m\xE1s. Default false."),
|
|
12556
12833
|
competitorScanEnabled: CreativeRefs.competitorScanEnabled,
|
|
12557
12834
|
brandId: CreativeRefs.brandId,
|
|
12558
12835
|
projectId: CreativeRefs.projectId
|
|
12559
12836
|
}).strict();
|
|
12560
|
-
var FreeVideoQualityEnum =
|
|
12837
|
+
var FreeVideoQualityEnum = z50.enum([
|
|
12561
12838
|
"economical",
|
|
12562
12839
|
"high_detail"
|
|
12563
12840
|
]);
|
|
@@ -12579,38 +12856,38 @@ var FREE_VIDEO_DURATIONS_BY_QUALITY = {
|
|
|
12579
12856
|
15
|
|
12580
12857
|
]
|
|
12581
12858
|
};
|
|
12582
|
-
var FreeVideoInput =
|
|
12859
|
+
var FreeVideoInput = z50.object({
|
|
12583
12860
|
images: ImagesInput.optional().describe("Opcional. 1-5 fotos del producto. Si el usuario adjunt\xF3 o mencion\xF3 fotos locales y no existe un assetId reutilizable, llama primero request_media_upload con mediaKind=image y usa los assetId devueltos; no pidas rutas ni URLs. Om\xEDtelo para generar desde texto puro."),
|
|
12584
|
-
rawPrompt:
|
|
12861
|
+
rawPrompt: z50.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."),
|
|
12585
12862
|
quality: FreeVideoQualityEnum.describe('Calidad del video: "economical" = Econ\xF3mico o "high_detail" = Alto detalle.'),
|
|
12586
|
-
durationSeconds:
|
|
12863
|
+
durationSeconds: z50.number().int().min(1).max(15).describe("Duraci\xF3n exacta. Econ\xF3mico permite 6, 8, 10 o 15s; Alto detalle permite 4, 8, 12 o 15s."),
|
|
12587
12864
|
videoResolution: VideoResolutionEnum.describe("Resoluci\xF3n del video: 480p o 720p."),
|
|
12588
|
-
generateAiMusic:
|
|
12589
|
-
addSubtitles:
|
|
12590
|
-
idempotencyKey:
|
|
12865
|
+
generateAiMusic: z50.boolean().default(false).describe("Opcional. Agrega m\xFAsica instrumental IA debajo del audio nativo. Default false."),
|
|
12866
|
+
addSubtitles: z50.boolean().default(false).describe("Opcional. Quema subt\xEDtulos usando el rawPrompt exacto. Default false."),
|
|
12867
|
+
idempotencyKey: z50.string().uuid().optional().describe("Opcional. UUID para reintentar exactamente esta misma solicitud sin crear ni cobrar otro video. Reutiliza el mismo valor si el cliente no recibi\xF3 la respuesta.")
|
|
12591
12868
|
}).strict();
|
|
12592
12869
|
var imageStyleList = ImageCreativeStyleEnum.options.join(" | ");
|
|
12593
12870
|
var archetypeList = ImageCreativeArchetypeEnum.options.join(" | ");
|
|
12594
12871
|
var visualTreatmentList = ImageCreativeVisualTreatmentEnum.options.join(" | ");
|
|
12595
12872
|
var aspectRatioList = ImageCreativeAspectRatioEnum.options.join(" | ");
|
|
12596
|
-
var ImageInput =
|
|
12873
|
+
var ImageInput = z50.object({
|
|
12597
12874
|
images: CreativeRefs.images,
|
|
12598
|
-
productTitle:
|
|
12599
|
-
primaryAngle:
|
|
12875
|
+
productTitle: z50.string().min(1).describe('Nombre del producto. Ej. "Crema Anti-Edad Premium".'),
|
|
12876
|
+
primaryAngle: z50.string().min(1).describe('\xC1ngulo de venta principal. Ej. "Reduce arrugas en 14 d\xEDas".'),
|
|
12600
12877
|
buyerPersona: CreativeRefs.buyerPersona,
|
|
12601
12878
|
language: CreativeRefs.language,
|
|
12602
12879
|
country: CreativeRefs.country,
|
|
12603
|
-
nVariants:
|
|
12604
|
-
imageModel:
|
|
12880
|
+
nVariants: z50.number().int().min(1).max(6).optional().describe("Opcional. N\xFAmero de variantes a generar (1-6). Default 3."),
|
|
12881
|
+
imageModel: z50.string().optional().describe('Opcional. Modelo de imagen. Ej. "gpt-image-2", "nano-banana-2". Om\xEDtelo para usar el default optimizado por costo/calidad.'),
|
|
12605
12882
|
imageStyle: ImageCreativeStyleEnum.optional().describe(`Opcional. Estilo del shot del producto. Valores: ${imageStyleList}. Si se omite, la IA escoge seg\xFAn el archetype.`),
|
|
12606
12883
|
creativeArchetype: ImageCreativeArchetypeEnum.optional().describe(`Opcional. Arquetipo CRO de la composici\xF3n. Valores: ${archetypeList}. Si se omite, la IA escoge seg\xFAn \xE1ngulo+oferta (ej. "offer_value_prop" cuando hay descuento, "social_proof_trust" cuando el \xE1ngulo es testimonial).`),
|
|
12607
12884
|
visualTreatment: ImageCreativeVisualTreatmentEnum.optional().describe(`Opcional. Tratamiento gr\xE1fico. Valores: ${visualTreatmentList} ("performance_product_ad" = dealer-style alto CTR para Meta/TikTok ads; "premium_dtc_carousel" = fotograf\xEDa limpia estilo Allbirds/Glossier). Si se omite, la IA escoge.`),
|
|
12608
|
-
styleMix:
|
|
12885
|
+
styleMix: z50.boolean().optional().describe("Opcional. Si true, las N variantes mezclan distintos imageStyle para A/B test. Si false/omitido, todas comparten estilo. Default false."),
|
|
12609
12886
|
aspectRatio: ImageCreativeAspectRatioEnum.optional().describe(`Opcional. Relaci\xF3n de aspecto. Valores: ${aspectRatioList}. Default "4:5" (vertical Instagram feed).`),
|
|
12610
|
-
generateAdCopy:
|
|
12611
|
-
pauseForAngleReview:
|
|
12612
|
-
features:
|
|
12613
|
-
brandColors:
|
|
12887
|
+
generateAdCopy: z50.boolean().optional().describe("Opcional. Si true, tambi\xE9n genera headline/body/CTA para el ad. Default false."),
|
|
12888
|
+
pauseForAngleReview: z50.boolean().optional().describe("Opcional. Si true, pausa despu\xE9s de generar el \xE1ngulo y antes de las im\xE1genes para que el usuario edite v\xEDa edit_task_draft. Default false."),
|
|
12889
|
+
features: z50.array(z50.string()).optional().describe('Opcional. Features/beneficios a destacar visualmente. Ej. ["resistente al agua", "ultra liviano"].'),
|
|
12890
|
+
brandColors: z50.array(z50.string()).optional().describe('Opcional. Paleta de marca en hex. Ej. ["#FF6B35", "#004E89"]. Si se omite, la IA elige seg\xFAn el producto.'),
|
|
12614
12891
|
price: CreativeRefs.price,
|
|
12615
12892
|
compareAtPrice: CreativeRefs.compareAtPrice,
|
|
12616
12893
|
offer: CreativeRefs.offer,
|
|
@@ -12624,17 +12901,17 @@ var ImageInput = z49.object({
|
|
|
12624
12901
|
headline: CreativeRefs.headline,
|
|
12625
12902
|
subheadline: CreativeRefs.subheadline
|
|
12626
12903
|
});
|
|
12627
|
-
var EditImageCreativeInput =
|
|
12628
|
-
taskId:
|
|
12629
|
-
variantIndex:
|
|
12630
|
-
instruction:
|
|
12904
|
+
var EditImageCreativeInput = z50.object({
|
|
12905
|
+
taskId: z50.string().uuid().describe("ID de la tarea de imagen ya finalizada."),
|
|
12906
|
+
variantIndex: z50.number().int().min(0).max(10).describe("\xCDndice de la variante a corregir (0 = primera imagen)."),
|
|
12907
|
+
instruction: z50.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".')
|
|
12631
12908
|
});
|
|
12632
|
-
var LandingInput =
|
|
12633
|
-
aiDecidesAll:
|
|
12909
|
+
var LandingInput = z50.object({
|
|
12910
|
+
aiDecidesAll: z50.boolean().optional().describe("Opcional. Si true, ProductMaker decide TODO y no vuelve a interrumpir al usuario: resuelve solo las confirmaciones de referencias del producto y no abre gates de aprobaci\xF3n. Act\xEDvalo \xFAnicamente cuando el usuario pida expl\xEDcitamente que la IA decida todo o que no le pregunten m\xE1s. Default false."),
|
|
12634
12911
|
images: CreativeRefs.images,
|
|
12635
12912
|
language: CreativeRefs.language,
|
|
12636
12913
|
country: CreativeRefs.country,
|
|
12637
|
-
salesAngle:
|
|
12914
|
+
salesAngle: z50.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.'),
|
|
12638
12915
|
buyerPersona: CreativeRefs.buyerPersona,
|
|
12639
12916
|
price: CreativeRefs.price,
|
|
12640
12917
|
compareAtPrice: CreativeRefs.compareAtPrice,
|
|
@@ -12649,11 +12926,12 @@ var LandingInput = z49.object({
|
|
|
12649
12926
|
creativeDirection: CreativeRefs.creativeDirection,
|
|
12650
12927
|
landingStrategy: LandingStrategyEnum.default("classic").describe('Opcional. Estructura de la landing cl\xE1sica: "classic" (estructura original probada y recomendada; default del primer rollout), "auto" (la IA decide; experimental), "pain_solution" (problema\u2192soluci\xF3n), "social_proof" (Landing Viral: UGC, WhatsApp, Instagram y evidencia social), "benefits_clarity" (beneficios claros), "offer_value" (promoci\xF3n/valor) o "lifestyle_identity" (estilo de vida). Default "classic".'),
|
|
12651
12928
|
landingImageFormat: LandingImageFormatEnum.default("9:16").describe('Opcional. Formato/relaci\xF3n de aspecto de las im\xE1genes de la landing cl\xE1sica: "9:16" (vertical, por defecto), "4:5" (vertical compacto para feed) o "1:1" (cuadrado). Default "9:16".'),
|
|
12929
|
+
pauseForAngleReview: z50.boolean().optional().describe("Opcional. Si true, pausa despu\xE9s de generar el \xE1ngulo para que el usuario lo confirme o edite antes de la creaci\xF3n de la landing. Default false."),
|
|
12652
12930
|
headline: CreativeRefs.headline,
|
|
12653
12931
|
subheadline: CreativeRefs.subheadline
|
|
12654
12932
|
});
|
|
12655
12933
|
function registerCreativeTools(server, api, { transport = "http" } = {}) {
|
|
12656
|
-
server.tool("generate_free_video", 'Genera UN video libre con prompt/guion exacto, opcionalmente con imagen de referencia. Si el usuario adjunta o menciona una imagen local, llama primero request_media_upload con mediaKind=image y usa el assetId devuelto; no pidas path ni URL. Devuelve un taskId y procesa el job de forma as\xEDncrona.\n\nREQUERIDOS: rawPrompt, quality, durationSeconds y videoResolution.\n\nCALIDAD: "economical" = Econ\xF3mico y "high_detail" = Alto detalle. La duraci\xF3n debe pertenecer a la lista admitida por la calidad escogida; si el usuario pide otra, preg\xFAntale una duraci\xF3n v\xE1lida.\n\nSUBT\xCDTULOS: si addSubtitles=true, el rawPrompt completo se usa como texto exacto de subt\xEDtulos y debe tener m\xE1ximo 500 caracteres; no existe una pregunta adicional de transcripci\xF3n.\n\nPara videos normales con narrativa/\xE1ngulo usa generate_video_creative. Para tareas combinadas de producto usa create_product_task.', FreeVideoInput.shape, async (input) => {
|
|
12934
|
+
server.tool("generate_free_video", 'Genera UN video libre con prompt/guion exacto, opcionalmente con imagen de referencia. Si el usuario adjunta o menciona una imagen local, llama primero request_media_upload con mediaKind=image y usa el assetId devuelto; no pidas path ni URL. Devuelve un taskId y procesa el job de forma as\xEDncrona.\n\nREQUERIDOS: rawPrompt, quality, durationSeconds y videoResolution.\n\nCALIDAD: "economical" = Econ\xF3mico y "high_detail" = Alto detalle. La duraci\xF3n debe pertenecer a la lista admitida por la calidad escogida; si el usuario pide otra, preg\xFAntale una duraci\xF3n v\xE1lida.\n\nSUBT\xCDTULOS: si addSubtitles=true, el rawPrompt completo se usa como texto exacto de subt\xEDtulos y debe tener m\xE1ximo 500 caracteres; no existe una pregunta adicional de transcripci\xF3n.\n\nPara videos normales con narrativa/\xE1ngulo usa generate_video_creative. Para tareas combinadas de producto usa create_product_task.', FreeVideoInput.shape, CREATES_EXTERNAL, async (input) => {
|
|
12657
12935
|
try {
|
|
12658
12936
|
const allowedDurations = FREE_VIDEO_DURATIONS_BY_QUALITY[input.quality];
|
|
12659
12937
|
if (!allowedDurations.includes(input.durationSeconds)) {
|
|
@@ -12683,7 +12961,7 @@ function registerCreativeTools(server, api, { transport = "http" } = {}) {
|
|
|
12683
12961
|
throw translateError(e);
|
|
12684
12962
|
}
|
|
12685
12963
|
});
|
|
12686
|
-
server.tool("generate_video_creative", 'Genera UN video creativo independiente. Devuelve un taskId.\n\nREQUERIDOS: images (array de 1-5 fotos), title, primaryAngle, language, country, aspectRatio. Si el usuario adjunta o menciona fotos locales y a\xFAn no hay assetId, llama primero `request_media_upload` con mediaKind=image y despu\xE9s usa los assetId en images; no pidas path ni URL.\n\nANTES de invocar, PREG\xDANTALE al usuario por price y offer si no los mencion\xF3 \u2014 son los inputs de mayor impacto en conversi\xF3n y nunca debes asumirlos. NO defaultes country/language desde el contexto: preg\xFAntale expl\xEDcitamente. Si el usuario te dice "t\xFA elige" para price/offer, om\xEDtelos.\n\nOTROS OPCIONALES (puedes invocar sin ellos y dejar que la IA elija defaults sensatos): durationSeconds (15 o 30), videoPreset (tipo/narrativa del video), features, cta, scriptNotes (dirige el guion Y el ambiente/escenario/est\xE9tica del video, ej. "en una playa, opulento").\n\nCOMPETENCIA Y GAP REPLICATION: con videoPreset="auto" (default) y competitorScanEnabled=true (default), el sistema analiza anuncios de competencia. Si encuentra un winner de video coherente, ancla el primaryAngle y genera autom\xE1ticamente la narrativa `gap_replication` para copiar su estructura audiovisual. No la selecciones manualmente: es un resultado del an\xE1lisis. Si competitorScanEnabled=false, esta b\xFAsqueda se omite.\n\nESTILO: 15s usa "Realista" (single_call); 30s usa "Multiescena" (multi-toma, aproximadamente 30s). Multiescena se factura por escena, as\xED que avisa el costo antes de activarlo.\n\nGUION: el usuario NO escribe el guion desde cero \u2014 la IA lo redacta. Si quiere REVISARLO/EDITARLO antes de renderizar (igual que en la web), pasa `pauseForVideoScriptReview: true`; cuando la tarea quede pausada, usa get_video_script para leer las escenas, ofr\xE9celas al usuario, y llama approve_video_script con las ediciones. Para dirigir el guion ANTES de generar (sin pausar), usa scriptNotes. Para revisar el \xE1ngulo y decidir duraci\xF3n tras el an\xE1lisis de competencia, pasa `pauseForAngleReview: true` y luego resuelve `angle_review_approval` con `gapDurationSeconds`.', VideoInput.shape, async (input) => {
|
|
12964
|
+
server.tool("generate_video_creative", 'Genera UN video creativo independiente. Devuelve un taskId.\n\nREQUERIDOS: images (array de 1-5 fotos), title, primaryAngle, language, country, aspectRatio. Si el usuario adjunta o menciona fotos locales y a\xFAn no hay assetId, llama primero `request_media_upload` con mediaKind=image y despu\xE9s usa los assetId en images; no pidas path ni URL.\n\nANTES de invocar, PREG\xDANTALE al usuario por price y offer si no los mencion\xF3 \u2014 son los inputs de mayor impacto en conversi\xF3n y nunca debes asumirlos. NO defaultes country/language desde el contexto: preg\xFAntale expl\xEDcitamente. Si el usuario te dice "t\xFA elige" para price/offer, om\xEDtelos.\n\nOTROS OPCIONALES (puedes invocar sin ellos y dejar que la IA elija defaults sensatos): durationSeconds (15 o 30), videoPreset (tipo/narrativa del video), features, cta, scriptNotes (dirige el guion Y el ambiente/escenario/est\xE9tica del video, ej. "en una playa, opulento").\n\nCOMPETENCIA Y GAP REPLICATION: con videoPreset="auto" (default) y competitorScanEnabled=true (default), el sistema analiza anuncios de competencia. Si encuentra un winner de video coherente, ancla el primaryAngle y genera autom\xE1ticamente la narrativa `gap_replication` para copiar su estructura audiovisual. No la selecciones manualmente: es un resultado del an\xE1lisis. Si competitorScanEnabled=false, esta b\xFAsqueda se omite.\n\nESTILO: 15s usa "Realista" (single_call); 30s usa "Multiescena" (multi-toma, aproximadamente 30s). Multiescena se factura por escena, as\xED que avisa el costo antes de activarlo.\n\nGUION: el usuario NO escribe el guion desde cero \u2014 la IA lo redacta. Si quiere REVISARLO/EDITARLO antes de renderizar (igual que en la web), pasa `pauseForVideoScriptReview: true`; cuando la tarea quede pausada, usa get_video_script para leer las escenas, ofr\xE9celas al usuario, y llama approve_video_script con las ediciones. Para dirigir el guion ANTES de generar (sin pausar), usa scriptNotes. Para revisar el \xE1ngulo y decidir duraci\xF3n tras el an\xE1lisis de competencia, pasa `pauseForAngleReview: true` y luego resuelve `angle_review_approval` con `gapDurationSeconds`.', VideoInput.shape, CREATES_EXTERNAL, async (input) => {
|
|
12687
12965
|
try {
|
|
12688
12966
|
const resolved = resolveVideoPreset(input.videoPreset);
|
|
12689
12967
|
const wireTier = resolveWireVideoTier(input.videoTier, input.durationSeconds);
|
|
@@ -12708,7 +12986,7 @@ function registerCreativeTools(server, api, { transport = "http" } = {}) {
|
|
|
12708
12986
|
throw translateError(e);
|
|
12709
12987
|
}
|
|
12710
12988
|
});
|
|
12711
|
-
server.tool("generate_image_creatives", 'Genera N variantes de imagen creativa (ads est\xE1ticos). Devuelve un taskId.\n\nREQUERIDOS: images (array de 1-5 fotos), productTitle, primaryAngle, language, country. Si el usuario adjunta o menciona fotos locales y a\xFAn no hay assetId, llama primero `request_media_upload` con mediaKind=image y despu\xE9s usa los assetId en images; no pidas path ni URL.\n\nANTES de invocar, PREG\xDANTALE al usuario:\n- price y offer (se renderizan en la imagen \u2014 nunca los asumas; si dice "t\xFA elige", om\xEDtelos).\n- compareAtPrice si hay descuento (ej. "$129.000" para tachar).\n- creativeArchetype y visualTreatment si quiere un look espec\xEDfico (performance vs premium DTC). Si no le importa, om\xEDtelos.\n\nNO defaultes country/language desde el contexto: preg\xFAntale expl\xEDcitamente.\n\nOTROS OPCIONALES: buyerPersona, nVariants (default 3), imageModel, imageStyle, styleMix, aspectRatio (default 4:5), generateAdCopy, pauseForAngleReview, features, brandColors, isCombo, creativeDirection.', ImageInput.shape, async (input) => {
|
|
12989
|
+
server.tool("generate_image_creatives", 'Genera N variantes de imagen creativa (ads est\xE1ticos). Devuelve un taskId.\n\nREQUERIDOS: images (array de 1-5 fotos), productTitle, primaryAngle, language, country. Si el usuario adjunta o menciona fotos locales y a\xFAn no hay assetId, llama primero `request_media_upload` con mediaKind=image y despu\xE9s usa los assetId en images; no pidas path ni URL.\n\nANTES de invocar, PREG\xDANTALE al usuario:\n- price y offer (se renderizan en la imagen \u2014 nunca los asumas; si dice "t\xFA elige", om\xEDtelos).\n- compareAtPrice si hay descuento (ej. "$129.000" para tachar).\n- creativeArchetype y visualTreatment si quiere un look espec\xEDfico (performance vs premium DTC). Si no le importa, om\xEDtelos.\n\nNO defaultes country/language desde el contexto: preg\xFAntale expl\xEDcitamente.\n\nOTROS OPCIONALES: buyerPersona, nVariants (default 3), imageModel, imageStyle, styleMix, aspectRatio (default 4:5), generateAdCopy, pauseForAngleReview, features, brandColors, isCombo, creativeDirection.', ImageInput.shape, CREATES_EXTERNAL, async (input) => {
|
|
12712
12990
|
try {
|
|
12713
12991
|
const { projectId, ...rest } = input;
|
|
12714
12992
|
return await submitCreative(api, {
|
|
@@ -12726,7 +13004,7 @@ function registerCreativeTools(server, api, { transport = "http" } = {}) {
|
|
|
12726
13004
|
throw translateError(e);
|
|
12727
13005
|
}
|
|
12728
13006
|
});
|
|
12729
|
-
server.tool("edit_image_creative", "Corrige UNA imagen ya generada de una tarea de creativos, sin regenerar el set completo.\n\nCUESTA CR\xC9DITOS: cobra una sola imagen (sin brief ni extracci\xF3n), mucho menos que volver a generar todo. ANTES de invocar, PREG\xDANTALE al usuario si quiere pagar esa correcci\xF3n.\n\nSolo funciona con la tarea en estado `done` \u2014 si a\xFAn est\xE1 procesando, espera con get_task_status. Una tarea en error terminal no se puede corregir.\n\nUsa variantIndex para elegir cu\xE1l de las im\xE1genes corregir (0 = la primera) e instruction para describir el cambio en lenguaje natural.", EditImageCreativeInput.shape, async (input) => {
|
|
13007
|
+
server.tool("edit_image_creative", "Corrige UNA imagen ya generada de una tarea de creativos, sin regenerar el set completo.\n\nCUESTA CR\xC9DITOS: cobra una sola imagen (sin brief ni extracci\xF3n), mucho menos que volver a generar todo. ANTES de invocar, PREG\xDANTALE al usuario si quiere pagar esa correcci\xF3n.\n\nSolo funciona con la tarea en estado `done` \u2014 si a\xFAn est\xE1 procesando, espera con get_task_status. Una tarea en error terminal no se puede corregir.\n\nUsa variantIndex para elegir cu\xE1l de las im\xE1genes corregir (0 = la primera) e instruction para describir el cambio en lenguaje natural.", EditImageCreativeInput.shape, OVERWRITES_EXTERNAL, async (input) => {
|
|
12730
13008
|
try {
|
|
12731
13009
|
const r = await api.postJson(`/v1/creatives/images/${encodeURIComponent(input.taskId)}/regenerate`, {
|
|
12732
13010
|
comment: input.instruction,
|
|
@@ -12737,7 +13015,7 @@ function registerCreativeTools(server, api, { transport = "http" } = {}) {
|
|
|
12737
13015
|
throw translateError(e);
|
|
12738
13016
|
}
|
|
12739
13017
|
});
|
|
12740
|
-
server.tool("generate_landing", 'Genera una landing page cl\xE1sica basada en im\xE1genes, lista para Shopify. Devuelve un taskId.\n\nREQUERIDOS: images (array de 1-5 fotos), language, country. Si el usuario dice algo natural como \u201Ccrea una landing con este producto\u201D y adjunta o menciona fotos locales, llama primero `request_media_upload` con mediaKind=image y despu\xE9s usa los assetId en images; no pidas path ni URL.\n\nANTES de invocar, PREG\xDANTALE al usuario por price, offer y paymentMethods si no los mencion\xF3 \u2014 son los inputs de mayor impacto en conversi\xF3n y nunca debes asumirlos. Si hay descuento, pregunta tambi\xE9n compareAtPrice. NO defaultes country/language desde el contexto: preg\xFAntale expl\xEDcitamente. Si el usuario te dice "t\xFA elige" para esos campos, om\xEDtelos.\n\nOTROS OPCIONALES (puedes invocar sin ellos y dejar que la IA elija defaults): salesAngle, buyerPersona, isCombo, creativeDirection, landingStrategy.', LandingInput.shape, async (input) => {
|
|
13018
|
+
server.tool("generate_landing", 'Genera una landing page cl\xE1sica basada en im\xE1genes, lista para Shopify. Devuelve un taskId.\n\nREQUERIDOS: images (array de 1-5 fotos), language, country. Si el usuario dice algo natural como \u201Ccrea una landing con este producto\u201D y adjunta o menciona fotos locales, llama primero `request_media_upload` con mediaKind=image y despu\xE9s usa los assetId en images; no pidas path ni URL.\n\nANTES de invocar, PREG\xDANTALE al usuario por price, offer y paymentMethods si no los mencion\xF3 \u2014 son los inputs de mayor impacto en conversi\xF3n y nunca debes asumirlos. Si hay descuento, pregunta tambi\xE9n compareAtPrice. NO defaultes country/language desde el contexto: preg\xFAntale expl\xEDcitamente. Si el usuario te dice "t\xFA elige" para esos campos, om\xEDtelos.\n\nOTROS OPCIONALES (puedes invocar sin ellos y dejar que la IA elija defaults): salesAngle, buyerPersona, isCombo, creativeDirection, landingStrategy.', LandingInput.shape, CREATES_EXTERNAL, async (input) => {
|
|
12741
13019
|
try {
|
|
12742
13020
|
const { projectId, ...rest } = input;
|
|
12743
13021
|
return await submitCreative(api, {
|
|
@@ -12759,8 +13037,8 @@ function registerCreativeTools(server, api, { transport = "http" } = {}) {
|
|
|
12759
13037
|
__name(registerCreativeTools, "registerCreativeTools");
|
|
12760
13038
|
|
|
12761
13039
|
// src/tools/creative-variants.ts
|
|
12762
|
-
import { z as
|
|
12763
|
-
var LandingSectionKeyEnum =
|
|
13040
|
+
import { z as z51 } from "zod";
|
|
13041
|
+
var LandingSectionKeyEnum = z51.enum([
|
|
12764
13042
|
"problem_hook",
|
|
12765
13043
|
"problem_cost",
|
|
12766
13044
|
"solution_mechanism",
|
|
@@ -12800,24 +13078,24 @@ var LandingSectionKeyEnum = z50.enum([
|
|
|
12800
13078
|
"lifestyle_fit",
|
|
12801
13079
|
"join_lifestyle"
|
|
12802
13080
|
]);
|
|
12803
|
-
var CreativeVariantKindEnum =
|
|
13081
|
+
var CreativeVariantKindEnum = z51.enum([
|
|
12804
13082
|
"video",
|
|
12805
13083
|
"image",
|
|
12806
13084
|
"landing"
|
|
12807
13085
|
]);
|
|
12808
|
-
var EstimateCreativeVariantInputBase =
|
|
12809
|
-
sourceTaskId:
|
|
13086
|
+
var EstimateCreativeVariantInputBase = z51.object({
|
|
13087
|
+
sourceTaskId: z51.string().uuid().describe("ID de la tarea original completada."),
|
|
12810
13088
|
kind: CreativeVariantKindEnum,
|
|
12811
13089
|
videoPreset: TaskCreativeVariantVideoPresetEnum.optional().describe("Opcional para video. Solo cambia el preset p\xFAblico; el servidor conserva el modelo/tier del task origen."),
|
|
12812
13090
|
videoResolution: VideoResolutionEnum.optional().describe("Opcional para video. Resoluci\xF3n del nuevo render."),
|
|
12813
|
-
nVariants:
|
|
13091
|
+
nVariants: z51.number().int().min(1).max(3).optional().describe("Opcional para imagen. N\xFAmero de im\xE1genes adicionales (1-3)."),
|
|
12814
13092
|
sectionKey: LandingSectionKeyEnum.optional().describe("Opcional para landing. Si ya conoces la secci\xF3n, evita cotizar la clasificaci\xF3n con IA."),
|
|
12815
|
-
instructions:
|
|
13093
|
+
instructions: z51.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.")
|
|
12816
13094
|
}).strict();
|
|
12817
13095
|
var EstimateCreativeVariantInput = EstimateCreativeVariantInputBase.superRefine((input, ctx) => {
|
|
12818
13096
|
if (input.kind === "video" && input.nVariants !== void 0) {
|
|
12819
13097
|
ctx.addIssue({
|
|
12820
|
-
code:
|
|
13098
|
+
code: z51.ZodIssueCode.custom,
|
|
12821
13099
|
path: [
|
|
12822
13100
|
"nVariants"
|
|
12823
13101
|
],
|
|
@@ -12827,7 +13105,7 @@ var EstimateCreativeVariantInput = EstimateCreativeVariantInputBase.superRefine(
|
|
|
12827
13105
|
if (input.kind !== "video") {
|
|
12828
13106
|
if (input.videoPreset !== void 0) {
|
|
12829
13107
|
ctx.addIssue({
|
|
12830
|
-
code:
|
|
13108
|
+
code: z51.ZodIssueCode.custom,
|
|
12831
13109
|
path: [
|
|
12832
13110
|
"videoPreset"
|
|
12833
13111
|
],
|
|
@@ -12836,7 +13114,7 @@ var EstimateCreativeVariantInput = EstimateCreativeVariantInputBase.superRefine(
|
|
|
12836
13114
|
}
|
|
12837
13115
|
if (input.videoResolution !== void 0) {
|
|
12838
13116
|
ctx.addIssue({
|
|
12839
|
-
code:
|
|
13117
|
+
code: z51.ZodIssueCode.custom,
|
|
12840
13118
|
path: [
|
|
12841
13119
|
"videoResolution"
|
|
12842
13120
|
],
|
|
@@ -12846,7 +13124,7 @@ var EstimateCreativeVariantInput = EstimateCreativeVariantInputBase.superRefine(
|
|
|
12846
13124
|
}
|
|
12847
13125
|
if (input.kind !== "image" && input.nVariants !== void 0) {
|
|
12848
13126
|
ctx.addIssue({
|
|
12849
|
-
code:
|
|
13127
|
+
code: z51.ZodIssueCode.custom,
|
|
12850
13128
|
path: [
|
|
12851
13129
|
"nVariants"
|
|
12852
13130
|
],
|
|
@@ -12856,7 +13134,7 @@ var EstimateCreativeVariantInput = EstimateCreativeVariantInputBase.superRefine(
|
|
|
12856
13134
|
if (input.kind !== "landing") {
|
|
12857
13135
|
if (input.sectionKey !== void 0) {
|
|
12858
13136
|
ctx.addIssue({
|
|
12859
|
-
code:
|
|
13137
|
+
code: z51.ZodIssueCode.custom,
|
|
12860
13138
|
path: [
|
|
12861
13139
|
"sectionKey"
|
|
12862
13140
|
],
|
|
@@ -12865,7 +13143,7 @@ var EstimateCreativeVariantInput = EstimateCreativeVariantInputBase.superRefine(
|
|
|
12865
13143
|
}
|
|
12866
13144
|
if (input.instructions !== void 0) {
|
|
12867
13145
|
ctx.addIssue({
|
|
12868
|
-
code:
|
|
13146
|
+
code: z51.ZodIssueCode.custom,
|
|
12869
13147
|
path: [
|
|
12870
13148
|
"instructions"
|
|
12871
13149
|
],
|
|
@@ -12874,31 +13152,31 @@ var EstimateCreativeVariantInput = EstimateCreativeVariantInputBase.superRefine(
|
|
|
12874
13152
|
}
|
|
12875
13153
|
}
|
|
12876
13154
|
});
|
|
12877
|
-
var CreateCreativeVariantInputBase =
|
|
12878
|
-
sourceTaskId:
|
|
13155
|
+
var CreateCreativeVariantInputBase = z51.object({
|
|
13156
|
+
sourceTaskId: z51.string().uuid().describe("ID de la tarea original completada."),
|
|
12879
13157
|
kind: CreativeVariantKindEnum,
|
|
12880
|
-
confirm:
|
|
12881
|
-
clientRequestId:
|
|
12882
|
-
angle:
|
|
12883
|
-
buyerPersona:
|
|
12884
|
-
scriptNotes:
|
|
12885
|
-
creativeDirection:
|
|
13158
|
+
confirm: z51.literal(true).describe("Confirmaci\xF3n expl\xEDcita del usuario despu\xE9s de mostrarle el estimado de cr\xE9ditos."),
|
|
13159
|
+
clientRequestId: z51.string().uuid().describe("UUID estable y obligatorio para esta intenci\xF3n. Reutil\xEDzalo ante timeouts o transporte ambiguo. Si la API devuelve status=error, una nueva generaci\xF3n exige otra confirmaci\xF3n y un UUID nuevo."),
|
|
13160
|
+
angle: z51.string().trim().min(1).max(500).optional().describe("\xC1ngulo nuevo para video o imagen."),
|
|
13161
|
+
buyerPersona: z51.string().trim().min(1).max(360).optional().describe("Buyer persona coherente con el \xE1ngulo."),
|
|
13162
|
+
scriptNotes: z51.string().trim().min(1).max(2e3).optional().describe("Direcci\xF3n breve del guion para una variante de video."),
|
|
13163
|
+
creativeDirection: z51.string().trim().min(1).max(2e3).optional().describe("Direcci\xF3n creativa para una variante de imagen."),
|
|
12886
13164
|
videoPreset: TaskCreativeVariantVideoPresetEnum.optional().describe("Opcional para video. Preset p\xFAblico; el servidor resuelve modelo/tier desde el task origen."),
|
|
12887
13165
|
videoResolution: VideoResolutionEnum.optional().describe("Opcional para video."),
|
|
12888
|
-
generateAiMusic:
|
|
12889
|
-
nVariants:
|
|
13166
|
+
generateAiMusic: z51.boolean().optional().describe("Opcional para video."),
|
|
13167
|
+
nVariants: z51.number().int().min(1).max(3).optional().describe("Opcional para imagen. N\xFAmero de im\xE1genes adicionales (1-3)."),
|
|
12890
13168
|
imageStyle: ImageCreativeStyleEnum.optional().describe("Opcional para imagen."),
|
|
12891
13169
|
creativeArchetype: ImageCreativeArchetypeEnum.optional().describe("Opcional para imagen."),
|
|
12892
13170
|
visualTreatment: ImageCreativeVisualTreatmentEnum.optional().describe("Opcional para imagen."),
|
|
12893
13171
|
aspectRatio: ImageCreativeAspectRatioEnum.optional().describe("Opcional para imagen."),
|
|
12894
|
-
styleMix:
|
|
13172
|
+
styleMix: z51.boolean().optional().describe("Opcional para imagen."),
|
|
12895
13173
|
sectionKey: LandingSectionKeyEnum.optional().describe("Opcional para landing. Si se omite, se clasifica con IA a partir de instructions."),
|
|
12896
|
-
instructions:
|
|
13174
|
+
instructions: z51.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.")
|
|
12897
13175
|
}).strict();
|
|
12898
13176
|
var CreateCreativeVariantInput = CreateCreativeVariantInputBase.superRefine((input, ctx) => {
|
|
12899
13177
|
if ((input.kind === "video" || input.kind === "image") && !input.angle) {
|
|
12900
13178
|
ctx.addIssue({
|
|
12901
|
-
code:
|
|
13179
|
+
code: z51.ZodIssueCode.custom,
|
|
12902
13180
|
path: [
|
|
12903
13181
|
"angle"
|
|
12904
13182
|
],
|
|
@@ -12907,7 +13185,7 @@ var CreateCreativeVariantInput = CreateCreativeVariantInputBase.superRefine((inp
|
|
|
12907
13185
|
}
|
|
12908
13186
|
if (input.kind === "landing" && !input.sectionKey && !input.instructions) {
|
|
12909
13187
|
ctx.addIssue({
|
|
12910
|
-
code:
|
|
13188
|
+
code: z51.ZodIssueCode.custom,
|
|
12911
13189
|
path: [
|
|
12912
13190
|
"instructions"
|
|
12913
13191
|
],
|
|
@@ -12953,7 +13231,7 @@ var CreateCreativeVariantInput = CreateCreativeVariantInputBase.superRefine((inp
|
|
|
12953
13231
|
for (const field of fieldsByKind[input.kind]) {
|
|
12954
13232
|
if (input[field] !== void 0) {
|
|
12955
13233
|
ctx.addIssue({
|
|
12956
|
-
code:
|
|
13234
|
+
code: z51.ZodIssueCode.custom,
|
|
12957
13235
|
path: [
|
|
12958
13236
|
field
|
|
12959
13237
|
],
|
|
@@ -12987,7 +13265,7 @@ function estimatePayload(input) {
|
|
|
12987
13265
|
}
|
|
12988
13266
|
__name(estimatePayload, "estimatePayload");
|
|
12989
13267
|
function registerCreativeVariantTools(server, api) {
|
|
12990
|
-
server.tool("estimate_creative_variant", "Calcula el costo de crear una variante desde una tarea existente, sin consumir cr\xE9ditos. Usa el task origen para resolver el modelo/tier real y devuelve m\xEDnimo, m\xE1ximo, saldo y si alcanza. Para landing env\xEDa las mismas instructions/sectionKey que usar\xE1s al crear: si hay instructions sin sectionKey, el rango incluye la clasificaci\xF3n pagada con IA. Llama esto antes de create_creative_variant y muestra el rango al usuario.", EstimateCreativeVariantInputBase.shape, async (input) => {
|
|
13268
|
+
server.tool("estimate_creative_variant", "Calcula el costo de crear una variante desde una tarea existente, sin consumir cr\xE9ditos. Usa el task origen para resolver el modelo/tier real y devuelve m\xEDnimo, m\xE1ximo, saldo y si alcanza. Para landing env\xEDa las mismas instructions/sectionKey que usar\xE1s al crear: si hay instructions sin sectionKey, el rango incluye la clasificaci\xF3n pagada con IA. Llama esto antes de create_creative_variant y muestra el rango al usuario.", EstimateCreativeVariantInputBase.shape, READS_INTERNAL, async (input) => {
|
|
12991
13269
|
try {
|
|
12992
13270
|
const validatedInput = parseVariantInput(EstimateCreativeVariantInput, input);
|
|
12993
13271
|
const estimate = await api.postJson(`/v1/tasks/${encodeURIComponent(validatedInput.sourceTaskId)}/creative-variants/estimate`, estimatePayload(validatedInput));
|
|
@@ -13000,7 +13278,7 @@ function registerCreativeVariantTools(server, api) {
|
|
|
13000
13278
|
throw translateError(error);
|
|
13001
13279
|
}
|
|
13002
13280
|
});
|
|
13003
|
-
server.tool("create_creative_variant", "Crea un video, imagen o secci\xF3n de landing adicional dentro de una tarea existente. ES UNA ACCI\xD3N DE PAGO: primero llama estimate_creative_variant, muestra el rango de cr\xE9ditos al usuario y solo despu\xE9s invoca este tool con confirm=true. Las recomendaciones IA tambi\xE9n son acciones pagas a 2x del costo real. clientRequestId es obligatorio: genera un UUID por intenci\xF3n y reutiliza el mismo ante timeouts o transporte ambiguo. Si recibes status=error, pide confirmaci\xF3n para un intento nuevo y usa otro UUID. Reutiliza el producto y referencias del task origen; crea un child task independiente y devuelve variantTaskId. Para landing puedes enviar instructions en texto libre y omitir sectionKey: el backend lo clasifica con IA. No expone ni permite cambiar videoModel, videoTier, videoStyle o narrativeStyle; esos valores se heredan del task origen.", CreateCreativeVariantInputBase.shape, async (input) => {
|
|
13281
|
+
server.tool("create_creative_variant", "Crea un video, imagen o secci\xF3n de landing adicional dentro de una tarea existente. ES UNA ACCI\xD3N DE PAGO: primero llama estimate_creative_variant, muestra el rango de cr\xE9ditos al usuario y solo despu\xE9s invoca este tool con confirm=true. Las recomendaciones IA tambi\xE9n son acciones pagas a 2x del costo real. clientRequestId es obligatorio: genera un UUID por intenci\xF3n y reutiliza el mismo ante timeouts o transporte ambiguo. Si recibes status=error, pide confirmaci\xF3n para un intento nuevo y usa otro UUID. Reutiliza el producto y referencias del task origen; crea un child task independiente y devuelve variantTaskId. Para landing puedes enviar instructions en texto libre y omitir sectionKey: el backend lo clasifica con IA. No expone ni permite cambiar videoModel, videoTier, videoStyle o narrativeStyle; esos valores se heredan del task origen.", CreateCreativeVariantInputBase.shape, CREATES_EXTERNAL, async (input) => {
|
|
13004
13282
|
try {
|
|
13005
13283
|
const validatedInput = parseVariantInput(CreateCreativeVariantInput, input);
|
|
13006
13284
|
const estimateInput = EstimateCreativeVariantInput.parse({
|
|
@@ -13070,31 +13348,31 @@ function registerCreativeVariantTools(server, api) {
|
|
|
13070
13348
|
__name(registerCreativeVariantTools, "registerCreativeVariantTools");
|
|
13071
13349
|
|
|
13072
13350
|
// src/tools/script-review.ts
|
|
13073
|
-
import { z as
|
|
13074
|
-
var GetVideoScriptInput =
|
|
13075
|
-
taskId:
|
|
13076
|
-
angleKey:
|
|
13351
|
+
import { z as z52 } from "zod";
|
|
13352
|
+
var GetVideoScriptInput = z52.object({
|
|
13353
|
+
taskId: z52.string().min(1).describe("ID de la tarea (la misma que devolvi\xF3 create_product_task / generate_video_creative)."),
|
|
13354
|
+
angleKey: z52.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.')
|
|
13077
13355
|
}).strict();
|
|
13078
13356
|
var VIDEO_SCRIPT_APPROVAL_MAX_SCENES2 = 50;
|
|
13079
13357
|
var VIDEO_SCRIPT_APPROVAL_MAX_DIALOGUE_CHARS2 = 2e3;
|
|
13080
|
-
var ApproveSceneEdit =
|
|
13081
|
-
index:
|
|
13082
|
-
dialogue:
|
|
13358
|
+
var ApproveSceneEdit = z52.object({
|
|
13359
|
+
index: z52.number().int().nonnegative().describe("\xCDndice de la escena (0-based, mismo que devuelve get_video_script)."),
|
|
13360
|
+
dialogue: z52.string().min(1).max(VIDEO_SCRIPT_APPROVAL_MAX_DIALOGUE_CHARS2).refine((v) => v.trim().length > 0, {
|
|
13083
13361
|
message: "dialogue no puede ser s\xF3lo espacios"
|
|
13084
13362
|
}).describe("Di\xE1logo final de la escena. NO puede estar vac\xEDo ni ser s\xF3lo espacios. Puede ser el original sin cambios o la edici\xF3n del usuario.")
|
|
13085
13363
|
}).strict();
|
|
13086
|
-
var ApproveVideoScriptInput =
|
|
13087
|
-
taskId:
|
|
13088
|
-
angleKey:
|
|
13089
|
-
scenes:
|
|
13364
|
+
var ApproveVideoScriptInput = z52.object({
|
|
13365
|
+
taskId: z52.string().min(1).describe("ID de la tarea."),
|
|
13366
|
+
angleKey: z52.string().trim().min(1).max(120).optional().describe('Mismo angleKey usado en get_video_script. REQUERIDO en multi-angle (si se omite el backend resuelve a "angle_0" literal). En single-angle puede omitirse.'),
|
|
13367
|
+
scenes: z52.array(ApproveSceneEdit).min(1).max(VIDEO_SCRIPT_APPROVAL_MAX_SCENES2).describe("Escenas con los di\xE1logos finales. El backend mergea por `index` \u2014 las escenas NO incluidas conservan el di\xE1logo original. Debe haber entre 1 y 50 entradas. Para cada entrada que mandes con un dialogue distinto del original, el backend valida minWords \u2264 words \u2264 maxWords y rechaza con 400 si te sales del rango.")
|
|
13090
13368
|
}).strict();
|
|
13091
|
-
var RegenerateVideoScriptInput =
|
|
13092
|
-
taskId:
|
|
13093
|
-
angleKey:
|
|
13369
|
+
var RegenerateVideoScriptInput = z52.object({
|
|
13370
|
+
taskId: z52.string().min(1).describe("ID de la tarea."),
|
|
13371
|
+
angleKey: z52.string().min(1).describe('Clave del \xE1ngulo a regenerar. Single-angle: pasa "angle_0". Multi-angle: el mismo que devuelve get_video_script. NO se puede omitir \u2014 el backend lo exige siempre.'),
|
|
13094
13372
|
narrativeStyle: NarrativeStyleEnum.describe('Narrativa para el nuevo guion. Incluye valores hist\xF3ricos porque esta herramienta tambi\xE9n regenera tareas antiguas. REQUERIDO \u2014 el backend NO acepta "mantener la actual"; siempre tienes que elegir una. Si el usuario quiere re-rolear con la misma narrativa, pasa la que ya tiene el \xE1ngulo (la ves en get_video_script o en get_task_status).')
|
|
13095
13373
|
}).strict();
|
|
13096
13374
|
function registerScriptReviewTools(server, api) {
|
|
13097
|
-
server.tool("get_video_script", 'Lee el guion del video pendiente de revisi\xF3n. S\xF3lo funciona cuando la tarea fue creada con `pauseForVideoScriptReview: true` (via generate_video_creative). Devuelve { status, angleKey, perClipSeconds?, scenes: [{ index, dialogue, maxWords, minWords?, beatId?, persuasiveFunction?, winnerVisual?, expectedVisual? }], error? }. En escenas copiadas de un winner, `persuasiveFunction` explica qu\xE9 logra ese beat, `winnerVisual` conserva la observaci\xF3n fuente y `expectedVisual` describe nuestra adaptaci\xF3n localizada; muestra los tres junto al di\xE1logo para que una edici\xF3n no desaline\xE9 guion y visual. El campo `error` (cuando viene con status="ready") es el motivo legible por el que un intento previo de aprobaci\xF3n fue rechazado (p.ej. el guion no cab\xEDa en el tiempo y el \xE1ngulo volvi\xF3 a "ready"); mu\xE9straselo al usuario.\n\nCU\xC1NDO LLAMAR: no existe un status top-level "awaiting_script_review" en get_task_status. Si la tarea fue creada con pauseForVideoScriptReview=true, polea get_task_status hasta que `status === "processing"` y a partir de ah\xED llama este tool peri\xF3dicamente \u2014 la respuesta de get_video_script trae su propio campo `status` que es la se\xF1al real:\n- "ready" \u2192 el guion est\xE1 listo; mu\xE9strale al usuario cada `scenes[].dialogue` por `index` con su tope `maxWords`, recoge ediciones, llama approve_video_script.\n- "not_ready" \u2192 el guion a\xFAn se est\xE1 generando; espera 5\u201310s y reintenta este tool.\n- "approved" \u2192 ya fue aprobado; en multi-angle puede ser que ese \xE1ngulo ya est\xE9 hecho \u2014 vuelve a llamar con otro angleKey.\n- "skipped" \u2192 el usuario opt\xF3 por no revisar en una pausa previa.\n- "error" \u2192 el backend no pudo generar el guion; usa `regenerate_video_script` para re-intentar (es la ruta de recuperaci\xF3n v\xE1lida \u2014 el guion no se aprueba en estado "error").\n\nMULTI-ANGLE: si la tarea tiene varios \xE1ngulos, omitir angleKey siempre devuelve "angle_0" literal \u2014 itera expl\xEDcitamente por "angle_0", "angle_1", ... hasta encontrar uno con status="ready".', GetVideoScriptInput.shape, async (input) => {
|
|
13375
|
+
server.tool("get_video_script", 'Lee el guion del video pendiente de revisi\xF3n. S\xF3lo funciona cuando la tarea fue creada con `pauseForVideoScriptReview: true` (via generate_video_creative). Devuelve { status, angleKey, perClipSeconds?, scenes: [{ index, dialogue, maxWords, minWords?, beatId?, persuasiveFunction?, winnerVisual?, expectedVisual? }], error? }. En escenas copiadas de un winner, `persuasiveFunction` explica qu\xE9 logra ese beat, `winnerVisual` conserva la observaci\xF3n fuente y `expectedVisual` describe nuestra adaptaci\xF3n localizada; muestra los tres junto al di\xE1logo para que una edici\xF3n no desaline\xE9 guion y visual. El campo `error` (cuando viene con status="ready") es el motivo legible por el que un intento previo de aprobaci\xF3n fue rechazado (p.ej. el guion no cab\xEDa en el tiempo y el \xE1ngulo volvi\xF3 a "ready"); mu\xE9straselo al usuario.\n\nCU\xC1NDO LLAMAR: no existe un status top-level "awaiting_script_review" en get_task_status. Si la tarea fue creada con pauseForVideoScriptReview=true, polea get_task_status hasta que `status === "processing"` y a partir de ah\xED llama este tool peri\xF3dicamente \u2014 la respuesta de get_video_script trae su propio campo `status` que es la se\xF1al real:\n- "ready" \u2192 el guion est\xE1 listo; mu\xE9strale al usuario cada `scenes[].dialogue` por `index` con su tope `maxWords`, recoge ediciones, llama approve_video_script.\n- "not_ready" \u2192 el guion a\xFAn se est\xE1 generando; espera 5\u201310s y reintenta este tool.\n- "approved" \u2192 ya fue aprobado; en multi-angle puede ser que ese \xE1ngulo ya est\xE9 hecho \u2014 vuelve a llamar con otro angleKey.\n- "skipped" \u2192 el usuario opt\xF3 por no revisar en una pausa previa.\n- "error" \u2192 el backend no pudo generar el guion; usa `regenerate_video_script` para re-intentar (es la ruta de recuperaci\xF3n v\xE1lida \u2014 el guion no se aprueba en estado "error").\n\nMULTI-ANGLE: si la tarea tiene varios \xE1ngulos, omitir angleKey siempre devuelve "angle_0" literal \u2014 itera expl\xEDcitamente por "angle_0", "angle_1", ... hasta encontrar uno con status="ready".', GetVideoScriptInput.shape, READS_INTERNAL, async (input) => {
|
|
13098
13376
|
try {
|
|
13099
13377
|
const qs = input.angleKey ? `?angleKey=${encodeURIComponent(input.angleKey)}` : "";
|
|
13100
13378
|
const r = await api.getJson(`/v1/tasks/${encodeURIComponent(input.taskId)}/video-script${qs}`);
|
|
@@ -13103,7 +13381,7 @@ function registerScriptReviewTools(server, api) {
|
|
|
13103
13381
|
throw translateError(e);
|
|
13104
13382
|
}
|
|
13105
13383
|
});
|
|
13106
|
-
server.tool("regenerate_video_script", 'Re-rolea (regenera) el guion del video pendiente de revisi\xF3n. \xDAsalo cuando:\n- get_video_script devuelve status="error" (el LLM fall\xF3 al redactar).\n- El usuario rechaza el guion entero y prefiere reintentar (no editar l\xEDnea por l\xEDnea).\n- Quieres probar otra narrativa (pasa `narrativeStyle`).\n\nFlujo: llama este tool \u2192 polea get_video_script hasta que vuelva a status="ready" \u2192 mu\xE9strale el nuevo guion al usuario \u2192 approve_video_script.\n\nIMPORTANTE: regenerar CUESTA cr\xE9ditos de LLM. No lo invoques sin confirmaci\xF3n del usuario salvo que el status sea "error" (sin retry el usuario no tiene salida).\n\nMULTI-ANGLE: pasa el mismo `angleKey` que usaste con get_video_script.', RegenerateVideoScriptInput.shape, async (input) => {
|
|
13384
|
+
server.tool("regenerate_video_script", 'Re-rolea (regenera) el guion del video pendiente de revisi\xF3n. \xDAsalo cuando:\n- get_video_script devuelve status="error" (el LLM fall\xF3 al redactar).\n- El usuario rechaza el guion entero y prefiere reintentar (no editar l\xEDnea por l\xEDnea).\n- Quieres probar otra narrativa (pasa `narrativeStyle`).\n\nFlujo: llama este tool \u2192 polea get_video_script hasta que vuelva a status="ready" \u2192 mu\xE9strale el nuevo guion al usuario \u2192 approve_video_script.\n\nIMPORTANTE: regenerar CUESTA cr\xE9ditos de LLM. No lo invoques sin confirmaci\xF3n del usuario salvo que el status sea "error" (sin retry el usuario no tiene salida).\n\nMULTI-ANGLE: pasa el mismo `angleKey` que usaste con get_video_script.', RegenerateVideoScriptInput.shape, OVERWRITES_EXTERNAL, async (input) => {
|
|
13107
13385
|
try {
|
|
13108
13386
|
const r = await api.postJson(`/v1/tasks/${encodeURIComponent(input.taskId)}/video-script/regenerate`, {
|
|
13109
13387
|
angleKey: input.angleKey,
|
|
@@ -13114,25 +13392,36 @@ function registerScriptReviewTools(server, api) {
|
|
|
13114
13392
|
throw translateError(e);
|
|
13115
13393
|
}
|
|
13116
13394
|
});
|
|
13117
|
-
server.tool(
|
|
13118
|
-
|
|
13119
|
-
|
|
13120
|
-
|
|
13121
|
-
|
|
13122
|
-
|
|
13123
|
-
|
|
13124
|
-
|
|
13125
|
-
|
|
13126
|
-
|
|
13395
|
+
server.tool(
|
|
13396
|
+
"approve_video_script",
|
|
13397
|
+
'Aprueba el guion del video (con o sin ediciones). Tras llamarlo, el render arranca y consume cr\xE9ditos.\n\nREGLAS DURAS:\n1. INCLUYE EN scenes[] S\xD3LO las escenas que cambian. El backend mergea por `index` \u2014 las escenas no incluidas conservan su di\xE1logo original. scenes vac\xEDo es rechazado con 400, as\xED que si el usuario no quiere editar nada, manda AL MENOS UNA escena con su `dialogue` original (ser\xE1 un no-op sem\xE1ntico pero satisface el min(1)).\n2. Para cada entrada incluida cuyo `dialogue` difiera del original, el backend valida `minWords \u2264 words(dialogue) \u2264 maxWords` (los rangos vienen en get_video_script). Si te sales del rango, rechaza con 400 \u2014 NO recortes en silencio; p\xEDdele al usuario que ajuste.\n3. Escenas con LLM-original ya fuera de rango: el backend NO valida el rango POR-ESCENA si no las incluyes (la regla #2 s\xF3lo aplica a escenas editadas).\n4. TOPE GLOBAL: adem\xE1s del rango por-escena, el backend rechaza con 400 ("El guion no cabe en el tiempo del video. Ac\xF3rtalo y vuelve a aprobar.") cuando el TOTAL de palabras de TODO el guion (incluidas las escenas que no editaste) supera el cupo de la voz \u2014 porque ese guion se pasar\xEDa del tope de duraci\xF3n al renderizar. Si lo ves, acorta las escenas m\xE1s largas (la suma de `maxWords` de get_video_script es el techo total) y reintenta; o usa regenerate_video_script. Este 400 es s\xEDncrono y NO consume render.\n5. NUNCA inventes ni reescribas el guion por tu cuenta. Aplica los cambios del usuario al pie de la letra dentro de los l\xEDmites de palabras.\n6. MULTI-ANGLE: pasa el mismo `angleKey` que usaste en get_video_script. Si lo omites, el backend resuelve a "angle_0" literal \u2014 puedes aprobar un \xE1ngulo equivocado.\n\nDevuelve { status }. status="approved" confirma la aprobaci\xF3n; otro valor significa que la pausa sigue activa.',
|
|
13398
|
+
ApproveVideoScriptInput.shape,
|
|
13399
|
+
// Not CREATES_INTERNAL: approving the script is what STARTS the render, so
|
|
13400
|
+
// this call dispatches jobs to the video providers and burns credits. Its own
|
|
13401
|
+
// description says so ("Tras llamarlo, el render arranca y consume créditos").
|
|
13402
|
+
// A host that decides when to interrupt the user by `destructiveHint ||
|
|
13403
|
+
// openWorldHint` would otherwise auto-invoke the one call that spends money.
|
|
13404
|
+
CREATES_EXTERNAL,
|
|
13405
|
+
async (input) => {
|
|
13406
|
+
try {
|
|
13407
|
+
const body = {
|
|
13408
|
+
scenes: input.scenes
|
|
13409
|
+
};
|
|
13410
|
+
if (input.angleKey) body.angleKey = input.angleKey;
|
|
13411
|
+
const r = await api.postJson(`/v1/tasks/${encodeURIComponent(input.taskId)}/video-script/approve`, body);
|
|
13412
|
+
return asText(r);
|
|
13413
|
+
} catch (e) {
|
|
13414
|
+
throw translateError(e);
|
|
13415
|
+
}
|
|
13127
13416
|
}
|
|
13128
|
-
|
|
13417
|
+
);
|
|
13129
13418
|
}
|
|
13130
13419
|
__name(registerScriptReviewTools, "registerScriptReviewTools");
|
|
13131
13420
|
|
|
13132
13421
|
// src/tools/connections.ts
|
|
13133
|
-
import { z as
|
|
13422
|
+
import { z as z53 } from "zod";
|
|
13134
13423
|
function registerConnectionTools(server, api) {
|
|
13135
|
-
server.tool("list_shopify_shops", "Lista las tiendas Shopify conectadas por este usuario. Si la lista est\xE1 vac\xEDa, dile al usuario que conecte una tienda en productmaker.app/integrations antes de intentar publicar.",
|
|
13424
|
+
server.tool("list_shopify_shops", "Lista las tiendas Shopify conectadas por este usuario. Si la lista est\xE1 vac\xEDa, dile al usuario que conecte una tienda en productmaker.app/integrations antes de intentar publicar.", z53.object({}).shape, READS_INTERNAL, async () => {
|
|
13136
13425
|
try {
|
|
13137
13426
|
const r = await api.getJson("/v1/shopify/shops");
|
|
13138
13427
|
return asText(r);
|
|
@@ -13140,11 +13429,11 @@ function registerConnectionTools(server, api) {
|
|
|
13140
13429
|
throw translateError(e);
|
|
13141
13430
|
}
|
|
13142
13431
|
});
|
|
13143
|
-
server.tool("list_shopify_products", 'Busca productos en una tienda Shopify conectada para elegir el target de una republicaci\xF3n. 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.',
|
|
13144
|
-
shopDomain:
|
|
13145
|
-
query:
|
|
13146
|
-
taskId:
|
|
13147
|
-
}).shape, async ({ shopDomain, query, taskId }) => {
|
|
13432
|
+
server.tool("list_shopify_products", 'Busca productos en una tienda Shopify conectada para elegir el target de una republicaci\xF3n. 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.', z53.object({
|
|
13433
|
+
shopDomain: z53.string().min(3).max(120),
|
|
13434
|
+
query: z53.string().trim().max(120).optional(),
|
|
13435
|
+
taskId: z53.string().uuid().optional()
|
|
13436
|
+
}).shape, READS_EXTERNAL, async ({ shopDomain, query, taskId }) => {
|
|
13148
13437
|
try {
|
|
13149
13438
|
const params = new URLSearchParams({
|
|
13150
13439
|
shopDomain
|
|
@@ -13157,7 +13446,7 @@ function registerConnectionTools(server, api) {
|
|
|
13157
13446
|
throw translateError(e);
|
|
13158
13447
|
}
|
|
13159
13448
|
});
|
|
13160
|
-
server.tool("list_meta_ad_accounts", "Lista las cuentas publicitarias de Meta conectadas. 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.",
|
|
13449
|
+
server.tool("list_meta_ad_accounts", "Lista las cuentas publicitarias de Meta conectadas. 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.", z53.object({}).shape, READS_EXTERNAL, async () => {
|
|
13161
13450
|
try {
|
|
13162
13451
|
const r = await api.getJson("/v1/facebook/ad-accounts");
|
|
13163
13452
|
return asText(r);
|
|
@@ -13167,7 +13456,8 @@ function registerConnectionTools(server, api) {
|
|
|
13167
13456
|
});
|
|
13168
13457
|
server.registerTool("list_meta_destinations", {
|
|
13169
13458
|
description: "Lista los destinos de publicaci\xF3n de Meta del usuario: Business portfolios, cuentas publicitarias dentro de cada portfolio y Facebook Pages disponibles. 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.",
|
|
13170
|
-
inputSchema:
|
|
13459
|
+
inputSchema: z53.object({}).shape,
|
|
13460
|
+
annotations: READS_EXTERNAL,
|
|
13171
13461
|
_meta: {
|
|
13172
13462
|
"openai/widgetAccessible": true
|
|
13173
13463
|
}
|
|
@@ -13179,7 +13469,7 @@ function registerConnectionTools(server, api) {
|
|
|
13179
13469
|
throw translateError(e);
|
|
13180
13470
|
}
|
|
13181
13471
|
});
|
|
13182
|
-
server.tool("list_tiktok_advertisers", "Lista las cuentas de TikTok Ads conectadas. Si est\xE1 vac\xEDa, ind\xEDcale al usuario conectar TikTok en productmaker.app/integrations.",
|
|
13472
|
+
server.tool("list_tiktok_advertisers", "Lista las cuentas de TikTok Ads conectadas. Si est\xE1 vac\xEDa, ind\xEDcale al usuario conectar TikTok en productmaker.app/integrations.", z53.object({}).shape, READS_EXTERNAL, async () => {
|
|
13183
13473
|
try {
|
|
13184
13474
|
const r = await api.getJson("/v1/tiktok/advertisers");
|
|
13185
13475
|
return asText(r);
|
|
@@ -13191,9 +13481,9 @@ function registerConnectionTools(server, api) {
|
|
|
13191
13481
|
__name(registerConnectionTools, "registerConnectionTools");
|
|
13192
13482
|
|
|
13193
13483
|
// src/tools/actors.ts
|
|
13194
|
-
import { z as
|
|
13484
|
+
import { z as z54 } from "zod";
|
|
13195
13485
|
function registerActorTools(server, api) {
|
|
13196
|
-
server.tool("list_actors", "Lista los personajes guardados del usuario (personas creadas con IA que pueden protagonizar los videos e im\xE1genes). Devuelve id, nombre, descripci\xF3n, g\xE9nero y voz. 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.",
|
|
13486
|
+
server.tool("list_actors", "Lista los personajes guardados del usuario (personas creadas con IA que pueden protagonizar los videos e im\xE1genes). Devuelve id, nombre, descripci\xF3n, g\xE9nero y voz. 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.", z54.object({}).shape, READS_INTERNAL, async () => {
|
|
13197
13487
|
try {
|
|
13198
13488
|
const rows = await api.getJson("/v1/actors");
|
|
13199
13489
|
const actors = (Array.isArray(rows) ? rows : []).filter((actor) => actor.status === "ready").map(({ id, name, description, gender, voiceId }) => ({
|
|
@@ -13214,116 +13504,145 @@ function registerActorTools(server, api) {
|
|
|
13214
13504
|
__name(registerActorTools, "registerActorTools");
|
|
13215
13505
|
|
|
13216
13506
|
// src/tools/publish.ts
|
|
13217
|
-
import { z as
|
|
13218
|
-
var ShopifyInput =
|
|
13219
|
-
taskId:
|
|
13220
|
-
shopDomain:
|
|
13507
|
+
import { z as z55 } from "zod";
|
|
13508
|
+
var ShopifyInput = z55.object({
|
|
13509
|
+
taskId: z55.string().min(1),
|
|
13510
|
+
shopDomain: z55.string().min(1),
|
|
13221
13511
|
/** Override: number of landing images to include. Default = ALL images the
|
|
13222
13512
|
* task generated (the MCP fetches /v1/results first to count). Bounded
|
|
13223
13513
|
* generously here; the backend validates each index against
|
|
13224
13514
|
* landing.images.length and rejects out-of-range. */
|
|
13225
|
-
numLandingImages:
|
|
13226
|
-
variantIndex:
|
|
13515
|
+
numLandingImages: z55.number().int().min(1).max(20).optional(),
|
|
13516
|
+
variantIndex: z55.number().int().min(0).optional(),
|
|
13227
13517
|
/** ASK THE USER before setting: "¿Tu producto tiene variantes (talla, color, etc.)?".
|
|
13228
13518
|
* When true, the landing renders variant-picker pills; when false, it ships
|
|
13229
13519
|
* a single-option product page. Defaults to false. */
|
|
13230
|
-
showVariantPicker:
|
|
13520
|
+
showVariantPicker: z55.boolean().optional(),
|
|
13231
13521
|
/** Default `auto` updates the product previously published for this task and
|
|
13232
13522
|
* shop. Use `existing` with targetProductId to choose another product, or
|
|
13233
13523
|
* `new` only when the user explicitly wants a duplicate/new listing. */
|
|
13234
|
-
publicationMode:
|
|
13524
|
+
publicationMode: z55.enum([
|
|
13235
13525
|
"auto",
|
|
13236
13526
|
"existing",
|
|
13237
13527
|
"new"
|
|
13238
13528
|
]).optional(),
|
|
13239
13529
|
/** Shopify product GID, numeric id, or admin product URL. Required when
|
|
13240
13530
|
* publicationMode is `existing`. */
|
|
13241
|
-
targetProductId:
|
|
13531
|
+
targetProductId: z55.string().trim().min(1).max(300).optional(),
|
|
13532
|
+
/** Add an announcement bar text above add-to-cart (optional). */
|
|
13533
|
+
announcementBarEnabled: z55.boolean().optional(),
|
|
13534
|
+
announcementBarText: z55.string().trim().min(1).max(SHOPIFY_ANNOUNCEMENT_BAR_MAX_LENGTH).optional()
|
|
13242
13535
|
});
|
|
13243
|
-
var
|
|
13536
|
+
var ShopifyInputWithAnnouncementBarValidation = ShopifyInput.superRefine((value, ctx) => {
|
|
13537
|
+
if (value.announcementBarEnabled && !value.announcementBarText) {
|
|
13538
|
+
ctx.addIssue({
|
|
13539
|
+
code: z55.ZodIssueCode.custom,
|
|
13540
|
+
path: [
|
|
13541
|
+
"announcementBarText"
|
|
13542
|
+
],
|
|
13543
|
+
message: "announcementBarText is required when announcementBarEnabled is true"
|
|
13544
|
+
});
|
|
13545
|
+
}
|
|
13546
|
+
if (!value.announcementBarEnabled && value.announcementBarText) {
|
|
13547
|
+
ctx.addIssue({
|
|
13548
|
+
code: z55.ZodIssueCode.custom,
|
|
13549
|
+
path: [
|
|
13550
|
+
"announcementBarText"
|
|
13551
|
+
],
|
|
13552
|
+
message: "announcementBarText requires announcementBarEnabled to be true"
|
|
13553
|
+
});
|
|
13554
|
+
}
|
|
13555
|
+
});
|
|
13556
|
+
function parseShopifyInput(rawInput) {
|
|
13557
|
+
const parsed = ShopifyInputWithAnnouncementBarValidation.safeParse(rawInput);
|
|
13558
|
+
if (!parsed.success) throw invalidInputFromZod(parsed.error);
|
|
13559
|
+
return parsed.data;
|
|
13560
|
+
}
|
|
13561
|
+
__name(parseShopifyInput, "parseShopifyInput");
|
|
13562
|
+
var McpCampaignConfig = z55.object({
|
|
13244
13563
|
/** ALWAYS in US dollars, whatever the ad account's currency is. ProductMaker
|
|
13245
13564
|
* converts to the account currency server-side. Do NOT pre-convert: on a COP
|
|
13246
13565
|
* account the user's "60.000 al día" is `dailyBudgetUsd: 15`, not 60000. */
|
|
13247
|
-
dailyBudgetUsd:
|
|
13566
|
+
dailyBudgetUsd: z55.number().positive().optional(),
|
|
13248
13567
|
/** ISO-3166-1 alpha-2 country codes. Meta accepts max 1 (single-country
|
|
13249
13568
|
* ad set). TikTok takes only the first entry. */
|
|
13250
|
-
targetCountries:
|
|
13251
|
-
campaignName:
|
|
13569
|
+
targetCountries: z55.array(z55.string().length(2)).max(1).optional(),
|
|
13570
|
+
campaignName: z55.string().trim().min(1).max(100).optional()
|
|
13252
13571
|
});
|
|
13253
13572
|
var McpAdAccountId = MetaAdAccountId.describe('Meta ad account id. Accepts "act_1234567890" or the bare numeric id "1234567890".');
|
|
13254
|
-
var MetaInput =
|
|
13255
|
-
taskId:
|
|
13573
|
+
var MetaInput = z55.object({
|
|
13574
|
+
taskId: z55.string().min(1),
|
|
13256
13575
|
/** Exact Meta Business portfolio selected from `list_meta_destinations`. */
|
|
13257
|
-
portfolioId:
|
|
13576
|
+
portfolioId: z55.string().trim().min(1),
|
|
13258
13577
|
/** Exact Facebook Page selected from `list_meta_destinations`. */
|
|
13259
|
-
pageId:
|
|
13578
|
+
pageId: z55.string().trim().min(1),
|
|
13260
13579
|
adAccountId: McpAdAccountId,
|
|
13261
|
-
pixelId:
|
|
13262
|
-
landingUrl:
|
|
13580
|
+
pixelId: z55.string().min(1).optional(),
|
|
13581
|
+
landingUrl: z55.string().url().optional(),
|
|
13263
13582
|
/** Select which angles to publish by 0-based index. Omit to publish all
|
|
13264
13583
|
* successful angles. Example: a task with 3 angles → `[0,2]` publishes #0 + #2. */
|
|
13265
|
-
angleIndexes:
|
|
13584
|
+
angleIndexes: z55.array(z55.number().int().nonnegative()).optional(),
|
|
13266
13585
|
campaignConfig: McpCampaignConfig.optional(),
|
|
13267
13586
|
/** Override the ad's primary text (body copy). If omitted, ProductMaker
|
|
13268
13587
|
* uses the auto-generated copy attached to the task. Capped at 500 chars
|
|
13269
13588
|
* (Meta's primary-text limit). */
|
|
13270
|
-
primaryText:
|
|
13589
|
+
primaryText: z55.string().trim().min(1).max(500).optional(),
|
|
13271
13590
|
/** Optional WhatsApp number. When set, the ad button opens a WhatsApp chat
|
|
13272
13591
|
* (wa.me) instead of a landing page. */
|
|
13273
|
-
phoneNumber:
|
|
13592
|
+
phoneNumber: z55.string().min(8).max(32).optional()
|
|
13274
13593
|
});
|
|
13275
|
-
var GroupedTaskSource =
|
|
13276
|
-
type:
|
|
13277
|
-
taskId:
|
|
13278
|
-
angleIndex:
|
|
13279
|
-
videoVariant:
|
|
13594
|
+
var GroupedTaskSource = z55.object({
|
|
13595
|
+
type: z55.literal("task"),
|
|
13596
|
+
taskId: z55.string().min(1),
|
|
13597
|
+
angleIndex: z55.number().int().min(0).optional(),
|
|
13598
|
+
videoVariant: z55.enum([
|
|
13280
13599
|
"with_subtitles",
|
|
13281
13600
|
"without_subtitles"
|
|
13282
13601
|
]).optional(),
|
|
13283
|
-
imageVariantIndex:
|
|
13284
|
-
});
|
|
13285
|
-
var GroupedMediaSource =
|
|
13286
|
-
type:
|
|
13287
|
-
url:
|
|
13288
|
-
path:
|
|
13289
|
-
thumbnailUrl:
|
|
13290
|
-
thumbnailPath:
|
|
13602
|
+
imageVariantIndex: z55.number().int().min(0).optional()
|
|
13603
|
+
});
|
|
13604
|
+
var GroupedMediaSource = z55.object({
|
|
13605
|
+
type: z55.literal("media"),
|
|
13606
|
+
url: z55.string().url().optional().describe("Public HTTPS media URL. For a file on the user laptop, use request_media_upload on the hosted MCP instead."),
|
|
13607
|
+
path: z55.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".'),
|
|
13608
|
+
thumbnailUrl: z55.string().url().optional(),
|
|
13609
|
+
thumbnailPath: z55.string().min(1).optional()
|
|
13291
13610
|
}).refine((value) => Boolean(value.url) !== Boolean(value.path), {
|
|
13292
13611
|
message: "media source needs exactly one of `url` or `path`"
|
|
13293
13612
|
}).refine((value) => !(value.thumbnailUrl && value.thumbnailPath), {
|
|
13294
13613
|
message: "media source needs at most one of `thumbnailUrl` or `thumbnailPath`"
|
|
13295
13614
|
});
|
|
13296
|
-
var GroupedUploadedAssetSource =
|
|
13297
|
-
type:
|
|
13615
|
+
var GroupedUploadedAssetSource = z55.object({
|
|
13616
|
+
type: z55.literal("asset"),
|
|
13298
13617
|
/** Returned by check_media_upload. ProductMaker resolves ownership server-side. */
|
|
13299
|
-
assetId:
|
|
13300
|
-
thumbnailAssetId:
|
|
13618
|
+
assetId: z55.string().uuid(),
|
|
13619
|
+
thumbnailAssetId: z55.string().uuid().optional()
|
|
13301
13620
|
});
|
|
13302
|
-
var GroupedCreative =
|
|
13303
|
-
type:
|
|
13621
|
+
var GroupedCreative = z55.object({
|
|
13622
|
+
type: z55.enum([
|
|
13304
13623
|
"image",
|
|
13305
13624
|
"video"
|
|
13306
13625
|
]),
|
|
13307
|
-
source:
|
|
13626
|
+
source: z55.union([
|
|
13308
13627
|
GroupedTaskSource,
|
|
13309
13628
|
GroupedMediaSource,
|
|
13310
13629
|
GroupedUploadedAssetSource
|
|
13311
13630
|
]),
|
|
13312
|
-
title:
|
|
13313
|
-
description:
|
|
13631
|
+
title: z55.string().max(120).optional(),
|
|
13632
|
+
description: z55.string().max(500).optional(),
|
|
13314
13633
|
cta: MetaCtaType.optional(),
|
|
13315
|
-
creativeId:
|
|
13634
|
+
creativeId: z55.string().min(1).optional()
|
|
13316
13635
|
});
|
|
13317
|
-
var GroupedCampaignConfigBase =
|
|
13636
|
+
var GroupedCampaignConfigBase = z55.object({
|
|
13318
13637
|
campaignType: MetaCampaignType.default("sales"),
|
|
13319
13638
|
optimizationMode: MetaOptimizationMode.default("advantage_plus"),
|
|
13320
13639
|
budgetMode: MetaBudgetMode.optional(),
|
|
13321
|
-
campaignDailyBudget:
|
|
13322
|
-
adSets:
|
|
13323
|
-
tempId:
|
|
13324
|
-
name:
|
|
13325
|
-
dailyBudget:
|
|
13326
|
-
creativeIds:
|
|
13640
|
+
campaignDailyBudget: z55.number().int().positive().optional(),
|
|
13641
|
+
adSets: z55.array(z55.object({
|
|
13642
|
+
tempId: z55.string().min(1).max(80),
|
|
13643
|
+
name: z55.string().min(1).max(100),
|
|
13644
|
+
dailyBudget: z55.number().int().positive().optional(),
|
|
13645
|
+
creativeIds: z55.array(z55.string().min(1)).min(1)
|
|
13327
13646
|
})).min(1).max(CAMPAIGN_LIMITS.MAX_AD_SETS),
|
|
13328
13647
|
/**
|
|
13329
13648
|
* Valid values are 'landing' and 'whatsapp' — there is no 'website'. This
|
|
@@ -13331,17 +13650,17 @@ var GroupedCampaignConfigBase = z54.object({
|
|
|
13331
13650
|
* opaque `invalid_literal` and made the WhatsApp destination — supported by
|
|
13332
13651
|
* the API since #849 — unreachable from this tool.
|
|
13333
13652
|
*/
|
|
13334
|
-
destination:
|
|
13335
|
-
|
|
13336
|
-
type:
|
|
13337
|
-
landingUrl:
|
|
13653
|
+
destination: z55.discriminatedUnion("type", [
|
|
13654
|
+
z55.object({
|
|
13655
|
+
type: z55.literal("landing"),
|
|
13656
|
+
landingUrl: z55.string().url()
|
|
13338
13657
|
}),
|
|
13339
|
-
|
|
13340
|
-
type:
|
|
13341
|
-
phoneNumber:
|
|
13658
|
+
z55.object({
|
|
13659
|
+
type: z55.literal("whatsapp"),
|
|
13660
|
+
phoneNumber: z55.string().min(8).max(32)
|
|
13342
13661
|
})
|
|
13343
13662
|
]),
|
|
13344
|
-
saveAsDefault:
|
|
13663
|
+
saveAsDefault: z55.boolean().default(false),
|
|
13345
13664
|
budgetPolicy: MetaBudgetPolicy.optional()
|
|
13346
13665
|
});
|
|
13347
13666
|
function resolveBudgetMode(value) {
|
|
@@ -13355,7 +13674,7 @@ var GroupedCampaignConfig = GroupedCampaignConfigBase.superRefine((value, ctx) =
|
|
|
13355
13674
|
const budgetMode = resolveBudgetMode(value);
|
|
13356
13675
|
if (budgetMode === "campaign" && value.campaignDailyBudget === void 0) {
|
|
13357
13676
|
ctx.addIssue({
|
|
13358
|
-
code:
|
|
13677
|
+
code: z55.ZodIssueCode.custom,
|
|
13359
13678
|
path: [
|
|
13360
13679
|
"campaignDailyBudget"
|
|
13361
13680
|
],
|
|
@@ -13366,7 +13685,7 @@ var GroupedCampaignConfig = GroupedCampaignConfigBase.superRefine((value, ctx) =
|
|
|
13366
13685
|
value.adSets.forEach((adSet, index) => {
|
|
13367
13686
|
if (adSet.dailyBudget === void 0) {
|
|
13368
13687
|
ctx.addIssue({
|
|
13369
|
-
code:
|
|
13688
|
+
code: z55.ZodIssueCode.custom,
|
|
13370
13689
|
path: [
|
|
13371
13690
|
"adSets",
|
|
13372
13691
|
index,
|
|
@@ -13381,26 +13700,26 @@ var GroupedCampaignConfig = GroupedCampaignConfigBase.superRefine((value, ctx) =
|
|
|
13381
13700
|
...value,
|
|
13382
13701
|
budgetMode: resolveBudgetMode(value)
|
|
13383
13702
|
}));
|
|
13384
|
-
var PublishCreativesToMetaInputBase =
|
|
13385
|
-
creatives:
|
|
13703
|
+
var PublishCreativesToMetaInputBase = z55.object({
|
|
13704
|
+
creatives: z55.array(GroupedCreative).min(1).max(CAMPAIGN_LIMITS.MAX_CREATIVES),
|
|
13386
13705
|
/** Exact Meta Business portfolio selected from `list_meta_destinations`. */
|
|
13387
|
-
portfolioId:
|
|
13706
|
+
portfolioId: z55.string().trim().min(1),
|
|
13388
13707
|
/** Exact Facebook Page selected from `list_meta_destinations`. */
|
|
13389
|
-
pageId:
|
|
13708
|
+
pageId: z55.string().trim().min(1),
|
|
13390
13709
|
adAccountId: McpAdAccountId,
|
|
13391
|
-
pixelId:
|
|
13710
|
+
pixelId: z55.string().min(1).optional(),
|
|
13392
13711
|
/** Required unless `campaignConfig.destination` is whatsapp — see the
|
|
13393
13712
|
* superRefine below. Mirrors the API's `UploadToMetaRequest`. */
|
|
13394
|
-
landingUrl:
|
|
13395
|
-
targetCountry:
|
|
13396
|
-
campaignName:
|
|
13713
|
+
landingUrl: z55.string().url().optional(),
|
|
13714
|
+
targetCountry: z55.string().length(2),
|
|
13715
|
+
campaignName: z55.string().trim().min(1).max(100),
|
|
13397
13716
|
campaignConfig: GroupedCampaignConfig.optional()
|
|
13398
13717
|
});
|
|
13399
13718
|
var PublishCreativesToMetaInput = PublishCreativesToMetaInputBase.superRefine((value, ctx) => {
|
|
13400
13719
|
const destination = value.campaignConfig?.destination;
|
|
13401
13720
|
if (destination?.type !== "whatsapp" && destination?.type !== "landing" && !value.landingUrl) {
|
|
13402
13721
|
ctx.addIssue({
|
|
13403
|
-
code:
|
|
13722
|
+
code: z55.ZodIssueCode.custom,
|
|
13404
13723
|
path: [
|
|
13405
13724
|
"landingUrl"
|
|
13406
13725
|
],
|
|
@@ -13409,7 +13728,7 @@ var PublishCreativesToMetaInput = PublishCreativesToMetaInputBase.superRefine((v
|
|
|
13409
13728
|
}
|
|
13410
13729
|
if (destination?.type === "landing" && value.landingUrl && destination.landingUrl !== value.landingUrl) {
|
|
13411
13730
|
ctx.addIssue({
|
|
13412
|
-
code:
|
|
13731
|
+
code: z55.ZodIssueCode.custom,
|
|
13413
13732
|
path: [
|
|
13414
13733
|
"campaignConfig",
|
|
13415
13734
|
"destination",
|
|
@@ -13428,16 +13747,16 @@ var PublishCreativesToMetaInput = PublishCreativesToMetaInputBase.superRefine((v
|
|
|
13428
13747
|
}
|
|
13429
13748
|
return value;
|
|
13430
13749
|
});
|
|
13431
|
-
var PresignUploadResponseSchema =
|
|
13432
|
-
uploadUrl:
|
|
13750
|
+
var PresignUploadResponseSchema = z55.object({
|
|
13751
|
+
uploadUrl: z55.string().url().refine((value) => {
|
|
13433
13752
|
const url = new URL(value);
|
|
13434
13753
|
return !url.username && !url.password && url.protocol === "https:";
|
|
13435
13754
|
}, "presigned upload URL must be HTTPS without embedded credentials"),
|
|
13436
|
-
publicUrl:
|
|
13755
|
+
publicUrl: z55.string().url().refine((value) => {
|
|
13437
13756
|
const url = new URL(value);
|
|
13438
13757
|
return !url.username && !url.password;
|
|
13439
13758
|
}, "public media URL must not contain embedded credentials"),
|
|
13440
|
-
stagingAssetId:
|
|
13759
|
+
stagingAssetId: z55.string().uuid()
|
|
13441
13760
|
});
|
|
13442
13761
|
async function uploadResolvedMedia(api, media, assetRole) {
|
|
13443
13762
|
if (!media.buffer) throw new ResolveMediaError("INVALID_INPUT", "resolved media has no bytes");
|
|
@@ -13462,22 +13781,23 @@ async function resolveAndUploadMedia(api, ref, expectedKind, assetRole, transpor
|
|
|
13462
13781
|
return uploadResolvedMedia(api, media, assetRole);
|
|
13463
13782
|
}
|
|
13464
13783
|
__name(resolveAndUploadMedia, "resolveAndUploadMedia");
|
|
13465
|
-
var TikTokInput =
|
|
13466
|
-
taskId:
|
|
13467
|
-
advertiserId:
|
|
13468
|
-
pixelId:
|
|
13469
|
-
landingUrl:
|
|
13470
|
-
angleIndexes:
|
|
13784
|
+
var TikTokInput = z55.object({
|
|
13785
|
+
taskId: z55.string().min(1),
|
|
13786
|
+
advertiserId: z55.string().min(1),
|
|
13787
|
+
pixelId: z55.string().min(1).optional(),
|
|
13788
|
+
landingUrl: z55.string().url().optional(),
|
|
13789
|
+
angleIndexes: z55.array(z55.number().int().nonnegative()).optional(),
|
|
13471
13790
|
campaignConfig: McpCampaignConfig.optional(),
|
|
13472
13791
|
/** Override the ad's description (TikTok's body copy). If omitted,
|
|
13473
13792
|
* ProductMaker uses the auto-generated copy attached to the task. */
|
|
13474
|
-
primaryText:
|
|
13793
|
+
primaryText: z55.string().trim().min(1).max(500).optional()
|
|
13475
13794
|
});
|
|
13476
13795
|
function registerPublishTools(server, api, opts = {}) {
|
|
13477
13796
|
const transport = opts.transport ?? "http";
|
|
13478
|
-
server.tool("publish_to_shopify", "Publica o actualiza la landing en una tienda Shopify conectada. Precondici\xF3n: la tarea debe estar en status='complete' \u2014 usa `get_task_status` antes. Por defecto `publicationMode=auto`: si esta tarea ya fue publicada en esa tienda, actualiza ESE MISMO producto y conserva URL, precio, variantes, inventario y SKU; si no, crea uno. Usa `existing` + `targetProductId` cuando el usuario elija otro producto y `new` s\xF3lo si pide crear uno nuevo. ANTES de llamar preg\xFAntale \"\xBFTu producto tiene variantes (talla, color, etc.)?\" y pasa `showVariantPicker: true` si responde que s\xED. Por defecto publica TODAS las im\xE1genes generadas; pasa `numLandingImages` s\xF3lo si el usuario pide menos. La respuesta es markdown con el link a la landing y al producto.", ShopifyInput.shape, async (input) => {
|
|
13797
|
+
server.tool("publish_to_shopify", "Publica o actualiza la landing en una tienda Shopify conectada. Precondici\xF3n: la tarea debe estar en status='complete' \u2014 usa `get_task_status` antes. Por defecto `publicationMode=auto`: si esta tarea ya fue publicada en esa tienda, actualiza ESE MISMO producto y conserva URL, precio, variantes, inventario y SKU; si no, crea uno. Usa `existing` + `targetProductId` cuando el usuario elija otro producto y `new` s\xF3lo si pide crear uno nuevo. ANTES de llamar preg\xFAntale \"\xBFTu producto tiene variantes (talla, color, etc.)?\" y pasa `showVariantPicker: true` si responde que s\xED. Por defecto publica TODAS las im\xE1genes generadas; pasa `numLandingImages` s\xF3lo si el usuario pide menos. La respuesta es markdown con el link a la landing y al producto.", ShopifyInput.shape, OVERWRITES_EXTERNAL, async (input) => {
|
|
13479
13798
|
try {
|
|
13480
|
-
|
|
13799
|
+
const parsed = parseShopifyInput(input);
|
|
13800
|
+
if (parsed.publicationMode === "existing" && !parsed.targetProductId) {
|
|
13481
13801
|
return {
|
|
13482
13802
|
isError: true,
|
|
13483
13803
|
content: [
|
|
@@ -13488,29 +13808,31 @@ function registerPublishTools(server, api, opts = {}) {
|
|
|
13488
13808
|
]
|
|
13489
13809
|
};
|
|
13490
13810
|
}
|
|
13491
|
-
const variantIndex =
|
|
13492
|
-
const availableImages = await fetchLandingImageCount(api,
|
|
13811
|
+
const variantIndex = parsed.variantIndex ?? 0;
|
|
13812
|
+
const availableImages = await fetchLandingImageCount(api, parsed.taskId);
|
|
13493
13813
|
const effectiveAvailable = availableImages > 0 ? availableImages : 3;
|
|
13494
|
-
const numImages = Math.min(
|
|
13814
|
+
const numImages = Math.min(parsed.numLandingImages ?? effectiveAvailable, effectiveAvailable);
|
|
13495
13815
|
const selectedLandingImages = Array.from({
|
|
13496
13816
|
length: numImages
|
|
13497
13817
|
}, (_, i) => ({
|
|
13498
13818
|
imageIndex: i,
|
|
13499
13819
|
variantIndex
|
|
13500
13820
|
}));
|
|
13501
|
-
const r = await api.postJson(`/v1/tasks/${encodeURIComponent(
|
|
13502
|
-
shopDomain:
|
|
13821
|
+
const r = await api.postJson(`/v1/tasks/${encodeURIComponent(parsed.taskId)}/publish/shopify`, {
|
|
13822
|
+
shopDomain: parsed.shopDomain,
|
|
13503
13823
|
selectedLandingImages,
|
|
13504
|
-
showVariantPicker:
|
|
13505
|
-
publicationMode:
|
|
13506
|
-
targetProductId:
|
|
13824
|
+
showVariantPicker: parsed.showVariantPicker,
|
|
13825
|
+
publicationMode: parsed.publicationMode,
|
|
13826
|
+
targetProductId: parsed.targetProductId,
|
|
13827
|
+
announcementBarEnabled: parsed.announcementBarEnabled,
|
|
13828
|
+
announcementBarText: parsed.announcementBarText
|
|
13507
13829
|
});
|
|
13508
13830
|
return renderShopifyPublishMarkdown(r);
|
|
13509
13831
|
} catch (e) {
|
|
13510
13832
|
throw translateError(e);
|
|
13511
13833
|
}
|
|
13512
13834
|
});
|
|
13513
|
-
server.tool("publish_to_meta", "Crea una campa\xF1a de Meta Ads (Facebook/Instagram) desde UNA sola tarea. Antes llama `list_meta_destinations`, deja que el usuario elija el `portfolioId` (Business portfolio), el `adAccountId` y el `pageId` exactos, y env\xEDa esos tres valores; no adivines ni uses la primera p\xE1gina. Si quieres juntar creativos de VARIAS tareas usa `publish_creatives_to_meta`. La campa\xF1a se crea SIEMPRE PAUSED y el usuario la activa manualmente. `pixelId` es opcional. `angleIndexes` permite seleccionar \xE1ngulos. `campaignConfig` admite presupuesto en USD, pa\xEDses ISO-2 y nombre. `primaryText` permite reemplazar el copy y `phoneNumber` configura WhatsApp. Despu\xE9s usa `get_meta_campaign_status`.", MetaInput.shape, async ({ taskId, campaignConfig, primaryText, phoneNumber, ...rest }) => {
|
|
13835
|
+
server.tool("publish_to_meta", "Crea una campa\xF1a de Meta Ads (Facebook/Instagram) desde UNA sola tarea. Antes llama `list_meta_destinations`, deja que el usuario elija el `portfolioId` (Business portfolio), el `adAccountId` y el `pageId` exactos, y env\xEDa esos tres valores; no adivines ni uses la primera p\xE1gina. Si quieres juntar creativos de VARIAS tareas usa `publish_creatives_to_meta`. La campa\xF1a se crea SIEMPRE PAUSED y el usuario la activa manualmente. `pixelId` es opcional. `angleIndexes` permite seleccionar \xE1ngulos. `campaignConfig` admite presupuesto en USD, pa\xEDses ISO-2 y nombre. `primaryText` permite reemplazar el copy y `phoneNumber` configura WhatsApp. Despu\xE9s usa `get_meta_campaign_status`.", MetaInput.shape, CREATES_EXTERNAL, async ({ taskId, campaignConfig, primaryText, phoneNumber, ...rest }) => {
|
|
13514
13836
|
try {
|
|
13515
13837
|
const body = {
|
|
13516
13838
|
...rest
|
|
@@ -13538,7 +13860,7 @@ function registerPublishTools(server, api, opts = {}) {
|
|
|
13538
13860
|
throw translateError(e);
|
|
13539
13861
|
}
|
|
13540
13862
|
});
|
|
13541
|
-
server.tool("publish_creatives_to_meta", "Crea UNA campa\xF1a de Meta Ads PAUSED agrupando hasta " + CAMPAIGN_LIMITS.MAX_CREATIVES + ' creativos. Si la petici\xF3n natural adjunta o menciona archivos locales, llama primero `request_media_upload`; tras OK usa cada resultado como `source.type="asset"` con su `assetId` y el `type` que coincide con `mediaKind`. No pidas path ni URL. Despu\xE9s llama `list_meta_destinations` y pregunta en el chat por el `portfolioId`, `adAccountId` y `pageId` exactos; el App es solo para archivos. Si la carga fue parcial, publica s\xF3lo los assets confirmados; nunca inventes assets ni superes diez creativos. Las rutas locales s\xF3lo funcionan con MCP stdio. Los medios propios se eliminan despu\xE9s de publicar correctamente; si falla, quedan durante el TTL para reintento. Para videos la miniatura es opcional y ProductMaker puede generarla con FFmpeg. `campaignConfig.destination` acepta landing o WhatsApp. Para landing basta `campaignConfig.destination.landingUrl`; no dupliques la URL arriba. Si omites `budgetMode`, se infiere `ad_set` cuando cada ad set trae `dailyBudget`, o `campaign` cuando env\xEDas `campaignDailyBudget`. No actives la campa\xF1a autom\xE1ticamente.', PublishCreativesToMetaInputBase.shape, async (rawInput) => {
|
|
13863
|
+
server.tool("publish_creatives_to_meta", "Crea UNA campa\xF1a de Meta Ads PAUSED agrupando hasta " + CAMPAIGN_LIMITS.MAX_CREATIVES + ' creativos. Si la petici\xF3n natural adjunta o menciona archivos locales, llama primero `request_media_upload`; tras OK usa cada resultado como `source.type="asset"` con su `assetId` y el `type` que coincide con `mediaKind`. No pidas path ni URL. Despu\xE9s llama `list_meta_destinations` y pregunta en el chat por el `portfolioId`, `adAccountId` y `pageId` exactos; el App es solo para archivos. Si la carga fue parcial, publica s\xF3lo los assets confirmados; nunca inventes assets ni superes diez creativos. Las rutas locales s\xF3lo funcionan con MCP stdio. Los medios propios se eliminan despu\xE9s de publicar correctamente; si falla, quedan durante el TTL para reintento. Para videos la miniatura es opcional y ProductMaker puede generarla con FFmpeg. `campaignConfig.destination` acepta landing o WhatsApp. Para landing basta `campaignConfig.destination.landingUrl`; no dupliques la URL arriba. Si omites `budgetMode`, se infiere `ad_set` cuando cada ad set trae `dailyBudget`, o `campaign` cuando env\xEDas `campaignDailyBudget`. No actives la campa\xF1a autom\xE1ticamente.', PublishCreativesToMetaInputBase.shape, CREATES_EXTERNAL, async (rawInput) => {
|
|
13542
13864
|
try {
|
|
13543
13865
|
const parsed = PublishCreativesToMetaInput.safeParse(rawInput);
|
|
13544
13866
|
if (!parsed.success) throw invalidInputFromZod(parsed.error);
|
|
@@ -13628,7 +13950,7 @@ function registerPublishTools(server, api, opts = {}) {
|
|
|
13628
13950
|
throw translateError(e);
|
|
13629
13951
|
}
|
|
13630
13952
|
});
|
|
13631
|
-
server.tool("publish_to_tiktok", "Crea una campa\xF1a de TikTok Ads desde la tarea. La campa\xF1a se crea SIEMPRE en estado PAUSED \u2014 el usuario la activa manualmente en TikTok Ads Manager. Use `list_tiktok_advertisers` antes para un advertiserId v\xE1lido. `pixelId` opcional pero recomendado para tracking. `angleIndexes` (opcional): selecciona qu\xE9 \xE1ngulos publicar \u2014 ej. [0,2]; omitir = publicar todos. `campaignConfig` (opcional): `dailyBudgetUsd` (presupuesto diario SIEMPRE en d\xF3lares, ej. 25 \u2014 nunca el n\xFAmero en moneda local), `targetCountries` (c\xF3digos ISO-2, primero se usa), `campaignName` (m\xE1x 100 chars). `primaryText` (opcional, m\xE1x 500 chars): override del copy del anuncio \u2014 solo \xFAsalo si el usuario te pas\xF3 un texto espec\xEDfico; si no, ProductMaker usa el copy auto-generado de la tarea. Despu\xE9s de publicar usa `get_tiktok_campaign_status` para chequear el estado.", TikTokInput.shape, async ({ taskId, campaignConfig, primaryText, ...rest }) => {
|
|
13953
|
+
server.tool("publish_to_tiktok", "Crea una campa\xF1a de TikTok Ads desde la tarea. La campa\xF1a se crea SIEMPRE en estado PAUSED \u2014 el usuario la activa manualmente en TikTok Ads Manager. Use `list_tiktok_advertisers` antes para un advertiserId v\xE1lido. `pixelId` opcional pero recomendado para tracking. `angleIndexes` (opcional): selecciona qu\xE9 \xE1ngulos publicar \u2014 ej. [0,2]; omitir = publicar todos. `campaignConfig` (opcional): `dailyBudgetUsd` (presupuesto diario SIEMPRE en d\xF3lares, ej. 25 \u2014 nunca el n\xFAmero en moneda local), `targetCountries` (c\xF3digos ISO-2, primero se usa), `campaignName` (m\xE1x 100 chars). `primaryText` (opcional, m\xE1x 500 chars): override del copy del anuncio \u2014 solo \xFAsalo si el usuario te pas\xF3 un texto espec\xEDfico; si no, ProductMaker usa el copy auto-generado de la tarea. Despu\xE9s de publicar usa `get_tiktok_campaign_status` para chequear el estado.", TikTokInput.shape, CREATES_EXTERNAL, async ({ taskId, campaignConfig, primaryText, ...rest }) => {
|
|
13632
13954
|
try {
|
|
13633
13955
|
const body = {
|
|
13634
13956
|
...rest
|
|
@@ -13718,38 +14040,38 @@ __name(toTikTokCampaignConfig, "toTikTokCampaignConfig");
|
|
|
13718
14040
|
|
|
13719
14041
|
// src/tools/meta-upload.ts
|
|
13720
14042
|
import { registerAppTool } from "@modelcontextprotocol/ext-apps/server";
|
|
13721
|
-
import { z as
|
|
13722
|
-
var UploadSessionInputBase =
|
|
13723
|
-
mediaKind:
|
|
14043
|
+
import { z as z56 } from "zod";
|
|
14044
|
+
var UploadSessionInputBase = z56.object({
|
|
14045
|
+
mediaKind: z56.enum([
|
|
13724
14046
|
"image",
|
|
13725
14047
|
"video",
|
|
13726
14048
|
"mixed"
|
|
13727
14049
|
]).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."),
|
|
13728
|
-
assetRole:
|
|
14050
|
+
assetRole: z56.enum([
|
|
13729
14051
|
"media",
|
|
13730
14052
|
"thumbnail"
|
|
13731
14053
|
]).default("media").describe("Use media for the creative itself; thumbnail only for an image thumbnail."),
|
|
13732
|
-
uploadPurpose:
|
|
14054
|
+
uploadPurpose: z56.enum([
|
|
13733
14055
|
"product_reference",
|
|
13734
14056
|
"creative",
|
|
13735
14057
|
"generic"
|
|
13736
14058
|
]).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."),
|
|
13737
|
-
maxFiles:
|
|
14059
|
+
maxFiles: z56.number().int().min(1).max(10).optional().describe("Optional batch limit: image accepts 1-5, video exactly 1, and mixed 1-10.")
|
|
13738
14060
|
});
|
|
13739
|
-
var OpenAiFileReference =
|
|
13740
|
-
download_url:
|
|
13741
|
-
file_id:
|
|
13742
|
-
mime_type:
|
|
13743
|
-
file_name:
|
|
14061
|
+
var OpenAiFileReference = z56.object({
|
|
14062
|
+
download_url: z56.string().url().optional().describe("Optional host-provided download URL. The App prefers getFileDownloadUrl(file_id) and the server never fetches this URL."),
|
|
14063
|
+
file_id: z56.string().min(1),
|
|
14064
|
+
mime_type: z56.string().min(1).optional(),
|
|
14065
|
+
file_name: z56.string().min(1).optional()
|
|
13744
14066
|
});
|
|
13745
14067
|
var UploadSessionToolInputShape = {
|
|
13746
14068
|
...UploadSessionInputBase.shape,
|
|
13747
|
-
files:
|
|
14069
|
+
files: z56.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.")
|
|
13748
14070
|
};
|
|
13749
|
-
var UploadSessionToolInput =
|
|
14071
|
+
var UploadSessionToolInput = z56.object(UploadSessionToolInputShape).superRefine((value, ctx) => {
|
|
13750
14072
|
if (value.assetRole === "thumbnail" && value.mediaKind !== "image") {
|
|
13751
14073
|
ctx.addIssue({
|
|
13752
|
-
code:
|
|
14074
|
+
code: z56.ZodIssueCode.custom,
|
|
13753
14075
|
path: [
|
|
13754
14076
|
"mediaKind"
|
|
13755
14077
|
],
|
|
@@ -13758,7 +14080,7 @@ var UploadSessionToolInput = z55.object(UploadSessionToolInputShape).superRefine
|
|
|
13758
14080
|
}
|
|
13759
14081
|
if (value.mediaKind === "video" && value.maxFiles !== void 0 && value.maxFiles !== 1) {
|
|
13760
14082
|
ctx.addIssue({
|
|
13761
|
-
code:
|
|
14083
|
+
code: z56.ZodIssueCode.custom,
|
|
13762
14084
|
path: [
|
|
13763
14085
|
"maxFiles"
|
|
13764
14086
|
],
|
|
@@ -13767,7 +14089,7 @@ var UploadSessionToolInput = z55.object(UploadSessionToolInputShape).superRefine
|
|
|
13767
14089
|
}
|
|
13768
14090
|
if (value.mediaKind === "image" && (value.maxFiles ?? 1) > 5) {
|
|
13769
14091
|
ctx.addIssue({
|
|
13770
|
-
code:
|
|
14092
|
+
code: z56.ZodIssueCode.custom,
|
|
13771
14093
|
path: [
|
|
13772
14094
|
"maxFiles"
|
|
13773
14095
|
],
|
|
@@ -13775,38 +14097,38 @@ var UploadSessionToolInput = z55.object(UploadSessionToolInputShape).superRefine
|
|
|
13775
14097
|
});
|
|
13776
14098
|
}
|
|
13777
14099
|
});
|
|
13778
|
-
var UploadSessionResponse =
|
|
13779
|
-
sessionId:
|
|
13780
|
-
uploadPageUrl:
|
|
13781
|
-
mediaKind:
|
|
14100
|
+
var UploadSessionResponse = z56.object({
|
|
14101
|
+
sessionId: z56.string().uuid(),
|
|
14102
|
+
uploadPageUrl: z56.string().url(),
|
|
14103
|
+
mediaKind: z56.enum([
|
|
13782
14104
|
"image",
|
|
13783
14105
|
"video",
|
|
13784
14106
|
"mixed"
|
|
13785
14107
|
]),
|
|
13786
|
-
assetRole:
|
|
14108
|
+
assetRole: z56.enum([
|
|
13787
14109
|
"media",
|
|
13788
14110
|
"thumbnail"
|
|
13789
14111
|
]),
|
|
13790
|
-
maxFiles:
|
|
13791
|
-
expiresAt:
|
|
14112
|
+
maxFiles: z56.number().int().positive(),
|
|
14113
|
+
expiresAt: z56.string().datetime()
|
|
13792
14114
|
});
|
|
13793
|
-
var UploadStatusInput =
|
|
13794
|
-
sessionId:
|
|
14115
|
+
var UploadStatusInput = z56.object({
|
|
14116
|
+
sessionId: z56.string().uuid().describe("The sessionId returned by request_media_upload.")
|
|
13795
14117
|
});
|
|
13796
|
-
var UniversalUploadStatusResponse =
|
|
13797
|
-
|
|
13798
|
-
status:
|
|
13799
|
-
assets:
|
|
14118
|
+
var UniversalUploadStatusResponse = z56.discriminatedUnion("status", [
|
|
14119
|
+
z56.object({
|
|
14120
|
+
status: z56.literal("pending"),
|
|
14121
|
+
assets: z56.array(z56.never()).length(0)
|
|
13800
14122
|
}),
|
|
13801
|
-
|
|
13802
|
-
status:
|
|
13803
|
-
assets:
|
|
13804
|
-
assetId:
|
|
13805
|
-
mediaKind:
|
|
14123
|
+
z56.object({
|
|
14124
|
+
status: z56.literal("uploaded"),
|
|
14125
|
+
assets: z56.array(z56.object({
|
|
14126
|
+
assetId: z56.string().uuid(),
|
|
14127
|
+
mediaKind: z56.enum([
|
|
13806
14128
|
"image",
|
|
13807
14129
|
"video"
|
|
13808
14130
|
]),
|
|
13809
|
-
assetRole:
|
|
14131
|
+
assetRole: z56.enum([
|
|
13810
14132
|
"media",
|
|
13811
14133
|
"thumbnail"
|
|
13812
14134
|
])
|
|
@@ -13814,13 +14136,13 @@ var UniversalUploadStatusResponse = z55.discriminatedUnion("status", [
|
|
|
13814
14136
|
})
|
|
13815
14137
|
]);
|
|
13816
14138
|
var UniversalUploadStructuredResponse = UploadSessionResponse.extend({
|
|
13817
|
-
uploadPurpose:
|
|
14139
|
+
uploadPurpose: z56.enum([
|
|
13818
14140
|
"product_reference",
|
|
13819
14141
|
"creative",
|
|
13820
14142
|
"generic"
|
|
13821
14143
|
]),
|
|
13822
|
-
checkToolName:
|
|
13823
|
-
resolveEndpoint:
|
|
14144
|
+
checkToolName: z56.literal("check_media_upload"),
|
|
14145
|
+
resolveEndpoint: z56.string().url()
|
|
13824
14146
|
});
|
|
13825
14147
|
function contextualizeUploadPageUrl(uploadPageUrl, uploadPurpose) {
|
|
13826
14148
|
const url = new URL(uploadPageUrl);
|
|
@@ -13854,6 +14176,7 @@ function registerMediaUploadTools(server, api) {
|
|
|
13854
14176
|
title: "Subir archivos",
|
|
13855
14177
|
description: "Abre el selector universal de ProductMaker para im\xE1genes o videos del usuario. Ll\xE1malo autom\xE1ticamente y antes de preguntar por campos faltantes cuando una petici\xF3n natural adjunte o mencione un archivo local, por ejemplo \u2018crea una landing con este producto\u2019, \u2018haz creativos con esta foto\u2019 o \u2018sube este video a Meta\u2019; hazlo aunque el usuario diga que todav\xEDa no generes ni publiques, porque esta herramienta solo prepara el archivo y no consume cr\xE9ditos, salvo que proh\xEDba expl\xEDcitamente subir, compartir o almacenar el archivo; no pidas una ruta local ni URL. Es la \xFAnica ruta de carga para create_product_task, generate_image_creatives, generate_video_creative, generate_free_video, generate_landing y publish_creatives_to_meta. Pasa las referencias opcionales de ChatGPT en files; si el host no puede transferirlas, el mismo App muestra el selector inline. Usa uploadPurpose=product_reference para fotos de producto y landings, creative para creativos o anuncios, y generic solo cuando ninguno aplique. Usa mediaKind=image para referencias de producto y landings; mixed solo cuando el consumidor acepte ambos tipos. El App NO te avisa cuando termina: en cuanto el usuario diga que ya subi\xF3 el archivo o que sigas, llama `check_media_upload` con este sessionId para obtener los assetId, y reanuda la intenci\xF3n original usando \xFAnicamente assetId y mediaKind, sin pedir que vuelva a subir ni mostrar datos t\xE9cnicos. El enlace externo es fallback solo si el App inline falla. Esta herramienta no genera, publica ni consume cr\xE9ditos.",
|
|
13856
14178
|
inputSchema: UploadSessionToolInputShape,
|
|
14179
|
+
annotations: CREATES_INTERNAL,
|
|
13857
14180
|
outputSchema: UniversalUploadStructuredResponse.shape,
|
|
13858
14181
|
_meta: {
|
|
13859
14182
|
ui: {
|
|
@@ -13894,6 +14217,7 @@ function registerMediaUploadTools(server, api) {
|
|
|
13894
14217
|
server.registerTool("check_media_upload", {
|
|
13895
14218
|
description: "Comprueba la carga universal. Ll\xE1mala cuando el usuario diga que ya subi\xF3 sus archivos: es la \xFAnica forma de enterarte, porque el App no manda mensajes al chat. Si status=pending, el usuario todav\xEDa no ha terminado: d\xEDselo y espera, no inventes un assetId ni abras otra sesi\xF3n de carga. Si status=uploaded, conserva assets[] para la petici\xF3n anterior y pasa cada assetId \xFAnicamente a un consumidor compatible. Un asset de video solo sirve donde se admite video; las referencias de producto, imagen y landing requieren mediaKind=image. No muestres IDs t\xE9cnicos como JSON al usuario.",
|
|
13896
14219
|
inputSchema: UploadStatusInput.shape,
|
|
14220
|
+
annotations: READS_INTERNAL,
|
|
13897
14221
|
_meta: {
|
|
13898
14222
|
"openai/widgetAccessible": true
|
|
13899
14223
|
}
|
|
@@ -13909,16 +14233,16 @@ function registerMediaUploadTools(server, api) {
|
|
|
13909
14233
|
__name(registerMediaUploadTools, "registerMediaUploadTools");
|
|
13910
14234
|
|
|
13911
14235
|
// src/tools/campaigns.ts
|
|
13912
|
-
import { z as
|
|
13913
|
-
var MetaStatusInput =
|
|
13914
|
-
campaignId:
|
|
14236
|
+
import { z as z57 } from "zod";
|
|
14237
|
+
var MetaStatusInput = z57.object({
|
|
14238
|
+
campaignId: z57.string().min(1).describe("Meta campaign ID returned by publish_to_meta.")
|
|
13915
14239
|
});
|
|
13916
|
-
var TikTokStatusInput =
|
|
13917
|
-
campaignId:
|
|
13918
|
-
advertiserId:
|
|
14240
|
+
var TikTokStatusInput = z57.object({
|
|
14241
|
+
campaignId: z57.string().min(1).describe("TikTok campaign ID returned by publish_to_tiktok."),
|
|
14242
|
+
advertiserId: z57.string().min(1).describe("The same advertiserId used to publish the campaign.")
|
|
13919
14243
|
});
|
|
13920
14244
|
function registerCampaignTools(server, api) {
|
|
13921
|
-
server.tool("get_meta_campaign_status", "Consulta el estado actual + m\xE9tricas lifetime (spend, impressions, clicks, reach) de una campa\xF1a Meta publicada. Llama esto DESPU\xC9S de `publish_to_meta` para confirmar que la campa\xF1a se cre\xF3 correctamente y para reportar performance al usuario. Es read-only \u2014 no modifica nada en Meta Ads Manager.", MetaStatusInput.shape, async (input) => {
|
|
14245
|
+
server.tool("get_meta_campaign_status", "Consulta el estado actual + m\xE9tricas lifetime (spend, impressions, clicks, reach) de una campa\xF1a Meta publicada. Llama esto DESPU\xC9S de `publish_to_meta` para confirmar que la campa\xF1a se cre\xF3 correctamente y para reportar performance al usuario. Es read-only \u2014 no modifica nada en Meta Ads Manager.", MetaStatusInput.shape, READS_EXTERNAL, async (input) => {
|
|
13922
14246
|
try {
|
|
13923
14247
|
const r = await api.getJson(`/v1/facebook/campaigns/${encodeURIComponent(input.campaignId)}`);
|
|
13924
14248
|
return renderMetaStatusMarkdown(r);
|
|
@@ -13926,7 +14250,7 @@ function registerCampaignTools(server, api) {
|
|
|
13926
14250
|
throw translateError(e);
|
|
13927
14251
|
}
|
|
13928
14252
|
});
|
|
13929
|
-
server.tool("get_tiktok_campaign_status", "Consulta el estado actual + reporte lifetime (spend, impressions, clicks, reach) de una campa\xF1a TikTok publicada. Requiere `advertiserId` (el mismo con el que se cre\xF3). Llama esto DESPU\xC9S de `publish_to_tiktok` para confirmar creaci\xF3n correcta y reportar performance. Es read-only.", TikTokStatusInput.shape, async (input) => {
|
|
14253
|
+
server.tool("get_tiktok_campaign_status", "Consulta el estado actual + reporte lifetime (spend, impressions, clicks, reach) de una campa\xF1a TikTok publicada. Requiere `advertiserId` (el mismo con el que se cre\xF3). Llama esto DESPU\xC9S de `publish_to_tiktok` para confirmar creaci\xF3n correcta y reportar performance. Es read-only.", TikTokStatusInput.shape, READS_EXTERNAL, async (input) => {
|
|
13930
14254
|
try {
|
|
13931
14255
|
const r = await api.getJson(`/v1/tiktok/campaigns/${encodeURIComponent(input.campaignId)}?advertiserId=${encodeURIComponent(input.advertiserId)}`);
|
|
13932
14256
|
return renderTikTokStatusMarkdown(r);
|
|
@@ -14012,10 +14336,10 @@ function renderTikTokStatusMarkdown(r) {
|
|
|
14012
14336
|
__name(renderTikTokStatusMarkdown, "renderTikTokStatusMarkdown");
|
|
14013
14337
|
|
|
14014
14338
|
// ../../packages/product-economics/src/product-costing.ts
|
|
14015
|
-
import { z as
|
|
14339
|
+
import { z as z59 } from "zod";
|
|
14016
14340
|
|
|
14017
14341
|
// ../../packages/product-economics/src/product-costing-countries.ts
|
|
14018
|
-
import { z as
|
|
14342
|
+
import { z as z58 } from "zod";
|
|
14019
14343
|
|
|
14020
14344
|
// ../../packages/product-economics/src/product-costing-shipping-references.json
|
|
14021
14345
|
var product_costing_shipping_references_default = {
|
|
@@ -14079,21 +14403,21 @@ var product_costing_shipping_references_default = {
|
|
|
14079
14403
|
};
|
|
14080
14404
|
|
|
14081
14405
|
// ../../packages/product-economics/src/product-costing-countries.ts
|
|
14082
|
-
var ShippingReferenceSchema =
|
|
14083
|
-
reference:
|
|
14084
|
-
min:
|
|
14085
|
-
max:
|
|
14086
|
-
updatedAt:
|
|
14087
|
-
confidence:
|
|
14406
|
+
var ShippingReferenceSchema = z58.object({
|
|
14407
|
+
reference: z58.number().positive(),
|
|
14408
|
+
min: z58.number().nonnegative(),
|
|
14409
|
+
max: z58.number().positive(),
|
|
14410
|
+
updatedAt: z58.string().date(),
|
|
14411
|
+
confidence: z58.enum([
|
|
14088
14412
|
"high",
|
|
14089
14413
|
"medium",
|
|
14090
14414
|
"medium-low"
|
|
14091
14415
|
]),
|
|
14092
|
-
sources:
|
|
14416
|
+
sources: z58.array(z58.string().url().startsWith("https://")).min(1).max(3)
|
|
14093
14417
|
}).superRefine(({ reference, min, max }, context) => {
|
|
14094
14418
|
if (min > reference || reference > max) {
|
|
14095
14419
|
context.addIssue({
|
|
14096
|
-
code:
|
|
14420
|
+
code: z58.ZodIssueCode.custom,
|
|
14097
14421
|
message: "shipping_reference_outside_range",
|
|
14098
14422
|
path: [
|
|
14099
14423
|
"reference"
|
|
@@ -14101,7 +14425,7 @@ var ShippingReferenceSchema = z57.object({
|
|
|
14101
14425
|
});
|
|
14102
14426
|
}
|
|
14103
14427
|
});
|
|
14104
|
-
var SHIPPING_REFERENCES =
|
|
14428
|
+
var SHIPPING_REFERENCES = z58.record(ShippingReferenceSchema).parse(product_costing_shipping_references_default);
|
|
14105
14429
|
var PRODUCT_COSTING_COUNTRY_CODES = Object.freeze(Object.keys(SHIPPING_REFERENCES));
|
|
14106
14430
|
var SPANISH_LOCALE_COUNTRIES = /* @__PURE__ */ new Set([
|
|
14107
14431
|
"MX",
|
|
@@ -14283,18 +14607,18 @@ function getProductCostingCountryName(country) {
|
|
|
14283
14607
|
__name(getProductCostingCountryName, "getProductCostingCountryName");
|
|
14284
14608
|
|
|
14285
14609
|
// ../../packages/product-economics/src/product-costing.ts
|
|
14286
|
-
var ProductCostingInputSchema =
|
|
14287
|
-
country:
|
|
14288
|
-
supplierCost:
|
|
14289
|
-
shippingCost:
|
|
14290
|
-
returnRate:
|
|
14291
|
-
cancellationRate:
|
|
14292
|
-
adsManagerCpa:
|
|
14293
|
-
desiredMargin:
|
|
14610
|
+
var ProductCostingInputSchema = z59.object({
|
|
14611
|
+
country: z59.string().refine(isProductCostingCountryCode, "unsupported_country"),
|
|
14612
|
+
supplierCost: z59.number().finite().max(Number.MAX_SAFE_INTEGER, "amount_too_large").positive("supplier_cost_positive"),
|
|
14613
|
+
shippingCost: z59.number().finite().max(Number.MAX_SAFE_INTEGER, "amount_too_large").nonnegative("shipping_cost_nonnegative"),
|
|
14614
|
+
returnRate: z59.number().finite().min(0, "rate_nonnegative").lt(1, "rate_below_one"),
|
|
14615
|
+
cancellationRate: z59.number().finite().min(0, "rate_nonnegative").lt(1, "rate_below_one"),
|
|
14616
|
+
adsManagerCpa: z59.number().finite().max(Number.MAX_SAFE_INTEGER, "amount_too_large").nonnegative("cpa_nonnegative"),
|
|
14617
|
+
desiredMargin: z59.number().finite().min(0, "margin_nonnegative").lt(1, "margin_below_one")
|
|
14294
14618
|
}).superRefine(({ returnRate, cancellationRate }, context) => {
|
|
14295
14619
|
if (returnRate + cancellationRate >= 1) {
|
|
14296
14620
|
context.addIssue({
|
|
14297
|
-
code:
|
|
14621
|
+
code: z59.ZodIssueCode.custom,
|
|
14298
14622
|
message: "delivery_rate_positive",
|
|
14299
14623
|
path: [
|
|
14300
14624
|
"cancellationRate"
|
|
@@ -14361,14 +14685,14 @@ function calculateProductCosting(rawInput) {
|
|
|
14361
14685
|
__name(calculateProductCosting, "calculateProductCosting");
|
|
14362
14686
|
|
|
14363
14687
|
// src/tools/product-costing.ts
|
|
14364
|
-
import { z as
|
|
14365
|
-
var optionalAmount = /* @__PURE__ */ __name((description) =>
|
|
14366
|
-
var optionalPercent = /* @__PURE__ */ __name((description) =>
|
|
14367
|
-
var ProductCostingToolInput =
|
|
14368
|
-
country:
|
|
14688
|
+
import { z as z60 } from "zod";
|
|
14689
|
+
var optionalAmount = /* @__PURE__ */ __name((description) => z60.number().finite().max(Number.MAX_SAFE_INTEGER).nonnegative().optional().describe(description), "optionalAmount");
|
|
14690
|
+
var optionalPercent = /* @__PURE__ */ __name((description) => z60.number().finite().min(0).lt(100).optional().describe(description), "optionalPercent");
|
|
14691
|
+
var ProductCostingToolInput = z60.object({
|
|
14692
|
+
country: z60.string().trim().toUpperCase().refine(isProductCostingCountryCode, {
|
|
14369
14693
|
message: "Pa\xEDs no soportado por ProductMaker."
|
|
14370
14694
|
}).optional().describe("C\xF3digo ISO-2 de cualquiera de los pa\xEDses disponibles en ProductMaker. Omite para Colombia."),
|
|
14371
|
-
supplierCost:
|
|
14695
|
+
supplierCost: z60.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."),
|
|
14372
14696
|
supplierCostCop: optionalAmount("Alias anterior para costo del proveedor en Colombia. Prefiere supplierCost."),
|
|
14373
14697
|
shippingCost: optionalAmount("Costo de un env\xEDo en la moneda del pa\xEDs. Omite para usar el valor gu\xEDa vigente."),
|
|
14374
14698
|
shippingCostCop: optionalAmount("Alias anterior para flete en Colombia. Prefiere shippingCost."),
|
|
@@ -14379,7 +14703,7 @@ var ProductCostingToolInput = z59.object({
|
|
|
14379
14703
|
desiredMarginPercent: optionalPercent("Margen de ganancia deseado. Ejemplo: 10 significa 10%. Omite para usar el valor gu\xEDa editable del pa\xEDs.")
|
|
14380
14704
|
});
|
|
14381
14705
|
function registerProductCostingTools(server) {
|
|
14382
|
-
server.tool("cost_product", "Calcula GRATIS y sin cr\xE9ditos cu\xE1nto cobrar por un producto de dropshipping en cualquiera de los pa\xEDses disponibles en ProductMaker. Es READ-ONLY: no crea tareas ni publica nada. Pide country y el costo real del proveedor; los dem\xE1s campos pueden usar valores gu\xEDa editables del pa\xEDs. Devuelve `price` y `compareAtPrice` listos para `create_product_task`. No inventes un costo de proveedor.", ProductCostingToolInput.shape, async (input) => {
|
|
14706
|
+
server.tool("cost_product", "Calcula GRATIS y sin cr\xE9ditos cu\xE1nto cobrar por un producto de dropshipping en cualquiera de los pa\xEDses disponibles en ProductMaker. Es READ-ONLY: no crea tareas ni publica nada. Pide country y el costo real del proveedor; los dem\xE1s campos pueden usar valores gu\xEDa editables del pa\xEDs. Devuelve `price` y `compareAtPrice` listos para `create_product_task`. No inventes un costo de proveedor.", ProductCostingToolInput.shape, READS_INTERNAL, async (input) => {
|
|
14383
14707
|
if (input.country !== void 0 && input.country !== "CO" && [
|
|
14384
14708
|
input.supplierCostCop,
|
|
14385
14709
|
input.shippingCostCop,
|
|
@@ -14500,7 +14824,7 @@ function formatPercent(value) {
|
|
|
14500
14824
|
__name(formatPercent, "formatPercent");
|
|
14501
14825
|
|
|
14502
14826
|
// src/tools/product-research.ts
|
|
14503
|
-
import { z as
|
|
14827
|
+
import { z as z61 } from "zod";
|
|
14504
14828
|
|
|
14505
14829
|
// src/tools/product-research-idempotency.ts
|
|
14506
14830
|
import { createHash as createHash2 } from "crypto";
|
|
@@ -14616,17 +14940,17 @@ var TERMINAL2 = /* @__PURE__ */ new Set([
|
|
|
14616
14940
|
]);
|
|
14617
14941
|
var POLL_INTERVAL_MS = 5e3;
|
|
14618
14942
|
var MAX_WAIT_SECONDS = 120;
|
|
14619
|
-
var FindInput =
|
|
14943
|
+
var FindInput = z61.object({
|
|
14620
14944
|
targetCountry: ResearchTargetCountryEnum.describe("Pa\xEDs objetivo. S\xF3lo CO, MX, PE, EC y CL tienen cobertura de investigaci\xF3n."),
|
|
14621
14945
|
categoryKey: ResearchCategoryEnum.default("auto").describe('Categor\xEDa a investigar. Usa "auto" para que la IA elija seg\xFAn el mercado.'),
|
|
14622
|
-
confirmed:
|
|
14623
|
-
waitSeconds:
|
|
14946
|
+
confirmed: z61.boolean().describe(`Ponlo en true SOLO despu\xE9s de que el usuario haya aceptado el cargo de ${PRICE_CREDITS} cr\xE9ditos. No lo asumas.`),
|
|
14947
|
+
waitSeconds: z61.number().int().min(0).max(MAX_WAIT_SECONDS).optional().describe("Segundos a esperar el reporte antes de devolver el runId. M\xE1ximo 120.")
|
|
14624
14948
|
});
|
|
14625
|
-
var GetInput =
|
|
14626
|
-
runId:
|
|
14949
|
+
var GetInput = z61.object({
|
|
14950
|
+
runId: z61.string().uuid().describe("ID del run devuelto por find_winning_products.")
|
|
14627
14951
|
});
|
|
14628
|
-
var ListInput =
|
|
14629
|
-
limit:
|
|
14952
|
+
var ListInput = z61.object({
|
|
14953
|
+
limit: z61.number().int().min(1).max(20).default(10)
|
|
14630
14954
|
});
|
|
14631
14955
|
function textResult(text) {
|
|
14632
14956
|
return {
|
|
@@ -14651,7 +14975,7 @@ async function pollUntilTerminal(api, runId, waitSeconds) {
|
|
|
14651
14975
|
}
|
|
14652
14976
|
__name(pollUntilTerminal, "pollUntilTerminal");
|
|
14653
14977
|
function registerProductResearchTools(server, api) {
|
|
14654
|
-
server.tool("find_winning_products", `Busca productos ganadores de dropshipping para un pa\xEDs y categor\xEDa, y explica POR QU\xC9 la IA eligi\xF3 cada uno (razones, riesgos y evidencia real de anuncios en Meta Ads). CUESTA ${PRICE_CREDITS} CR\xC9DITOS por ejecuci\xF3n. ANTES de invocar, dile al usuario el costo y espera su aceptaci\xF3n expl\xEDcita; s\xF3lo entonces pasa confirmed: true. Consulta primero list_product_research_runs (GRATIS) \u2014 puede que el usuario ya tenga un reporte pagado para ese pa\xEDs/categor\xEDa y no necesites cobrar de nuevo. Pa\xEDses disponibles: CO, MX, PE, EC, CL. Tarda varios minutos: usa waitSeconds para esperar, y si expira consulta el resultado con get_product_research_run \u2014 el reporte NO se pierde.`, FindInput.shape, async (input) => {
|
|
14978
|
+
server.tool("find_winning_products", `Busca productos ganadores de dropshipping para un pa\xEDs y categor\xEDa, y explica POR QU\xC9 la IA eligi\xF3 cada uno (razones, riesgos y evidencia real de anuncios en Meta Ads). CUESTA ${PRICE_CREDITS} CR\xC9DITOS por ejecuci\xF3n. ANTES de invocar, dile al usuario el costo y espera su aceptaci\xF3n expl\xEDcita; s\xF3lo entonces pasa confirmed: true. Consulta primero list_product_research_runs (GRATIS) \u2014 puede que el usuario ya tenga un reporte pagado para ese pa\xEDs/categor\xEDa y no necesites cobrar de nuevo. Pa\xEDses disponibles: CO, MX, PE, EC, CL. Tarda varios minutos: usa waitSeconds para esperar, y si expira consulta el resultado con get_product_research_run \u2014 el reporte NO se pierde.`, FindInput.shape, CREATES_EXTERNAL, async (input) => {
|
|
14655
14979
|
const parsed = FindInput.parse(input);
|
|
14656
14980
|
if (!parsed.confirmed) {
|
|
14657
14981
|
throw mcp(McpErrorCode.INVALID_INPUT, `This research run costs ${PRICE_CREDITS} credits. Tell the user the cost, get an explicit yes, then call again with confirmed: true.`);
|
|
@@ -14685,7 +15009,7 @@ _Esta llamada no cobr\xF3 cr\xE9ditos: se reutiliz\xF3 una investigaci\xF3n reci
|
|
|
14685
15009
|
throw translateError(error);
|
|
14686
15010
|
}
|
|
14687
15011
|
});
|
|
14688
|
-
server.tool("get_product_research_run", "Devuelve el reporte completo de una investigaci\xF3n de productos ganadores: score, veredicto, confianza, razones, riesgos y evidencia de Meta Ads por producto. GRATIS y read-only. \xDAsalo cuando find_winning_products expir\xF3 la espera, o para releer un reporte anterior encontrado con list_product_research_runs.", GetInput.shape, async (input) => {
|
|
15012
|
+
server.tool("get_product_research_run", "Devuelve el reporte completo de una investigaci\xF3n de productos ganadores: score, veredicto, confianza, razones, riesgos y evidencia de Meta Ads por producto. GRATIS y read-only. \xDAsalo cuando find_winning_products expir\xF3 la espera, o para releer un reporte anterior encontrado con list_product_research_runs.", GetInput.shape, READS_INTERNAL, async (input) => {
|
|
14689
15013
|
const parsed = GetInput.parse(input);
|
|
14690
15014
|
try {
|
|
14691
15015
|
const run = await api.getJson(`/v1/product-research/runs/${parsed.runId}`);
|
|
@@ -14705,7 +15029,7 @@ _Esta llamada no cobr\xF3 cr\xE9ditos: se reutiliz\xF3 una investigaci\xF3n reci
|
|
|
14705
15029
|
throw translateError(error);
|
|
14706
15030
|
}
|
|
14707
15031
|
});
|
|
14708
|
-
server.tool("list_product_research_runs", "Lista las investigaciones de productos ganadores ya pagadas por el usuario, de la m\xE1s reciente a la m\xE1s antigua. GRATIS y read-only. Cons\xFAltalo ANTES de lanzar una nueva: un reporte ya pagado se relee gratis con get_product_research_run.", ListInput.shape, async (input) => {
|
|
15032
|
+
server.tool("list_product_research_runs", "Lista las investigaciones de productos ganadores ya pagadas por el usuario, de la m\xE1s reciente a la m\xE1s antigua. GRATIS y read-only. Cons\xFAltalo ANTES de lanzar una nueva: un reporte ya pagado se relee gratis con get_product_research_run.", ListInput.shape, READS_INTERNAL, async (input) => {
|
|
14709
15033
|
const parsed = ListInput.parse(input);
|
|
14710
15034
|
try {
|
|
14711
15035
|
const runs = await api.getJson(`/v1/product-research/runs?limit=${parsed.limit}`);
|
|
@@ -14718,14 +15042,14 @@ _Esta llamada no cobr\xF3 cr\xE9ditos: se reutiliz\xF3 una investigaci\xF3n reci
|
|
|
14718
15042
|
__name(registerProductResearchTools, "registerProductResearchTools");
|
|
14719
15043
|
|
|
14720
15044
|
// src/tools/dropshipping-projects.ts
|
|
14721
|
-
import { z as
|
|
14722
|
-
var ProjectId =
|
|
14723
|
-
var DropshippingProjectStatus =
|
|
15045
|
+
import { z as z62 } from "zod";
|
|
15046
|
+
var ProjectId = z62.string().uuid();
|
|
15047
|
+
var DropshippingProjectStatus = z62.enum([
|
|
14724
15048
|
"active",
|
|
14725
15049
|
"completed",
|
|
14726
15050
|
"archived"
|
|
14727
15051
|
]);
|
|
14728
|
-
var DropshippingProjectPhase =
|
|
15052
|
+
var DropshippingProjectPhase = z62.enum([
|
|
14729
15053
|
"research",
|
|
14730
15054
|
"costing",
|
|
14731
15055
|
"media",
|
|
@@ -14734,21 +15058,21 @@ var DropshippingProjectPhase = z61.enum([
|
|
|
14734
15058
|
"advertising",
|
|
14735
15059
|
"complete"
|
|
14736
15060
|
]);
|
|
14737
|
-
var DropshippingRewardMilestone =
|
|
15061
|
+
var DropshippingRewardMilestone = z62.enum([
|
|
14738
15062
|
"product_selected",
|
|
14739
15063
|
"costing_saved",
|
|
14740
15064
|
"product_media_ready",
|
|
14741
15065
|
"creatives_completed",
|
|
14742
15066
|
"campaign_created"
|
|
14743
15067
|
]);
|
|
14744
|
-
var TargetCountry =
|
|
15068
|
+
var TargetCountry = z62.enum([
|
|
14745
15069
|
"CO",
|
|
14746
15070
|
"MX",
|
|
14747
15071
|
"PE",
|
|
14748
15072
|
"EC",
|
|
14749
15073
|
"CL"
|
|
14750
15074
|
]);
|
|
14751
|
-
var ResearchCategory =
|
|
15075
|
+
var ResearchCategory = z62.enum([
|
|
14752
15076
|
"auto",
|
|
14753
15077
|
"automotive",
|
|
14754
15078
|
"kitchen",
|
|
@@ -14763,7 +15087,7 @@ var ResearchCategory = z61.enum([
|
|
|
14763
15087
|
"garden",
|
|
14764
15088
|
"cleaning"
|
|
14765
15089
|
]);
|
|
14766
|
-
var HttpUrl3 =
|
|
15090
|
+
var HttpUrl3 = z62.string().url().max(2e3).refine((value) => {
|
|
14767
15091
|
try {
|
|
14768
15092
|
return [
|
|
14769
15093
|
"http:",
|
|
@@ -14773,43 +15097,43 @@ var HttpUrl3 = z61.string().url().max(2e3).refine((value) => {
|
|
|
14773
15097
|
return false;
|
|
14774
15098
|
}
|
|
14775
15099
|
}, "URL must use HTTP(S)");
|
|
14776
|
-
var ResearchCandidateOrigin =
|
|
14777
|
-
type:
|
|
15100
|
+
var ResearchCandidateOrigin = z62.object({
|
|
15101
|
+
type: z62.literal("research_candidate"),
|
|
14778
15102
|
runId: ProjectId,
|
|
14779
|
-
candidateSourceId:
|
|
15103
|
+
candidateSourceId: z62.string().min(1).max(160)
|
|
14780
15104
|
});
|
|
14781
|
-
var TaskOrigin =
|
|
14782
|
-
type:
|
|
15105
|
+
var TaskOrigin = z62.object({
|
|
15106
|
+
type: z62.literal("task"),
|
|
14783
15107
|
taskId: ProjectId
|
|
14784
15108
|
});
|
|
14785
|
-
var ManualOrigin =
|
|
14786
|
-
type:
|
|
15109
|
+
var ManualOrigin = z62.object({
|
|
15110
|
+
type: z62.literal("manual"),
|
|
14787
15111
|
clientRequestId: ProjectId,
|
|
14788
|
-
country:
|
|
15112
|
+
country: z62.string().min(2).max(3).optional()
|
|
14789
15113
|
});
|
|
14790
|
-
var StartProjectInput =
|
|
14791
|
-
origin:
|
|
15114
|
+
var StartProjectInput = z62.object({
|
|
15115
|
+
origin: z62.discriminatedUnion("type", [
|
|
14792
15116
|
ResearchCandidateOrigin,
|
|
14793
15117
|
TaskOrigin,
|
|
14794
15118
|
ManualOrigin
|
|
14795
15119
|
]),
|
|
14796
|
-
productName:
|
|
15120
|
+
productName: z62.string().trim().min(1).max(240).optional(),
|
|
14797
15121
|
productUrl: HttpUrl3.optional(),
|
|
14798
|
-
country:
|
|
14799
|
-
languageCode:
|
|
15122
|
+
country: z62.string().min(2).max(3).optional(),
|
|
15123
|
+
languageCode: z62.string().min(2).max(16).optional()
|
|
14800
15124
|
}).strict();
|
|
14801
|
-
var CostProjectInput =
|
|
15125
|
+
var CostProjectInput = z62.object({
|
|
14802
15126
|
projectId: ProjectId,
|
|
14803
|
-
version:
|
|
14804
|
-
country:
|
|
14805
|
-
supplierCost:
|
|
14806
|
-
shippingCost:
|
|
14807
|
-
returnRate:
|
|
14808
|
-
cancellationRate:
|
|
14809
|
-
adsManagerCpa:
|
|
14810
|
-
desiredMargin:
|
|
14811
|
-
});
|
|
14812
|
-
var GuideInput =
|
|
15127
|
+
version: z62.number().int().positive(),
|
|
15128
|
+
country: z62.string().trim().min(2).max(3),
|
|
15129
|
+
supplierCost: z62.number().finite().positive(),
|
|
15130
|
+
shippingCost: z62.number().finite().nonnegative(),
|
|
15131
|
+
returnRate: z62.number().finite().min(0).lt(1),
|
|
15132
|
+
cancellationRate: z62.number().finite().min(0).lt(1),
|
|
15133
|
+
adsManagerCpa: z62.number().finite().nonnegative(),
|
|
15134
|
+
desiredMargin: z62.number().finite().min(0).lt(1)
|
|
15135
|
+
});
|
|
15136
|
+
var GuideInput = z62.object({
|
|
14813
15137
|
projectId: ProjectId.optional()
|
|
14814
15138
|
}).strict();
|
|
14815
15139
|
var NEXT_ACTION_KINDS = [
|
|
@@ -14827,29 +15151,29 @@ var NEXT_ACTION_KINDS = [
|
|
|
14827
15151
|
"repair_meta_campaign",
|
|
14828
15152
|
"review_paused_campaign"
|
|
14829
15153
|
];
|
|
14830
|
-
var DropshippingProjectNextAction =
|
|
14831
|
-
kind:
|
|
14832
|
-
href:
|
|
15154
|
+
var DropshippingProjectNextAction = z62.discriminatedUnion("kind", NEXT_ACTION_KINDS.map((kind) => z62.object({
|
|
15155
|
+
kind: z62.literal(kind),
|
|
15156
|
+
href: z62.string().min(1)
|
|
14833
15157
|
})));
|
|
14834
|
-
var CostingInputSchema =
|
|
14835
|
-
country:
|
|
14836
|
-
supplierCost:
|
|
14837
|
-
shippingCost:
|
|
14838
|
-
returnRate:
|
|
14839
|
-
cancellationRate:
|
|
14840
|
-
adsManagerCpa:
|
|
14841
|
-
desiredMargin:
|
|
15158
|
+
var CostingInputSchema = z62.object({
|
|
15159
|
+
country: z62.string().min(2).max(3),
|
|
15160
|
+
supplierCost: z62.number().finite().positive(),
|
|
15161
|
+
shippingCost: z62.number().finite().nonnegative(),
|
|
15162
|
+
returnRate: z62.number().finite().min(0).lt(1),
|
|
15163
|
+
cancellationRate: z62.number().finite().min(0).lt(1),
|
|
15164
|
+
adsManagerCpa: z62.number().finite().nonnegative(),
|
|
15165
|
+
desiredMargin: z62.number().finite().min(0).lt(1)
|
|
14842
15166
|
}).superRefine(({ returnRate, cancellationRate }, context) => {
|
|
14843
15167
|
if (returnRate + cancellationRate >= 1) context.addIssue({
|
|
14844
|
-
code:
|
|
15168
|
+
code: z62.ZodIssueCode.custom,
|
|
14845
15169
|
path: [
|
|
14846
15170
|
"cancellationRate"
|
|
14847
15171
|
]
|
|
14848
15172
|
});
|
|
14849
15173
|
});
|
|
14850
|
-
var FiniteCostingNumber =
|
|
14851
|
-
var CostingResultSchema =
|
|
14852
|
-
country:
|
|
15174
|
+
var FiniteCostingNumber = z62.number().finite();
|
|
15175
|
+
var CostingResultSchema = z62.object({
|
|
15176
|
+
country: z62.string().min(2).max(3),
|
|
14853
15177
|
deliveryRate: FiniteCostingNumber,
|
|
14854
15178
|
adjustedShippingCost: FiniteCostingNumber,
|
|
14855
15179
|
adjustedCpa: FiniteCostingNumber,
|
|
@@ -14862,44 +15186,44 @@ var CostingResultSchema = z61.object({
|
|
|
14862
15186
|
estimatedProfit: FiniteCostingNumber,
|
|
14863
15187
|
actualMargin: FiniteCostingNumber,
|
|
14864
15188
|
breakEvenAdsManagerCpa: FiniteCostingNumber,
|
|
14865
|
-
wasPriceAutoAdjusted:
|
|
14866
|
-
profitabilityStatus:
|
|
14867
|
-
});
|
|
14868
|
-
var RewardProgressSchema =
|
|
14869
|
-
campaignKey:
|
|
14870
|
-
totalCredits:
|
|
14871
|
-
earnedCredits:
|
|
14872
|
-
milestones:
|
|
15189
|
+
wasPriceAutoAdjusted: z62.boolean(),
|
|
15190
|
+
profitabilityStatus: z62.literal("healthy")
|
|
15191
|
+
});
|
|
15192
|
+
var RewardProgressSchema = z62.object({
|
|
15193
|
+
campaignKey: z62.literal("dropshipping-launch-v1"),
|
|
15194
|
+
totalCredits: z62.number().int().positive(),
|
|
15195
|
+
earnedCredits: z62.number().int().nonnegative(),
|
|
15196
|
+
milestones: z62.array(z62.object({
|
|
14873
15197
|
milestone: DropshippingRewardMilestone,
|
|
14874
|
-
credits:
|
|
14875
|
-
available:
|
|
14876
|
-
earned:
|
|
15198
|
+
credits: z62.number().int().positive(),
|
|
15199
|
+
available: z62.boolean().optional(),
|
|
15200
|
+
earned: z62.boolean(),
|
|
14877
15201
|
grantId: ProjectId.nullable(),
|
|
14878
|
-
grantedAt:
|
|
15202
|
+
grantedAt: z62.string().datetime().nullable()
|
|
14879
15203
|
})).length(DropshippingRewardMilestone.options.length)
|
|
14880
15204
|
});
|
|
14881
|
-
var ProjectResponseSchema =
|
|
15205
|
+
var ProjectResponseSchema = z62.object({
|
|
14882
15206
|
id: ProjectId,
|
|
14883
15207
|
taskId: ProjectId.nullable(),
|
|
14884
15208
|
status: DropshippingProjectStatus,
|
|
14885
|
-
originKey:
|
|
15209
|
+
originKey: z62.string().min(1),
|
|
14886
15210
|
researchRunId: ProjectId.nullable(),
|
|
14887
|
-
candidateSourceId:
|
|
14888
|
-
productName:
|
|
15211
|
+
candidateSourceId: z62.string().nullable(),
|
|
15212
|
+
productName: z62.string().nullable(),
|
|
14889
15213
|
productUrl: HttpUrl3.nullable(),
|
|
14890
15214
|
referenceImageUrl: HttpUrl3.nullable(),
|
|
14891
|
-
targetCountry:
|
|
14892
|
-
languageCode:
|
|
14893
|
-
hasReferenceImage:
|
|
15215
|
+
targetCountry: z62.string().nullable(),
|
|
15216
|
+
languageCode: z62.string().nullable(),
|
|
15217
|
+
hasReferenceImage: z62.boolean(),
|
|
14894
15218
|
costingInput: CostingInputSchema.nullable(),
|
|
14895
15219
|
costingResult: CostingResultSchema.nullable(),
|
|
14896
|
-
costingVersion:
|
|
14897
|
-
version:
|
|
15220
|
+
costingVersion: z62.number().int().positive().nullable(),
|
|
15221
|
+
version: z62.number().int().positive(),
|
|
14898
15222
|
phase: DropshippingProjectPhase,
|
|
14899
15223
|
nextAction: DropshippingProjectNextAction.nullable(),
|
|
14900
15224
|
rewards: RewardProgressSchema,
|
|
14901
|
-
createdAt:
|
|
14902
|
-
updatedAt:
|
|
15225
|
+
createdAt: z62.string().datetime(),
|
|
15226
|
+
updatedAt: z62.string().datetime()
|
|
14903
15227
|
}).passthrough();
|
|
14904
15228
|
var NEXT_ACTION_GUIDANCE = {
|
|
14905
15229
|
cost_product: "Llama `cost_dropshipping_project` con el costo real del proveedor. Nunca lo inventes: p\xEDdeselo al usuario.",
|
|
@@ -14948,28 +15272,38 @@ function projectText(raw) {
|
|
|
14948
15272
|
}
|
|
14949
15273
|
__name(projectText, "projectText");
|
|
14950
15274
|
function registerDropshippingProjectTools(server, api) {
|
|
14951
|
-
server.tool("start_dropshipping_project", "Inicia o reanuda un proyecto opcional de dropshipping. \xDAsalo cuando el usuario quiera seguir un producto hasta publicarlo; para una operaci\xF3n puntual usa el tool directo. La creaci\xF3n es idempotente y no cobra cr\xE9ditos.", StartProjectInput.shape, async (input) => {
|
|
15275
|
+
server.tool("start_dropshipping_project", "Inicia o reanuda un proyecto opcional de dropshipping. \xDAsalo cuando el usuario quiera seguir un producto hasta publicarlo; para una operaci\xF3n puntual usa el tool directo. La creaci\xF3n es idempotente y no cobra cr\xE9ditos.", StartProjectInput.shape, CREATES_INTERNAL, async (input) => {
|
|
14952
15276
|
try {
|
|
14953
15277
|
return projectText(await api.postJson("/v1/dropshipping-projects", input));
|
|
14954
15278
|
} catch (error) {
|
|
14955
15279
|
throw translateError(error);
|
|
14956
15280
|
}
|
|
14957
15281
|
});
|
|
14958
|
-
server.tool(
|
|
14959
|
-
|
|
14960
|
-
|
|
14961
|
-
|
|
15282
|
+
server.tool(
|
|
15283
|
+
"cost_dropshipping_project",
|
|
15284
|
+
"Guarda el costeo de un proyecto guiado. Escribe solo inputs; el backend recalcula el resultado can\xF3nico y controla la versi\xF3n. Pide al usuario el costo real del proveedor: nunca lo inventes. No crea tarea ni publica nada.",
|
|
15285
|
+
CostProjectInput.shape,
|
|
15286
|
+
// Overwrites the costing inputs already saved on the project — the same
|
|
15287
|
+
// "previous values are gone" case as edit_task_draft, so destructive.
|
|
15288
|
+
// idempotentHint stays false despite the PUT: the `version` guard makes a
|
|
15289
|
+
// blind retry FAIL rather than replay, so a host should not auto-retry it.
|
|
15290
|
+
OVERWRITES_INTERNAL,
|
|
15291
|
+
async (input) => {
|
|
15292
|
+
try {
|
|
15293
|
+
if (input.returnRate + input.cancellationRate >= 1) {
|
|
15294
|
+
throw new Error("returnRate + cancellationRate debe ser menor que 1.");
|
|
15295
|
+
}
|
|
15296
|
+
const { projectId, version, ...costingInput } = input;
|
|
15297
|
+
return projectText(await api.putJson(`/v1/dropshipping-projects/${encodeURIComponent(projectId)}/costing`, {
|
|
15298
|
+
version,
|
|
15299
|
+
input: costingInput
|
|
15300
|
+
}));
|
|
15301
|
+
} catch (error) {
|
|
15302
|
+
throw translateError(error);
|
|
14962
15303
|
}
|
|
14963
|
-
const { projectId, version, ...costingInput } = input;
|
|
14964
|
-
return projectText(await api.putJson(`/v1/dropshipping-projects/${encodeURIComponent(projectId)}/costing`, {
|
|
14965
|
-
version,
|
|
14966
|
-
input: costingInput
|
|
14967
|
-
}));
|
|
14968
|
-
} catch (error) {
|
|
14969
|
-
throw translateError(error);
|
|
14970
15304
|
}
|
|
14971
|
-
|
|
14972
|
-
server.tool("guide_dropshipping_launch", "Gu\xEDa read-only de un recorrido COMPLETO de dropshipping, desde producto/costeo hasta creativos, tienda y publicidad. No uses este tool para subir o publicar un creativo puntual en Meta: llama directamente `request_media_upload`, `list_meta_destinations` y `publish_creatives_to_meta`. Sin projectId explica cu\xE1ndo usar un flujo directo versus uno guiado; con projectId devuelve la fase y la \xFAnica nextAction calculada por ProductMaker. Despu\xE9s de cada mutaci\xF3n vuelve a llamar este tool o lee el proyecto. Nunca activa Meta.", GuideInput.shape, async (input) => {
|
|
15305
|
+
);
|
|
15306
|
+
server.tool("guide_dropshipping_launch", "Gu\xEDa read-only de un recorrido COMPLETO de dropshipping, desde producto/costeo hasta creativos, tienda y publicidad. No uses este tool para subir o publicar un creativo puntual en Meta: llama directamente `request_media_upload`, `list_meta_destinations` y `publish_creatives_to_meta`. Sin projectId explica cu\xE1ndo usar un flujo directo versus uno guiado; con projectId devuelve la fase y la \xFAnica nextAction calculada por ProductMaker. Despu\xE9s de cada mutaci\xF3n vuelve a llamar este tool o lee el proyecto. Nunca activa Meta.", GuideInput.shape, READS_INTERNAL, async (input) => {
|
|
14973
15307
|
try {
|
|
14974
15308
|
if (!input.projectId) {
|
|
14975
15309
|
return asText({
|