@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/http.js
CHANGED
|
@@ -896,6 +896,42 @@ var VideoTier = z5.preprocess((v) => v === "grok" ? "single_call" : v, z5.enum([
|
|
|
896
896
|
]));
|
|
897
897
|
var DEFAULT_VIDEO_TIER = "single_call";
|
|
898
898
|
|
|
899
|
+
// ../../packages/types/src/image-provider.ts
|
|
900
|
+
var GPT_IMAGE_2_RATIOS_BLOCKED_ABOVE_1K = [
|
|
901
|
+
"4:5",
|
|
902
|
+
"5:4"
|
|
903
|
+
];
|
|
904
|
+
function gptImage2SupportsTier(aspectRatio, tier) {
|
|
905
|
+
if (tier === "gpt-image-2:1k") return true;
|
|
906
|
+
return !GPT_IMAGE_2_RATIOS_BLOCKED_ABOVE_1K.includes(aspectRatio);
|
|
907
|
+
}
|
|
908
|
+
__name(gptImage2SupportsTier, "gptImage2SupportsTier");
|
|
909
|
+
var IMAGE_TIER_UNSUPPORTED_FOR_RATIO_RULE = "image_tier_unsupported_for_ratio";
|
|
910
|
+
var IMAGE_TIER_LIST = [
|
|
911
|
+
"gpt-image-2:1k",
|
|
912
|
+
"gpt-image-2:2k",
|
|
913
|
+
"gpt-image-2:4k"
|
|
914
|
+
];
|
|
915
|
+
function parseImageTier(imageModel) {
|
|
916
|
+
const normalized = imageModel.trim().toLowerCase();
|
|
917
|
+
return IMAGE_TIER_LIST.includes(normalized) ? normalized : null;
|
|
918
|
+
}
|
|
919
|
+
__name(parseImageTier, "parseImageTier");
|
|
920
|
+
function validateImageTierForRatio(aspectRatio, imageModel) {
|
|
921
|
+
if (!aspectRatio || !imageModel) return null;
|
|
922
|
+
const tier = parseImageTier(imageModel);
|
|
923
|
+
if (!tier) return null;
|
|
924
|
+
const ratio = aspectRatio.trim();
|
|
925
|
+
if (!GPT_IMAGE_2_RATIOS_BLOCKED_ABOVE_1K.includes(ratio)) return null;
|
|
926
|
+
if (gptImage2SupportsTier(ratio, tier)) return null;
|
|
927
|
+
return buildImageTierUnsupportedMessage(ratio);
|
|
928
|
+
}
|
|
929
|
+
__name(validateImageTierForRatio, "validateImageTierForRatio");
|
|
930
|
+
function buildImageTierUnsupportedMessage(aspectRatio) {
|
|
931
|
+
return `El formato ${aspectRatio} solo est\xE1 disponible en calidad est\xE1ndar (1K). Elige 1K, o cambia el formato para usar Premium.`;
|
|
932
|
+
}
|
|
933
|
+
__name(buildImageTierUnsupportedMessage, "buildImageTierUnsupportedMessage");
|
|
934
|
+
|
|
899
935
|
// ../../packages/types/src/angle.ts
|
|
900
936
|
import { z as z6 } from "zod";
|
|
901
937
|
var ImageWinnerSignal = z6.enum([
|
|
@@ -2236,6 +2272,19 @@ var IngestOptions = z15.object({
|
|
|
2236
2272
|
message
|
|
2237
2273
|
});
|
|
2238
2274
|
}
|
|
2275
|
+
const tierMessage = validateImageTierForRatio(data.aspectRatio, data.imageModel);
|
|
2276
|
+
if (tierMessage) {
|
|
2277
|
+
ctx.addIssue({
|
|
2278
|
+
code: z15.ZodIssueCode.custom,
|
|
2279
|
+
path: [
|
|
2280
|
+
"imageModel"
|
|
2281
|
+
],
|
|
2282
|
+
params: {
|
|
2283
|
+
rule: IMAGE_TIER_UNSUPPORTED_FOR_RATIO_RULE
|
|
2284
|
+
},
|
|
2285
|
+
message: tierMessage
|
|
2286
|
+
});
|
|
2287
|
+
}
|
|
2239
2288
|
});
|
|
2240
2289
|
var IngestRequest = z15.object({
|
|
2241
2290
|
url: z15.string().url(),
|
|
@@ -2780,6 +2829,7 @@ var ShopifyPublicationMode = z20.enum([
|
|
|
2780
2829
|
"existing",
|
|
2781
2830
|
"new"
|
|
2782
2831
|
]);
|
|
2832
|
+
var SHOPIFY_ANNOUNCEMENT_BAR_MAX_LENGTH = 160;
|
|
2783
2833
|
var ShopifyPublishRequest = z20.object({
|
|
2784
2834
|
shopDomain: z20.string().min(3).max(120).optional(),
|
|
2785
2835
|
/**
|
|
@@ -2798,7 +2848,9 @@ var ShopifyPublishRequest = z20.object({
|
|
|
2798
2848
|
*/
|
|
2799
2849
|
publicationMode: ShopifyPublicationMode.default("auto"),
|
|
2800
2850
|
/** Shopify product GID, numeric id, or admin product URL. Required for existing mode. */
|
|
2801
|
-
targetProductId: z20.string().trim().min(1).max(300).optional()
|
|
2851
|
+
targetProductId: z20.string().trim().min(1).max(300).optional(),
|
|
2852
|
+
announcementBarEnabled: z20.boolean().optional(),
|
|
2853
|
+
announcementBarText: z20.string().trim().min(1).max(SHOPIFY_ANNOUNCEMENT_BAR_MAX_LENGTH).optional()
|
|
2802
2854
|
}).superRefine((data, ctx) => {
|
|
2803
2855
|
if (data.publicationMode === "existing" && !data.targetProductId) {
|
|
2804
2856
|
ctx.addIssue({
|
|
@@ -2809,6 +2861,24 @@ var ShopifyPublishRequest = z20.object({
|
|
|
2809
2861
|
message: "Selecciona un producto de Shopify."
|
|
2810
2862
|
});
|
|
2811
2863
|
}
|
|
2864
|
+
if (data.announcementBarEnabled && !data.announcementBarText) {
|
|
2865
|
+
ctx.addIssue({
|
|
2866
|
+
code: z20.ZodIssueCode.custom,
|
|
2867
|
+
path: [
|
|
2868
|
+
"announcementBarText"
|
|
2869
|
+
],
|
|
2870
|
+
message: "Escribe el texto del anuncio."
|
|
2871
|
+
});
|
|
2872
|
+
}
|
|
2873
|
+
if (!data.announcementBarEnabled && data.announcementBarText) {
|
|
2874
|
+
ctx.addIssue({
|
|
2875
|
+
code: z20.ZodIssueCode.custom,
|
|
2876
|
+
path: [
|
|
2877
|
+
"announcementBarText"
|
|
2878
|
+
],
|
|
2879
|
+
message: "Activa el anuncio para usar texto."
|
|
2880
|
+
});
|
|
2881
|
+
}
|
|
2812
2882
|
});
|
|
2813
2883
|
var ShopifyPublicationStatus = z20.enum([
|
|
2814
2884
|
"pending",
|
|
@@ -2891,6 +2961,10 @@ var CreateShopifyBillingChargeRequest = z20.object({
|
|
|
2891
2961
|
var CancelShopifySubscriptionRequest = z20.object({
|
|
2892
2962
|
subscriptionId: z20.string().min(1)
|
|
2893
2963
|
});
|
|
2964
|
+
var ShopifyAnnouncementBarState = z20.object({
|
|
2965
|
+
enabled: z20.boolean(),
|
|
2966
|
+
text: z20.string().max(SHOPIFY_ANNOUNCEMENT_BAR_MAX_LENGTH).optional()
|
|
2967
|
+
});
|
|
2894
2968
|
var LandingHealthResponse = z20.object({
|
|
2895
2969
|
taskId: z20.string(),
|
|
2896
2970
|
state: z20.enum([
|
|
@@ -2932,9 +3006,20 @@ var ApproveAnglesRequest = z21.object({
|
|
|
2932
3006
|
"mixed"
|
|
2933
3007
|
]).optional()
|
|
2934
3008
|
});
|
|
3009
|
+
var ApproveAnglesRefusalReason = z21.enum([
|
|
3010
|
+
"photo_review_pending",
|
|
3011
|
+
"photos_not_ready",
|
|
3012
|
+
"reference_out_of_date"
|
|
3013
|
+
]);
|
|
2935
3014
|
var ApproveAnglesResponse = z21.object({
|
|
2936
3015
|
success: z21.boolean(),
|
|
2937
|
-
taskId: z21.string().uuid()
|
|
3016
|
+
taskId: z21.string().uuid(),
|
|
3017
|
+
reason: ApproveAnglesRefusalReason.optional()
|
|
3018
|
+
});
|
|
3019
|
+
var IngestResumeRefusalResponse = z21.object({
|
|
3020
|
+
success: z21.literal(false),
|
|
3021
|
+
taskId: z21.string().uuid(),
|
|
3022
|
+
reason: ApproveAnglesRefusalReason.optional()
|
|
2938
3023
|
});
|
|
2939
3024
|
var GenerateSuggestionsResponse = z21.object({
|
|
2940
3025
|
painPoints: z21.array(z21.string()),
|
|
@@ -3050,7 +3135,8 @@ var ApproveVideoAnglesRequest = z21.object({
|
|
|
3050
3135
|
});
|
|
3051
3136
|
var ApproveVideoAnglesResponse = z21.object({
|
|
3052
3137
|
success: z21.boolean(),
|
|
3053
|
-
taskId: z21.string().uuid()
|
|
3138
|
+
taskId: z21.string().uuid(),
|
|
3139
|
+
reason: ApproveAnglesRefusalReason.optional()
|
|
3054
3140
|
});
|
|
3055
3141
|
var SaveVideoDraftRequest = z21.object({
|
|
3056
3142
|
/** Global creative direction for all videos. Not spoken dialogue. */
|
|
@@ -3082,7 +3168,8 @@ var ApproveImageAnglesRequest = z21.object({
|
|
|
3082
3168
|
});
|
|
3083
3169
|
var ApproveImageAnglesResponse = z21.object({
|
|
3084
3170
|
success: z21.boolean(),
|
|
3085
|
-
taskId: z21.string().uuid()
|
|
3171
|
+
taskId: z21.string().uuid(),
|
|
3172
|
+
reason: ApproveAnglesRefusalReason.optional()
|
|
3086
3173
|
});
|
|
3087
3174
|
var SaveImageDraftRequest = z21.object({
|
|
3088
3175
|
customAngle: z21.string().max(480).optional(),
|
|
@@ -3439,6 +3526,10 @@ var MVPResult = z24.object({
|
|
|
3439
3526
|
videoAngleCount: z24.number().int().min(1).max(3).optional(),
|
|
3440
3527
|
adCreative: AdCreative.optional(),
|
|
3441
3528
|
shopifyPageUrl: z24.string().url().optional(),
|
|
3529
|
+
/** What the announcement bar looks like ON THE PUBLISHED landing right now.
|
|
3530
|
+
* The publish dialog prefills from this: without it the checkbox always opens
|
|
3531
|
+
* unchecked, and republishing anything would read as "remove the bar". */
|
|
3532
|
+
shopifyAnnouncementBar: ShopifyAnnouncementBarState.optional(),
|
|
3442
3533
|
// Pain points and benefits for image wizard pre-filling
|
|
3443
3534
|
painPoints: z24.string().optional(),
|
|
3444
3535
|
benefits: z24.string().optional(),
|
|
@@ -3564,7 +3655,19 @@ var StatusResponse = z24.object({
|
|
|
3564
3655
|
"done",
|
|
3565
3656
|
"error"
|
|
3566
3657
|
]),
|
|
3658
|
+
/** ENGINEERING string (`Task.error`). Never render this to a seller. */
|
|
3567
3659
|
error: z24.string().optional(),
|
|
3660
|
+
/**
|
|
3661
|
+
* SELLER-FACING failure copy, in Spanish, already decided by the backend —
|
|
3662
|
+
* render it VERBATIM. Present only for `status: 'error'`.
|
|
3663
|
+
*
|
|
3664
|
+
* Clients used to receive only `error` and guess at it with a local regex
|
|
3665
|
+
* table; measured 2026-08-18 over 187 failed tasks in 60 days, that guess
|
|
3666
|
+
* produced contentless copy ("Error desconocido." / "Error interno") for 123
|
|
3667
|
+
* of them. Populated from `Task.userMessage`, falling back to the same
|
|
3668
|
+
* resolver for rows written before that column existed.
|
|
3669
|
+
*/
|
|
3670
|
+
userMessage: z24.string().optional(),
|
|
3568
3671
|
startedAt: z24.string().datetime().optional(),
|
|
3569
3672
|
updatedAt: z24.string().datetime().optional(),
|
|
3570
3673
|
stageStatuses: StageStatusSnapshot,
|
|
@@ -3575,6 +3678,10 @@ var StatusResponse = z24.object({
|
|
|
3575
3678
|
* campaign target geo when creating Meta ads from this task. */
|
|
3576
3679
|
targetCountry: z24.string().optional(),
|
|
3577
3680
|
shopifyPageUrl: z24.string().url().optional(),
|
|
3681
|
+
/** What the announcement bar looks like ON THE PUBLISHED landing right now.
|
|
3682
|
+
* The publish dialog prefills from this: without it the checkbox always opens
|
|
3683
|
+
* unchecked, and republishing anything would read as "remove the bar". */
|
|
3684
|
+
shopifyAnnouncementBar: ShopifyAnnouncementBarState.optional(),
|
|
3578
3685
|
shopifyShops: z24.array(z24.object({
|
|
3579
3686
|
shopDomain: z24.string(),
|
|
3580
3687
|
primaryDomain: z24.string().nullable()
|
|
@@ -3585,6 +3692,10 @@ var StatusResponse = z24.object({
|
|
|
3585
3692
|
requiresAction: z24.boolean().optional(),
|
|
3586
3693
|
/** True when the task needs the user's attention rather than passive progress. */
|
|
3587
3694
|
needsReview: z24.boolean().optional(),
|
|
3695
|
+
/** True when an open gate exists that somebody can actually ANSWER. A strictly
|
|
3696
|
+
* narrower fact than `needsReview` — see the identical field on `TaskSummary`
|
|
3697
|
+
* below for why conflating the two produced a call to action that led nowhere. */
|
|
3698
|
+
hasAnswerableGate: z24.boolean().optional(),
|
|
3588
3699
|
/** Which landing pipeline produced this task's landing. 'code' tasks have no
|
|
3589
3700
|
* classic LandingImage rows — the result lives in the inline code editor. */
|
|
3590
3701
|
landingMode: LandingModeSchema.optional(),
|
|
@@ -3655,6 +3766,27 @@ var TaskSummary = z24.object({
|
|
|
3655
3766
|
/** True when user action is ACTUALLY needed (angle review or script review ready).
|
|
3656
3767
|
* Use this for status display to show "Revisar" vs "Progreso". */
|
|
3657
3768
|
needsReview: z24.boolean().optional(),
|
|
3769
|
+
/**
|
|
3770
|
+
* True when an open gate exists that somebody can actually ANSWER.
|
|
3771
|
+
*
|
|
3772
|
+
* DIFFERENT FACT from `needsReview`, and the difference is the bug. `needsReview`
|
|
3773
|
+
* means "this task is waiting on the seller"; the list badge renders «Revisar»
|
|
3774
|
+
* from it. But a task whose dispatch was lost is also "waiting", with no question
|
|
3775
|
+
* open anywhere — and so is the autopilot angle wait, whose resume button is
|
|
3776
|
+
* measurably INERT (`resumeAfterAngles` CASes on a flag that is false there, so
|
|
3777
|
+
* it returns 200 and dispatches nothing). Both render «Revisar» today and both
|
|
3778
|
+
* lead the seller nowhere. The owner spent hours one night convinced the platform
|
|
3779
|
+
* was frozen, chasing a ghost the badge itself invented.
|
|
3780
|
+
*
|
|
3781
|
+
* Derived from the reconciler's gate registry, NOT from a hand-maintained flag:
|
|
3782
|
+
* being a registry entry is what "answerable" means, because every entry declares
|
|
3783
|
+
* a decider and a deadline at compile time. A gate added tomorrow reports itself
|
|
3784
|
+
* here on its own. See `answerableGateIdsFor` in
|
|
3785
|
+
* apps/api/src/modules/cron/gate-reconciler/gate-registry.ts.
|
|
3786
|
+
*
|
|
3787
|
+
* Render «Revisar» on THIS, not on `needsReview`.
|
|
3788
|
+
*/
|
|
3789
|
+
hasAnswerableGate: z24.boolean().optional(),
|
|
3658
3790
|
createdAt: z24.string().datetime().optional(),
|
|
3659
3791
|
startedAt: z24.string().datetime().optional(),
|
|
3660
3792
|
updatedAt: z24.string().datetime().optional(),
|
|
@@ -5446,6 +5578,10 @@ var TikTokCreateCampaignResponseSchema = z35.object({
|
|
|
5446
5578
|
adsManagerUrl: z35.string().optional()
|
|
5447
5579
|
});
|
|
5448
5580
|
|
|
5581
|
+
// ../../packages/types/src/pricing-gemini-omni.ts
|
|
5582
|
+
var GEMINI_OMNI_FLAT_OUR_COST_CENTS = 84;
|
|
5583
|
+
var GEMINI_OMNI_FLAT_USER_PRICE_CENTS = 168;
|
|
5584
|
+
|
|
5449
5585
|
// ../../packages/types/src/pricing.ts
|
|
5450
5586
|
var CREDITS_PER_USD_CENT = 10;
|
|
5451
5587
|
var CREDITS_PER_DOLLAR = 100 * CREDITS_PER_USD_CENT;
|
|
@@ -5854,6 +5990,18 @@ var DEFAULT_PRICING = {
|
|
|
5854
5990
|
ourCostCents: 120,
|
|
5855
5991
|
userPriceCents: 240
|
|
5856
5992
|
},
|
|
5993
|
+
// Gemini Omni Video (gemini-omni-video) — Mix primary generator since
|
|
5994
|
+
// 2026-08-18. FLAT per generation: 168 KIE credits = $0.84, identical at
|
|
5995
|
+
// 720p and 1080p and independent of the 3.5-10s trim window. Do NOT add
|
|
5996
|
+
// per-second overrides for this key — resolveSuccessBilling bills it flat.
|
|
5997
|
+
//
|
|
5998
|
+
// Shares the constants with the pre-submit ESTIMATOR (mix-cost.ts) rather
|
|
5999
|
+
// than repeating the numbers: a literal here that drifted from the estimator
|
|
6000
|
+
// would quote one price and charge another on every mix.
|
|
6001
|
+
"kie:gemini-omni-video": {
|
|
6002
|
+
ourCostCents: GEMINI_OMNI_FLAT_OUR_COST_CENTS,
|
|
6003
|
+
userPriceCents: GEMINI_OMNI_FLAT_USER_PRICE_CENTS
|
|
6004
|
+
},
|
|
5857
6005
|
"kie:video-lipsync-generation": {
|
|
5858
6006
|
ourCostCents: 45,
|
|
5859
6007
|
userPriceCents: 90
|
|
@@ -7817,7 +7965,57 @@ var FeatureSpec = z36.object({
|
|
|
7817
7965
|
});
|
|
7818
7966
|
|
|
7819
7967
|
// ../../packages/types/src/mix.ts
|
|
7968
|
+
import { z as z38 } from "zod";
|
|
7969
|
+
|
|
7970
|
+
// ../../packages/types/src/mix-degradation.ts
|
|
7820
7971
|
import { z as z37 } from "zod";
|
|
7972
|
+
var MIX_DEGRADATION_REASONS = [
|
|
7973
|
+
/**
|
|
7974
|
+
* At least one scene the mix PLANNED is missing from the delivered video, so
|
|
7975
|
+
* the result is shorter than the source. Set at assemble time by comparing
|
|
7976
|
+
* the concat set against the planned scene set — the scene's own code (if it
|
|
7977
|
+
* had one) never reaches the mix-level rollup, which only sees scenes that
|
|
7978
|
+
* SUCCEEDED, and that is exactly how a live 17.00s-of-20.00s delivery
|
|
7979
|
+
* shipped with degradedReason NULL and no banner at all.
|
|
7980
|
+
*
|
|
7981
|
+
* ORDERING: FIRST, above every refusal code. A short delivery must never be
|
|
7982
|
+
* MASKED. It used to sit below the three content refusals on the theory that
|
|
7983
|
+
* a refusal names something the user can change AND explains the missing
|
|
7984
|
+
* scene — but it does neither reliably: in a 3-scene mix where scene 1 fails
|
|
7985
|
+
* outright and scene 2 ships through Wan after a reference refusal, the
|
|
7986
|
+
* refusal copy reads "we used a lower-quality alternative", a claim about
|
|
7987
|
+
* QUALITY that says nothing about the video being shorter than what was paid
|
|
7988
|
+
* for, and that is actively wrong about the dropped scene (for which we used
|
|
7989
|
+
* nothing). Every other code in this list describes a scene that DID ship.
|
|
7990
|
+
*/
|
|
7991
|
+
"scenes_dropped",
|
|
7992
|
+
/** Google's safety filter rejected the user's reference photo. Deterministic. */
|
|
7993
|
+
"reference_rejected",
|
|
7994
|
+
/** Google refused the wording of the change description. */
|
|
7995
|
+
"prompt_rejected",
|
|
7996
|
+
/**
|
|
7997
|
+
* The provider refused the content but its message names neither the
|
|
7998
|
+
* reference nor the wording, so we cannot say which. Distinct from
|
|
7999
|
+
* 'provider_unavailable' on purpose: "unavailable" invites the user to retry
|
|
8000
|
+
* unchanged, which can never succeed against a content refusal and only
|
|
8001
|
+
* burns their credits. This code tells them something must change.
|
|
8002
|
+
*/
|
|
8003
|
+
"content_rejected",
|
|
8004
|
+
/**
|
|
8005
|
+
* The scene's length fell outside Gemini's usable 3.5s-10s trim window —
|
|
8006
|
+
* EITHER bound. Deliberately ONE code for both: the user controls neither
|
|
8007
|
+
* (scene segmentation is entirely ours), so "too short" and "too long" are
|
|
8008
|
+
* not differently actionable, and one honest explanation beats two that
|
|
8009
|
+
* both amount to "our segmentation produced a length the primary generator
|
|
8010
|
+
* cannot take".
|
|
8011
|
+
*/
|
|
8012
|
+
"scene_length_unsupported",
|
|
8013
|
+
/** Transient provider failures exhausted the budget. */
|
|
8014
|
+
"provider_unavailable"
|
|
8015
|
+
];
|
|
8016
|
+
var MixDegradationReasonSchema = z37.enum(MIX_DEGRADATION_REASONS);
|
|
8017
|
+
|
|
8018
|
+
// ../../packages/types/src/mix.ts
|
|
7821
8019
|
var MEDIA_TYPES = [
|
|
7822
8020
|
"video",
|
|
7823
8021
|
"image"
|
|
@@ -7855,38 +8053,38 @@ function isPublicHttpsUrl(raw) {
|
|
|
7855
8053
|
return true;
|
|
7856
8054
|
}
|
|
7857
8055
|
__name(isPublicHttpsUrl, "isPublicHttpsUrl");
|
|
7858
|
-
var PublicHttpsUrl =
|
|
8056
|
+
var PublicHttpsUrl = z38.string().url().refine(isPublicHttpsUrl, {
|
|
7859
8057
|
message: "must be a public HTTPS URL"
|
|
7860
8058
|
});
|
|
7861
|
-
var InternalAssetPath =
|
|
7862
|
-
var MixImageUrl =
|
|
8059
|
+
var InternalAssetPath = z38.string().regex(/^\/(?:api|v1)\/assets\/[A-Za-z0-9._~%/-]+$/, "must be a public HTTPS URL or an internal asset path");
|
|
8060
|
+
var MixImageUrl = z38.union([
|
|
7863
8061
|
PublicHttpsUrl,
|
|
7864
8062
|
InternalAssetPath
|
|
7865
8063
|
]);
|
|
7866
|
-
var MixAxes =
|
|
7867
|
-
product:
|
|
7868
|
-
person:
|
|
7869
|
-
setting:
|
|
7870
|
-
});
|
|
7871
|
-
var MixReferences =
|
|
7872
|
-
product:
|
|
7873
|
-
person:
|
|
7874
|
-
setting:
|
|
7875
|
-
});
|
|
7876
|
-
var MixImageEdit =
|
|
7877
|
-
instruction:
|
|
7878
|
-
imageUrls:
|
|
7879
|
-
});
|
|
7880
|
-
var MixRequest =
|
|
7881
|
-
mediaType:
|
|
7882
|
-
originalUrl:
|
|
8064
|
+
var MixAxes = z38.object({
|
|
8065
|
+
product: z38.boolean(),
|
|
8066
|
+
person: z38.boolean(),
|
|
8067
|
+
setting: z38.boolean()
|
|
8068
|
+
});
|
|
8069
|
+
var MixReferences = z38.object({
|
|
8070
|
+
product: z38.array(PublicHttpsUrl).max(3, "at most 3 product references"),
|
|
8071
|
+
person: z38.array(PublicHttpsUrl).max(3, "at most 3 person references"),
|
|
8072
|
+
setting: z38.array(PublicHttpsUrl).max(3, "at most 3 setting references")
|
|
8073
|
+
});
|
|
8074
|
+
var MixImageEdit = z38.object({
|
|
8075
|
+
instruction: z38.string().min(1).max(600),
|
|
8076
|
+
imageUrls: z38.array(MixImageUrl).min(1).max(3)
|
|
8077
|
+
});
|
|
8078
|
+
var MixRequest = z38.object({
|
|
8079
|
+
mediaType: z38.enum(MEDIA_TYPES),
|
|
8080
|
+
originalUrl: z38.union([
|
|
7883
8081
|
PublicHttpsUrl,
|
|
7884
8082
|
InternalAssetPath
|
|
7885
8083
|
]),
|
|
7886
8084
|
axes: MixAxes,
|
|
7887
8085
|
references: MixReferences,
|
|
7888
|
-
personText:
|
|
7889
|
-
settingText:
|
|
8086
|
+
personText: z38.string().max(500).optional(),
|
|
8087
|
+
settingText: z38.string().max(500).optional(),
|
|
7890
8088
|
/**
|
|
7891
8089
|
* Optional public URL of a user-uploaded audio file (mp3/wav). Wan Video
|
|
7892
8090
|
* Edit has no audio input and the mix never lipsyncs: the Wan output is
|
|
@@ -7895,11 +8093,11 @@ var MixRequest = z37.object({
|
|
|
7895
8093
|
* (mute + overlay). No per-scene slicing, no `reference_audio_urls`.
|
|
7896
8094
|
*/
|
|
7897
8095
|
audioUrl: PublicHttpsUrl.optional(),
|
|
7898
|
-
resolution:
|
|
7899
|
-
originalDurationSec:
|
|
8096
|
+
resolution: z38.enum(RESOLUTIONS).default("720p"),
|
|
8097
|
+
originalDurationSec: z38.number().positive().max(600).optional(),
|
|
7900
8098
|
image: MixImageEdit.optional(),
|
|
7901
|
-
sourceTaskId:
|
|
7902
|
-
sourceCreativeId:
|
|
8099
|
+
sourceTaskId: z38.string().max(64).optional(),
|
|
8100
|
+
sourceCreativeId: z38.string().max(64).optional()
|
|
7903
8101
|
}).superRefine((req, ctx) => {
|
|
7904
8102
|
if (req.mediaType === "image") {
|
|
7905
8103
|
if (!req.image) {
|
|
@@ -7933,18 +8131,18 @@ var MixRequest = z37.object({
|
|
|
7933
8131
|
});
|
|
7934
8132
|
}
|
|
7935
8133
|
});
|
|
7936
|
-
var ReferenceMap =
|
|
7937
|
-
product:
|
|
7938
|
-
start:
|
|
7939
|
-
end:
|
|
8134
|
+
var ReferenceMap = z38.object({
|
|
8135
|
+
product: z38.object({
|
|
8136
|
+
start: z38.number().int(),
|
|
8137
|
+
end: z38.number().int()
|
|
7940
8138
|
}).optional(),
|
|
7941
|
-
person:
|
|
7942
|
-
start:
|
|
7943
|
-
end:
|
|
8139
|
+
person: z38.object({
|
|
8140
|
+
start: z38.number().int(),
|
|
8141
|
+
end: z38.number().int()
|
|
7944
8142
|
}).optional(),
|
|
7945
|
-
setting:
|
|
7946
|
-
start:
|
|
7947
|
-
end:
|
|
8143
|
+
setting: z38.object({
|
|
8144
|
+
start: z38.number().int(),
|
|
8145
|
+
end: z38.number().int()
|
|
7948
8146
|
}).optional()
|
|
7949
8147
|
});
|
|
7950
8148
|
var SCENE_CLIP_TYPES = [
|
|
@@ -7960,39 +8158,39 @@ var INSTANCE_CARDINALITIES = [
|
|
|
7960
8158
|
"multiple_same",
|
|
7961
8159
|
"multiple_distinct"
|
|
7962
8160
|
];
|
|
7963
|
-
var SceneSegment =
|
|
7964
|
-
index:
|
|
7965
|
-
startTs:
|
|
7966
|
-
endTs:
|
|
7967
|
-
seedanceDuration:
|
|
8161
|
+
var SceneSegment = z38.object({
|
|
8162
|
+
index: z38.number().int().positive(),
|
|
8163
|
+
startTs: z38.number().nonnegative(),
|
|
8164
|
+
endTs: z38.number().positive(),
|
|
8165
|
+
seedanceDuration: z38.number().int().min(4).max(15),
|
|
7968
8166
|
// Pass 1 / Pass 2 outputs (all optional — populated when the rich analyzer
|
|
7969
8167
|
// runs and empty on fallback).
|
|
7970
|
-
action:
|
|
7971
|
-
motion:
|
|
7972
|
-
clipType:
|
|
7973
|
-
hasCharacter:
|
|
7974
|
-
faceVisible:
|
|
8168
|
+
action: z38.string().optional(),
|
|
8169
|
+
motion: z38.string().optional(),
|
|
8170
|
+
clipType: z38.enum(SCENE_CLIP_TYPES).optional(),
|
|
8171
|
+
hasCharacter: z38.boolean().optional(),
|
|
8172
|
+
faceVisible: z38.boolean().optional(),
|
|
7975
8173
|
// Pass 2 refinement — rich per-scene metadata used by buildMixPrompt.
|
|
7976
|
-
startState:
|
|
7977
|
-
endState:
|
|
7978
|
-
cameraMovement:
|
|
7979
|
-
emotion:
|
|
7980
|
-
productPlacement:
|
|
7981
|
-
productInteraction:
|
|
7982
|
-
settingDescription:
|
|
7983
|
-
dialogue:
|
|
8174
|
+
startState: z38.string().optional(),
|
|
8175
|
+
endState: z38.string().optional(),
|
|
8176
|
+
cameraMovement: z38.string().optional(),
|
|
8177
|
+
emotion: z38.string().optional(),
|
|
8178
|
+
productPlacement: z38.string().optional(),
|
|
8179
|
+
productInteraction: z38.string().optional(),
|
|
8180
|
+
settingDescription: z38.string().optional(),
|
|
8181
|
+
dialogue: z38.string().optional(),
|
|
7984
8182
|
// Main vs extras / noise (our addition on top of the script's schema).
|
|
7985
|
-
mainPersonDescription:
|
|
7986
|
-
mainProductDescription:
|
|
7987
|
-
personInstances:
|
|
7988
|
-
productInstances:
|
|
8183
|
+
mainPersonDescription: z38.string().optional(),
|
|
8184
|
+
mainProductDescription: z38.string().optional(),
|
|
8185
|
+
personInstances: z38.enum(INSTANCE_CARDINALITIES).optional(),
|
|
8186
|
+
productInstances: z38.enum(INSTANCE_CARDINALITIES).optional(),
|
|
7989
8187
|
/**
|
|
7990
8188
|
* Seconds of prev-clipType content absorbed at the start of this scene by
|
|
7991
8189
|
* Pass 2.7's clipType boundary margin. When > 0 and the scene is a
|
|
7992
8190
|
* pass-through (no Seedance), the concatenator prepends a held-still
|
|
7993
8191
|
* preamble (nano-banana-swapped first frame) to hide the leak.
|
|
7994
8192
|
*/
|
|
7995
|
-
absorbedMarginSec:
|
|
8193
|
+
absorbedMarginSec: z38.number().nonnegative().optional(),
|
|
7996
8194
|
/**
|
|
7997
8195
|
* True when the scene shows post-production graphics baked into the frame:
|
|
7998
8196
|
* caption/subtitle text, infographic overlay cards, or inset
|
|
@@ -8001,33 +8199,39 @@ var SceneSegment = z37.object({
|
|
|
8001
8199
|
* (mentioning nonexistent overlays gives the generator license to
|
|
8002
8200
|
* fabricate them).
|
|
8003
8201
|
*/
|
|
8004
|
-
hasOverlayGraphics:
|
|
8202
|
+
hasOverlayGraphics: z38.boolean().optional()
|
|
8005
8203
|
});
|
|
8006
|
-
var MixSceneResult =
|
|
8007
|
-
index:
|
|
8008
|
-
status:
|
|
8204
|
+
var MixSceneResult = z38.object({
|
|
8205
|
+
index: z38.number().int().positive(),
|
|
8206
|
+
status: z38.enum([
|
|
8009
8207
|
"queued",
|
|
8010
8208
|
"submitted",
|
|
8011
8209
|
"done",
|
|
8012
8210
|
"failed"
|
|
8013
8211
|
]),
|
|
8014
|
-
seedanceTaskId:
|
|
8015
|
-
videoUrl:
|
|
8016
|
-
error:
|
|
8017
|
-
|
|
8018
|
-
|
|
8019
|
-
|
|
8020
|
-
|
|
8212
|
+
seedanceTaskId: z38.string().optional(),
|
|
8213
|
+
videoUrl: z38.string().url().optional(),
|
|
8214
|
+
error: z38.string().optional(),
|
|
8215
|
+
degradedReason: MixDegradationReasonSchema.optional()
|
|
8216
|
+
});
|
|
8217
|
+
var MixResult = z38.object({
|
|
8218
|
+
mixTaskId: z38.string().uuid(),
|
|
8219
|
+
status: z38.enum([
|
|
8021
8220
|
"queued",
|
|
8022
8221
|
"processing",
|
|
8023
8222
|
"done",
|
|
8024
8223
|
"error"
|
|
8025
8224
|
]),
|
|
8026
|
-
resultUrl:
|
|
8027
|
-
scenes:
|
|
8028
|
-
failReason:
|
|
8225
|
+
resultUrl: z38.string().url().optional(),
|
|
8226
|
+
scenes: z38.array(MixSceneResult).default([]),
|
|
8227
|
+
failReason: z38.string().optional(),
|
|
8228
|
+
degradedReason: MixDegradationReasonSchema.optional()
|
|
8029
8229
|
});
|
|
8030
8230
|
|
|
8231
|
+
// ../../packages/types/src/mix-cost.ts
|
|
8232
|
+
var MAX_QA_RETRIES_PER_SCENE_UPPER_BOUND = 1;
|
|
8233
|
+
var MAX_BILLABLE_GENERATIONS_PER_BLOCK = 1 + MAX_QA_RETRIES_PER_SCENE_UPPER_BOUND;
|
|
8234
|
+
|
|
8031
8235
|
// ../../packages/types/src/product-delivery-format.ts
|
|
8032
8236
|
var DELIVERY_FAMILIES = [
|
|
8033
8237
|
"solid_dose",
|
|
@@ -8116,41 +8320,41 @@ var DECLARATION = DELIVERY_FAMILIES.map((family) => [
|
|
|
8116
8320
|
]);
|
|
8117
8321
|
|
|
8118
8322
|
// ../../packages/types/src/task-type.ts
|
|
8119
|
-
import { z as
|
|
8120
|
-
var TaskTypeSchema =
|
|
8323
|
+
import { z as z39 } from "zod";
|
|
8324
|
+
var TaskTypeSchema = z39.enum([
|
|
8121
8325
|
"product",
|
|
8122
8326
|
"service"
|
|
8123
8327
|
]);
|
|
8124
8328
|
|
|
8125
8329
|
// ../../packages/types/src/landing-regeneration.ts
|
|
8126
|
-
import { z as
|
|
8127
|
-
var LandingRegenerationStatus =
|
|
8330
|
+
import { z as z40 } from "zod";
|
|
8331
|
+
var LandingRegenerationStatus = z40.enum([
|
|
8128
8332
|
"queued",
|
|
8129
8333
|
"processing",
|
|
8130
8334
|
"published",
|
|
8131
8335
|
"failed"
|
|
8132
8336
|
]);
|
|
8133
|
-
var RegenerateLandingRoleResponse =
|
|
8134
|
-
ok:
|
|
8337
|
+
var RegenerateLandingRoleResponse = z40.object({
|
|
8338
|
+
ok: z40.literal(true),
|
|
8135
8339
|
role: LandingRole,
|
|
8136
|
-
promptIndex:
|
|
8137
|
-
operationId:
|
|
8340
|
+
promptIndex: z40.number().int().nonnegative(),
|
|
8341
|
+
operationId: z40.string().min(1)
|
|
8138
8342
|
});
|
|
8139
|
-
var LandingRegenerationStatusResponse =
|
|
8140
|
-
ok:
|
|
8141
|
-
operationId:
|
|
8343
|
+
var LandingRegenerationStatusResponse = z40.object({
|
|
8344
|
+
ok: z40.literal(true),
|
|
8345
|
+
operationId: z40.string().min(1),
|
|
8142
8346
|
role: LandingRole,
|
|
8143
8347
|
status: LandingRegenerationStatus,
|
|
8144
|
-
refunded:
|
|
8145
|
-
failureReason:
|
|
8146
|
-
publishedAssetKey:
|
|
8147
|
-
updatedAt:
|
|
8348
|
+
refunded: z40.boolean(),
|
|
8349
|
+
failureReason: z40.string().nullable(),
|
|
8350
|
+
publishedAssetKey: z40.string().nullable(),
|
|
8351
|
+
updatedAt: z40.string().datetime()
|
|
8148
8352
|
});
|
|
8149
8353
|
|
|
8150
8354
|
// ../../packages/types/src/video-edit.ts
|
|
8151
|
-
import { z as
|
|
8355
|
+
import { z as z41 } from "zod";
|
|
8152
8356
|
var MAX_SEGMENT_SECONDS = 3;
|
|
8153
|
-
var VideoEditStatusSchema =
|
|
8357
|
+
var VideoEditStatusSchema = z41.enum([
|
|
8154
8358
|
"pending",
|
|
8155
8359
|
"processing",
|
|
8156
8360
|
"assembling",
|
|
@@ -8159,9 +8363,9 @@ var VideoEditStatusSchema = z40.enum([
|
|
|
8159
8363
|
"discarded",
|
|
8160
8364
|
"failed"
|
|
8161
8365
|
]);
|
|
8162
|
-
var VideoEditTimeSectionSchema =
|
|
8163
|
-
startSec:
|
|
8164
|
-
endSec:
|
|
8366
|
+
var VideoEditTimeSectionSchema = z41.object({
|
|
8367
|
+
startSec: z41.number().min(0),
|
|
8368
|
+
endSec: z41.number().positive()
|
|
8165
8369
|
}).refine((v) => v.endSec > v.startSec, {
|
|
8166
8370
|
message: "endSec must be after startSec",
|
|
8167
8371
|
path: [
|
|
@@ -8173,45 +8377,45 @@ var VideoEditTimeSectionSchema = z40.object({
|
|
|
8173
8377
|
"endSec"
|
|
8174
8378
|
]
|
|
8175
8379
|
});
|
|
8176
|
-
var VideoEditRequestSchema =
|
|
8177
|
-
comment:
|
|
8178
|
-
sections:
|
|
8380
|
+
var VideoEditRequestSchema = z41.object({
|
|
8381
|
+
comment: z41.string().trim().min(1).max(2e3),
|
|
8382
|
+
sections: z41.array(VideoEditTimeSectionSchema).min(1).optional()
|
|
8179
8383
|
});
|
|
8180
|
-
var VideoEditModeSchema =
|
|
8384
|
+
var VideoEditModeSchema = z41.enum([
|
|
8181
8385
|
"wan_edit",
|
|
8182
8386
|
"regenerate"
|
|
8183
8387
|
]);
|
|
8184
|
-
var VideoRegenerationEngineSchema =
|
|
8388
|
+
var VideoRegenerationEngineSchema = z41.enum([
|
|
8185
8389
|
"free_text",
|
|
8186
8390
|
"free_image",
|
|
8187
8391
|
"single_call",
|
|
8188
8392
|
"multi_shot"
|
|
8189
8393
|
]);
|
|
8190
|
-
var VideoRegenerationRouteFingerprintSchema =
|
|
8191
|
-
var RegenerateEditRequestSchema =
|
|
8192
|
-
angleKey:
|
|
8193
|
-
comment:
|
|
8394
|
+
var VideoRegenerationRouteFingerprintSchema = z41.string().trim().min(1).max(128);
|
|
8395
|
+
var RegenerateEditRequestSchema = z41.object({
|
|
8396
|
+
angleKey: z41.string().min(1),
|
|
8397
|
+
comment: z41.string().min(1).max(2e3)
|
|
8194
8398
|
});
|
|
8195
8399
|
|
|
8196
8400
|
// ../../packages/types/src/uploaded-asset.ts
|
|
8197
|
-
import { z as
|
|
8198
|
-
var UploadedAssetMediaKind =
|
|
8401
|
+
import { z as z42 } from "zod";
|
|
8402
|
+
var UploadedAssetMediaKind = z42.enum([
|
|
8199
8403
|
"image",
|
|
8200
8404
|
"video"
|
|
8201
8405
|
]);
|
|
8202
|
-
var UploadedAssetSessionMediaKind =
|
|
8406
|
+
var UploadedAssetSessionMediaKind = z42.enum([
|
|
8203
8407
|
"image",
|
|
8204
8408
|
"video",
|
|
8205
8409
|
"mixed"
|
|
8206
8410
|
]);
|
|
8207
|
-
var UploadedAssetRole =
|
|
8411
|
+
var UploadedAssetRole = z42.enum([
|
|
8208
8412
|
"media",
|
|
8209
8413
|
"thumbnail"
|
|
8210
8414
|
]);
|
|
8211
8415
|
var UPLOADED_ASSET_MAX_FILES = 10;
|
|
8212
8416
|
var UPLOADED_ASSET_IMAGE_MAX_FILES = 5;
|
|
8213
|
-
var UploadedAssetId =
|
|
8214
|
-
var HttpsUrl =
|
|
8417
|
+
var UploadedAssetId = z42.string().uuid();
|
|
8418
|
+
var HttpsUrl = z42.string().url().refine((value) => {
|
|
8215
8419
|
try {
|
|
8216
8420
|
const url = new URL(value);
|
|
8217
8421
|
return url.protocol === "https:" && !url.username && !url.password;
|
|
@@ -8219,23 +8423,23 @@ var HttpsUrl = z41.string().url().refine((value) => {
|
|
|
8219
8423
|
return false;
|
|
8220
8424
|
}
|
|
8221
8425
|
}, "URL must use https");
|
|
8222
|
-
var UtcDateTime =
|
|
8426
|
+
var UtcDateTime = z42.string().datetime().refine((value) => {
|
|
8223
8427
|
const parts = /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})(?:\.\d+)?Z$/.exec(value);
|
|
8224
8428
|
const timestamp = Date.parse(value);
|
|
8225
8429
|
if (!parts || Number.isNaN(timestamp)) return false;
|
|
8226
8430
|
const date = new Date(timestamp);
|
|
8227
8431
|
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]);
|
|
8228
8432
|
}, "datetime must be a valid UTC calendar instant");
|
|
8229
|
-
var UploadedAssetRef =
|
|
8433
|
+
var UploadedAssetRef = z42.object({
|
|
8230
8434
|
assetId: UploadedAssetId
|
|
8231
8435
|
}).strict();
|
|
8232
8436
|
var UploadedAssetCompletion = UploadedAssetRef.extend({
|
|
8233
8437
|
mediaKind: UploadedAssetMediaKind,
|
|
8234
8438
|
assetRole: UploadedAssetRole
|
|
8235
8439
|
}).strict();
|
|
8236
|
-
var UploadedAssetMetadata =
|
|
8440
|
+
var UploadedAssetMetadata = z42.object({
|
|
8237
8441
|
assetId: UploadedAssetId,
|
|
8238
|
-
ownerId:
|
|
8442
|
+
ownerId: z42.string().min(1),
|
|
8239
8443
|
mediaKind: UploadedAssetMediaKind,
|
|
8240
8444
|
assetRole: UploadedAssetRole,
|
|
8241
8445
|
createdAt: UtcDateTime,
|
|
@@ -8243,7 +8447,7 @@ var UploadedAssetMetadata = z41.object({
|
|
|
8243
8447
|
}).strict().superRefine((value, ctx) => {
|
|
8244
8448
|
if (Date.parse(value.expiresAt) <= Date.parse(value.createdAt)) {
|
|
8245
8449
|
ctx.addIssue({
|
|
8246
|
-
code:
|
|
8450
|
+
code: z42.ZodIssueCode.custom,
|
|
8247
8451
|
path: [
|
|
8248
8452
|
"expiresAt"
|
|
8249
8453
|
],
|
|
@@ -8251,27 +8455,27 @@ var UploadedAssetMetadata = z41.object({
|
|
|
8251
8455
|
});
|
|
8252
8456
|
}
|
|
8253
8457
|
});
|
|
8254
|
-
var MediaSourceRef =
|
|
8458
|
+
var MediaSourceRef = z42.object({
|
|
8255
8459
|
url: HttpsUrl.optional(),
|
|
8256
|
-
path:
|
|
8460
|
+
path: z42.string().min(1).optional(),
|
|
8257
8461
|
assetId: UploadedAssetId.optional()
|
|
8258
8462
|
}).strict().superRefine((value, ctx) => {
|
|
8259
8463
|
const sourceCount = Number(value.url !== void 0) + Number(value.path !== void 0) + Number(value.assetId !== void 0);
|
|
8260
8464
|
if (sourceCount !== 1) {
|
|
8261
8465
|
ctx.addIssue({
|
|
8262
|
-
code:
|
|
8466
|
+
code: z42.ZodIssueCode.custom,
|
|
8263
8467
|
message: "media source needs exactly one of url, path, or assetId"
|
|
8264
8468
|
});
|
|
8265
8469
|
}
|
|
8266
8470
|
});
|
|
8267
|
-
var UploadedAssetSessionRequest =
|
|
8471
|
+
var UploadedAssetSessionRequest = z42.object({
|
|
8268
8472
|
mediaKind: UploadedAssetSessionMediaKind,
|
|
8269
8473
|
assetRole: UploadedAssetRole.default("media"),
|
|
8270
|
-
maxFiles:
|
|
8474
|
+
maxFiles: z42.number().int().min(1).max(UPLOADED_ASSET_MAX_FILES).optional()
|
|
8271
8475
|
}).strict().superRefine((value, ctx) => {
|
|
8272
8476
|
if (value.mediaKind === "mixed" && value.assetRole === "thumbnail") {
|
|
8273
8477
|
ctx.addIssue({
|
|
8274
|
-
code:
|
|
8478
|
+
code: z42.ZodIssueCode.custom,
|
|
8275
8479
|
path: [
|
|
8276
8480
|
"mediaKind"
|
|
8277
8481
|
],
|
|
@@ -8280,7 +8484,7 @@ var UploadedAssetSessionRequest = z41.object({
|
|
|
8280
8484
|
}
|
|
8281
8485
|
if (value.mediaKind === "video" && value.maxFiles !== void 0 && value.maxFiles !== 1) {
|
|
8282
8486
|
ctx.addIssue({
|
|
8283
|
-
code:
|
|
8487
|
+
code: z42.ZodIssueCode.custom,
|
|
8284
8488
|
path: [
|
|
8285
8489
|
"maxFiles"
|
|
8286
8490
|
],
|
|
@@ -8289,7 +8493,7 @@ var UploadedAssetSessionRequest = z41.object({
|
|
|
8289
8493
|
}
|
|
8290
8494
|
if (value.mediaKind === "image" && (value.maxFiles ?? 1) > UPLOADED_ASSET_IMAGE_MAX_FILES) {
|
|
8291
8495
|
ctx.addIssue({
|
|
8292
|
-
code:
|
|
8496
|
+
code: z42.ZodIssueCode.custom,
|
|
8293
8497
|
path: [
|
|
8294
8498
|
"maxFiles"
|
|
8295
8499
|
],
|
|
@@ -8298,7 +8502,7 @@ var UploadedAssetSessionRequest = z41.object({
|
|
|
8298
8502
|
}
|
|
8299
8503
|
if (value.assetRole === "thumbnail" && value.mediaKind !== "image") {
|
|
8300
8504
|
ctx.addIssue({
|
|
8301
|
-
code:
|
|
8505
|
+
code: z42.ZodIssueCode.custom,
|
|
8302
8506
|
path: [
|
|
8303
8507
|
"mediaKind"
|
|
8304
8508
|
],
|
|
@@ -8306,32 +8510,32 @@ var UploadedAssetSessionRequest = z41.object({
|
|
|
8306
8510
|
});
|
|
8307
8511
|
}
|
|
8308
8512
|
});
|
|
8309
|
-
var UploadedAssetSessionStatus =
|
|
8310
|
-
|
|
8311
|
-
status:
|
|
8312
|
-
assets:
|
|
8513
|
+
var UploadedAssetSessionStatus = z42.discriminatedUnion("status", [
|
|
8514
|
+
z42.object({
|
|
8515
|
+
status: z42.literal("pending"),
|
|
8516
|
+
assets: z42.array(UploadedAssetCompletion).length(0)
|
|
8313
8517
|
}).strict(),
|
|
8314
|
-
|
|
8315
|
-
status:
|
|
8316
|
-
assets:
|
|
8518
|
+
z42.object({
|
|
8519
|
+
status: z42.literal("uploaded"),
|
|
8520
|
+
assets: z42.array(UploadedAssetCompletion).min(1).max(UPLOADED_ASSET_MAX_FILES)
|
|
8317
8521
|
}).strict()
|
|
8318
8522
|
]);
|
|
8319
|
-
var UploadedAssetAdoptionRequest =
|
|
8523
|
+
var UploadedAssetAdoptionRequest = z42.object({
|
|
8320
8524
|
assetId: UploadedAssetId,
|
|
8321
|
-
ownerId:
|
|
8322
|
-
consumer:
|
|
8323
|
-
idempotencyKey:
|
|
8525
|
+
ownerId: z42.string().min(1),
|
|
8526
|
+
consumer: z42.string().min(1).max(100),
|
|
8527
|
+
idempotencyKey: z42.string().min(1).max(255)
|
|
8324
8528
|
}).strict();
|
|
8325
8529
|
|
|
8326
8530
|
// ../../packages/types/src/dropshipping-project.ts
|
|
8327
|
-
import { z as
|
|
8328
|
-
var DropshippingProjectStatusSchema =
|
|
8531
|
+
import { z as z43 } from "zod";
|
|
8532
|
+
var DropshippingProjectStatusSchema = z43.enum([
|
|
8329
8533
|
"active",
|
|
8330
8534
|
"completed",
|
|
8331
8535
|
"archived"
|
|
8332
8536
|
]);
|
|
8333
8537
|
var DROPSHIPPING_LAUNCH_REWARD_CAMPAIGN = "dropshipping-launch-v1";
|
|
8334
|
-
var DropshippingRewardMilestoneSchema =
|
|
8538
|
+
var DropshippingRewardMilestoneSchema = z43.enum([
|
|
8335
8539
|
"product_selected",
|
|
8336
8540
|
"costing_saved",
|
|
8337
8541
|
"product_media_ready",
|
|
@@ -8346,21 +8550,21 @@ var DROPSHIPPING_LAUNCH_REWARD_CREDITS = {
|
|
|
8346
8550
|
campaign_created: 200
|
|
8347
8551
|
};
|
|
8348
8552
|
var DROPSHIPPING_LAUNCH_REWARD_TOTAL_CREDITS = Object.values(DROPSHIPPING_LAUNCH_REWARD_CREDITS).reduce((sum, credits) => sum + credits, 0);
|
|
8349
|
-
var DropshippingProjectRewardMilestoneStateSchema =
|
|
8553
|
+
var DropshippingProjectRewardMilestoneStateSchema = z43.object({
|
|
8350
8554
|
milestone: DropshippingRewardMilestoneSchema,
|
|
8351
|
-
credits:
|
|
8352
|
-
available:
|
|
8353
|
-
earned:
|
|
8354
|
-
grantId:
|
|
8355
|
-
grantedAt:
|
|
8356
|
-
});
|
|
8357
|
-
var DropshippingProjectRewardProgressSchema =
|
|
8358
|
-
campaignKey:
|
|
8359
|
-
totalCredits:
|
|
8360
|
-
earnedCredits:
|
|
8361
|
-
milestones:
|
|
8362
|
-
});
|
|
8363
|
-
var DropshippingProjectPhaseSchema =
|
|
8555
|
+
credits: z43.number().int().positive(),
|
|
8556
|
+
available: z43.boolean().optional(),
|
|
8557
|
+
earned: z43.boolean(),
|
|
8558
|
+
grantId: z43.string().uuid().nullable(),
|
|
8559
|
+
grantedAt: z43.string().datetime().nullable()
|
|
8560
|
+
});
|
|
8561
|
+
var DropshippingProjectRewardProgressSchema = z43.object({
|
|
8562
|
+
campaignKey: z43.literal(DROPSHIPPING_LAUNCH_REWARD_CAMPAIGN),
|
|
8563
|
+
totalCredits: z43.number().int().positive(),
|
|
8564
|
+
earnedCredits: z43.number().int().nonnegative(),
|
|
8565
|
+
milestones: z43.array(DropshippingProjectRewardMilestoneStateSchema).length(DropshippingRewardMilestoneSchema.options.length)
|
|
8566
|
+
});
|
|
8567
|
+
var DropshippingProjectPhaseSchema = z43.enum([
|
|
8364
8568
|
"research",
|
|
8365
8569
|
"costing",
|
|
8366
8570
|
"media",
|
|
@@ -8369,18 +8573,18 @@ var DropshippingProjectPhaseSchema = z42.enum([
|
|
|
8369
8573
|
"advertising",
|
|
8370
8574
|
"complete"
|
|
8371
8575
|
]);
|
|
8372
|
-
var DropshippingProjectCostingInputSchema =
|
|
8373
|
-
country:
|
|
8374
|
-
supplierCost:
|
|
8375
|
-
shippingCost:
|
|
8376
|
-
returnRate:
|
|
8377
|
-
cancellationRate:
|
|
8378
|
-
adsManagerCpa:
|
|
8379
|
-
desiredMargin:
|
|
8576
|
+
var DropshippingProjectCostingInputSchema = z43.object({
|
|
8577
|
+
country: z43.string().min(2).max(3),
|
|
8578
|
+
supplierCost: z43.number().finite().positive(),
|
|
8579
|
+
shippingCost: z43.number().finite().nonnegative(),
|
|
8580
|
+
returnRate: z43.number().finite().min(0).lt(1),
|
|
8581
|
+
cancellationRate: z43.number().finite().min(0).lt(1),
|
|
8582
|
+
adsManagerCpa: z43.number().finite().nonnegative(),
|
|
8583
|
+
desiredMargin: z43.number().finite().min(0).lt(1)
|
|
8380
8584
|
}).superRefine(({ returnRate, cancellationRate }, context) => {
|
|
8381
8585
|
if (returnRate + cancellationRate >= 1) {
|
|
8382
8586
|
context.addIssue({
|
|
8383
|
-
code:
|
|
8587
|
+
code: z43.ZodIssueCode.custom,
|
|
8384
8588
|
path: [
|
|
8385
8589
|
"cancellationRate"
|
|
8386
8590
|
],
|
|
@@ -8388,24 +8592,24 @@ var DropshippingProjectCostingInputSchema = z42.object({
|
|
|
8388
8592
|
});
|
|
8389
8593
|
}
|
|
8390
8594
|
});
|
|
8391
|
-
var DropshippingProjectCostingResultSchema =
|
|
8392
|
-
country:
|
|
8393
|
-
deliveryRate:
|
|
8394
|
-
adjustedShippingCost:
|
|
8395
|
-
adjustedCpa:
|
|
8396
|
-
totalCost:
|
|
8397
|
-
suggestedPrice:
|
|
8398
|
-
baseCommercialPrice:
|
|
8399
|
-
minimumSafePrice:
|
|
8400
|
-
commercialPrice:
|
|
8401
|
-
comparisonPrice:
|
|
8402
|
-
estimatedProfit:
|
|
8403
|
-
actualMargin:
|
|
8404
|
-
breakEvenAdsManagerCpa:
|
|
8405
|
-
wasPriceAutoAdjusted:
|
|
8406
|
-
profitabilityStatus:
|
|
8407
|
-
});
|
|
8408
|
-
var HttpUrlSchema =
|
|
8595
|
+
var DropshippingProjectCostingResultSchema = z43.object({
|
|
8596
|
+
country: z43.string().min(2).max(3),
|
|
8597
|
+
deliveryRate: z43.number().finite(),
|
|
8598
|
+
adjustedShippingCost: z43.number().finite(),
|
|
8599
|
+
adjustedCpa: z43.number().finite(),
|
|
8600
|
+
totalCost: z43.number().finite(),
|
|
8601
|
+
suggestedPrice: z43.number().finite(),
|
|
8602
|
+
baseCommercialPrice: z43.number().finite(),
|
|
8603
|
+
minimumSafePrice: z43.number().finite(),
|
|
8604
|
+
commercialPrice: z43.number().finite(),
|
|
8605
|
+
comparisonPrice: z43.number().finite(),
|
|
8606
|
+
estimatedProfit: z43.number().finite(),
|
|
8607
|
+
actualMargin: z43.number().finite(),
|
|
8608
|
+
breakEvenAdsManagerCpa: z43.number().finite(),
|
|
8609
|
+
wasPriceAutoAdjusted: z43.boolean(),
|
|
8610
|
+
profitabilityStatus: z43.literal("healthy")
|
|
8611
|
+
});
|
|
8612
|
+
var HttpUrlSchema = z43.string().url().max(2e3).refine((value) => {
|
|
8409
8613
|
try {
|
|
8410
8614
|
return [
|
|
8411
8615
|
"http:",
|
|
@@ -8415,124 +8619,124 @@ var HttpUrlSchema = z42.string().url().max(2e3).refine((value) => {
|
|
|
8415
8619
|
return false;
|
|
8416
8620
|
}
|
|
8417
8621
|
}, "Product URL must use HTTP(S)");
|
|
8418
|
-
var ResearchCandidateOriginSchema =
|
|
8419
|
-
type:
|
|
8420
|
-
runId:
|
|
8421
|
-
candidateSourceId:
|
|
8422
|
-
});
|
|
8423
|
-
var TaskOriginSchema =
|
|
8424
|
-
type:
|
|
8425
|
-
taskId:
|
|
8426
|
-
});
|
|
8427
|
-
var ManualOriginSchema =
|
|
8428
|
-
type:
|
|
8429
|
-
clientRequestId:
|
|
8430
|
-
country:
|
|
8431
|
-
});
|
|
8432
|
-
var CreateDropshippingProjectRequestSchema =
|
|
8433
|
-
origin:
|
|
8622
|
+
var ResearchCandidateOriginSchema = z43.object({
|
|
8623
|
+
type: z43.literal("research_candidate"),
|
|
8624
|
+
runId: z43.string().uuid(),
|
|
8625
|
+
candidateSourceId: z43.string().min(1).max(160)
|
|
8626
|
+
});
|
|
8627
|
+
var TaskOriginSchema = z43.object({
|
|
8628
|
+
type: z43.literal("task"),
|
|
8629
|
+
taskId: z43.string().uuid()
|
|
8630
|
+
});
|
|
8631
|
+
var ManualOriginSchema = z43.object({
|
|
8632
|
+
type: z43.literal("manual"),
|
|
8633
|
+
clientRequestId: z43.string().uuid(),
|
|
8634
|
+
country: z43.string().min(2).max(3).optional()
|
|
8635
|
+
});
|
|
8636
|
+
var CreateDropshippingProjectRequestSchema = z43.object({
|
|
8637
|
+
origin: z43.discriminatedUnion("type", [
|
|
8434
8638
|
ResearchCandidateOriginSchema,
|
|
8435
8639
|
TaskOriginSchema,
|
|
8436
8640
|
ManualOriginSchema
|
|
8437
8641
|
]),
|
|
8438
|
-
productName:
|
|
8642
|
+
productName: z43.string().trim().min(1).max(240).optional(),
|
|
8439
8643
|
productUrl: HttpUrlSchema.optional(),
|
|
8440
|
-
country:
|
|
8441
|
-
languageCode:
|
|
8644
|
+
country: z43.string().min(2).max(3).optional(),
|
|
8645
|
+
languageCode: z43.string().min(2).max(16).optional()
|
|
8442
8646
|
}).strict();
|
|
8443
|
-
var SaveDropshippingProjectCostingRequestSchema =
|
|
8647
|
+
var SaveDropshippingProjectCostingRequestSchema = z43.object({
|
|
8444
8648
|
input: DropshippingProjectCostingInputSchema,
|
|
8445
|
-
version:
|
|
8649
|
+
version: z43.number().int().positive()
|
|
8446
8650
|
}).strict();
|
|
8447
|
-
var DropshippingProjectNextActionSchema =
|
|
8448
|
-
|
|
8449
|
-
kind:
|
|
8450
|
-
href:
|
|
8651
|
+
var DropshippingProjectNextActionSchema = z43.discriminatedUnion("kind", [
|
|
8652
|
+
z43.object({
|
|
8653
|
+
kind: z43.literal("cost_product"),
|
|
8654
|
+
href: z43.string().min(1)
|
|
8451
8655
|
}),
|
|
8452
|
-
|
|
8453
|
-
kind:
|
|
8454
|
-
href:
|
|
8656
|
+
z43.object({
|
|
8657
|
+
kind: z43.literal("provide_product_media"),
|
|
8658
|
+
href: z43.string().min(1)
|
|
8455
8659
|
}),
|
|
8456
|
-
|
|
8457
|
-
kind:
|
|
8458
|
-
href:
|
|
8660
|
+
z43.object({
|
|
8661
|
+
kind: z43.literal("generate_creatives"),
|
|
8662
|
+
href: z43.string().min(1)
|
|
8459
8663
|
}),
|
|
8460
|
-
|
|
8461
|
-
kind:
|
|
8462
|
-
href:
|
|
8664
|
+
z43.object({
|
|
8665
|
+
kind: z43.literal("wait_for_creatives"),
|
|
8666
|
+
href: z43.string().min(1)
|
|
8463
8667
|
}),
|
|
8464
|
-
|
|
8465
|
-
kind:
|
|
8466
|
-
href:
|
|
8668
|
+
z43.object({
|
|
8669
|
+
kind: z43.literal("repair_creatives"),
|
|
8670
|
+
href: z43.string().min(1)
|
|
8467
8671
|
}),
|
|
8468
|
-
|
|
8469
|
-
kind:
|
|
8470
|
-
href:
|
|
8672
|
+
z43.object({
|
|
8673
|
+
kind: z43.literal("complete_creative_set"),
|
|
8674
|
+
href: z43.string().min(1)
|
|
8471
8675
|
}),
|
|
8472
|
-
|
|
8473
|
-
kind:
|
|
8474
|
-
href:
|
|
8676
|
+
z43.object({
|
|
8677
|
+
kind: z43.literal("connect_shopify"),
|
|
8678
|
+
href: z43.string().min(1)
|
|
8475
8679
|
}),
|
|
8476
|
-
|
|
8477
|
-
kind:
|
|
8478
|
-
href:
|
|
8680
|
+
z43.object({
|
|
8681
|
+
kind: z43.literal("publish_storefront"),
|
|
8682
|
+
href: z43.string().min(1)
|
|
8479
8683
|
}),
|
|
8480
|
-
|
|
8481
|
-
kind:
|
|
8482
|
-
href:
|
|
8684
|
+
z43.object({
|
|
8685
|
+
kind: z43.literal("wait_for_storefront"),
|
|
8686
|
+
href: z43.string().min(1)
|
|
8483
8687
|
}),
|
|
8484
|
-
|
|
8485
|
-
kind:
|
|
8486
|
-
href:
|
|
8688
|
+
z43.object({
|
|
8689
|
+
kind: z43.literal("connect_meta"),
|
|
8690
|
+
href: z43.string().min(1)
|
|
8487
8691
|
}),
|
|
8488
|
-
|
|
8489
|
-
kind:
|
|
8490
|
-
href:
|
|
8692
|
+
z43.object({
|
|
8693
|
+
kind: z43.literal("create_meta_campaign"),
|
|
8694
|
+
href: z43.string().min(1)
|
|
8491
8695
|
}),
|
|
8492
|
-
|
|
8493
|
-
kind:
|
|
8494
|
-
href:
|
|
8696
|
+
z43.object({
|
|
8697
|
+
kind: z43.literal("repair_meta_campaign"),
|
|
8698
|
+
href: z43.string().min(1)
|
|
8495
8699
|
}),
|
|
8496
|
-
|
|
8497
|
-
kind:
|
|
8498
|
-
href:
|
|
8700
|
+
z43.object({
|
|
8701
|
+
kind: z43.literal("review_paused_campaign"),
|
|
8702
|
+
href: z43.string().min(1)
|
|
8499
8703
|
})
|
|
8500
8704
|
]);
|
|
8501
|
-
var DropshippingProjectResponseSchema =
|
|
8502
|
-
id:
|
|
8503
|
-
taskId:
|
|
8705
|
+
var DropshippingProjectResponseSchema = z43.object({
|
|
8706
|
+
id: z43.string().uuid(),
|
|
8707
|
+
taskId: z43.string().uuid().nullable(),
|
|
8504
8708
|
status: DropshippingProjectStatusSchema,
|
|
8505
|
-
originKey:
|
|
8506
|
-
researchRunId:
|
|
8507
|
-
candidateSourceId:
|
|
8508
|
-
productName:
|
|
8709
|
+
originKey: z43.string().min(1),
|
|
8710
|
+
researchRunId: z43.string().uuid().nullable(),
|
|
8711
|
+
candidateSourceId: z43.string().nullable(),
|
|
8712
|
+
productName: z43.string().nullable(),
|
|
8509
8713
|
productUrl: HttpUrlSchema.nullable(),
|
|
8510
|
-
referenceImageUrl:
|
|
8511
|
-
hasReferenceImage:
|
|
8512
|
-
targetCountry:
|
|
8513
|
-
languageCode:
|
|
8714
|
+
referenceImageUrl: z43.string().url().nullable(),
|
|
8715
|
+
hasReferenceImage: z43.boolean(),
|
|
8716
|
+
targetCountry: z43.string().nullable(),
|
|
8717
|
+
languageCode: z43.string().nullable(),
|
|
8514
8718
|
costingInput: DropshippingProjectCostingInputSchema.nullable(),
|
|
8515
8719
|
costingResult: DropshippingProjectCostingResultSchema.nullable(),
|
|
8516
|
-
costingVersion:
|
|
8517
|
-
version:
|
|
8720
|
+
costingVersion: z43.number().int().positive().nullable(),
|
|
8721
|
+
version: z43.number().int().positive(),
|
|
8518
8722
|
phase: DropshippingProjectPhaseSchema,
|
|
8519
8723
|
nextAction: DropshippingProjectNextActionSchema.nullable(),
|
|
8520
8724
|
rewards: DropshippingProjectRewardProgressSchema,
|
|
8521
|
-
createdAt:
|
|
8522
|
-
updatedAt:
|
|
8725
|
+
createdAt: z43.string().datetime(),
|
|
8726
|
+
updatedAt: z43.string().datetime()
|
|
8523
8727
|
});
|
|
8524
8728
|
|
|
8525
8729
|
// ../../packages/types/src/scene-subject-contract.ts
|
|
8526
|
-
import { z as
|
|
8527
|
-
var SceneSubjectContractSchema =
|
|
8528
|
-
maxPeople:
|
|
8529
|
-
minPeople:
|
|
8530
|
-
minDistinctPeople:
|
|
8531
|
-
maxHands:
|
|
8730
|
+
import { z as z44 } from "zod";
|
|
8731
|
+
var SceneSubjectContractSchema = z44.object({
|
|
8732
|
+
maxPeople: z44.number().int().min(1).max(10),
|
|
8733
|
+
minPeople: z44.number().int().min(0).max(10),
|
|
8734
|
+
minDistinctPeople: z44.number().int().min(0).max(10),
|
|
8735
|
+
maxHands: z44.number().int().min(0).max(20)
|
|
8532
8736
|
}).superRefine((value, ctx) => {
|
|
8533
8737
|
if (value.minPeople > value.maxPeople) {
|
|
8534
8738
|
ctx.addIssue({
|
|
8535
|
-
code:
|
|
8739
|
+
code: z44.ZodIssueCode.custom,
|
|
8536
8740
|
path: [
|
|
8537
8741
|
"minPeople"
|
|
8538
8742
|
],
|
|
@@ -8541,7 +8745,7 @@ var SceneSubjectContractSchema = z43.object({
|
|
|
8541
8745
|
}
|
|
8542
8746
|
if (value.minDistinctPeople > value.maxPeople) {
|
|
8543
8747
|
ctx.addIssue({
|
|
8544
|
-
code:
|
|
8748
|
+
code: z44.ZodIssueCode.custom,
|
|
8545
8749
|
path: [
|
|
8546
8750
|
"minDistinctPeople"
|
|
8547
8751
|
],
|
|
@@ -8550,7 +8754,7 @@ var SceneSubjectContractSchema = z43.object({
|
|
|
8550
8754
|
}
|
|
8551
8755
|
if (value.minDistinctPeople > value.minPeople) {
|
|
8552
8756
|
ctx.addIssue({
|
|
8553
|
-
code:
|
|
8757
|
+
code: z44.ZodIssueCode.custom,
|
|
8554
8758
|
path: [
|
|
8555
8759
|
"minDistinctPeople"
|
|
8556
8760
|
],
|
|
@@ -8560,7 +8764,7 @@ var SceneSubjectContractSchema = z43.object({
|
|
|
8560
8764
|
});
|
|
8561
8765
|
|
|
8562
8766
|
// ../../packages/types/src/service-creative.ts
|
|
8563
|
-
import { z as
|
|
8767
|
+
import { z as z45 } from "zod";
|
|
8564
8768
|
var SERVICE_CREATIVE_PUBLIC_PRESETS = [
|
|
8565
8769
|
"auto",
|
|
8566
8770
|
"ugc_dynamic",
|
|
@@ -8568,19 +8772,19 @@ var SERVICE_CREATIVE_PUBLIC_PRESETS = [
|
|
|
8568
8772
|
"cinematic",
|
|
8569
8773
|
"wild_card"
|
|
8570
8774
|
];
|
|
8571
|
-
var ServiceCreativePresetSchema =
|
|
8775
|
+
var ServiceCreativePresetSchema = z45.enum(SERVICE_CREATIVE_PUBLIC_PRESETS);
|
|
8572
8776
|
var SERVICE_REVIEW_MODES = [
|
|
8573
8777
|
"automatic",
|
|
8574
8778
|
"guided"
|
|
8575
8779
|
];
|
|
8576
|
-
var ServiceReviewModeSchema =
|
|
8780
|
+
var ServiceReviewModeSchema = z45.enum(SERVICE_REVIEW_MODES);
|
|
8577
8781
|
var SERVICE_HIGGSFIELD_MODES = [
|
|
8578
8782
|
"product_review",
|
|
8579
8783
|
"ugc_how_to",
|
|
8580
8784
|
"tv_spot",
|
|
8581
8785
|
"wild_card"
|
|
8582
8786
|
];
|
|
8583
|
-
var ServiceHiggsfieldModeSchema =
|
|
8787
|
+
var ServiceHiggsfieldModeSchema = z45.enum(SERVICE_HIGGSFIELD_MODES);
|
|
8584
8788
|
var SERVICE_MEDIA_ROLES = [
|
|
8585
8789
|
"screen_demo",
|
|
8586
8790
|
"ad_reference",
|
|
@@ -8588,20 +8792,20 @@ var SERVICE_MEDIA_ROLES = [
|
|
|
8588
8792
|
"presenter_reference",
|
|
8589
8793
|
"brand_asset"
|
|
8590
8794
|
];
|
|
8591
|
-
var ServiceMediaRoleSchema =
|
|
8592
|
-
var ServiceMediaAssetSchema =
|
|
8795
|
+
var ServiceMediaRoleSchema = z45.enum(SERVICE_MEDIA_ROLES);
|
|
8796
|
+
var ServiceMediaAssetSchema = z45.object({
|
|
8593
8797
|
role: ServiceMediaRoleSchema,
|
|
8594
|
-
s3Key:
|
|
8798
|
+
s3Key: z45.string().min(1),
|
|
8595
8799
|
/** HMAC ticket returned by the authenticated service-media presign route. */
|
|
8596
|
-
uploadToken:
|
|
8597
|
-
mimeType:
|
|
8598
|
-
durationSec:
|
|
8599
|
-
sourceName:
|
|
8800
|
+
uploadToken: z45.string().min(1).optional(),
|
|
8801
|
+
mimeType: z45.string().min(1),
|
|
8802
|
+
durationSec: z45.number().positive().max(30).optional(),
|
|
8803
|
+
sourceName: z45.string().max(255).optional()
|
|
8600
8804
|
}).superRefine((asset, ctx) => {
|
|
8601
8805
|
const isVideoRole = asset.role === "screen_demo" || asset.role === "ad_reference";
|
|
8602
8806
|
if (isVideoRole && !asset.mimeType.startsWith("video/")) {
|
|
8603
8807
|
ctx.addIssue({
|
|
8604
|
-
code:
|
|
8808
|
+
code: z45.ZodIssueCode.custom,
|
|
8605
8809
|
message: `${asset.role} requires video media`,
|
|
8606
8810
|
path: [
|
|
8607
8811
|
"mimeType"
|
|
@@ -8610,7 +8814,7 @@ var ServiceMediaAssetSchema = z44.object({
|
|
|
8610
8814
|
}
|
|
8611
8815
|
if (isVideoRole && asset.durationSec === void 0) {
|
|
8612
8816
|
ctx.addIssue({
|
|
8613
|
-
code:
|
|
8817
|
+
code: z45.ZodIssueCode.custom,
|
|
8614
8818
|
message: `${asset.role} requires durationSec`,
|
|
8615
8819
|
path: [
|
|
8616
8820
|
"durationSec"
|
|
@@ -8619,7 +8823,7 @@ var ServiceMediaAssetSchema = z44.object({
|
|
|
8619
8823
|
}
|
|
8620
8824
|
if (!isVideoRole && !asset.mimeType.startsWith("image/")) {
|
|
8621
8825
|
ctx.addIssue({
|
|
8622
|
-
code:
|
|
8826
|
+
code: z45.ZodIssueCode.custom,
|
|
8623
8827
|
message: `${asset.role} requires image media`,
|
|
8624
8828
|
path: [
|
|
8625
8829
|
"mimeType"
|
|
@@ -8627,13 +8831,13 @@ var ServiceMediaAssetSchema = z44.object({
|
|
|
8627
8831
|
});
|
|
8628
8832
|
}
|
|
8629
8833
|
});
|
|
8630
|
-
var ServiceMediaBundleSchema =
|
|
8631
|
-
assets:
|
|
8834
|
+
var ServiceMediaBundleSchema = z45.object({
|
|
8835
|
+
assets: z45.array(ServiceMediaAssetSchema).max(15).default([])
|
|
8632
8836
|
}).superRefine((bundle, ctx) => {
|
|
8633
8837
|
const adReferences = bundle.assets.filter((asset) => asset.role === "ad_reference");
|
|
8634
8838
|
if (adReferences.length > 5) {
|
|
8635
8839
|
ctx.addIssue({
|
|
8636
|
-
code:
|
|
8840
|
+
code: z45.ZodIssueCode.custom,
|
|
8637
8841
|
message: "A maximum of five ad_reference videos is allowed",
|
|
8638
8842
|
path: [
|
|
8639
8843
|
"assets"
|
|
@@ -8643,7 +8847,7 @@ var ServiceMediaBundleSchema = z44.object({
|
|
|
8643
8847
|
const screenDemos = bundle.assets.filter((asset) => asset.role === "screen_demo");
|
|
8644
8848
|
if (screenDemos.length > 3) {
|
|
8645
8849
|
ctx.addIssue({
|
|
8646
|
-
code:
|
|
8850
|
+
code: z45.ZodIssueCode.custom,
|
|
8647
8851
|
message: "A maximum of three screen_demo videos is allowed",
|
|
8648
8852
|
path: [
|
|
8649
8853
|
"assets"
|
|
@@ -8660,33 +8864,33 @@ var SERVICE_MEDIA_ANALYSIS_KINDS = [
|
|
|
8660
8864
|
"product",
|
|
8661
8865
|
"other"
|
|
8662
8866
|
];
|
|
8663
|
-
var ServiceMediaAnalysisKindSchema =
|
|
8664
|
-
var ServiceMediaAnalysisItemSchema =
|
|
8665
|
-
assetIndex:
|
|
8666
|
-
mediaType:
|
|
8867
|
+
var ServiceMediaAnalysisKindSchema = z45.enum(SERVICE_MEDIA_ANALYSIS_KINDS);
|
|
8868
|
+
var ServiceMediaAnalysisItemSchema = z45.object({
|
|
8869
|
+
assetIndex: z45.number().int().nonnegative(),
|
|
8870
|
+
mediaType: z45.enum([
|
|
8667
8871
|
"image",
|
|
8668
8872
|
"video"
|
|
8669
8873
|
]),
|
|
8670
8874
|
kind: ServiceMediaAnalysisKindSchema,
|
|
8671
|
-
description:
|
|
8672
|
-
tags:
|
|
8673
|
-
suggestedRoles:
|
|
8674
|
-
visiblePeople:
|
|
8675
|
-
ocrText:
|
|
8676
|
-
speechSummary:
|
|
8677
|
-
});
|
|
8678
|
-
var ServiceMediaAnalysisSchema =
|
|
8679
|
-
version:
|
|
8680
|
-
analyzedAt:
|
|
8681
|
-
pricingAction:
|
|
8682
|
-
assetFingerprint:
|
|
8875
|
+
description: z45.string().min(1).max(1200),
|
|
8876
|
+
tags: z45.array(z45.string().min(1).max(80)).max(12),
|
|
8877
|
+
suggestedRoles: z45.array(ServiceMediaRoleSchema).max(4),
|
|
8878
|
+
visiblePeople: z45.number().int().nonnegative().max(20).optional(),
|
|
8879
|
+
ocrText: z45.string().max(2e3).optional(),
|
|
8880
|
+
speechSummary: z45.string().max(2e3).optional()
|
|
8881
|
+
});
|
|
8882
|
+
var ServiceMediaAnalysisSchema = z45.object({
|
|
8883
|
+
version: z45.literal(1),
|
|
8884
|
+
analyzedAt: z45.string().datetime(),
|
|
8885
|
+
pricingAction: z45.literal("service-media-analysis"),
|
|
8886
|
+
assetFingerprint: z45.string().regex(/^[a-f0-9]{64}$/),
|
|
8683
8887
|
/** Language used for human-readable descriptions and tags. Optional for old checkpoints. */
|
|
8684
|
-
language:
|
|
8888
|
+
language: z45.enum([
|
|
8685
8889
|
"es",
|
|
8686
8890
|
"en",
|
|
8687
8891
|
"pt"
|
|
8688
8892
|
]).optional(),
|
|
8689
|
-
items:
|
|
8893
|
+
items: z45.array(ServiceMediaAnalysisItemSchema).max(15)
|
|
8690
8894
|
});
|
|
8691
8895
|
|
|
8692
8896
|
// ../../packages/types/src/productmaker-capability-registry.ts
|
|
@@ -8744,6 +8948,7 @@ function slot(name, required, mcp2, whatsapp = mcp2, source = "user") {
|
|
|
8744
8948
|
}
|
|
8745
8949
|
__name(slot, "slot");
|
|
8746
8950
|
var SERVER_SELECTED_OR_ARTIFACT_FIELDS = /* @__PURE__ */ new Set([
|
|
8951
|
+
"actionKind",
|
|
8747
8952
|
"actorId",
|
|
8748
8953
|
"adAccountId",
|
|
8749
8954
|
"advertiserId",
|
|
@@ -9202,7 +9407,7 @@ var PRODUCTMAKER_CAPABILITY_REGISTRY = deepFreeze([
|
|
|
9202
9407
|
], [
|
|
9203
9408
|
"creative"
|
|
9204
9409
|
]),
|
|
9205
|
-
defineCapability("generate_landing", "
|
|
9410
|
+
defineCapability("generate_landing", "67520417176396516cd5ad358dca39f70a5c4a74169eecd85987217226c12251", "credit_consuming", "creative", [
|
|
9206
9411
|
"credit_consuming",
|
|
9207
9412
|
"state_change"
|
|
9208
9413
|
], [
|
|
@@ -9247,12 +9452,14 @@ var PRODUCTMAKER_CAPABILITY_REGISTRY = deepFreeze([
|
|
|
9247
9452
|
"competitorScanEnabled",
|
|
9248
9453
|
"landingStrategy",
|
|
9249
9454
|
"landingImageFormat",
|
|
9455
|
+
"pauseForAngleReview",
|
|
9250
9456
|
// MCP-only, like create_product_task's aiDecidesAll.
|
|
9251
9457
|
"aiDecidesAll"
|
|
9252
9458
|
], [
|
|
9253
9459
|
"competitorScanEnabled",
|
|
9254
9460
|
"landingStrategy",
|
|
9255
|
-
"landingImageFormat"
|
|
9461
|
+
"landingImageFormat",
|
|
9462
|
+
"pauseForAngleReview"
|
|
9256
9463
|
])
|
|
9257
9464
|
], [
|
|
9258
9465
|
"product_reference"
|
|
@@ -9568,7 +9775,7 @@ var PRODUCTMAKER_CAPABILITY_REGISTRY = deepFreeze([
|
|
|
9568
9775
|
], [
|
|
9569
9776
|
"meta_publication"
|
|
9570
9777
|
]),
|
|
9571
|
-
defineCapability("publish_to_shopify", "
|
|
9778
|
+
defineCapability("publish_to_shopify", "6a75ad9bfe04aed24ed41564011ac785657bab12caccd4d78c1de846ce798e82", "external_mutation", "publishing", [
|
|
9572
9779
|
"external_mutation",
|
|
9573
9780
|
"state_change"
|
|
9574
9781
|
], [
|
|
@@ -9588,7 +9795,13 @@ var PRODUCTMAKER_CAPABILITY_REGISTRY = deepFreeze([
|
|
|
9588
9795
|
slot("publication_target", false, [
|
|
9589
9796
|
"publicationMode",
|
|
9590
9797
|
"targetProductId"
|
|
9591
|
-
])
|
|
9798
|
+
]),
|
|
9799
|
+
// MCP + web only: the WhatsApp publish tool exposes no announcement fields,
|
|
9800
|
+
// and claiming them here breaks the schema-parity invariant.
|
|
9801
|
+
slot("announcement_bar", false, [
|
|
9802
|
+
"announcementBarEnabled",
|
|
9803
|
+
"announcementBarText"
|
|
9804
|
+
], [])
|
|
9592
9805
|
], [
|
|
9593
9806
|
"product",
|
|
9594
9807
|
"shop"
|
|
@@ -9762,8 +9975,8 @@ var PRODUCTMAKER_MCP_CAPABILITIES = toLegacyCapabilityCatalog(PRODUCTMAKER_MCP_C
|
|
|
9762
9975
|
var PRODUCTMAKER_MCP_CAPABILITY_NAMES = PRODUCTMAKER_MCP_CAPABILITY_REGISTRY.map((capability) => capability.name);
|
|
9763
9976
|
|
|
9764
9977
|
// ../../packages/types/src/actor.ts
|
|
9765
|
-
import { z as
|
|
9766
|
-
var ActorStatusSchema =
|
|
9978
|
+
import { z as z46 } from "zod";
|
|
9979
|
+
var ActorStatusSchema = z46.enum([
|
|
9767
9980
|
"draft",
|
|
9768
9981
|
"generating",
|
|
9769
9982
|
"ready",
|
|
@@ -9772,18 +9985,18 @@ var ActorStatusSchema = z45.enum([
|
|
|
9772
9985
|
var ACTOR_NAME_MAX_CHARS = 60;
|
|
9773
9986
|
var ACTOR_DESCRIPTION_MAX_CHARS = 600;
|
|
9774
9987
|
var ACTOR_CANDIDATE_COUNT = 4;
|
|
9775
|
-
var ActorGenderSchema =
|
|
9988
|
+
var ActorGenderSchema = z46.enum([
|
|
9776
9989
|
"female",
|
|
9777
9990
|
"male",
|
|
9778
9991
|
"non_binary"
|
|
9779
9992
|
]);
|
|
9780
|
-
var ActorSchema =
|
|
9781
|
-
id:
|
|
9782
|
-
name:
|
|
9783
|
-
description:
|
|
9993
|
+
var ActorSchema = z46.object({
|
|
9994
|
+
id: z46.string(),
|
|
9995
|
+
name: z46.string().min(1).max(ACTOR_NAME_MAX_CHARS),
|
|
9996
|
+
description: z46.string().max(ACTOR_DESCRIPTION_MAX_CHARS),
|
|
9784
9997
|
gender: ActorGenderSchema,
|
|
9785
|
-
voiceId:
|
|
9786
|
-
brandId:
|
|
9998
|
+
voiceId: z46.string().nullable().optional(),
|
|
9999
|
+
brandId: z46.string().nullable().optional(),
|
|
9787
10000
|
status: ActorStatusSchema,
|
|
9788
10001
|
/**
|
|
9789
10002
|
* A candidate-grid run is in flight RIGHT NOW.
|
|
@@ -9795,36 +10008,36 @@ var ActorSchema = z45.object({
|
|
|
9795
10008
|
* timestamp with a TTL, so an orphaned run reads as `false` instead of
|
|
9796
10009
|
* pinning the personaje forever.
|
|
9797
10010
|
*/
|
|
9798
|
-
generating:
|
|
9799
|
-
photoUrl:
|
|
9800
|
-
avatarUrl:
|
|
9801
|
-
candidateUrls:
|
|
10011
|
+
generating: z46.boolean().optional(),
|
|
10012
|
+
photoUrl: z46.string().url().nullable().optional(),
|
|
10013
|
+
avatarUrl: z46.string().url().nullable().optional(),
|
|
10014
|
+
candidateUrls: z46.array(z46.string().url()).optional(),
|
|
9802
10015
|
/** Indices of candidateUrls that failed the anatomy check (Task 6). */
|
|
9803
|
-
flaggedCandidateIndices:
|
|
10016
|
+
flaggedCandidateIndices: z46.array(z46.number().int()).optional()
|
|
9804
10017
|
});
|
|
9805
|
-
var CreateActorRequest =
|
|
9806
|
-
name:
|
|
9807
|
-
description:
|
|
10018
|
+
var CreateActorRequest = z46.object({
|
|
10019
|
+
name: z46.string().min(1).max(ACTOR_NAME_MAX_CHARS),
|
|
10020
|
+
description: z46.string().max(ACTOR_DESCRIPTION_MAX_CHARS).default(""),
|
|
9808
10021
|
gender: ActorGenderSchema,
|
|
9809
|
-
voiceId:
|
|
9810
|
-
brandId:
|
|
9811
|
-
sourcePhotoKey:
|
|
10022
|
+
voiceId: z46.string().optional(),
|
|
10023
|
+
brandId: z46.string().optional(),
|
|
10024
|
+
sourcePhotoKey: z46.string().optional()
|
|
9812
10025
|
}).refine((v) => v.description.trim().length > 0 || !!v.sourcePhotoKey, {
|
|
9813
10026
|
message: "Se necesita una descripci\xF3n o una foto"
|
|
9814
10027
|
});
|
|
9815
|
-
var UpdateActorRequest =
|
|
9816
|
-
name:
|
|
9817
|
-
description:
|
|
10028
|
+
var UpdateActorRequest = z46.object({
|
|
10029
|
+
name: z46.string().min(1).max(ACTOR_NAME_MAX_CHARS).optional(),
|
|
10030
|
+
description: z46.string().max(ACTOR_DESCRIPTION_MAX_CHARS).optional(),
|
|
9818
10031
|
gender: ActorGenderSchema.optional(),
|
|
9819
|
-
voiceId:
|
|
9820
|
-
brandId:
|
|
10032
|
+
voiceId: z46.string().nullable().optional(),
|
|
10033
|
+
brandId: z46.string().nullable().optional()
|
|
9821
10034
|
});
|
|
9822
|
-
var ApproveActorRequest =
|
|
9823
|
-
candidateIndex:
|
|
10035
|
+
var ApproveActorRequest = z46.object({
|
|
10036
|
+
candidateIndex: z46.number().int().min(0).max(ACTOR_CANDIDATE_COUNT - 1)
|
|
9824
10037
|
});
|
|
9825
10038
|
|
|
9826
10039
|
// ../../packages/types/src/referral-dashboard.ts
|
|
9827
|
-
import { z as
|
|
10040
|
+
import { z as z47 } from "zod";
|
|
9828
10041
|
var REFERRAL_DASHBOARD_RAW_STATUSES = [
|
|
9829
10042
|
"ACTIVE",
|
|
9830
10043
|
"PENDING",
|
|
@@ -9843,66 +10056,66 @@ var REFERRAL_DASHBOARD_PLAN_KINDS = [
|
|
|
9843
10056
|
"last",
|
|
9844
10057
|
"none"
|
|
9845
10058
|
];
|
|
9846
|
-
var ReferralDashboardRawStatusSchema =
|
|
9847
|
-
var ReferralDashboardStateSchema =
|
|
9848
|
-
var ReferralDashboardPlanKindSchema =
|
|
9849
|
-
var ReferralDashboardStatusSchema =
|
|
10059
|
+
var ReferralDashboardRawStatusSchema = z47.enum(REFERRAL_DASHBOARD_RAW_STATUSES);
|
|
10060
|
+
var ReferralDashboardStateSchema = z47.enum(REFERRAL_DASHBOARD_STATES);
|
|
10061
|
+
var ReferralDashboardPlanKindSchema = z47.enum(REFERRAL_DASHBOARD_PLAN_KINDS);
|
|
10062
|
+
var ReferralDashboardStatusSchema = z47.enum([
|
|
9850
10063
|
"all",
|
|
9851
10064
|
"active",
|
|
9852
10065
|
"pending",
|
|
9853
10066
|
"inactive"
|
|
9854
10067
|
]).default("all");
|
|
9855
|
-
var ReferralDashboardPlanSchema =
|
|
9856
|
-
var ReferralDashboardFiltersSchema =
|
|
10068
|
+
var ReferralDashboardPlanSchema = z47.string().trim().min(1).max(100).optional();
|
|
10069
|
+
var ReferralDashboardFiltersSchema = z47.object({
|
|
9857
10070
|
status: ReferralDashboardStatusSchema,
|
|
9858
10071
|
plan: ReferralDashboardPlanSchema
|
|
9859
10072
|
});
|
|
9860
|
-
var ReferralDashboardSubscriptionSchema =
|
|
9861
|
-
planName:
|
|
10073
|
+
var ReferralDashboardSubscriptionSchema = z47.object({
|
|
10074
|
+
planName: z47.string().nullable(),
|
|
9862
10075
|
planKind: ReferralDashboardPlanKindSchema,
|
|
9863
10076
|
rawStatus: ReferralDashboardRawStatusSchema.nullable(),
|
|
9864
10077
|
state: ReferralDashboardStateSchema,
|
|
9865
|
-
currentPeriodEnd:
|
|
10078
|
+
currentPeriodEnd: z47.string().datetime({
|
|
9866
10079
|
offset: true
|
|
9867
10080
|
}).nullable(),
|
|
9868
|
-
cancelAtPeriodEnd:
|
|
10081
|
+
cancelAtPeriodEnd: z47.boolean()
|
|
9869
10082
|
});
|
|
9870
|
-
var ReferralDashboardReferralSchema =
|
|
9871
|
-
id:
|
|
9872
|
-
registeredAt:
|
|
10083
|
+
var ReferralDashboardReferralSchema = z47.object({
|
|
10084
|
+
id: z47.string().min(1),
|
|
10085
|
+
registeredAt: z47.string().datetime({
|
|
9873
10086
|
offset: true
|
|
9874
10087
|
}),
|
|
9875
10088
|
// Kept in the response for compatibility with existing referral API clients.
|
|
9876
10089
|
// The referrer dashboard intentionally does not render these legacy fields.
|
|
9877
|
-
expiresAt:
|
|
10090
|
+
expiresAt: z47.string().datetime({
|
|
9878
10091
|
offset: true
|
|
9879
10092
|
}).nullable().optional(),
|
|
9880
|
-
status:
|
|
9881
|
-
referredUser:
|
|
9882
|
-
email:
|
|
10093
|
+
status: z47.string().min(1).optional(),
|
|
10094
|
+
referredUser: z47.object({
|
|
10095
|
+
email: z47.string().nullable()
|
|
9883
10096
|
}),
|
|
9884
10097
|
subscription: ReferralDashboardSubscriptionSchema
|
|
9885
10098
|
});
|
|
9886
|
-
var ReferralDashboardReferralsPageSchema =
|
|
9887
|
-
referrals:
|
|
9888
|
-
total:
|
|
10099
|
+
var ReferralDashboardReferralsPageSchema = z47.object({
|
|
10100
|
+
referrals: z47.array(ReferralDashboardReferralSchema),
|
|
10101
|
+
total: z47.number().int().nonnegative()
|
|
9889
10102
|
});
|
|
9890
|
-
var ReferralDashboardPlanBreakdownSchema =
|
|
9891
|
-
planName:
|
|
9892
|
-
count:
|
|
10103
|
+
var ReferralDashboardPlanBreakdownSchema = z47.object({
|
|
10104
|
+
planName: z47.string().nullable(),
|
|
10105
|
+
count: z47.number().int().nonnegative()
|
|
9893
10106
|
});
|
|
9894
|
-
var ReferralDashboardMonthlyRegistrationSchema =
|
|
9895
|
-
month:
|
|
9896
|
-
count:
|
|
10107
|
+
var ReferralDashboardMonthlyRegistrationSchema = z47.object({
|
|
10108
|
+
month: z47.string().regex(/^\d{4}-(0[1-9]|1[0-2])$/),
|
|
10109
|
+
count: z47.number().int().nonnegative()
|
|
9897
10110
|
});
|
|
9898
|
-
var ReferralDashboardOverviewSchema =
|
|
9899
|
-
totalReferredUsers:
|
|
9900
|
-
activeUsers:
|
|
9901
|
-
pendingUsers:
|
|
9902
|
-
inactiveUsers:
|
|
9903
|
-
noPlanUsers:
|
|
9904
|
-
planBreakdown:
|
|
9905
|
-
monthlyRegistrations:
|
|
10111
|
+
var ReferralDashboardOverviewSchema = z47.object({
|
|
10112
|
+
totalReferredUsers: z47.number().int().nonnegative(),
|
|
10113
|
+
activeUsers: z47.number().int().nonnegative(),
|
|
10114
|
+
pendingUsers: z47.number().int().nonnegative(),
|
|
10115
|
+
inactiveUsers: z47.number().int().nonnegative(),
|
|
10116
|
+
noPlanUsers: z47.number().int().nonnegative(),
|
|
10117
|
+
planBreakdown: z47.array(ReferralDashboardPlanBreakdownSchema),
|
|
10118
|
+
monthlyRegistrations: z47.array(ReferralDashboardMonthlyRegistrationSchema).refine((months) => months.length === 6, "Expected six monthly registration buckets")
|
|
9906
10119
|
});
|
|
9907
10120
|
|
|
9908
10121
|
// ../../packages/types/src/mcp-oauth.ts
|
|
@@ -10664,19 +10877,19 @@ import { InsufficientScopeError, InvalidTokenError } from "@modelcontextprotocol
|
|
|
10664
10877
|
import { createRemoteJWKSet as createRemoteJWKSet2, jwtVerify as jwtVerify2 } from "jose";
|
|
10665
10878
|
|
|
10666
10879
|
// src/auth/oauth-config.ts
|
|
10667
|
-
import { z as
|
|
10880
|
+
import { z as z48 } from "zod";
|
|
10668
10881
|
var MCP_RESOURCE = new URL("https://mcp.productmaker.app/mcp");
|
|
10669
10882
|
var MCP_SCOPE = "mcp:access";
|
|
10670
|
-
var oauthEnvSchema =
|
|
10671
|
-
MCP_REMOTE_AUTH_MODE:
|
|
10672
|
-
MCP_AUTH0_ISSUER:
|
|
10673
|
-
MCP_AUTH0_AUDIENCE:
|
|
10883
|
+
var oauthEnvSchema = z48.object({
|
|
10884
|
+
MCP_REMOTE_AUTH_MODE: z48.literal("oauth"),
|
|
10885
|
+
MCP_AUTH0_ISSUER: z48.string().trim().min(1),
|
|
10886
|
+
MCP_AUTH0_AUDIENCE: z48.string().trim().refine((value) => value === MCP_RESOURCE.href, {
|
|
10674
10887
|
message: `must equal ${MCP_RESOURCE.href}`
|
|
10675
10888
|
}),
|
|
10676
|
-
MCP_AUTH0_CLIENT_ID:
|
|
10677
|
-
MCP_AUTH0_CLIENT_SECRET:
|
|
10678
|
-
PM_API_AUDIENCE:
|
|
10679
|
-
AUTH0_AUDIENCE:
|
|
10889
|
+
MCP_AUTH0_CLIENT_ID: z48.string().trim().min(1),
|
|
10890
|
+
MCP_AUTH0_CLIENT_SECRET: z48.string().trim().min(1),
|
|
10891
|
+
PM_API_AUDIENCE: z48.string().trim().min(1),
|
|
10892
|
+
AUTH0_AUDIENCE: z48.string().trim().min(1)
|
|
10680
10893
|
});
|
|
10681
10894
|
function formatZodIssues(error) {
|
|
10682
10895
|
return error.issues.map((issue) => `${issue.path.join(".")}: ${issue.message}`).join("; ");
|
|
@@ -11418,7 +11631,7 @@ var ApiClient = class {
|
|
|
11418
11631
|
};
|
|
11419
11632
|
|
|
11420
11633
|
// src/index.ts
|
|
11421
|
-
var MCP_VERSION = "3.
|
|
11634
|
+
var MCP_VERSION = "3.4.1";
|
|
11422
11635
|
|
|
11423
11636
|
// src/meta-upload-app.ts
|
|
11424
11637
|
import { readFileSync } from "fs";
|
|
@@ -11490,7 +11703,7 @@ function registerMediaUploadAppResource(server, resolveEndpoint) {
|
|
|
11490
11703
|
__name(registerMediaUploadAppResource, "registerMediaUploadAppResource");
|
|
11491
11704
|
|
|
11492
11705
|
// src/tools/tasks.ts
|
|
11493
|
-
import { z as
|
|
11706
|
+
import { z as z50 } from "zod";
|
|
11494
11707
|
|
|
11495
11708
|
// src/resolve-image.ts
|
|
11496
11709
|
import { lookup as dnsLookup } from "dns/promises";
|
|
@@ -12017,10 +12230,10 @@ async function resolveMediaRef(ref, opts) {
|
|
|
12017
12230
|
__name(resolveMediaRef, "resolveMediaRef");
|
|
12018
12231
|
|
|
12019
12232
|
// src/tools/shared.ts
|
|
12020
|
-
import { z as
|
|
12021
|
-
var Lang =
|
|
12022
|
-
var Country =
|
|
12023
|
-
var LandingStrategyEnum =
|
|
12233
|
+
import { z as z49 } from "zod";
|
|
12234
|
+
var Lang = z49.string().min(2).max(10);
|
|
12235
|
+
var Country = z49.string().min(2).max(3);
|
|
12236
|
+
var LandingStrategyEnum = z49.enum([
|
|
12024
12237
|
"auto",
|
|
12025
12238
|
"classic",
|
|
12026
12239
|
"pain_solution",
|
|
@@ -12029,12 +12242,12 @@ var LandingStrategyEnum = z48.enum([
|
|
|
12029
12242
|
"offer_value",
|
|
12030
12243
|
"lifestyle_identity"
|
|
12031
12244
|
]);
|
|
12032
|
-
var LandingImageFormatEnum =
|
|
12245
|
+
var LandingImageFormatEnum = z49.enum([
|
|
12033
12246
|
"9:16",
|
|
12034
12247
|
"4:5",
|
|
12035
12248
|
"1:1"
|
|
12036
12249
|
]);
|
|
12037
|
-
var VideoPresetEnum =
|
|
12250
|
+
var VideoPresetEnum = z49.enum([
|
|
12038
12251
|
"auto",
|
|
12039
12252
|
"ugc_dynamic",
|
|
12040
12253
|
"ugc_how_to",
|
|
@@ -12047,7 +12260,7 @@ var VideoPresetEnum = z48.enum([
|
|
|
12047
12260
|
"pixar_problem",
|
|
12048
12261
|
"pixar_sassy"
|
|
12049
12262
|
]);
|
|
12050
|
-
var TaskCreativeVariantVideoPresetEnum =
|
|
12263
|
+
var TaskCreativeVariantVideoPresetEnum = z49.enum([
|
|
12051
12264
|
"auto",
|
|
12052
12265
|
"ugc_dynamic",
|
|
12053
12266
|
"ugc_how_to",
|
|
@@ -12118,7 +12331,7 @@ function resolveVideoPreset(preset) {
|
|
|
12118
12331
|
return VIDEO_PRESET_RESOLUTION[preset] ?? null;
|
|
12119
12332
|
}
|
|
12120
12333
|
__name(resolveVideoPreset, "resolveVideoPreset");
|
|
12121
|
-
var VideoTierEnum =
|
|
12334
|
+
var VideoTierEnum = z49.enum([
|
|
12122
12335
|
"basic"
|
|
12123
12336
|
]);
|
|
12124
12337
|
function resolveVideoTier(_tier) {
|
|
@@ -12133,17 +12346,17 @@ function resolveVideoFormatForDuration(durationSeconds) {
|
|
|
12133
12346
|
return durationSeconds === 30 ? "double_30s" : "single_15s";
|
|
12134
12347
|
}
|
|
12135
12348
|
__name(resolveVideoFormatForDuration, "resolveVideoFormatForDuration");
|
|
12136
|
-
var VideoResolutionEnum =
|
|
12349
|
+
var VideoResolutionEnum = z49.enum([
|
|
12137
12350
|
"480p",
|
|
12138
12351
|
"720p"
|
|
12139
12352
|
]);
|
|
12140
|
-
var VideoStyleEnum =
|
|
12353
|
+
var VideoStyleEnum = z49.enum([
|
|
12141
12354
|
"ugc",
|
|
12142
12355
|
"cartoon_3d",
|
|
12143
12356
|
"product_only",
|
|
12144
12357
|
"ai_dynamic"
|
|
12145
12358
|
]);
|
|
12146
|
-
var NarrativeStyleEnum =
|
|
12359
|
+
var NarrativeStyleEnum = z49.enum([
|
|
12147
12360
|
"review",
|
|
12148
12361
|
"ugc_how_to",
|
|
12149
12362
|
"problem_product_cta",
|
|
@@ -12160,7 +12373,7 @@ var NarrativeStyleEnum = z48.enum([
|
|
|
12160
12373
|
"ugc_skeptic_confession",
|
|
12161
12374
|
"ugc_demo_review"
|
|
12162
12375
|
]);
|
|
12163
|
-
var PublicNarrativeStyleEnum =
|
|
12376
|
+
var PublicNarrativeStyleEnum = z49.enum([
|
|
12164
12377
|
"review",
|
|
12165
12378
|
"ugc_how_to",
|
|
12166
12379
|
"ugc_unboxing_chaos",
|
|
@@ -12172,7 +12385,7 @@ var PublicNarrativeStyleEnum = z48.enum([
|
|
|
12172
12385
|
"animated_problem_pixar",
|
|
12173
12386
|
"sassy_product_pixar"
|
|
12174
12387
|
]);
|
|
12175
|
-
var VideoModelEnum =
|
|
12388
|
+
var VideoModelEnum = z49.enum([
|
|
12176
12389
|
"veo",
|
|
12177
12390
|
"wan",
|
|
12178
12391
|
"bytedance",
|
|
@@ -12180,29 +12393,29 @@ var VideoModelEnum = z48.enum([
|
|
|
12180
12393
|
"grok",
|
|
12181
12394
|
"kling"
|
|
12182
12395
|
]);
|
|
12183
|
-
var ProductReferenceUserChoiceEnum =
|
|
12396
|
+
var ProductReferenceUserChoiceEnum = z49.enum([
|
|
12184
12397
|
"same_product",
|
|
12185
12398
|
"variants",
|
|
12186
12399
|
"different_products",
|
|
12187
12400
|
"combo_distinct"
|
|
12188
12401
|
]);
|
|
12189
|
-
var ProductReferenceCandidateGroupingEnum =
|
|
12402
|
+
var ProductReferenceCandidateGroupingEnum = z49.enum([
|
|
12190
12403
|
"same_product_same_variant",
|
|
12191
12404
|
"same_product_different_variants",
|
|
12192
12405
|
"different_products",
|
|
12193
12406
|
"unclear"
|
|
12194
12407
|
]);
|
|
12195
|
-
var ProductReferenceScopeEnum =
|
|
12408
|
+
var ProductReferenceScopeEnum = z49.enum([
|
|
12196
12409
|
"base",
|
|
12197
12410
|
"video_view_pack"
|
|
12198
12411
|
]);
|
|
12199
|
-
var ProductReferenceResolutionDecisionEnum =
|
|
12412
|
+
var ProductReferenceResolutionDecisionEnum = z49.enum([
|
|
12200
12413
|
"approve",
|
|
12201
12414
|
"reject",
|
|
12202
12415
|
"observed_only",
|
|
12203
12416
|
"correct_view"
|
|
12204
12417
|
]);
|
|
12205
|
-
var ProductReferenceRejectionReasonEnum =
|
|
12418
|
+
var ProductReferenceRejectionReasonEnum = z49.enum([
|
|
12206
12419
|
"replacement_requested",
|
|
12207
12420
|
"wrong_product_identity",
|
|
12208
12421
|
"wrong_variant",
|
|
@@ -12213,13 +12426,13 @@ var ProductReferenceRejectionReasonEnum = z48.enum([
|
|
|
12213
12426
|
"not_a_product_photo",
|
|
12214
12427
|
"other"
|
|
12215
12428
|
]);
|
|
12216
|
-
var GapDurationChoiceMirror =
|
|
12217
|
-
angleIndex:
|
|
12218
|
-
recommendedSec:
|
|
12219
|
-
|
|
12220
|
-
|
|
12429
|
+
var GapDurationChoiceMirror = z49.object({
|
|
12430
|
+
angleIndex: z49.number().int().nonnegative(),
|
|
12431
|
+
recommendedSec: z49.union([
|
|
12432
|
+
z49.literal(15),
|
|
12433
|
+
z49.literal(30)
|
|
12221
12434
|
]),
|
|
12222
|
-
estimatedDeltaCredits:
|
|
12435
|
+
estimatedDeltaCredits: z49.number().int().nonnegative().optional()
|
|
12223
12436
|
});
|
|
12224
12437
|
function buildSelectedImageUrlsHash(urls) {
|
|
12225
12438
|
const input = [
|
|
@@ -12229,13 +12442,13 @@ function buildSelectedImageUrlsHash(urls) {
|
|
|
12229
12442
|
return `fnv1a:${hash.toString(16).padStart(8, "0")}:${urls.length}`;
|
|
12230
12443
|
}
|
|
12231
12444
|
__name(buildSelectedImageUrlsHash, "buildSelectedImageUrlsHash");
|
|
12232
|
-
var ImageCreativeStyleEnum =
|
|
12445
|
+
var ImageCreativeStyleEnum = z49.enum([
|
|
12233
12446
|
"studio",
|
|
12234
12447
|
"floating",
|
|
12235
12448
|
"ingredients",
|
|
12236
12449
|
"in_use"
|
|
12237
12450
|
]);
|
|
12238
|
-
var ImageCreativeArchetypeEnum =
|
|
12451
|
+
var ImageCreativeArchetypeEnum = z49.enum([
|
|
12239
12452
|
"direct_response_product_ad",
|
|
12240
12453
|
"hero_outcome",
|
|
12241
12454
|
"offer_value_prop",
|
|
@@ -12244,20 +12457,20 @@ var ImageCreativeArchetypeEnum = z48.enum([
|
|
|
12244
12457
|
"problem_solution",
|
|
12245
12458
|
"lifestyle_identity"
|
|
12246
12459
|
]);
|
|
12247
|
-
var ImageCreativeVisualTreatmentEnum =
|
|
12460
|
+
var ImageCreativeVisualTreatmentEnum = z49.enum([
|
|
12248
12461
|
"performance_product_ad",
|
|
12249
12462
|
"premium_dtc_carousel"
|
|
12250
12463
|
]);
|
|
12251
|
-
var ImageCreativeAspectRatioEnum =
|
|
12464
|
+
var ImageCreativeAspectRatioEnum = z49.enum([
|
|
12252
12465
|
"1:1",
|
|
12253
12466
|
"4:5",
|
|
12254
12467
|
"9:16",
|
|
12255
12468
|
"16:9"
|
|
12256
12469
|
]);
|
|
12257
|
-
var ImageRefSchema =
|
|
12258
|
-
url:
|
|
12259
|
-
path:
|
|
12260
|
-
assetId:
|
|
12470
|
+
var ImageRefSchema = z49.object({
|
|
12471
|
+
url: z49.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.'),
|
|
12472
|
+
path: z49.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`.'),
|
|
12473
|
+
assetId: z49.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.")
|
|
12261
12474
|
}).refine((v) => [
|
|
12262
12475
|
v.url,
|
|
12263
12476
|
v.path,
|
|
@@ -12265,11 +12478,11 @@ var ImageRefSchema = z48.object({
|
|
|
12265
12478
|
].filter(Boolean).length === 1, {
|
|
12266
12479
|
message: "each image entry must have exactly one of `url`, `path`, or `assetId`"
|
|
12267
12480
|
});
|
|
12268
|
-
var ImagesInput =
|
|
12481
|
+
var ImagesInput = z49.array(ImageRefSchema).min(1).max(5).superRefine((images, ctx) => {
|
|
12269
12482
|
const assetCount = images.filter((image) => Boolean(image.assetId)).length;
|
|
12270
12483
|
if (assetCount > 0 && assetCount !== images.length) {
|
|
12271
12484
|
ctx.addIssue({
|
|
12272
|
-
code:
|
|
12485
|
+
code: z49.ZodIssueCode.custom,
|
|
12273
12486
|
message: "all image entries must use assetId, or all must use URL/path sources"
|
|
12274
12487
|
});
|
|
12275
12488
|
}
|
|
@@ -12339,14 +12552,14 @@ async function submitCreative(api, opts) {
|
|
|
12339
12552
|
});
|
|
12340
12553
|
}
|
|
12341
12554
|
__name(submitCreative, "submitCreative");
|
|
12342
|
-
var ResearchTargetCountryEnum =
|
|
12555
|
+
var ResearchTargetCountryEnum = z49.enum([
|
|
12343
12556
|
"CO",
|
|
12344
12557
|
"MX",
|
|
12345
12558
|
"PE",
|
|
12346
12559
|
"EC",
|
|
12347
12560
|
"CL"
|
|
12348
12561
|
]);
|
|
12349
|
-
var ResearchCategoryEnum =
|
|
12562
|
+
var ResearchCategoryEnum = z49.enum([
|
|
12350
12563
|
"auto",
|
|
12351
12564
|
"automotive",
|
|
12352
12565
|
"kitchen",
|
|
@@ -12361,6 +12574,42 @@ var ResearchCategoryEnum = z48.enum([
|
|
|
12361
12574
|
"garden",
|
|
12362
12575
|
"cleaning"
|
|
12363
12576
|
]);
|
|
12577
|
+
var READS_INTERNAL = {
|
|
12578
|
+
readOnlyHint: true,
|
|
12579
|
+
destructiveHint: false,
|
|
12580
|
+
idempotentHint: true,
|
|
12581
|
+
openWorldHint: false
|
|
12582
|
+
};
|
|
12583
|
+
var READS_EXTERNAL = {
|
|
12584
|
+
readOnlyHint: true,
|
|
12585
|
+
destructiveHint: false,
|
|
12586
|
+
idempotentHint: true,
|
|
12587
|
+
openWorldHint: true
|
|
12588
|
+
};
|
|
12589
|
+
var CREATES_INTERNAL = {
|
|
12590
|
+
readOnlyHint: false,
|
|
12591
|
+
destructiveHint: false,
|
|
12592
|
+
idempotentHint: false,
|
|
12593
|
+
openWorldHint: false
|
|
12594
|
+
};
|
|
12595
|
+
var CREATES_EXTERNAL = {
|
|
12596
|
+
readOnlyHint: false,
|
|
12597
|
+
destructiveHint: false,
|
|
12598
|
+
idempotentHint: false,
|
|
12599
|
+
openWorldHint: true
|
|
12600
|
+
};
|
|
12601
|
+
var OVERWRITES_INTERNAL = {
|
|
12602
|
+
readOnlyHint: false,
|
|
12603
|
+
destructiveHint: true,
|
|
12604
|
+
idempotentHint: false,
|
|
12605
|
+
openWorldHint: false
|
|
12606
|
+
};
|
|
12607
|
+
var OVERWRITES_EXTERNAL = {
|
|
12608
|
+
readOnlyHint: false,
|
|
12609
|
+
destructiveHint: true,
|
|
12610
|
+
idempotentHint: false,
|
|
12611
|
+
openWorldHint: true
|
|
12612
|
+
};
|
|
12364
12613
|
|
|
12365
12614
|
// src/errors.ts
|
|
12366
12615
|
var McpErrorCode = {
|
|
@@ -12818,6 +13067,18 @@ function translateError(err) {
|
|
|
12818
13067
|
}
|
|
12819
13068
|
__name(translateError, "translateError");
|
|
12820
13069
|
|
|
13070
|
+
// src/tools/approve-refusal-message.ts
|
|
13071
|
+
function approveRefusalMessage(reason) {
|
|
13072
|
+
if (reason === "photo_review_pending") {
|
|
13073
|
+
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.";
|
|
13074
|
+
}
|
|
13075
|
+
if (reason === "photos_not_ready") {
|
|
13076
|
+
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.";
|
|
13077
|
+
}
|
|
13078
|
+
return "No se inici\xF3 la generaci\xF3n y no se cobr\xF3 nada. Los cambios siguen guardados: vuelve a intentar.";
|
|
13079
|
+
}
|
|
13080
|
+
__name(approveRefusalMessage, "approveRefusalMessage");
|
|
13081
|
+
|
|
12821
13082
|
// src/tools/task-outputs.ts
|
|
12822
13083
|
var WEBAPP_BASE = "https://productmaker.app";
|
|
12823
13084
|
function proxifyOutputs(outputs, proxyBaseUrl) {
|
|
@@ -12946,54 +13207,54 @@ function renderOutputsMarkdown(outputs) {
|
|
|
12946
13207
|
__name(renderOutputsMarkdown, "renderOutputsMarkdown");
|
|
12947
13208
|
|
|
12948
13209
|
// src/tools/tasks.ts
|
|
12949
|
-
var CreateProductTaskInput =
|
|
13210
|
+
var CreateProductTaskInput = z50.object({
|
|
12950
13211
|
images: ImagesInput.optional().describe("1\u20135 fotos del mismo producto. Omitir \xFAnicamente cuando projectId apunta a un proyecto propio con imagen guardada."),
|
|
12951
|
-
projectId:
|
|
12952
|
-
productUrl:
|
|
13212
|
+
projectId: z50.string().uuid().optional().describe("Opcional. Proyecto guiado propio. Permite continuar sin volver a subir la imagen guardada; nunca evita cr\xE9ditos, cuotas ni confirmaciones."),
|
|
13213
|
+
productUrl: z50.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."),
|
|
12953
13214
|
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.'),
|
|
12954
13215
|
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.'),
|
|
12955
|
-
price:
|
|
12956
|
-
compareAtPrice:
|
|
12957
|
-
offer:
|
|
12958
|
-
paymentMethods:
|
|
12959
|
-
productContext:
|
|
12960
|
-
productLabel:
|
|
13216
|
+
price: z50.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.'),
|
|
13217
|
+
compareAtPrice: z50.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".'),
|
|
13218
|
+
offer: z50.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.'),
|
|
13219
|
+
paymentMethods: z50.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.'),
|
|
13220
|
+
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."),
|
|
13221
|
+
productLabel: z50.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.'),
|
|
12961
13222
|
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.'),
|
|
12962
|
-
durationSeconds:
|
|
12963
|
-
|
|
12964
|
-
|
|
13223
|
+
durationSeconds: z50.union([
|
|
13224
|
+
z50.literal(15),
|
|
13225
|
+
z50.literal(30)
|
|
12965
13226
|
]).default(15).describe("Opcional. Duraci\xF3n objetivo del video: 15s (single_call, default) o 30s (Multiescena, aproximadamente 30s y facturado por escena)."),
|
|
12966
|
-
pauseForAngleReview:
|
|
12967
|
-
aiDecidesAll:
|
|
13227
|
+
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. \xDAsalo si quiere decidir 15/30s cuando aparezca un winner. Default false."),
|
|
13228
|
+
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, 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."),
|
|
12968
13229
|
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.'),
|
|
12969
13230
|
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).'),
|
|
12970
|
-
multiAngleVideos:
|
|
13231
|
+
multiAngleVideos: z50.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."),
|
|
12971
13232
|
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.'),
|
|
12972
|
-
outputs:
|
|
13233
|
+
outputs: z50.array(z50.enum([
|
|
12973
13234
|
"landing",
|
|
12974
13235
|
"video",
|
|
12975
13236
|
"image"
|
|
12976
13237
|
])).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.'),
|
|
12977
|
-
competitorScanEnabled:
|
|
12978
|
-
brandId:
|
|
12979
|
-
actorId:
|
|
12980
|
-
creativeDirection:
|
|
12981
|
-
headline:
|
|
12982
|
-
subheadline:
|
|
13238
|
+
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."),
|
|
13239
|
+
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 los outputs generados."),
|
|
13240
|
+
actorId: z50.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."),
|
|
13241
|
+
creativeDirection: z50.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.'),
|
|
13242
|
+
headline: z50.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."),
|
|
13243
|
+
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."),
|
|
12983
13244
|
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".'),
|
|
12984
13245
|
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".')
|
|
12985
13246
|
}).strict();
|
|
12986
|
-
var CreateServiceVideoInput =
|
|
12987
|
-
landingUrl:
|
|
12988
|
-
additionalContext:
|
|
13247
|
+
var CreateServiceVideoInput = z50.object({
|
|
13248
|
+
landingUrl: z50.string().url().describe("URL p\xFAblica de la landing del servicio. Es suficiente para generar el video; los medios son opcionales en la web."),
|
|
13249
|
+
additionalContext: z50.string().max(3e3).optional().describe('Instrucci\xF3n natural prioritaria. Ej.: "adapta el ritmo de estos competidores", "enfatiza reservas de demos" o restricciones de claims.'),
|
|
12989
13250
|
language: Lang.describe("Idioma del video: es, en o pt."),
|
|
12990
|
-
voiceId:
|
|
12991
|
-
brandId:
|
|
12992
|
-
reviewMode:
|
|
13251
|
+
voiceId: z50.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.'),
|
|
13252
|
+
brandId: z50.string().uuid().optional().describe("Marca guardada del usuario. Aplica voz, mensajes, paleta y persona predeterminada."),
|
|
13253
|
+
reviewMode: z50.enum([
|
|
12993
13254
|
"automatic",
|
|
12994
13255
|
"guided"
|
|
12995
13256
|
]).default("automatic").describe("automatic = la IA elige concepto y guion sin pausas; guided = el usuario aprueba concepto y storyboard."),
|
|
12996
|
-
videoPreset:
|
|
13257
|
+
videoPreset: z50.enum([
|
|
12997
13258
|
"auto",
|
|
12998
13259
|
"ugc_dynamic",
|
|
12999
13260
|
"ugc_how_to",
|
|
@@ -13001,51 +13262,51 @@ var CreateServiceVideoInput = z49.object({
|
|
|
13001
13262
|
"wild_card"
|
|
13002
13263
|
]).default("auto")
|
|
13003
13264
|
}).strict();
|
|
13004
|
-
var GetTaskStatusInput =
|
|
13005
|
-
taskId:
|
|
13006
|
-
waitSeconds:
|
|
13007
|
-
});
|
|
13008
|
-
var ListTasksInput =
|
|
13009
|
-
limit:
|
|
13010
|
-
offset:
|
|
13011
|
-
search:
|
|
13012
|
-
});
|
|
13013
|
-
var EditTaskDraftInput =
|
|
13014
|
-
taskId:
|
|
13015
|
-
angleIndex:
|
|
13016
|
-
angle:
|
|
13017
|
-
angle:
|
|
13018
|
-
buyerPersona:
|
|
13019
|
-
actor:
|
|
13020
|
-
actorId:
|
|
13021
|
-
gender:
|
|
13265
|
+
var GetTaskStatusInput = z50.object({
|
|
13266
|
+
taskId: z50.string().min(1),
|
|
13267
|
+
waitSeconds: z50.number().int().min(0).max(120).optional()
|
|
13268
|
+
});
|
|
13269
|
+
var ListTasksInput = z50.object({
|
|
13270
|
+
limit: z50.number().int().min(1).max(50).optional(),
|
|
13271
|
+
offset: z50.number().int().min(0).optional(),
|
|
13272
|
+
search: z50.string().optional()
|
|
13273
|
+
});
|
|
13274
|
+
var EditTaskDraftInput = z50.object({
|
|
13275
|
+
taskId: z50.string().min(1),
|
|
13276
|
+
angleIndex: z50.number().int().min(0),
|
|
13277
|
+
angle: z50.object({
|
|
13278
|
+
angle: z50.string().min(1).max(480),
|
|
13279
|
+
buyerPersona: z50.string().min(1).max(360),
|
|
13280
|
+
actor: z50.string().min(1).max(360).optional(),
|
|
13281
|
+
actorId: z50.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."),
|
|
13282
|
+
gender: z50.enum([
|
|
13022
13283
|
"female",
|
|
13023
13284
|
"male"
|
|
13024
13285
|
]).optional(),
|
|
13025
|
-
scriptNotes:
|
|
13286
|
+
scriptNotes: z50.string().max(2e3).optional()
|
|
13026
13287
|
})
|
|
13027
13288
|
});
|
|
13028
|
-
var PendingActionPayload =
|
|
13029
|
-
|
|
13030
|
-
kind:
|
|
13289
|
+
var PendingActionPayload = z50.discriminatedUnion("kind", [
|
|
13290
|
+
z50.object({
|
|
13291
|
+
kind: z50.literal("product_reference_set_approval"),
|
|
13031
13292
|
scope: ProductReferenceScopeEnum,
|
|
13032
|
-
scopeKey:
|
|
13033
|
-
scopeSnapshotId:
|
|
13034
|
-
expectedScopeHash:
|
|
13293
|
+
scopeKey: z50.string().min(1).max(120),
|
|
13294
|
+
scopeSnapshotId: z50.string().uuid(),
|
|
13295
|
+
expectedScopeHash: z50.string().min(1).max(128),
|
|
13035
13296
|
decision: ProductReferenceResolutionDecisionEnum,
|
|
13036
13297
|
reason: ProductReferenceRejectionReasonEnum.optional(),
|
|
13037
|
-
affectedRefKeys:
|
|
13038
|
-
recoveryAction:
|
|
13298
|
+
affectedRefKeys: z50.array(z50.string().min(1).max(200)).min(1).optional(),
|
|
13299
|
+
recoveryAction: z50.enum([
|
|
13039
13300
|
"remove",
|
|
13040
13301
|
"replace",
|
|
13041
13302
|
"upload",
|
|
13042
13303
|
"return_to_selection"
|
|
13043
13304
|
]).optional()
|
|
13044
13305
|
}),
|
|
13045
|
-
|
|
13046
|
-
kind:
|
|
13306
|
+
z50.object({
|
|
13307
|
+
kind: z50.literal("product_reference_confirmation"),
|
|
13047
13308
|
userChoice: ProductReferenceUserChoiceEnum.optional(),
|
|
13048
|
-
confirmedProductNames:
|
|
13309
|
+
confirmedProductNames: z50.array(z50.string().min(1).max(120)).min(2).max(8).optional(),
|
|
13049
13310
|
// SCOPED multi-scene opt-in — the ONLY way STANDARD ("Multiescena") is
|
|
13050
13311
|
// reachable via MCP. Valid ONLY together with userChoice='variants' (same
|
|
13051
13312
|
// product in several colors): renders one shot per color and stitches them,
|
|
@@ -13056,50 +13317,50 @@ var PendingActionPayload = z49.discriminatedUnion("kind", [
|
|
|
13056
13317
|
// safe because multi-scene IS the intended render for a color variant set,
|
|
13057
13318
|
// not a blind quality upsell. Maps to `videoTier: 'standard'` on the
|
|
13058
13319
|
// confirm call; the backend re-gates it (variants + variant_set only).
|
|
13059
|
-
useMultiSceneShowcase:
|
|
13320
|
+
useMultiSceneShowcase: z50.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".')
|
|
13060
13321
|
}),
|
|
13061
|
-
|
|
13062
|
-
kind:
|
|
13322
|
+
z50.object({
|
|
13323
|
+
kind: z50.literal("angle_review_approval"),
|
|
13063
13324
|
// All edits are optional — empty action body just approves the
|
|
13064
13325
|
// backend-generated draft as-is. Field shapes mirror the angle-review
|
|
13065
13326
|
// request contracts in @packages/types/wizard.ts; the handler routes to
|
|
13066
13327
|
// the dedicated video/image/autopilot endpoint based on StatusResponse.kind.
|
|
13067
|
-
customAngle:
|
|
13328
|
+
customAngle: z50.string().min(1).max(480).optional(),
|
|
13068
13329
|
// Keep the shared MCP action within the strictest downstream contract:
|
|
13069
13330
|
// autopilot/image resume endpoints cap these fields at 1500 characters.
|
|
13070
|
-
painPoints:
|
|
13071
|
-
benefits:
|
|
13072
|
-
features:
|
|
13073
|
-
gapDurationSeconds:
|
|
13074
|
-
|
|
13075
|
-
|
|
13331
|
+
painPoints: z50.string().max(1500).optional(),
|
|
13332
|
+
benefits: z50.string().max(1500).optional(),
|
|
13333
|
+
features: z50.array(z50.string().min(1).max(300)).max(10).optional(),
|
|
13334
|
+
gapDurationSeconds: z50.union([
|
|
13335
|
+
z50.literal(15),
|
|
13336
|
+
z50.literal(30)
|
|
13076
13337
|
]).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.")
|
|
13077
13338
|
}),
|
|
13078
|
-
|
|
13079
|
-
kind:
|
|
13080
|
-
angleId:
|
|
13081
|
-
content:
|
|
13082
|
-
creativeDirection:
|
|
13083
|
-
actor:
|
|
13339
|
+
z50.object({
|
|
13340
|
+
kind: z50.literal("service_angle_approval"),
|
|
13341
|
+
angleId: z50.string().uuid(),
|
|
13342
|
+
content: z50.string().min(1).max(480).optional(),
|
|
13343
|
+
creativeDirection: z50.string().min(1).max(1500).optional(),
|
|
13344
|
+
actor: z50.string().min(1).max(360).optional()
|
|
13084
13345
|
}),
|
|
13085
|
-
|
|
13086
|
-
kind:
|
|
13087
|
-
voiceovers:
|
|
13088
|
-
visualDirections:
|
|
13346
|
+
z50.object({
|
|
13347
|
+
kind: z50.literal("service_script_approval"),
|
|
13348
|
+
voiceovers: z50.record(z50.string(), z50.string().min(1).max(600)).optional(),
|
|
13349
|
+
visualDirections: z50.record(z50.string(), z50.string().min(1).max(1200)).optional()
|
|
13089
13350
|
})
|
|
13090
13351
|
]);
|
|
13091
|
-
var ResolvePendingActionInput =
|
|
13092
|
-
taskId:
|
|
13352
|
+
var ResolvePendingActionInput = z50.object({
|
|
13353
|
+
taskId: z50.string().min(1),
|
|
13093
13354
|
action: PendingActionPayload
|
|
13094
13355
|
}).strict();
|
|
13095
13356
|
function resolveVariantConfirmVideoTier(action) {
|
|
13096
13357
|
return action.useMultiSceneShowcase && action.userChoice === "variants" ? "standard" : void 0;
|
|
13097
13358
|
}
|
|
13098
13359
|
__name(resolveVariantConfirmVideoTier, "resolveVariantConfirmVideoTier");
|
|
13099
|
-
var StatusForResolveSchema =
|
|
13360
|
+
var StatusForResolveSchema = z50.object({
|
|
13100
13361
|
// Metadata used to select the backend resume contract. Older API responses
|
|
13101
13362
|
// may omit these fields, so the resolver keeps the generic fallback.
|
|
13102
|
-
kind:
|
|
13363
|
+
kind: z50.enum([
|
|
13103
13364
|
"autopilot",
|
|
13104
13365
|
"video",
|
|
13105
13366
|
"image",
|
|
@@ -13107,24 +13368,24 @@ var StatusForResolveSchema = z49.object({
|
|
|
13107
13368
|
"mix",
|
|
13108
13369
|
"service"
|
|
13109
13370
|
]).optional(),
|
|
13110
|
-
selectedOutputs:
|
|
13371
|
+
selectedOutputs: z50.array(z50.enum([
|
|
13111
13372
|
"landing",
|
|
13112
13373
|
"video",
|
|
13113
13374
|
"image"
|
|
13114
13375
|
])).optional(),
|
|
13115
|
-
videoAngleCount:
|
|
13116
|
-
ugcFormat:
|
|
13376
|
+
videoAngleCount: z50.number().int().min(1).max(3).optional(),
|
|
13377
|
+
ugcFormat: z50.enum([
|
|
13117
13378
|
"single_8s",
|
|
13118
13379
|
"single_15s",
|
|
13119
13380
|
"double_30s"
|
|
13120
13381
|
]).optional(),
|
|
13121
|
-
videoStyle:
|
|
13382
|
+
videoStyle: z50.enum([
|
|
13122
13383
|
"ugc",
|
|
13123
13384
|
"cartoon_3d",
|
|
13124
13385
|
"product_only",
|
|
13125
13386
|
"ai_dynamic"
|
|
13126
13387
|
]).optional(),
|
|
13127
|
-
videoModel:
|
|
13388
|
+
videoModel: z50.enum([
|
|
13128
13389
|
"veo",
|
|
13129
13390
|
"wan",
|
|
13130
13391
|
"bytedance",
|
|
@@ -13132,62 +13393,62 @@ var StatusForResolveSchema = z49.object({
|
|
|
13132
13393
|
"grok",
|
|
13133
13394
|
"kling"
|
|
13134
13395
|
]).optional(),
|
|
13135
|
-
videoTier:
|
|
13396
|
+
videoTier: z50.enum([
|
|
13136
13397
|
"legacy",
|
|
13137
13398
|
"standard",
|
|
13138
13399
|
"pro",
|
|
13139
13400
|
"single_call"
|
|
13140
13401
|
]).optional(),
|
|
13141
|
-
videoResolution:
|
|
13402
|
+
videoResolution: z50.enum([
|
|
13142
13403
|
"480p",
|
|
13143
13404
|
"720p"
|
|
13144
13405
|
]).optional(),
|
|
13145
13406
|
narrativeStyle: NarrativeStyleEnum.optional(),
|
|
13146
|
-
pendingUserAction:
|
|
13147
|
-
kind:
|
|
13148
|
-
candidateGrouping:
|
|
13149
|
-
selectedImageUrls:
|
|
13150
|
-
proposedProductNames:
|
|
13407
|
+
pendingUserAction: z50.object({
|
|
13408
|
+
kind: z50.string(),
|
|
13409
|
+
candidateGrouping: z50.string().optional(),
|
|
13410
|
+
selectedImageUrls: z50.array(z50.string()).optional(),
|
|
13411
|
+
proposedProductNames: z50.array(z50.string()).optional(),
|
|
13151
13412
|
// Adaptive reference questions are server-owned. Echo only the evidence
|
|
13152
13413
|
// identity and the choices shown by the current snapshot; never accept a
|
|
13153
13414
|
// client-supplied reference set as authorization.
|
|
13154
|
-
options:
|
|
13155
|
-
mode:
|
|
13415
|
+
options: z50.array(ProductReferenceUserChoiceEnum).max(4).optional(),
|
|
13416
|
+
mode: z50.enum([
|
|
13156
13417
|
"question",
|
|
13157
13418
|
"visual_recovery"
|
|
13158
13419
|
]).optional(),
|
|
13159
|
-
decisionKey:
|
|
13160
|
-
questionOrdinal:
|
|
13161
|
-
questionCeiling:
|
|
13162
|
-
|
|
13163
|
-
|
|
13420
|
+
decisionKey: z50.string().optional(),
|
|
13421
|
+
questionOrdinal: z50.number().int().min(1).max(5).optional(),
|
|
13422
|
+
questionCeiling: z50.union([
|
|
13423
|
+
z50.literal(3),
|
|
13424
|
+
z50.literal(5)
|
|
13164
13425
|
]).optional(),
|
|
13165
|
-
referenceEvidenceVersion:
|
|
13166
|
-
expectedEvidenceHash:
|
|
13167
|
-
question:
|
|
13168
|
-
prompt:
|
|
13169
|
-
helpText:
|
|
13170
|
-
options:
|
|
13426
|
+
referenceEvidenceVersion: z50.number().int().positive().optional(),
|
|
13427
|
+
expectedEvidenceHash: z50.string().optional(),
|
|
13428
|
+
question: z50.object({
|
|
13429
|
+
prompt: z50.string().min(1).max(240),
|
|
13430
|
+
helpText: z50.string().min(1).max(300).optional(),
|
|
13431
|
+
options: z50.array(z50.object({
|
|
13171
13432
|
value: ProductReferenceUserChoiceEnum,
|
|
13172
|
-
label:
|
|
13173
|
-
description:
|
|
13433
|
+
label: z50.string().min(1).max(120),
|
|
13434
|
+
description: z50.string().min(1).max(240).optional()
|
|
13174
13435
|
})).min(2).max(4)
|
|
13175
13436
|
}).optional(),
|
|
13176
13437
|
scope: ProductReferenceScopeEnum.optional(),
|
|
13177
|
-
scopeKey:
|
|
13178
|
-
scopeSnapshotId:
|
|
13179
|
-
scopeHash:
|
|
13180
|
-
baseReferenceSetHash:
|
|
13181
|
-
phase:
|
|
13438
|
+
scopeKey: z50.string().optional(),
|
|
13439
|
+
scopeSnapshotId: z50.string().uuid().optional(),
|
|
13440
|
+
scopeHash: z50.string().optional(),
|
|
13441
|
+
baseReferenceSetHash: z50.string().optional(),
|
|
13442
|
+
phase: z50.enum([
|
|
13182
13443
|
"preparation",
|
|
13183
13444
|
"final"
|
|
13184
13445
|
]).optional(),
|
|
13185
|
-
selectedRefKeys:
|
|
13186
|
-
allowedDecisions:
|
|
13187
|
-
customAngleDraft:
|
|
13188
|
-
painPointsDraft:
|
|
13189
|
-
benefitsDraft:
|
|
13190
|
-
featuresDraft:
|
|
13446
|
+
selectedRefKeys: z50.array(z50.string().min(1).max(200)).optional(),
|
|
13447
|
+
allowedDecisions: z50.array(ProductReferenceResolutionDecisionEnum).optional(),
|
|
13448
|
+
customAngleDraft: z50.string().optional(),
|
|
13449
|
+
painPointsDraft: z50.string().optional(),
|
|
13450
|
+
benefitsDraft: z50.string().optional(),
|
|
13451
|
+
featuresDraft: z50.array(z50.string()).optional(),
|
|
13191
13452
|
// Recommendation consumed only when the caller explicitly sends
|
|
13192
13453
|
// gapDurationSeconds: 30 in the angle approval action.
|
|
13193
13454
|
gapDurationChoice: GapDurationChoiceMirror.optional(),
|
|
@@ -13196,17 +13457,17 @@ var StatusForResolveSchema = z49.object({
|
|
|
13196
13457
|
// keys. `actorId` (the saved custom actor the picker stored) was silently
|
|
13197
13458
|
// dropped here, so the approval handler below could not echo it back on the
|
|
13198
13459
|
// PATCH — the selection looked accepted and reached no render.
|
|
13199
|
-
angles:
|
|
13200
|
-
id:
|
|
13201
|
-
content:
|
|
13202
|
-
creativeDirection:
|
|
13203
|
-
actor:
|
|
13204
|
-
actorId:
|
|
13460
|
+
angles: z50.array(z50.object({
|
|
13461
|
+
id: z50.string().uuid(),
|
|
13462
|
+
content: z50.string(),
|
|
13463
|
+
creativeDirection: z50.string(),
|
|
13464
|
+
actor: z50.string(),
|
|
13465
|
+
actorId: z50.string().nullable().optional()
|
|
13205
13466
|
}).passthrough()).optional(),
|
|
13206
|
-
panels:
|
|
13207
|
-
beatId:
|
|
13208
|
-
voiceover:
|
|
13209
|
-
visualDirection:
|
|
13467
|
+
panels: z50.array(z50.object({
|
|
13468
|
+
beatId: z50.string(),
|
|
13469
|
+
voiceover: z50.string(),
|
|
13470
|
+
visualDirection: z50.string()
|
|
13210
13471
|
}).passthrough()).optional()
|
|
13211
13472
|
}).optional()
|
|
13212
13473
|
}).passthrough();
|
|
@@ -13245,7 +13506,7 @@ function resolveStatusTaskKind(status) {
|
|
|
13245
13506
|
}
|
|
13246
13507
|
__name(resolveStatusTaskKind, "resolveStatusTaskKind");
|
|
13247
13508
|
function registerTaskTools(server, api, { proxyBaseUrl = null, transport = "http" } = {}) {
|
|
13248
|
-
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) => {
|
|
13509
|
+
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) => {
|
|
13249
13510
|
try {
|
|
13250
13511
|
if (!input.images && !input.projectId) {
|
|
13251
13512
|
throw new Error("images es requerido salvo que env\xEDes projectId.");
|
|
@@ -13313,7 +13574,7 @@ function registerTaskTools(server, api, { proxyBaseUrl = null, transport = "http
|
|
|
13313
13574
|
throw translateError(e);
|
|
13314
13575
|
}
|
|
13315
13576
|
});
|
|
13316
|
-
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) => {
|
|
13577
|
+
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) => {
|
|
13317
13578
|
try {
|
|
13318
13579
|
const created = await api.postJson("/v2/ingest", {
|
|
13319
13580
|
taskType: "service",
|
|
@@ -13364,7 +13625,7 @@ function registerTaskTools(server, api, { proxyBaseUrl = null, transport = "http
|
|
|
13364
13625
|
throw translateError(e);
|
|
13365
13626
|
}
|
|
13366
13627
|
});
|
|
13367
|
-
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) => {
|
|
13628
|
+
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) => {
|
|
13368
13629
|
try {
|
|
13369
13630
|
const signal = extra?.signal;
|
|
13370
13631
|
const fetcher = /* @__PURE__ */ __name(() => api.getJson(`/v1/status/${encodeURIComponent(input.taskId)}`, {
|
|
@@ -13424,7 +13685,7 @@ function registerTaskTools(server, api, { proxyBaseUrl = null, transport = "http
|
|
|
13424
13685
|
throw translateError(e);
|
|
13425
13686
|
}
|
|
13426
13687
|
});
|
|
13427
|
-
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) => {
|
|
13688
|
+
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) => {
|
|
13428
13689
|
try {
|
|
13429
13690
|
const qs = new URLSearchParams();
|
|
13430
13691
|
if (input.limit != null) qs.set("limit", String(input.limit));
|
|
@@ -13437,7 +13698,7 @@ function registerTaskTools(server, api, { proxyBaseUrl = null, transport = "http
|
|
|
13437
13698
|
throw translateError(e);
|
|
13438
13699
|
}
|
|
13439
13700
|
});
|
|
13440
|
-
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) => {
|
|
13701
|
+
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) => {
|
|
13441
13702
|
try {
|
|
13442
13703
|
const angle = await api.patchJson(`/v1/tasks/${encodeURIComponent(input.taskId)}/angles/${input.angleIndex}`, input.angle ?? {});
|
|
13443
13704
|
return asText({
|
|
@@ -13447,315 +13708,331 @@ function registerTaskTools(server, api, { proxyBaseUrl = null, transport = "http
|
|
|
13447
13708
|
throw translateError(e);
|
|
13448
13709
|
}
|
|
13449
13710
|
});
|
|
13450
|
-
server.tool(
|
|
13451
|
-
|
|
13452
|
-
|
|
13453
|
-
|
|
13454
|
-
|
|
13455
|
-
|
|
13456
|
-
|
|
13457
|
-
|
|
13458
|
-
|
|
13459
|
-
|
|
13460
|
-
|
|
13461
|
-
|
|
13462
|
-
|
|
13463
|
-
|
|
13464
|
-
|
|
13465
|
-
|
|
13466
|
-
if (input.action.decision === "reject" && (!input.action.reason || !input.action.affectedRefKeys?.length || !input.action.recoveryAction)) {
|
|
13467
|
-
throw new Error("Una referencia rechazada necesita motivo, referencias afectadas y acci\xF3n de recuperaci\xF3n.");
|
|
13468
|
-
}
|
|
13469
|
-
if (input.action.decision === "correct_view" && (!input.action.reason || !input.action.affectedRefKeys?.length)) {
|
|
13470
|
-
throw new Error("Una correcci\xF3n de \xE1ngulo necesita motivo y referencias afectadas.");
|
|
13471
|
-
}
|
|
13472
|
-
if (input.action.decision === "correct_view" && input.action.affectedRefKeys?.length !== 1) {
|
|
13473
|
-
throw new Error("Corrige una sola vista por intento.");
|
|
13474
|
-
}
|
|
13475
|
-
if (pending.phase === "preparation" && input.action.decision === "approve" && (!pending.selectedRefKeys || pending.selectedRefKeys.length === 0)) {
|
|
13476
|
-
throw new Error("No hay fotos seleccionadas para preparar. Abre la tarea en ProductMaker Web y elige al menos una.");
|
|
13477
|
-
}
|
|
13478
|
-
const result = await api.postJson(`/v1/tasks/${encodeURIComponent(input.taskId)}/resolve-product-reference-set`, {
|
|
13479
|
-
...input.action,
|
|
13480
|
-
...pending.phase === "preparation" && input.action.decision === "approve" && pending.selectedRefKeys ? {
|
|
13481
|
-
selectedRefKeys: pending.selectedRefKeys
|
|
13482
|
-
} : {}
|
|
13483
|
-
});
|
|
13484
|
-
return asText(result);
|
|
13711
|
+
server.tool(
|
|
13712
|
+
"resolve_pending_action",
|
|
13713
|
+
"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.",
|
|
13714
|
+
ResolvePendingActionInput.shape,
|
|
13715
|
+
// Approving the gate RELEASES the pipeline into paid generation at the video
|
|
13716
|
+
// and image providers (registry effects: credit_consuming + state_change),
|
|
13717
|
+
// and decision=reject discards the approved reference set. Both halves, hence
|
|
13718
|
+
// destructive AND open-world.
|
|
13719
|
+
OVERWRITES_EXTERNAL,
|
|
13720
|
+
async (input) => {
|
|
13721
|
+
try {
|
|
13722
|
+
const raw = await api.getJson(`/v1/status/${encodeURIComponent(input.taskId)}`);
|
|
13723
|
+
const status = StatusForResolveSchema.parse(raw);
|
|
13724
|
+
const pending = status.pendingUserAction;
|
|
13725
|
+
if (!pending) {
|
|
13726
|
+
throw new Error("No hay acci\xF3n pendiente para esta tarea \u2014 llama get_task_status primero para confirmar el estado.");
|
|
13485
13727
|
}
|
|
13486
|
-
|
|
13487
|
-
|
|
13488
|
-
const selectedImageUrls = pending.selectedImageUrls ?? [];
|
|
13489
|
-
if (pending.mode === "visual_recovery") {
|
|
13490
|
-
throw new Error("Esta recuperaci\xF3n necesita subir fotos con un ticket ligado al usuario. \xC1brela en ProductMaker Web; MCP no acepta URLs arbitrarias.");
|
|
13491
|
-
}
|
|
13492
|
-
if (!candidateGrouping || selectedImageUrls.length === 0) {
|
|
13493
|
-
throw new Error("Estado inconsistente: la tarea est\xE1 pausada pero falta candidateGrouping o selectedImageUrls.");
|
|
13494
|
-
}
|
|
13495
|
-
const userChoice = input.action.userChoice;
|
|
13496
|
-
if (!userChoice) {
|
|
13497
|
-
throw new Error("La pregunta adaptativa necesita userChoice.");
|
|
13498
|
-
}
|
|
13499
|
-
const offeredChoices = pending.question?.options.map((option) => option.value) ?? pending.options;
|
|
13500
|
-
if (!offeredChoices) {
|
|
13501
|
-
throw new Error("Estado inconsistente: falta la pregunta adaptativa con sus opciones verificables. Vuelve a llamar get_task_status.");
|
|
13502
|
-
}
|
|
13503
|
-
if (!offeredChoices.includes(userChoice)) {
|
|
13504
|
-
throw new Error(`La opci\xF3n "${userChoice}" no fue ofrecida en la pregunta pendiente. Elige una opci\xF3n mostrada por get_task_status.`);
|
|
13505
|
-
}
|
|
13506
|
-
const r = await api.postJson(`/v1/tasks/${encodeURIComponent(input.taskId)}/confirm-product-references`, {
|
|
13507
|
-
productReferenceConfirmation: {
|
|
13508
|
-
candidateGrouping,
|
|
13509
|
-
userChoice,
|
|
13510
|
-
// Server overwrites with its own server-side computation;
|
|
13511
|
-
// we send the matching MCP-mirror value so the wire body
|
|
13512
|
-
// is internally coherent if the server skips overwrite.
|
|
13513
|
-
selectedImageUrlsHash: buildSelectedImageUrlsHash(selectedImageUrls)
|
|
13514
|
-
},
|
|
13515
|
-
...input.action.confirmedProductNames ? {
|
|
13516
|
-
confirmedProductNames: input.action.confirmedProductNames
|
|
13517
|
-
} : {},
|
|
13518
|
-
...pending.decisionKey && pending.referenceEvidenceVersion && pending.expectedEvidenceHash ? {
|
|
13519
|
-
decisionKey: pending.decisionKey,
|
|
13520
|
-
referenceEvidenceVersion: pending.referenceEvidenceVersion,
|
|
13521
|
-
expectedEvidenceHash: pending.expectedEvidenceHash
|
|
13522
|
-
} : {},
|
|
13523
|
-
// Multi-scene color showcase → upgrade to STANDARD. Gated to the
|
|
13524
|
-
// variants choice client-side so the wire body stays coherent; the
|
|
13525
|
-
// backend re-gates on variant_set grouping.
|
|
13526
|
-
...resolveVariantConfirmVideoTier(input.action) === "standard" ? {
|
|
13527
|
-
videoTier: "standard"
|
|
13528
|
-
} : {}
|
|
13529
|
-
});
|
|
13530
|
-
return asText(r);
|
|
13728
|
+
if (pending.kind !== input.action.kind) {
|
|
13729
|
+
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.`);
|
|
13531
13730
|
}
|
|
13532
|
-
|
|
13533
|
-
|
|
13534
|
-
|
|
13535
|
-
|
|
13731
|
+
switch (input.action.kind) {
|
|
13732
|
+
case "product_reference_set_approval": {
|
|
13733
|
+
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)) {
|
|
13734
|
+
throw new Error("La aprobaci\xF3n de referencias no coincide con el snapshot mostrado; vuelve a llamar get_task_status.");
|
|
13735
|
+
}
|
|
13736
|
+
if (input.action.decision === "reject" && (!input.action.reason || !input.action.affectedRefKeys?.length || !input.action.recoveryAction)) {
|
|
13737
|
+
throw new Error("Una referencia rechazada necesita motivo, referencias afectadas y acci\xF3n de recuperaci\xF3n.");
|
|
13738
|
+
}
|
|
13739
|
+
if (input.action.decision === "correct_view" && (!input.action.reason || !input.action.affectedRefKeys?.length)) {
|
|
13740
|
+
throw new Error("Una correcci\xF3n de \xE1ngulo necesita motivo y referencias afectadas.");
|
|
13741
|
+
}
|
|
13742
|
+
if (input.action.decision === "correct_view" && input.action.affectedRefKeys?.length !== 1) {
|
|
13743
|
+
throw new Error("Corrige una sola vista por intento.");
|
|
13744
|
+
}
|
|
13745
|
+
if (pending.phase === "preparation" && input.action.decision === "approve" && (!pending.selectedRefKeys || pending.selectedRefKeys.length === 0)) {
|
|
13746
|
+
throw new Error("No hay fotos seleccionadas para preparar. Abre la tarea en ProductMaker Web y elige al menos una.");
|
|
13747
|
+
}
|
|
13748
|
+
const result = await api.postJson(`/v1/tasks/${encodeURIComponent(input.taskId)}/resolve-product-reference-set`, {
|
|
13749
|
+
...input.action,
|
|
13750
|
+
...pending.phase === "preparation" && input.action.decision === "approve" && pending.selectedRefKeys ? {
|
|
13751
|
+
selectedRefKeys: pending.selectedRefKeys
|
|
13752
|
+
} : {}
|
|
13753
|
+
});
|
|
13754
|
+
return asText(result);
|
|
13536
13755
|
}
|
|
13537
|
-
|
|
13538
|
-
|
|
13539
|
-
|
|
13540
|
-
|
|
13541
|
-
|
|
13542
|
-
|
|
13543
|
-
|
|
13544
|
-
|
|
13545
|
-
|
|
13546
|
-
|
|
13547
|
-
|
|
13756
|
+
case "product_reference_confirmation": {
|
|
13757
|
+
const candidateGrouping = pending.candidateGrouping;
|
|
13758
|
+
const selectedImageUrls = pending.selectedImageUrls ?? [];
|
|
13759
|
+
if (pending.mode === "visual_recovery") {
|
|
13760
|
+
throw new Error("Esta recuperaci\xF3n necesita subir fotos con un ticket ligado al usuario. \xC1brela en ProductMaker Web; MCP no acepta URLs arbitrarias.");
|
|
13761
|
+
}
|
|
13762
|
+
if (!candidateGrouping || selectedImageUrls.length === 0) {
|
|
13763
|
+
throw new Error("Estado inconsistente: la tarea est\xE1 pausada pero falta candidateGrouping o selectedImageUrls.");
|
|
13764
|
+
}
|
|
13765
|
+
const userChoice = input.action.userChoice;
|
|
13766
|
+
if (!userChoice) {
|
|
13767
|
+
throw new Error("La pregunta adaptativa necesita userChoice.");
|
|
13768
|
+
}
|
|
13769
|
+
const offeredChoices = pending.question?.options.map((option) => option.value) ?? pending.options;
|
|
13770
|
+
if (!offeredChoices) {
|
|
13771
|
+
throw new Error("Estado inconsistente: falta la pregunta adaptativa con sus opciones verificables. Vuelve a llamar get_task_status.");
|
|
13772
|
+
}
|
|
13773
|
+
if (!offeredChoices.includes(userChoice)) {
|
|
13774
|
+
throw new Error(`La opci\xF3n "${userChoice}" no fue ofrecida en la pregunta pendiente. Elige una opci\xF3n mostrada por get_task_status.`);
|
|
13775
|
+
}
|
|
13776
|
+
const r = await api.postJson(`/v1/tasks/${encodeURIComponent(input.taskId)}/confirm-product-references`, {
|
|
13777
|
+
productReferenceConfirmation: {
|
|
13778
|
+
candidateGrouping,
|
|
13779
|
+
userChoice,
|
|
13780
|
+
// Server overwrites with its own server-side computation;
|
|
13781
|
+
// we send the matching MCP-mirror value so the wire body
|
|
13782
|
+
// is internally coherent if the server skips overwrite.
|
|
13783
|
+
selectedImageUrlsHash: buildSelectedImageUrlsHash(selectedImageUrls)
|
|
13784
|
+
},
|
|
13785
|
+
...input.action.confirmedProductNames ? {
|
|
13786
|
+
confirmedProductNames: input.action.confirmedProductNames
|
|
13787
|
+
} : {},
|
|
13788
|
+
...pending.decisionKey && pending.referenceEvidenceVersion && pending.expectedEvidenceHash ? {
|
|
13789
|
+
decisionKey: pending.decisionKey,
|
|
13790
|
+
referenceEvidenceVersion: pending.referenceEvidenceVersion,
|
|
13791
|
+
expectedEvidenceHash: pending.expectedEvidenceHash
|
|
13792
|
+
} : {},
|
|
13793
|
+
// Multi-scene color showcase → upgrade to STANDARD. Gated to the
|
|
13794
|
+
// variants choice client-side so the wire body stays coherent; the
|
|
13795
|
+
// backend re-gates on variant_set grouping.
|
|
13796
|
+
...resolveVariantConfirmVideoTier(input.action) === "standard" ? {
|
|
13797
|
+
videoTier: "standard"
|
|
13798
|
+
} : {}
|
|
13799
|
+
});
|
|
13800
|
+
return asText(r);
|
|
13548
13801
|
}
|
|
13549
|
-
|
|
13550
|
-
const
|
|
13551
|
-
if (
|
|
13552
|
-
|
|
13553
|
-
throw new Error(`La recomendaci\xF3n de duraci\xF3n para este winner es ${choice.recommendedSec}s; no se puede solicitar 30s con este estado.`);
|
|
13802
|
+
case "angle_review_approval": {
|
|
13803
|
+
const body = {};
|
|
13804
|
+
if (pending.customAngleDraft && pending.customAngleDraft.length > 0) {
|
|
13805
|
+
body.customAngle = pending.customAngleDraft;
|
|
13554
13806
|
}
|
|
13555
|
-
body.
|
|
13556
|
-
|
|
13557
|
-
|
|
13558
|
-
|
|
13559
|
-
}
|
|
13807
|
+
if (pending.painPointsDraft != null) body.painPoints = pending.painPointsDraft;
|
|
13808
|
+
if (pending.benefitsDraft != null) body.benefits = pending.benefitsDraft;
|
|
13809
|
+
if (pending.featuresDraft != null) body.features = [
|
|
13810
|
+
...pending.featuresDraft
|
|
13560
13811
|
];
|
|
13561
|
-
|
|
13562
|
-
|
|
13563
|
-
|
|
13564
|
-
|
|
13565
|
-
|
|
13566
|
-
|
|
13567
|
-
|
|
13568
|
-
|
|
13569
|
-
|
|
13570
|
-
|
|
13571
|
-
|
|
13812
|
+
if (input.action.customAngle != null) body.customAngle = input.action.customAngle;
|
|
13813
|
+
if (input.action.painPoints != null) body.painPoints = input.action.painPoints;
|
|
13814
|
+
if (input.action.benefits != null) body.benefits = input.action.benefits;
|
|
13815
|
+
if (input.action.features != null) body.features = input.action.features;
|
|
13816
|
+
if (input.action.gapDurationSeconds != null && !pending.gapDurationChoice) {
|
|
13817
|
+
throw new Error("No hay una recomendaci\xF3n gapDurationChoice pendiente para elegir una duraci\xF3n. Consulta get_task_status y confirma el winner primero.");
|
|
13818
|
+
}
|
|
13819
|
+
if (input.action.gapDurationSeconds === 30) {
|
|
13820
|
+
const choice = pending.gapDurationChoice;
|
|
13821
|
+
if (!choice) throw new Error("No hay una recomendaci\xF3n gapDurationChoice pendiente para aceptar 30s.");
|
|
13822
|
+
if (choice.recommendedSec !== 30) {
|
|
13823
|
+
throw new Error(`La recomendaci\xF3n de duraci\xF3n para este winner es ${choice.recommendedSec}s; no se puede solicitar 30s con este estado.`);
|
|
13572
13824
|
}
|
|
13573
|
-
|
|
13574
|
-
|
|
13575
|
-
|
|
13576
|
-
|
|
13577
|
-
|
|
13825
|
+
body.angleVideoOptions = [
|
|
13826
|
+
{
|
|
13827
|
+
angleIndex: choice.angleIndex,
|
|
13828
|
+
ugcFormat: "double_30s"
|
|
13829
|
+
}
|
|
13830
|
+
];
|
|
13831
|
+
}
|
|
13832
|
+
const selectedAngleIndices = resolveSelectedAngleIndices(status, pending.gapDurationChoice);
|
|
13833
|
+
const resolvedTaskKind = resolveStatusTaskKind(status);
|
|
13834
|
+
const isVideoTask = resolvedTaskKind === "video";
|
|
13835
|
+
const isAutopilotTask = resolvedTaskKind === "autopilot";
|
|
13836
|
+
if ((isVideoTask || isAutopilotTask) && body.customAngle != null) {
|
|
13837
|
+
const existingOptions = Array.isArray(body.angleVideoOptions) ? body.angleVideoOptions : [];
|
|
13838
|
+
const optionsByIndex = new Map(existingOptions.map((option) => [
|
|
13839
|
+
option.angleIndex,
|
|
13840
|
+
{
|
|
13841
|
+
...option
|
|
13842
|
+
}
|
|
13843
|
+
]));
|
|
13844
|
+
for (const angleIndex of selectedAngleIndices) {
|
|
13845
|
+
optionsByIndex.set(angleIndex, {
|
|
13846
|
+
...optionsByIndex.get(angleIndex) ?? {
|
|
13847
|
+
angleIndex
|
|
13848
|
+
},
|
|
13849
|
+
angle: body.customAngle
|
|
13850
|
+
});
|
|
13851
|
+
}
|
|
13852
|
+
body.angleVideoOptions = [
|
|
13853
|
+
...optionsByIndex.values()
|
|
13854
|
+
].sort((left, right) => Number(left.angleIndex) - Number(right.angleIndex));
|
|
13855
|
+
}
|
|
13856
|
+
if (isVideoTask) {
|
|
13857
|
+
body.selectedAngleIndices = selectedAngleIndices;
|
|
13858
|
+
const r2 = await api.postJson(`/v1/tasks/${encodeURIComponent(input.taskId)}/approve-video-angles`, body);
|
|
13859
|
+
return asText(r2);
|
|
13860
|
+
}
|
|
13861
|
+
if (isAutopilotTask) {
|
|
13862
|
+
const resumeBody = {
|
|
13863
|
+
selectedAngleIndices,
|
|
13864
|
+
...status.ugcFormat ? {
|
|
13865
|
+
ugcFormat: status.ugcFormat
|
|
13866
|
+
} : {
|
|
13867
|
+
ugcFormat: "single_15s"
|
|
13578
13868
|
},
|
|
13579
|
-
|
|
13580
|
-
|
|
13869
|
+
...status.videoStyle ? {
|
|
13870
|
+
videoStyle: status.videoStyle
|
|
13871
|
+
} : {},
|
|
13872
|
+
...status.videoModel ? {
|
|
13873
|
+
videoModel: status.videoModel
|
|
13874
|
+
} : {},
|
|
13875
|
+
...status.videoTier ? {
|
|
13876
|
+
videoTier: status.videoTier
|
|
13877
|
+
} : {},
|
|
13878
|
+
...status.videoResolution ? {
|
|
13879
|
+
videoResolution: status.videoResolution
|
|
13880
|
+
} : {},
|
|
13881
|
+
...status.narrativeStyle ? {
|
|
13882
|
+
narrativeStyle: status.narrativeStyle
|
|
13883
|
+
} : {},
|
|
13884
|
+
...body.customAngle != null ? {
|
|
13885
|
+
customAngleOverride: body.customAngle
|
|
13886
|
+
} : {},
|
|
13887
|
+
...body.painPoints != null ? {
|
|
13888
|
+
painPoints: body.painPoints
|
|
13889
|
+
} : {},
|
|
13890
|
+
...body.benefits != null ? {
|
|
13891
|
+
benefits: body.benefits
|
|
13892
|
+
} : {},
|
|
13893
|
+
...body.angleVideoOptions ? {
|
|
13894
|
+
angleVideoOptions: body.angleVideoOptions
|
|
13895
|
+
} : {}
|
|
13896
|
+
};
|
|
13897
|
+
if (body.features !== void 0) {
|
|
13898
|
+
await api.patchJson(`/v1/tasks/${encodeURIComponent(input.taskId)}/update-product-features`, {
|
|
13899
|
+
features: body.features
|
|
13900
|
+
});
|
|
13901
|
+
}
|
|
13902
|
+
const r2 = await api.postJson(`/v1/ingest/${encodeURIComponent(input.taskId)}/resume`, resumeBody);
|
|
13903
|
+
if (r2?.success === false) {
|
|
13904
|
+
throw new Error(approveRefusalMessage(r2.reason));
|
|
13905
|
+
}
|
|
13906
|
+
return asText(r2);
|
|
13581
13907
|
}
|
|
13582
|
-
|
|
13583
|
-
|
|
13584
|
-
].sort((left, right) => Number(left.angleIndex) - Number(right.angleIndex));
|
|
13585
|
-
}
|
|
13586
|
-
if (isVideoTask) {
|
|
13587
|
-
body.selectedAngleIndices = selectedAngleIndices;
|
|
13588
|
-
const r2 = await api.postJson(`/v1/tasks/${encodeURIComponent(input.taskId)}/approve-video-angles`, body);
|
|
13589
|
-
return asText(r2);
|
|
13590
|
-
}
|
|
13591
|
-
if (isAutopilotTask) {
|
|
13592
|
-
const resumeBody = {
|
|
13593
|
-
selectedAngleIndices,
|
|
13594
|
-
...status.ugcFormat ? {
|
|
13595
|
-
ugcFormat: status.ugcFormat
|
|
13596
|
-
} : {
|
|
13597
|
-
ugcFormat: "single_15s"
|
|
13598
|
-
},
|
|
13599
|
-
...status.videoStyle ? {
|
|
13600
|
-
videoStyle: status.videoStyle
|
|
13601
|
-
} : {},
|
|
13602
|
-
...status.videoModel ? {
|
|
13603
|
-
videoModel: status.videoModel
|
|
13604
|
-
} : {},
|
|
13605
|
-
...status.videoTier ? {
|
|
13606
|
-
videoTier: status.videoTier
|
|
13607
|
-
} : {},
|
|
13608
|
-
...status.videoResolution ? {
|
|
13609
|
-
videoResolution: status.videoResolution
|
|
13610
|
-
} : {},
|
|
13611
|
-
...status.narrativeStyle ? {
|
|
13612
|
-
narrativeStyle: status.narrativeStyle
|
|
13613
|
-
} : {},
|
|
13908
|
+
const route = resolvedTaskKind === "image" ? `/v1/tasks/${encodeURIComponent(input.taskId)}/approve-image-angles` : `/v1/tasks/${encodeURIComponent(input.taskId)}/approve-angles`;
|
|
13909
|
+
const imageBody = status.kind === "image" ? {
|
|
13614
13910
|
...body.customAngle != null ? {
|
|
13615
|
-
|
|
13911
|
+
customAngle: body.customAngle
|
|
13616
13912
|
} : {},
|
|
13617
13913
|
...body.painPoints != null ? {
|
|
13618
13914
|
painPoints: body.painPoints
|
|
13619
13915
|
} : {},
|
|
13620
|
-
...body.
|
|
13621
|
-
benefits: body.benefits
|
|
13622
|
-
} : {},
|
|
13623
|
-
...body.angleVideoOptions ? {
|
|
13624
|
-
angleVideoOptions: body.angleVideoOptions
|
|
13625
|
-
} : {}
|
|
13626
|
-
};
|
|
13627
|
-
if (body.features !== void 0) {
|
|
13628
|
-
await api.patchJson(`/v1/tasks/${encodeURIComponent(input.taskId)}/update-product-features`, {
|
|
13916
|
+
...body.features != null ? {
|
|
13629
13917
|
features: body.features
|
|
13630
|
-
}
|
|
13918
|
+
} : {}
|
|
13919
|
+
} : body;
|
|
13920
|
+
const r = await api.postJson(route, imageBody);
|
|
13921
|
+
if (r?.success === false) {
|
|
13922
|
+
throw new Error(approveRefusalMessage(r.reason));
|
|
13631
13923
|
}
|
|
13632
|
-
|
|
13633
|
-
return asText(r2);
|
|
13924
|
+
return asText(r);
|
|
13634
13925
|
}
|
|
13635
|
-
|
|
13636
|
-
|
|
13637
|
-
|
|
13638
|
-
|
|
13639
|
-
|
|
13640
|
-
|
|
13641
|
-
|
|
13642
|
-
|
|
13643
|
-
|
|
13644
|
-
|
|
13645
|
-
|
|
13646
|
-
|
|
13647
|
-
|
|
13648
|
-
|
|
13649
|
-
|
|
13650
|
-
|
|
13651
|
-
|
|
13652
|
-
|
|
13653
|
-
|
|
13654
|
-
|
|
13926
|
+
case "service_angle_approval": {
|
|
13927
|
+
const action = input.action;
|
|
13928
|
+
const candidate = pending.angles?.find((angle) => angle.id === action.angleId);
|
|
13929
|
+
if (!candidate) {
|
|
13930
|
+
throw new Error("El angleId no pertenece a los conceptos pendientes de esta tarea.");
|
|
13931
|
+
}
|
|
13932
|
+
const edits = {
|
|
13933
|
+
content: action.content ?? candidate.content,
|
|
13934
|
+
creativeDirection: action.creativeDirection ?? candidate.creativeDirection,
|
|
13935
|
+
actor: action.actor ?? candidate.actor,
|
|
13936
|
+
// Echo the persisted saved actor. Omitted when absent so the PATCH
|
|
13937
|
+
// never writes a null over a selection made elsewhere — the
|
|
13938
|
+
// backend only updates the keys it receives.
|
|
13939
|
+
...candidate.actorId ? {
|
|
13940
|
+
actorId: candidate.actorId
|
|
13941
|
+
} : {}
|
|
13942
|
+
};
|
|
13943
|
+
await api.patchJson(`/v1/tasks/${encodeURIComponent(input.taskId)}/service-angles/${encodeURIComponent(candidate.id)}`, edits);
|
|
13944
|
+
const result = await api.postJson(`/v1/tasks/${encodeURIComponent(input.taskId)}/service-angles/select`, {
|
|
13945
|
+
angleIds: [
|
|
13946
|
+
candidate.id
|
|
13947
|
+
]
|
|
13948
|
+
});
|
|
13949
|
+
return asText(result);
|
|
13655
13950
|
}
|
|
13656
|
-
|
|
13657
|
-
|
|
13658
|
-
|
|
13659
|
-
|
|
13660
|
-
|
|
13661
|
-
|
|
13662
|
-
|
|
13663
|
-
|
|
13664
|
-
|
|
13665
|
-
|
|
13666
|
-
|
|
13667
|
-
|
|
13668
|
-
|
|
13669
|
-
|
|
13670
|
-
|
|
13671
|
-
|
|
13672
|
-
});
|
|
13673
|
-
return asText(result);
|
|
13674
|
-
}
|
|
13675
|
-
case "service_script_approval": {
|
|
13676
|
-
const panels = pending.panels ?? [];
|
|
13677
|
-
if (panels.length === 0) {
|
|
13678
|
-
throw new Error("El storyboard pendiente no contiene paneles aprobables.");
|
|
13951
|
+
case "service_script_approval": {
|
|
13952
|
+
const panels = pending.panels ?? [];
|
|
13953
|
+
if (panels.length === 0) {
|
|
13954
|
+
throw new Error("El storyboard pendiente no contiene paneles aprobables.");
|
|
13955
|
+
}
|
|
13956
|
+
const voiceovers = {};
|
|
13957
|
+
const visualDirections = {};
|
|
13958
|
+
for (const panel of panels) {
|
|
13959
|
+
voiceovers[panel.beatId] = input.action.voiceovers?.[panel.beatId] ?? panel.voiceover;
|
|
13960
|
+
visualDirections[panel.beatId] = input.action.visualDirections?.[panel.beatId] ?? panel.visualDirection;
|
|
13961
|
+
}
|
|
13962
|
+
const result = await api.postJson(`/v1/tasks/${encodeURIComponent(input.taskId)}/service-script/approve`, {
|
|
13963
|
+
voiceovers,
|
|
13964
|
+
visualDirections
|
|
13965
|
+
});
|
|
13966
|
+
return asText(result);
|
|
13679
13967
|
}
|
|
13680
|
-
|
|
13681
|
-
|
|
13682
|
-
|
|
13683
|
-
voiceovers[panel.beatId] = input.action.voiceovers?.[panel.beatId] ?? panel.voiceover;
|
|
13684
|
-
visualDirections[panel.beatId] = input.action.visualDirections?.[panel.beatId] ?? panel.visualDirection;
|
|
13968
|
+
default: {
|
|
13969
|
+
const _exhaustive = input.action;
|
|
13970
|
+
throw new Error(`Unsupported pending action kind: ${String(_exhaustive.kind)}`);
|
|
13685
13971
|
}
|
|
13686
|
-
const result = await api.postJson(`/v1/tasks/${encodeURIComponent(input.taskId)}/service-script/approve`, {
|
|
13687
|
-
voiceovers,
|
|
13688
|
-
visualDirections
|
|
13689
|
-
});
|
|
13690
|
-
return asText(result);
|
|
13691
|
-
}
|
|
13692
|
-
default: {
|
|
13693
|
-
const _exhaustive = input.action;
|
|
13694
|
-
throw new Error(`Unsupported pending action kind: ${String(_exhaustive.kind)}`);
|
|
13695
13972
|
}
|
|
13973
|
+
} catch (e) {
|
|
13974
|
+
throw translateError(e);
|
|
13696
13975
|
}
|
|
13697
|
-
} catch (e) {
|
|
13698
|
-
throw translateError(e);
|
|
13699
13976
|
}
|
|
13700
|
-
|
|
13977
|
+
);
|
|
13701
13978
|
}
|
|
13702
13979
|
__name(registerTaskTools, "registerTaskTools");
|
|
13703
13980
|
|
|
13704
13981
|
// src/tools/creatives.ts
|
|
13705
13982
|
import { randomUUID } from "crypto";
|
|
13706
|
-
import { z as
|
|
13983
|
+
import { z as z51 } from "zod";
|
|
13707
13984
|
var CreativeRefs = {
|
|
13708
13985
|
images: ImagesInput,
|
|
13709
13986
|
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.'),
|
|
13710
13987
|
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.'),
|
|
13711
|
-
price:
|
|
13712
|
-
offer:
|
|
13713
|
-
compareAtPrice:
|
|
13714
|
-
paymentMethods:
|
|
13715
|
-
productContext:
|
|
13716
|
-
productLabel:
|
|
13717
|
-
buyerPersona:
|
|
13718
|
-
isCombo:
|
|
13719
|
-
competitorScanEnabled:
|
|
13720
|
-
brandId:
|
|
13721
|
-
projectId:
|
|
13722
|
-
creativeDirection:
|
|
13723
|
-
headline:
|
|
13724
|
-
subheadline:
|
|
13988
|
+
price: z51.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.'),
|
|
13989
|
+
offer: z51.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.'),
|
|
13990
|
+
compareAtPrice: z51.string().min(1).max(120).optional().describe('Opcional. Precio anterior o de referencia para mostrar descuento. Ej. "$129.000" cuando price="$89.900".'),
|
|
13991
|
+
paymentMethods: z51.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.'),
|
|
13992
|
+
productContext: z51.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."),
|
|
13993
|
+
productLabel: z51.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.'),
|
|
13994
|
+
buyerPersona: z51.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.'),
|
|
13995
|
+
isCombo: z51.boolean().optional().describe("Opcional. Si true, el creativo presenta el producto como combo/pack. Default false."),
|
|
13996
|
+
competitorScanEnabled: z51.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."),
|
|
13997
|
+
brandId: z51.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."),
|
|
13998
|
+
projectId: z51.string().uuid().optional().describe("Opcional. ID de un proyecto guiado de dropshipping activo del usuario. Enlaza esta generaci\xF3n con su progreso y recompensas."),
|
|
13999
|
+
creativeDirection: z51.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.'),
|
|
14000
|
+
headline: z51.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."),
|
|
14001
|
+
subheadline: z51.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.")
|
|
13725
14002
|
};
|
|
13726
|
-
var VideoInput =
|
|
14003
|
+
var VideoInput = z51.object({
|
|
13727
14004
|
images: CreativeRefs.images,
|
|
13728
|
-
title:
|
|
13729
|
-
primaryAngle:
|
|
14005
|
+
title: z51.string().min(1).describe('T\xEDtulo corto del producto. Aparece en la primera vista del video. Ej. "Crema Anti-Edad Premium".'),
|
|
14006
|
+
primaryAngle: z51.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".'),
|
|
13730
14007
|
language: CreativeRefs.language,
|
|
13731
14008
|
country: CreativeRefs.country,
|
|
13732
|
-
aspectRatio:
|
|
14009
|
+
aspectRatio: z51.enum([
|
|
13733
14010
|
"9:16",
|
|
13734
14011
|
"16:9",
|
|
13735
14012
|
"Auto"
|
|
13736
14013
|
]).describe('Relaci\xF3n de aspecto. "9:16" = vertical (TikTok/Reels \u2014 default recomendado para ads). "16:9" = horizontal (YouTube). "Auto" = decide la IA.'),
|
|
13737
|
-
durationSeconds:
|
|
13738
|
-
|
|
13739
|
-
|
|
14014
|
+
durationSeconds: z51.union([
|
|
14015
|
+
z51.literal(15),
|
|
14016
|
+
z51.literal(30)
|
|
13740
14017
|
]).default(15).describe("Opcional. Duraci\xF3n objetivo: 15s (single_call, default) o 30s (Multiescena, aproximadamente 30s y facturado por escena)."),
|
|
13741
14018
|
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).'),
|
|
13742
14019
|
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.'),
|
|
13743
14020
|
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).'),
|
|
13744
|
-
features:
|
|
14021
|
+
features: z51.array(z51.string()).optional().describe('Opcional. Lista de features/beneficios clave a mencionar. Ej. ["resistente al agua", "bater\xEDa 24h", "carga inal\xE1mbrica"].'),
|
|
13745
14022
|
price: CreativeRefs.price,
|
|
13746
14023
|
offer: CreativeRefs.offer,
|
|
13747
14024
|
productContext: CreativeRefs.productContext,
|
|
13748
14025
|
productLabel: CreativeRefs.productLabel,
|
|
13749
|
-
cta:
|
|
13750
|
-
scriptNotes:
|
|
13751
|
-
pauseForVideoScriptReview:
|
|
13752
|
-
pauseForAngleReview:
|
|
13753
|
-
aiDecidesAll:
|
|
14026
|
+
cta: z51.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.'),
|
|
14027
|
+
scriptNotes: z51.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.'),
|
|
14028
|
+
pauseForVideoScriptReview: z51.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."),
|
|
14029
|
+
pauseForAngleReview: z51.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."),
|
|
14030
|
+
aiDecidesAll: z51.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."),
|
|
13754
14031
|
competitorScanEnabled: CreativeRefs.competitorScanEnabled,
|
|
13755
14032
|
brandId: CreativeRefs.brandId,
|
|
13756
14033
|
projectId: CreativeRefs.projectId
|
|
13757
14034
|
}).strict();
|
|
13758
|
-
var FreeVideoQualityEnum =
|
|
14035
|
+
var FreeVideoQualityEnum = z51.enum([
|
|
13759
14036
|
"economical",
|
|
13760
14037
|
"high_detail"
|
|
13761
14038
|
]);
|
|
@@ -13777,38 +14054,38 @@ var FREE_VIDEO_DURATIONS_BY_QUALITY = {
|
|
|
13777
14054
|
15
|
|
13778
14055
|
]
|
|
13779
14056
|
};
|
|
13780
|
-
var FreeVideoInput =
|
|
14057
|
+
var FreeVideoInput = z51.object({
|
|
13781
14058
|
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."),
|
|
13782
|
-
rawPrompt:
|
|
14059
|
+
rawPrompt: z51.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."),
|
|
13783
14060
|
quality: FreeVideoQualityEnum.describe('Calidad del video: "economical" = Econ\xF3mico o "high_detail" = Alto detalle.'),
|
|
13784
|
-
durationSeconds:
|
|
14061
|
+
durationSeconds: z51.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."),
|
|
13785
14062
|
videoResolution: VideoResolutionEnum.describe("Resoluci\xF3n del video: 480p o 720p."),
|
|
13786
|
-
generateAiMusic:
|
|
13787
|
-
addSubtitles:
|
|
13788
|
-
idempotencyKey:
|
|
14063
|
+
generateAiMusic: z51.boolean().default(false).describe("Opcional. Agrega m\xFAsica instrumental IA debajo del audio nativo. Default false."),
|
|
14064
|
+
addSubtitles: z51.boolean().default(false).describe("Opcional. Quema subt\xEDtulos usando el rawPrompt exacto. Default false."),
|
|
14065
|
+
idempotencyKey: z51.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.")
|
|
13789
14066
|
}).strict();
|
|
13790
14067
|
var imageStyleList = ImageCreativeStyleEnum.options.join(" | ");
|
|
13791
14068
|
var archetypeList = ImageCreativeArchetypeEnum.options.join(" | ");
|
|
13792
14069
|
var visualTreatmentList = ImageCreativeVisualTreatmentEnum.options.join(" | ");
|
|
13793
14070
|
var aspectRatioList = ImageCreativeAspectRatioEnum.options.join(" | ");
|
|
13794
|
-
var ImageInput =
|
|
14071
|
+
var ImageInput = z51.object({
|
|
13795
14072
|
images: CreativeRefs.images,
|
|
13796
|
-
productTitle:
|
|
13797
|
-
primaryAngle:
|
|
14073
|
+
productTitle: z51.string().min(1).describe('Nombre del producto. Ej. "Crema Anti-Edad Premium".'),
|
|
14074
|
+
primaryAngle: z51.string().min(1).describe('\xC1ngulo de venta principal. Ej. "Reduce arrugas en 14 d\xEDas".'),
|
|
13798
14075
|
buyerPersona: CreativeRefs.buyerPersona,
|
|
13799
14076
|
language: CreativeRefs.language,
|
|
13800
14077
|
country: CreativeRefs.country,
|
|
13801
|
-
nVariants:
|
|
13802
|
-
imageModel:
|
|
14078
|
+
nVariants: z51.number().int().min(1).max(6).optional().describe("Opcional. N\xFAmero de variantes a generar (1-6). Default 3."),
|
|
14079
|
+
imageModel: z51.string().optional().describe('Opcional. Modelo de imagen. Ej. "gpt-image-2", "nano-banana-2". Om\xEDtelo para usar el default optimizado por costo/calidad.'),
|
|
13803
14080
|
imageStyle: ImageCreativeStyleEnum.optional().describe(`Opcional. Estilo del shot del producto. Valores: ${imageStyleList}. Si se omite, la IA escoge seg\xFAn el archetype.`),
|
|
13804
14081
|
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).`),
|
|
13805
14082
|
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.`),
|
|
13806
|
-
styleMix:
|
|
14083
|
+
styleMix: z51.boolean().optional().describe("Opcional. Si true, las N variantes mezclan distintos imageStyle para A/B test. Si false/omitido, todas comparten estilo. Default false."),
|
|
13807
14084
|
aspectRatio: ImageCreativeAspectRatioEnum.optional().describe(`Opcional. Relaci\xF3n de aspecto. Valores: ${aspectRatioList}. Default "4:5" (vertical Instagram feed).`),
|
|
13808
|
-
generateAdCopy:
|
|
13809
|
-
pauseForAngleReview:
|
|
13810
|
-
features:
|
|
13811
|
-
brandColors:
|
|
14085
|
+
generateAdCopy: z51.boolean().optional().describe("Opcional. Si true, tambi\xE9n genera headline/body/CTA para el ad. Default false."),
|
|
14086
|
+
pauseForAngleReview: z51.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."),
|
|
14087
|
+
features: z51.array(z51.string()).optional().describe('Opcional. Features/beneficios a destacar visualmente. Ej. ["resistente al agua", "ultra liviano"].'),
|
|
14088
|
+
brandColors: z51.array(z51.string()).optional().describe('Opcional. Paleta de marca en hex. Ej. ["#FF6B35", "#004E89"]. Si se omite, la IA elige seg\xFAn el producto.'),
|
|
13812
14089
|
price: CreativeRefs.price,
|
|
13813
14090
|
compareAtPrice: CreativeRefs.compareAtPrice,
|
|
13814
14091
|
offer: CreativeRefs.offer,
|
|
@@ -13822,17 +14099,17 @@ var ImageInput = z50.object({
|
|
|
13822
14099
|
headline: CreativeRefs.headline,
|
|
13823
14100
|
subheadline: CreativeRefs.subheadline
|
|
13824
14101
|
});
|
|
13825
|
-
var EditImageCreativeInput =
|
|
13826
|
-
taskId:
|
|
13827
|
-
variantIndex:
|
|
13828
|
-
instruction:
|
|
14102
|
+
var EditImageCreativeInput = z51.object({
|
|
14103
|
+
taskId: z51.string().uuid().describe("ID de la tarea de imagen ya finalizada."),
|
|
14104
|
+
variantIndex: z51.number().int().min(0).max(10).describe("\xCDndice de la variante a corregir (0 = primera imagen)."),
|
|
14105
|
+
instruction: z51.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".')
|
|
13829
14106
|
});
|
|
13830
|
-
var LandingInput =
|
|
13831
|
-
aiDecidesAll:
|
|
14107
|
+
var LandingInput = z51.object({
|
|
14108
|
+
aiDecidesAll: z51.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."),
|
|
13832
14109
|
images: CreativeRefs.images,
|
|
13833
14110
|
language: CreativeRefs.language,
|
|
13834
14111
|
country: CreativeRefs.country,
|
|
13835
|
-
salesAngle:
|
|
14112
|
+
salesAngle: z51.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.'),
|
|
13836
14113
|
buyerPersona: CreativeRefs.buyerPersona,
|
|
13837
14114
|
price: CreativeRefs.price,
|
|
13838
14115
|
compareAtPrice: CreativeRefs.compareAtPrice,
|
|
@@ -13847,11 +14124,12 @@ var LandingInput = z50.object({
|
|
|
13847
14124
|
creativeDirection: CreativeRefs.creativeDirection,
|
|
13848
14125
|
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".'),
|
|
13849
14126
|
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".'),
|
|
14127
|
+
pauseForAngleReview: z51.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."),
|
|
13850
14128
|
headline: CreativeRefs.headline,
|
|
13851
14129
|
subheadline: CreativeRefs.subheadline
|
|
13852
14130
|
});
|
|
13853
14131
|
function registerCreativeTools(server, api, { transport = "http" } = {}) {
|
|
13854
|
-
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) => {
|
|
14132
|
+
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) => {
|
|
13855
14133
|
try {
|
|
13856
14134
|
const allowedDurations = FREE_VIDEO_DURATIONS_BY_QUALITY[input.quality];
|
|
13857
14135
|
if (!allowedDurations.includes(input.durationSeconds)) {
|
|
@@ -13881,7 +14159,7 @@ function registerCreativeTools(server, api, { transport = "http" } = {}) {
|
|
|
13881
14159
|
throw translateError(e);
|
|
13882
14160
|
}
|
|
13883
14161
|
});
|
|
13884
|
-
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) => {
|
|
14162
|
+
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) => {
|
|
13885
14163
|
try {
|
|
13886
14164
|
const resolved = resolveVideoPreset(input.videoPreset);
|
|
13887
14165
|
const wireTier = resolveWireVideoTier(input.videoTier, input.durationSeconds);
|
|
@@ -13906,7 +14184,7 @@ function registerCreativeTools(server, api, { transport = "http" } = {}) {
|
|
|
13906
14184
|
throw translateError(e);
|
|
13907
14185
|
}
|
|
13908
14186
|
});
|
|
13909
|
-
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) => {
|
|
14187
|
+
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) => {
|
|
13910
14188
|
try {
|
|
13911
14189
|
const { projectId, ...rest } = input;
|
|
13912
14190
|
return await submitCreative(api, {
|
|
@@ -13924,7 +14202,7 @@ function registerCreativeTools(server, api, { transport = "http" } = {}) {
|
|
|
13924
14202
|
throw translateError(e);
|
|
13925
14203
|
}
|
|
13926
14204
|
});
|
|
13927
|
-
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) => {
|
|
14205
|
+
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) => {
|
|
13928
14206
|
try {
|
|
13929
14207
|
const r = await api.postJson(`/v1/creatives/images/${encodeURIComponent(input.taskId)}/regenerate`, {
|
|
13930
14208
|
comment: input.instruction,
|
|
@@ -13935,7 +14213,7 @@ function registerCreativeTools(server, api, { transport = "http" } = {}) {
|
|
|
13935
14213
|
throw translateError(e);
|
|
13936
14214
|
}
|
|
13937
14215
|
});
|
|
13938
|
-
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) => {
|
|
14216
|
+
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) => {
|
|
13939
14217
|
try {
|
|
13940
14218
|
const { projectId, ...rest } = input;
|
|
13941
14219
|
return await submitCreative(api, {
|
|
@@ -13957,8 +14235,8 @@ function registerCreativeTools(server, api, { transport = "http" } = {}) {
|
|
|
13957
14235
|
__name(registerCreativeTools, "registerCreativeTools");
|
|
13958
14236
|
|
|
13959
14237
|
// src/tools/creative-variants.ts
|
|
13960
|
-
import { z as
|
|
13961
|
-
var LandingSectionKeyEnum =
|
|
14238
|
+
import { z as z52 } from "zod";
|
|
14239
|
+
var LandingSectionKeyEnum = z52.enum([
|
|
13962
14240
|
"problem_hook",
|
|
13963
14241
|
"problem_cost",
|
|
13964
14242
|
"solution_mechanism",
|
|
@@ -13998,24 +14276,24 @@ var LandingSectionKeyEnum = z51.enum([
|
|
|
13998
14276
|
"lifestyle_fit",
|
|
13999
14277
|
"join_lifestyle"
|
|
14000
14278
|
]);
|
|
14001
|
-
var CreativeVariantKindEnum =
|
|
14279
|
+
var CreativeVariantKindEnum = z52.enum([
|
|
14002
14280
|
"video",
|
|
14003
14281
|
"image",
|
|
14004
14282
|
"landing"
|
|
14005
14283
|
]);
|
|
14006
|
-
var EstimateCreativeVariantInputBase =
|
|
14007
|
-
sourceTaskId:
|
|
14284
|
+
var EstimateCreativeVariantInputBase = z52.object({
|
|
14285
|
+
sourceTaskId: z52.string().uuid().describe("ID de la tarea original completada."),
|
|
14008
14286
|
kind: CreativeVariantKindEnum,
|
|
14009
14287
|
videoPreset: TaskCreativeVariantVideoPresetEnum.optional().describe("Opcional para video. Solo cambia el preset p\xFAblico; el servidor conserva el modelo/tier del task origen."),
|
|
14010
14288
|
videoResolution: VideoResolutionEnum.optional().describe("Opcional para video. Resoluci\xF3n del nuevo render."),
|
|
14011
|
-
nVariants:
|
|
14289
|
+
nVariants: z52.number().int().min(1).max(3).optional().describe("Opcional para imagen. N\xFAmero de im\xE1genes adicionales (1-3)."),
|
|
14012
14290
|
sectionKey: LandingSectionKeyEnum.optional().describe("Opcional para landing. Si ya conoces la secci\xF3n, evita cotizar la clasificaci\xF3n con IA."),
|
|
14013
|
-
instructions:
|
|
14291
|
+
instructions: z52.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.")
|
|
14014
14292
|
}).strict();
|
|
14015
14293
|
var EstimateCreativeVariantInput = EstimateCreativeVariantInputBase.superRefine((input, ctx) => {
|
|
14016
14294
|
if (input.kind === "video" && input.nVariants !== void 0) {
|
|
14017
14295
|
ctx.addIssue({
|
|
14018
|
-
code:
|
|
14296
|
+
code: z52.ZodIssueCode.custom,
|
|
14019
14297
|
path: [
|
|
14020
14298
|
"nVariants"
|
|
14021
14299
|
],
|
|
@@ -14025,7 +14303,7 @@ var EstimateCreativeVariantInput = EstimateCreativeVariantInputBase.superRefine(
|
|
|
14025
14303
|
if (input.kind !== "video") {
|
|
14026
14304
|
if (input.videoPreset !== void 0) {
|
|
14027
14305
|
ctx.addIssue({
|
|
14028
|
-
code:
|
|
14306
|
+
code: z52.ZodIssueCode.custom,
|
|
14029
14307
|
path: [
|
|
14030
14308
|
"videoPreset"
|
|
14031
14309
|
],
|
|
@@ -14034,7 +14312,7 @@ var EstimateCreativeVariantInput = EstimateCreativeVariantInputBase.superRefine(
|
|
|
14034
14312
|
}
|
|
14035
14313
|
if (input.videoResolution !== void 0) {
|
|
14036
14314
|
ctx.addIssue({
|
|
14037
|
-
code:
|
|
14315
|
+
code: z52.ZodIssueCode.custom,
|
|
14038
14316
|
path: [
|
|
14039
14317
|
"videoResolution"
|
|
14040
14318
|
],
|
|
@@ -14044,7 +14322,7 @@ var EstimateCreativeVariantInput = EstimateCreativeVariantInputBase.superRefine(
|
|
|
14044
14322
|
}
|
|
14045
14323
|
if (input.kind !== "image" && input.nVariants !== void 0) {
|
|
14046
14324
|
ctx.addIssue({
|
|
14047
|
-
code:
|
|
14325
|
+
code: z52.ZodIssueCode.custom,
|
|
14048
14326
|
path: [
|
|
14049
14327
|
"nVariants"
|
|
14050
14328
|
],
|
|
@@ -14054,7 +14332,7 @@ var EstimateCreativeVariantInput = EstimateCreativeVariantInputBase.superRefine(
|
|
|
14054
14332
|
if (input.kind !== "landing") {
|
|
14055
14333
|
if (input.sectionKey !== void 0) {
|
|
14056
14334
|
ctx.addIssue({
|
|
14057
|
-
code:
|
|
14335
|
+
code: z52.ZodIssueCode.custom,
|
|
14058
14336
|
path: [
|
|
14059
14337
|
"sectionKey"
|
|
14060
14338
|
],
|
|
@@ -14063,7 +14341,7 @@ var EstimateCreativeVariantInput = EstimateCreativeVariantInputBase.superRefine(
|
|
|
14063
14341
|
}
|
|
14064
14342
|
if (input.instructions !== void 0) {
|
|
14065
14343
|
ctx.addIssue({
|
|
14066
|
-
code:
|
|
14344
|
+
code: z52.ZodIssueCode.custom,
|
|
14067
14345
|
path: [
|
|
14068
14346
|
"instructions"
|
|
14069
14347
|
],
|
|
@@ -14072,31 +14350,31 @@ var EstimateCreativeVariantInput = EstimateCreativeVariantInputBase.superRefine(
|
|
|
14072
14350
|
}
|
|
14073
14351
|
}
|
|
14074
14352
|
});
|
|
14075
|
-
var CreateCreativeVariantInputBase =
|
|
14076
|
-
sourceTaskId:
|
|
14353
|
+
var CreateCreativeVariantInputBase = z52.object({
|
|
14354
|
+
sourceTaskId: z52.string().uuid().describe("ID de la tarea original completada."),
|
|
14077
14355
|
kind: CreativeVariantKindEnum,
|
|
14078
|
-
confirm:
|
|
14079
|
-
clientRequestId:
|
|
14080
|
-
angle:
|
|
14081
|
-
buyerPersona:
|
|
14082
|
-
scriptNotes:
|
|
14083
|
-
creativeDirection:
|
|
14356
|
+
confirm: z52.literal(true).describe("Confirmaci\xF3n expl\xEDcita del usuario despu\xE9s de mostrarle el estimado de cr\xE9ditos."),
|
|
14357
|
+
clientRequestId: z52.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."),
|
|
14358
|
+
angle: z52.string().trim().min(1).max(500).optional().describe("\xC1ngulo nuevo para video o imagen."),
|
|
14359
|
+
buyerPersona: z52.string().trim().min(1).max(360).optional().describe("Buyer persona coherente con el \xE1ngulo."),
|
|
14360
|
+
scriptNotes: z52.string().trim().min(1).max(2e3).optional().describe("Direcci\xF3n breve del guion para una variante de video."),
|
|
14361
|
+
creativeDirection: z52.string().trim().min(1).max(2e3).optional().describe("Direcci\xF3n creativa para una variante de imagen."),
|
|
14084
14362
|
videoPreset: TaskCreativeVariantVideoPresetEnum.optional().describe("Opcional para video. Preset p\xFAblico; el servidor resuelve modelo/tier desde el task origen."),
|
|
14085
14363
|
videoResolution: VideoResolutionEnum.optional().describe("Opcional para video."),
|
|
14086
|
-
generateAiMusic:
|
|
14087
|
-
nVariants:
|
|
14364
|
+
generateAiMusic: z52.boolean().optional().describe("Opcional para video."),
|
|
14365
|
+
nVariants: z52.number().int().min(1).max(3).optional().describe("Opcional para imagen. N\xFAmero de im\xE1genes adicionales (1-3)."),
|
|
14088
14366
|
imageStyle: ImageCreativeStyleEnum.optional().describe("Opcional para imagen."),
|
|
14089
14367
|
creativeArchetype: ImageCreativeArchetypeEnum.optional().describe("Opcional para imagen."),
|
|
14090
14368
|
visualTreatment: ImageCreativeVisualTreatmentEnum.optional().describe("Opcional para imagen."),
|
|
14091
14369
|
aspectRatio: ImageCreativeAspectRatioEnum.optional().describe("Opcional para imagen."),
|
|
14092
|
-
styleMix:
|
|
14370
|
+
styleMix: z52.boolean().optional().describe("Opcional para imagen."),
|
|
14093
14371
|
sectionKey: LandingSectionKeyEnum.optional().describe("Opcional para landing. Si se omite, se clasifica con IA a partir de instructions."),
|
|
14094
|
-
instructions:
|
|
14372
|
+
instructions: z52.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.")
|
|
14095
14373
|
}).strict();
|
|
14096
14374
|
var CreateCreativeVariantInput = CreateCreativeVariantInputBase.superRefine((input, ctx) => {
|
|
14097
14375
|
if ((input.kind === "video" || input.kind === "image") && !input.angle) {
|
|
14098
14376
|
ctx.addIssue({
|
|
14099
|
-
code:
|
|
14377
|
+
code: z52.ZodIssueCode.custom,
|
|
14100
14378
|
path: [
|
|
14101
14379
|
"angle"
|
|
14102
14380
|
],
|
|
@@ -14105,7 +14383,7 @@ var CreateCreativeVariantInput = CreateCreativeVariantInputBase.superRefine((inp
|
|
|
14105
14383
|
}
|
|
14106
14384
|
if (input.kind === "landing" && !input.sectionKey && !input.instructions) {
|
|
14107
14385
|
ctx.addIssue({
|
|
14108
|
-
code:
|
|
14386
|
+
code: z52.ZodIssueCode.custom,
|
|
14109
14387
|
path: [
|
|
14110
14388
|
"instructions"
|
|
14111
14389
|
],
|
|
@@ -14151,7 +14429,7 @@ var CreateCreativeVariantInput = CreateCreativeVariantInputBase.superRefine((inp
|
|
|
14151
14429
|
for (const field of fieldsByKind[input.kind]) {
|
|
14152
14430
|
if (input[field] !== void 0) {
|
|
14153
14431
|
ctx.addIssue({
|
|
14154
|
-
code:
|
|
14432
|
+
code: z52.ZodIssueCode.custom,
|
|
14155
14433
|
path: [
|
|
14156
14434
|
field
|
|
14157
14435
|
],
|
|
@@ -14185,7 +14463,7 @@ function estimatePayload(input) {
|
|
|
14185
14463
|
}
|
|
14186
14464
|
__name(estimatePayload, "estimatePayload");
|
|
14187
14465
|
function registerCreativeVariantTools(server, api) {
|
|
14188
|
-
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) => {
|
|
14466
|
+
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) => {
|
|
14189
14467
|
try {
|
|
14190
14468
|
const validatedInput = parseVariantInput(EstimateCreativeVariantInput, input);
|
|
14191
14469
|
const estimate = await api.postJson(`/v1/tasks/${encodeURIComponent(validatedInput.sourceTaskId)}/creative-variants/estimate`, estimatePayload(validatedInput));
|
|
@@ -14198,7 +14476,7 @@ function registerCreativeVariantTools(server, api) {
|
|
|
14198
14476
|
throw translateError(error);
|
|
14199
14477
|
}
|
|
14200
14478
|
});
|
|
14201
|
-
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) => {
|
|
14479
|
+
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) => {
|
|
14202
14480
|
try {
|
|
14203
14481
|
const validatedInput = parseVariantInput(CreateCreativeVariantInput, input);
|
|
14204
14482
|
const estimateInput = EstimateCreativeVariantInput.parse({
|
|
@@ -14268,31 +14546,31 @@ function registerCreativeVariantTools(server, api) {
|
|
|
14268
14546
|
__name(registerCreativeVariantTools, "registerCreativeVariantTools");
|
|
14269
14547
|
|
|
14270
14548
|
// src/tools/script-review.ts
|
|
14271
|
-
import { z as
|
|
14272
|
-
var GetVideoScriptInput =
|
|
14273
|
-
taskId:
|
|
14274
|
-
angleKey:
|
|
14549
|
+
import { z as z53 } from "zod";
|
|
14550
|
+
var GetVideoScriptInput = z53.object({
|
|
14551
|
+
taskId: z53.string().min(1).describe("ID de la tarea (la misma que devolvi\xF3 create_product_task / generate_video_creative)."),
|
|
14552
|
+
angleKey: z53.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.')
|
|
14275
14553
|
}).strict();
|
|
14276
14554
|
var VIDEO_SCRIPT_APPROVAL_MAX_SCENES2 = 50;
|
|
14277
14555
|
var VIDEO_SCRIPT_APPROVAL_MAX_DIALOGUE_CHARS2 = 2e3;
|
|
14278
|
-
var ApproveSceneEdit =
|
|
14279
|
-
index:
|
|
14280
|
-
dialogue:
|
|
14556
|
+
var ApproveSceneEdit = z53.object({
|
|
14557
|
+
index: z53.number().int().nonnegative().describe("\xCDndice de la escena (0-based, mismo que devuelve get_video_script)."),
|
|
14558
|
+
dialogue: z53.string().min(1).max(VIDEO_SCRIPT_APPROVAL_MAX_DIALOGUE_CHARS2).refine((v) => v.trim().length > 0, {
|
|
14281
14559
|
message: "dialogue no puede ser s\xF3lo espacios"
|
|
14282
14560
|
}).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.")
|
|
14283
14561
|
}).strict();
|
|
14284
|
-
var ApproveVideoScriptInput =
|
|
14285
|
-
taskId:
|
|
14286
|
-
angleKey:
|
|
14287
|
-
scenes:
|
|
14562
|
+
var ApproveVideoScriptInput = z53.object({
|
|
14563
|
+
taskId: z53.string().min(1).describe("ID de la tarea."),
|
|
14564
|
+
angleKey: z53.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.'),
|
|
14565
|
+
scenes: z53.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.")
|
|
14288
14566
|
}).strict();
|
|
14289
|
-
var RegenerateVideoScriptInput =
|
|
14290
|
-
taskId:
|
|
14291
|
-
angleKey:
|
|
14567
|
+
var RegenerateVideoScriptInput = z53.object({
|
|
14568
|
+
taskId: z53.string().min(1).describe("ID de la tarea."),
|
|
14569
|
+
angleKey: z53.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.'),
|
|
14292
14570
|
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).')
|
|
14293
14571
|
}).strict();
|
|
14294
14572
|
function registerScriptReviewTools(server, api) {
|
|
14295
|
-
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) => {
|
|
14573
|
+
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) => {
|
|
14296
14574
|
try {
|
|
14297
14575
|
const qs = input.angleKey ? `?angleKey=${encodeURIComponent(input.angleKey)}` : "";
|
|
14298
14576
|
const r = await api.getJson(`/v1/tasks/${encodeURIComponent(input.taskId)}/video-script${qs}`);
|
|
@@ -14301,7 +14579,7 @@ function registerScriptReviewTools(server, api) {
|
|
|
14301
14579
|
throw translateError(e);
|
|
14302
14580
|
}
|
|
14303
14581
|
});
|
|
14304
|
-
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) => {
|
|
14582
|
+
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) => {
|
|
14305
14583
|
try {
|
|
14306
14584
|
const r = await api.postJson(`/v1/tasks/${encodeURIComponent(input.taskId)}/video-script/regenerate`, {
|
|
14307
14585
|
angleKey: input.angleKey,
|
|
@@ -14312,25 +14590,36 @@ function registerScriptReviewTools(server, api) {
|
|
|
14312
14590
|
throw translateError(e);
|
|
14313
14591
|
}
|
|
14314
14592
|
});
|
|
14315
|
-
server.tool(
|
|
14316
|
-
|
|
14317
|
-
|
|
14318
|
-
|
|
14319
|
-
|
|
14320
|
-
|
|
14321
|
-
|
|
14322
|
-
|
|
14323
|
-
|
|
14324
|
-
|
|
14593
|
+
server.tool(
|
|
14594
|
+
"approve_video_script",
|
|
14595
|
+
'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.',
|
|
14596
|
+
ApproveVideoScriptInput.shape,
|
|
14597
|
+
// Not CREATES_INTERNAL: approving the script is what STARTS the render, so
|
|
14598
|
+
// this call dispatches jobs to the video providers and burns credits. Its own
|
|
14599
|
+
// description says so ("Tras llamarlo, el render arranca y consume créditos").
|
|
14600
|
+
// A host that decides when to interrupt the user by `destructiveHint ||
|
|
14601
|
+
// openWorldHint` would otherwise auto-invoke the one call that spends money.
|
|
14602
|
+
CREATES_EXTERNAL,
|
|
14603
|
+
async (input) => {
|
|
14604
|
+
try {
|
|
14605
|
+
const body = {
|
|
14606
|
+
scenes: input.scenes
|
|
14607
|
+
};
|
|
14608
|
+
if (input.angleKey) body.angleKey = input.angleKey;
|
|
14609
|
+
const r = await api.postJson(`/v1/tasks/${encodeURIComponent(input.taskId)}/video-script/approve`, body);
|
|
14610
|
+
return asText(r);
|
|
14611
|
+
} catch (e) {
|
|
14612
|
+
throw translateError(e);
|
|
14613
|
+
}
|
|
14325
14614
|
}
|
|
14326
|
-
|
|
14615
|
+
);
|
|
14327
14616
|
}
|
|
14328
14617
|
__name(registerScriptReviewTools, "registerScriptReviewTools");
|
|
14329
14618
|
|
|
14330
14619
|
// src/tools/connections.ts
|
|
14331
|
-
import { z as
|
|
14620
|
+
import { z as z54 } from "zod";
|
|
14332
14621
|
function registerConnectionTools(server, api) {
|
|
14333
|
-
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.",
|
|
14622
|
+
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.", z54.object({}).shape, READS_INTERNAL, async () => {
|
|
14334
14623
|
try {
|
|
14335
14624
|
const r = await api.getJson("/v1/shopify/shops");
|
|
14336
14625
|
return asText(r);
|
|
@@ -14338,11 +14627,11 @@ function registerConnectionTools(server, api) {
|
|
|
14338
14627
|
throw translateError(e);
|
|
14339
14628
|
}
|
|
14340
14629
|
});
|
|
14341
|
-
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.',
|
|
14342
|
-
shopDomain:
|
|
14343
|
-
query:
|
|
14344
|
-
taskId:
|
|
14345
|
-
}).shape, async ({ shopDomain, query, taskId }) => {
|
|
14630
|
+
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.', z54.object({
|
|
14631
|
+
shopDomain: z54.string().min(3).max(120),
|
|
14632
|
+
query: z54.string().trim().max(120).optional(),
|
|
14633
|
+
taskId: z54.string().uuid().optional()
|
|
14634
|
+
}).shape, READS_EXTERNAL, async ({ shopDomain, query, taskId }) => {
|
|
14346
14635
|
try {
|
|
14347
14636
|
const params = new URLSearchParams({
|
|
14348
14637
|
shopDomain
|
|
@@ -14355,7 +14644,7 @@ function registerConnectionTools(server, api) {
|
|
|
14355
14644
|
throw translateError(e);
|
|
14356
14645
|
}
|
|
14357
14646
|
});
|
|
14358
|
-
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.",
|
|
14647
|
+
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.", z54.object({}).shape, READS_EXTERNAL, async () => {
|
|
14359
14648
|
try {
|
|
14360
14649
|
const r = await api.getJson("/v1/facebook/ad-accounts");
|
|
14361
14650
|
return asText(r);
|
|
@@ -14365,7 +14654,8 @@ function registerConnectionTools(server, api) {
|
|
|
14365
14654
|
});
|
|
14366
14655
|
server.registerTool("list_meta_destinations", {
|
|
14367
14656
|
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.",
|
|
14368
|
-
inputSchema:
|
|
14657
|
+
inputSchema: z54.object({}).shape,
|
|
14658
|
+
annotations: READS_EXTERNAL,
|
|
14369
14659
|
_meta: {
|
|
14370
14660
|
"openai/widgetAccessible": true
|
|
14371
14661
|
}
|
|
@@ -14377,7 +14667,7 @@ function registerConnectionTools(server, api) {
|
|
|
14377
14667
|
throw translateError(e);
|
|
14378
14668
|
}
|
|
14379
14669
|
});
|
|
14380
|
-
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.",
|
|
14670
|
+
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.", z54.object({}).shape, READS_EXTERNAL, async () => {
|
|
14381
14671
|
try {
|
|
14382
14672
|
const r = await api.getJson("/v1/tiktok/advertisers");
|
|
14383
14673
|
return asText(r);
|
|
@@ -14389,9 +14679,9 @@ function registerConnectionTools(server, api) {
|
|
|
14389
14679
|
__name(registerConnectionTools, "registerConnectionTools");
|
|
14390
14680
|
|
|
14391
14681
|
// src/tools/actors.ts
|
|
14392
|
-
import { z as
|
|
14682
|
+
import { z as z55 } from "zod";
|
|
14393
14683
|
function registerActorTools(server, api) {
|
|
14394
|
-
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.",
|
|
14684
|
+
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.", z55.object({}).shape, READS_INTERNAL, async () => {
|
|
14395
14685
|
try {
|
|
14396
14686
|
const rows = await api.getJson("/v1/actors");
|
|
14397
14687
|
const actors = (Array.isArray(rows) ? rows : []).filter((actor) => actor.status === "ready").map(({ id, name, description, gender, voiceId }) => ({
|
|
@@ -14412,116 +14702,145 @@ function registerActorTools(server, api) {
|
|
|
14412
14702
|
__name(registerActorTools, "registerActorTools");
|
|
14413
14703
|
|
|
14414
14704
|
// src/tools/publish.ts
|
|
14415
|
-
import { z as
|
|
14416
|
-
var ShopifyInput =
|
|
14417
|
-
taskId:
|
|
14418
|
-
shopDomain:
|
|
14705
|
+
import { z as z56 } from "zod";
|
|
14706
|
+
var ShopifyInput = z56.object({
|
|
14707
|
+
taskId: z56.string().min(1),
|
|
14708
|
+
shopDomain: z56.string().min(1),
|
|
14419
14709
|
/** Override: number of landing images to include. Default = ALL images the
|
|
14420
14710
|
* task generated (the MCP fetches /v1/results first to count). Bounded
|
|
14421
14711
|
* generously here; the backend validates each index against
|
|
14422
14712
|
* landing.images.length and rejects out-of-range. */
|
|
14423
|
-
numLandingImages:
|
|
14424
|
-
variantIndex:
|
|
14713
|
+
numLandingImages: z56.number().int().min(1).max(20).optional(),
|
|
14714
|
+
variantIndex: z56.number().int().min(0).optional(),
|
|
14425
14715
|
/** ASK THE USER before setting: "¿Tu producto tiene variantes (talla, color, etc.)?".
|
|
14426
14716
|
* When true, the landing renders variant-picker pills; when false, it ships
|
|
14427
14717
|
* a single-option product page. Defaults to false. */
|
|
14428
|
-
showVariantPicker:
|
|
14718
|
+
showVariantPicker: z56.boolean().optional(),
|
|
14429
14719
|
/** Default `auto` updates the product previously published for this task and
|
|
14430
14720
|
* shop. Use `existing` with targetProductId to choose another product, or
|
|
14431
14721
|
* `new` only when the user explicitly wants a duplicate/new listing. */
|
|
14432
|
-
publicationMode:
|
|
14722
|
+
publicationMode: z56.enum([
|
|
14433
14723
|
"auto",
|
|
14434
14724
|
"existing",
|
|
14435
14725
|
"new"
|
|
14436
14726
|
]).optional(),
|
|
14437
14727
|
/** Shopify product GID, numeric id, or admin product URL. Required when
|
|
14438
14728
|
* publicationMode is `existing`. */
|
|
14439
|
-
targetProductId:
|
|
14729
|
+
targetProductId: z56.string().trim().min(1).max(300).optional(),
|
|
14730
|
+
/** Add an announcement bar text above add-to-cart (optional). */
|
|
14731
|
+
announcementBarEnabled: z56.boolean().optional(),
|
|
14732
|
+
announcementBarText: z56.string().trim().min(1).max(SHOPIFY_ANNOUNCEMENT_BAR_MAX_LENGTH).optional()
|
|
14733
|
+
});
|
|
14734
|
+
var ShopifyInputWithAnnouncementBarValidation = ShopifyInput.superRefine((value, ctx) => {
|
|
14735
|
+
if (value.announcementBarEnabled && !value.announcementBarText) {
|
|
14736
|
+
ctx.addIssue({
|
|
14737
|
+
code: z56.ZodIssueCode.custom,
|
|
14738
|
+
path: [
|
|
14739
|
+
"announcementBarText"
|
|
14740
|
+
],
|
|
14741
|
+
message: "announcementBarText is required when announcementBarEnabled is true"
|
|
14742
|
+
});
|
|
14743
|
+
}
|
|
14744
|
+
if (!value.announcementBarEnabled && value.announcementBarText) {
|
|
14745
|
+
ctx.addIssue({
|
|
14746
|
+
code: z56.ZodIssueCode.custom,
|
|
14747
|
+
path: [
|
|
14748
|
+
"announcementBarText"
|
|
14749
|
+
],
|
|
14750
|
+
message: "announcementBarText requires announcementBarEnabled to be true"
|
|
14751
|
+
});
|
|
14752
|
+
}
|
|
14440
14753
|
});
|
|
14441
|
-
|
|
14754
|
+
function parseShopifyInput(rawInput) {
|
|
14755
|
+
const parsed = ShopifyInputWithAnnouncementBarValidation.safeParse(rawInput);
|
|
14756
|
+
if (!parsed.success) throw invalidInputFromZod(parsed.error);
|
|
14757
|
+
return parsed.data;
|
|
14758
|
+
}
|
|
14759
|
+
__name(parseShopifyInput, "parseShopifyInput");
|
|
14760
|
+
var McpCampaignConfig = z56.object({
|
|
14442
14761
|
/** ALWAYS in US dollars, whatever the ad account's currency is. ProductMaker
|
|
14443
14762
|
* converts to the account currency server-side. Do NOT pre-convert: on a COP
|
|
14444
14763
|
* account the user's "60.000 al día" is `dailyBudgetUsd: 15`, not 60000. */
|
|
14445
|
-
dailyBudgetUsd:
|
|
14764
|
+
dailyBudgetUsd: z56.number().positive().optional(),
|
|
14446
14765
|
/** ISO-3166-1 alpha-2 country codes. Meta accepts max 1 (single-country
|
|
14447
14766
|
* ad set). TikTok takes only the first entry. */
|
|
14448
|
-
targetCountries:
|
|
14449
|
-
campaignName:
|
|
14767
|
+
targetCountries: z56.array(z56.string().length(2)).max(1).optional(),
|
|
14768
|
+
campaignName: z56.string().trim().min(1).max(100).optional()
|
|
14450
14769
|
});
|
|
14451
14770
|
var McpAdAccountId = MetaAdAccountId.describe('Meta ad account id. Accepts "act_1234567890" or the bare numeric id "1234567890".');
|
|
14452
|
-
var MetaInput =
|
|
14453
|
-
taskId:
|
|
14771
|
+
var MetaInput = z56.object({
|
|
14772
|
+
taskId: z56.string().min(1),
|
|
14454
14773
|
/** Exact Meta Business portfolio selected from `list_meta_destinations`. */
|
|
14455
|
-
portfolioId:
|
|
14774
|
+
portfolioId: z56.string().trim().min(1),
|
|
14456
14775
|
/** Exact Facebook Page selected from `list_meta_destinations`. */
|
|
14457
|
-
pageId:
|
|
14776
|
+
pageId: z56.string().trim().min(1),
|
|
14458
14777
|
adAccountId: McpAdAccountId,
|
|
14459
|
-
pixelId:
|
|
14460
|
-
landingUrl:
|
|
14778
|
+
pixelId: z56.string().min(1).optional(),
|
|
14779
|
+
landingUrl: z56.string().url().optional(),
|
|
14461
14780
|
/** Select which angles to publish by 0-based index. Omit to publish all
|
|
14462
14781
|
* successful angles. Example: a task with 3 angles → `[0,2]` publishes #0 + #2. */
|
|
14463
|
-
angleIndexes:
|
|
14782
|
+
angleIndexes: z56.array(z56.number().int().nonnegative()).optional(),
|
|
14464
14783
|
campaignConfig: McpCampaignConfig.optional(),
|
|
14465
14784
|
/** Override the ad's primary text (body copy). If omitted, ProductMaker
|
|
14466
14785
|
* uses the auto-generated copy attached to the task. Capped at 500 chars
|
|
14467
14786
|
* (Meta's primary-text limit). */
|
|
14468
|
-
primaryText:
|
|
14787
|
+
primaryText: z56.string().trim().min(1).max(500).optional(),
|
|
14469
14788
|
/** Optional WhatsApp number. When set, the ad button opens a WhatsApp chat
|
|
14470
14789
|
* (wa.me) instead of a landing page. */
|
|
14471
|
-
phoneNumber:
|
|
14790
|
+
phoneNumber: z56.string().min(8).max(32).optional()
|
|
14472
14791
|
});
|
|
14473
|
-
var GroupedTaskSource =
|
|
14474
|
-
type:
|
|
14475
|
-
taskId:
|
|
14476
|
-
angleIndex:
|
|
14477
|
-
videoVariant:
|
|
14792
|
+
var GroupedTaskSource = z56.object({
|
|
14793
|
+
type: z56.literal("task"),
|
|
14794
|
+
taskId: z56.string().min(1),
|
|
14795
|
+
angleIndex: z56.number().int().min(0).optional(),
|
|
14796
|
+
videoVariant: z56.enum([
|
|
14478
14797
|
"with_subtitles",
|
|
14479
14798
|
"without_subtitles"
|
|
14480
14799
|
]).optional(),
|
|
14481
|
-
imageVariantIndex:
|
|
14482
|
-
});
|
|
14483
|
-
var GroupedMediaSource =
|
|
14484
|
-
type:
|
|
14485
|
-
url:
|
|
14486
|
-
path:
|
|
14487
|
-
thumbnailUrl:
|
|
14488
|
-
thumbnailPath:
|
|
14800
|
+
imageVariantIndex: z56.number().int().min(0).optional()
|
|
14801
|
+
});
|
|
14802
|
+
var GroupedMediaSource = z56.object({
|
|
14803
|
+
type: z56.literal("media"),
|
|
14804
|
+
url: z56.string().url().optional().describe("Public HTTPS media URL. For a file on the user laptop, use request_media_upload on the hosted MCP instead."),
|
|
14805
|
+
path: z56.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".'),
|
|
14806
|
+
thumbnailUrl: z56.string().url().optional(),
|
|
14807
|
+
thumbnailPath: z56.string().min(1).optional()
|
|
14489
14808
|
}).refine((value) => Boolean(value.url) !== Boolean(value.path), {
|
|
14490
14809
|
message: "media source needs exactly one of `url` or `path`"
|
|
14491
14810
|
}).refine((value) => !(value.thumbnailUrl && value.thumbnailPath), {
|
|
14492
14811
|
message: "media source needs at most one of `thumbnailUrl` or `thumbnailPath`"
|
|
14493
14812
|
});
|
|
14494
|
-
var GroupedUploadedAssetSource =
|
|
14495
|
-
type:
|
|
14813
|
+
var GroupedUploadedAssetSource = z56.object({
|
|
14814
|
+
type: z56.literal("asset"),
|
|
14496
14815
|
/** Returned by check_media_upload. ProductMaker resolves ownership server-side. */
|
|
14497
|
-
assetId:
|
|
14498
|
-
thumbnailAssetId:
|
|
14816
|
+
assetId: z56.string().uuid(),
|
|
14817
|
+
thumbnailAssetId: z56.string().uuid().optional()
|
|
14499
14818
|
});
|
|
14500
|
-
var GroupedCreative =
|
|
14501
|
-
type:
|
|
14819
|
+
var GroupedCreative = z56.object({
|
|
14820
|
+
type: z56.enum([
|
|
14502
14821
|
"image",
|
|
14503
14822
|
"video"
|
|
14504
14823
|
]),
|
|
14505
|
-
source:
|
|
14824
|
+
source: z56.union([
|
|
14506
14825
|
GroupedTaskSource,
|
|
14507
14826
|
GroupedMediaSource,
|
|
14508
14827
|
GroupedUploadedAssetSource
|
|
14509
14828
|
]),
|
|
14510
|
-
title:
|
|
14511
|
-
description:
|
|
14829
|
+
title: z56.string().max(120).optional(),
|
|
14830
|
+
description: z56.string().max(500).optional(),
|
|
14512
14831
|
cta: MetaCtaType.optional(),
|
|
14513
|
-
creativeId:
|
|
14832
|
+
creativeId: z56.string().min(1).optional()
|
|
14514
14833
|
});
|
|
14515
|
-
var GroupedCampaignConfigBase =
|
|
14834
|
+
var GroupedCampaignConfigBase = z56.object({
|
|
14516
14835
|
campaignType: MetaCampaignType.default("sales"),
|
|
14517
14836
|
optimizationMode: MetaOptimizationMode.default("advantage_plus"),
|
|
14518
14837
|
budgetMode: MetaBudgetMode.optional(),
|
|
14519
|
-
campaignDailyBudget:
|
|
14520
|
-
adSets:
|
|
14521
|
-
tempId:
|
|
14522
|
-
name:
|
|
14523
|
-
dailyBudget:
|
|
14524
|
-
creativeIds:
|
|
14838
|
+
campaignDailyBudget: z56.number().int().positive().optional(),
|
|
14839
|
+
adSets: z56.array(z56.object({
|
|
14840
|
+
tempId: z56.string().min(1).max(80),
|
|
14841
|
+
name: z56.string().min(1).max(100),
|
|
14842
|
+
dailyBudget: z56.number().int().positive().optional(),
|
|
14843
|
+
creativeIds: z56.array(z56.string().min(1)).min(1)
|
|
14525
14844
|
})).min(1).max(CAMPAIGN_LIMITS.MAX_AD_SETS),
|
|
14526
14845
|
/**
|
|
14527
14846
|
* Valid values are 'landing' and 'whatsapp' — there is no 'website'. This
|
|
@@ -14529,17 +14848,17 @@ var GroupedCampaignConfigBase = z55.object({
|
|
|
14529
14848
|
* opaque `invalid_literal` and made the WhatsApp destination — supported by
|
|
14530
14849
|
* the API since #849 — unreachable from this tool.
|
|
14531
14850
|
*/
|
|
14532
|
-
destination:
|
|
14533
|
-
|
|
14534
|
-
type:
|
|
14535
|
-
landingUrl:
|
|
14851
|
+
destination: z56.discriminatedUnion("type", [
|
|
14852
|
+
z56.object({
|
|
14853
|
+
type: z56.literal("landing"),
|
|
14854
|
+
landingUrl: z56.string().url()
|
|
14536
14855
|
}),
|
|
14537
|
-
|
|
14538
|
-
type:
|
|
14539
|
-
phoneNumber:
|
|
14856
|
+
z56.object({
|
|
14857
|
+
type: z56.literal("whatsapp"),
|
|
14858
|
+
phoneNumber: z56.string().min(8).max(32)
|
|
14540
14859
|
})
|
|
14541
14860
|
]),
|
|
14542
|
-
saveAsDefault:
|
|
14861
|
+
saveAsDefault: z56.boolean().default(false),
|
|
14543
14862
|
budgetPolicy: MetaBudgetPolicy.optional()
|
|
14544
14863
|
});
|
|
14545
14864
|
function resolveBudgetMode(value) {
|
|
@@ -14553,7 +14872,7 @@ var GroupedCampaignConfig = GroupedCampaignConfigBase.superRefine((value, ctx) =
|
|
|
14553
14872
|
const budgetMode = resolveBudgetMode(value);
|
|
14554
14873
|
if (budgetMode === "campaign" && value.campaignDailyBudget === void 0) {
|
|
14555
14874
|
ctx.addIssue({
|
|
14556
|
-
code:
|
|
14875
|
+
code: z56.ZodIssueCode.custom,
|
|
14557
14876
|
path: [
|
|
14558
14877
|
"campaignDailyBudget"
|
|
14559
14878
|
],
|
|
@@ -14564,7 +14883,7 @@ var GroupedCampaignConfig = GroupedCampaignConfigBase.superRefine((value, ctx) =
|
|
|
14564
14883
|
value.adSets.forEach((adSet, index) => {
|
|
14565
14884
|
if (adSet.dailyBudget === void 0) {
|
|
14566
14885
|
ctx.addIssue({
|
|
14567
|
-
code:
|
|
14886
|
+
code: z56.ZodIssueCode.custom,
|
|
14568
14887
|
path: [
|
|
14569
14888
|
"adSets",
|
|
14570
14889
|
index,
|
|
@@ -14579,26 +14898,26 @@ var GroupedCampaignConfig = GroupedCampaignConfigBase.superRefine((value, ctx) =
|
|
|
14579
14898
|
...value,
|
|
14580
14899
|
budgetMode: resolveBudgetMode(value)
|
|
14581
14900
|
}));
|
|
14582
|
-
var PublishCreativesToMetaInputBase =
|
|
14583
|
-
creatives:
|
|
14901
|
+
var PublishCreativesToMetaInputBase = z56.object({
|
|
14902
|
+
creatives: z56.array(GroupedCreative).min(1).max(CAMPAIGN_LIMITS.MAX_CREATIVES),
|
|
14584
14903
|
/** Exact Meta Business portfolio selected from `list_meta_destinations`. */
|
|
14585
|
-
portfolioId:
|
|
14904
|
+
portfolioId: z56.string().trim().min(1),
|
|
14586
14905
|
/** Exact Facebook Page selected from `list_meta_destinations`. */
|
|
14587
|
-
pageId:
|
|
14906
|
+
pageId: z56.string().trim().min(1),
|
|
14588
14907
|
adAccountId: McpAdAccountId,
|
|
14589
|
-
pixelId:
|
|
14908
|
+
pixelId: z56.string().min(1).optional(),
|
|
14590
14909
|
/** Required unless `campaignConfig.destination` is whatsapp — see the
|
|
14591
14910
|
* superRefine below. Mirrors the API's `UploadToMetaRequest`. */
|
|
14592
|
-
landingUrl:
|
|
14593
|
-
targetCountry:
|
|
14594
|
-
campaignName:
|
|
14911
|
+
landingUrl: z56.string().url().optional(),
|
|
14912
|
+
targetCountry: z56.string().length(2),
|
|
14913
|
+
campaignName: z56.string().trim().min(1).max(100),
|
|
14595
14914
|
campaignConfig: GroupedCampaignConfig.optional()
|
|
14596
14915
|
});
|
|
14597
14916
|
var PublishCreativesToMetaInput = PublishCreativesToMetaInputBase.superRefine((value, ctx) => {
|
|
14598
14917
|
const destination = value.campaignConfig?.destination;
|
|
14599
14918
|
if (destination?.type !== "whatsapp" && destination?.type !== "landing" && !value.landingUrl) {
|
|
14600
14919
|
ctx.addIssue({
|
|
14601
|
-
code:
|
|
14920
|
+
code: z56.ZodIssueCode.custom,
|
|
14602
14921
|
path: [
|
|
14603
14922
|
"landingUrl"
|
|
14604
14923
|
],
|
|
@@ -14607,7 +14926,7 @@ var PublishCreativesToMetaInput = PublishCreativesToMetaInputBase.superRefine((v
|
|
|
14607
14926
|
}
|
|
14608
14927
|
if (destination?.type === "landing" && value.landingUrl && destination.landingUrl !== value.landingUrl) {
|
|
14609
14928
|
ctx.addIssue({
|
|
14610
|
-
code:
|
|
14929
|
+
code: z56.ZodIssueCode.custom,
|
|
14611
14930
|
path: [
|
|
14612
14931
|
"campaignConfig",
|
|
14613
14932
|
"destination",
|
|
@@ -14626,16 +14945,16 @@ var PublishCreativesToMetaInput = PublishCreativesToMetaInputBase.superRefine((v
|
|
|
14626
14945
|
}
|
|
14627
14946
|
return value;
|
|
14628
14947
|
});
|
|
14629
|
-
var PresignUploadResponseSchema =
|
|
14630
|
-
uploadUrl:
|
|
14948
|
+
var PresignUploadResponseSchema = z56.object({
|
|
14949
|
+
uploadUrl: z56.string().url().refine((value) => {
|
|
14631
14950
|
const url = new URL(value);
|
|
14632
14951
|
return !url.username && !url.password && url.protocol === "https:";
|
|
14633
14952
|
}, "presigned upload URL must be HTTPS without embedded credentials"),
|
|
14634
|
-
publicUrl:
|
|
14953
|
+
publicUrl: z56.string().url().refine((value) => {
|
|
14635
14954
|
const url = new URL(value);
|
|
14636
14955
|
return !url.username && !url.password;
|
|
14637
14956
|
}, "public media URL must not contain embedded credentials"),
|
|
14638
|
-
stagingAssetId:
|
|
14957
|
+
stagingAssetId: z56.string().uuid()
|
|
14639
14958
|
});
|
|
14640
14959
|
async function uploadResolvedMedia(api, media, assetRole) {
|
|
14641
14960
|
if (!media.buffer) throw new ResolveMediaError("INVALID_INPUT", "resolved media has no bytes");
|
|
@@ -14660,22 +14979,23 @@ async function resolveAndUploadMedia(api, ref, expectedKind, assetRole, transpor
|
|
|
14660
14979
|
return uploadResolvedMedia(api, media, assetRole);
|
|
14661
14980
|
}
|
|
14662
14981
|
__name(resolveAndUploadMedia, "resolveAndUploadMedia");
|
|
14663
|
-
var TikTokInput =
|
|
14664
|
-
taskId:
|
|
14665
|
-
advertiserId:
|
|
14666
|
-
pixelId:
|
|
14667
|
-
landingUrl:
|
|
14668
|
-
angleIndexes:
|
|
14982
|
+
var TikTokInput = z56.object({
|
|
14983
|
+
taskId: z56.string().min(1),
|
|
14984
|
+
advertiserId: z56.string().min(1),
|
|
14985
|
+
pixelId: z56.string().min(1).optional(),
|
|
14986
|
+
landingUrl: z56.string().url().optional(),
|
|
14987
|
+
angleIndexes: z56.array(z56.number().int().nonnegative()).optional(),
|
|
14669
14988
|
campaignConfig: McpCampaignConfig.optional(),
|
|
14670
14989
|
/** Override the ad's description (TikTok's body copy). If omitted,
|
|
14671
14990
|
* ProductMaker uses the auto-generated copy attached to the task. */
|
|
14672
|
-
primaryText:
|
|
14991
|
+
primaryText: z56.string().trim().min(1).max(500).optional()
|
|
14673
14992
|
});
|
|
14674
14993
|
function registerPublishTools(server, api, opts = {}) {
|
|
14675
14994
|
const transport = opts.transport ?? "http";
|
|
14676
|
-
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) => {
|
|
14995
|
+
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) => {
|
|
14677
14996
|
try {
|
|
14678
|
-
|
|
14997
|
+
const parsed = parseShopifyInput(input);
|
|
14998
|
+
if (parsed.publicationMode === "existing" && !parsed.targetProductId) {
|
|
14679
14999
|
return {
|
|
14680
15000
|
isError: true,
|
|
14681
15001
|
content: [
|
|
@@ -14686,29 +15006,31 @@ function registerPublishTools(server, api, opts = {}) {
|
|
|
14686
15006
|
]
|
|
14687
15007
|
};
|
|
14688
15008
|
}
|
|
14689
|
-
const variantIndex =
|
|
14690
|
-
const availableImages = await fetchLandingImageCount(api,
|
|
15009
|
+
const variantIndex = parsed.variantIndex ?? 0;
|
|
15010
|
+
const availableImages = await fetchLandingImageCount(api, parsed.taskId);
|
|
14691
15011
|
const effectiveAvailable = availableImages > 0 ? availableImages : 3;
|
|
14692
|
-
const numImages = Math.min(
|
|
15012
|
+
const numImages = Math.min(parsed.numLandingImages ?? effectiveAvailable, effectiveAvailable);
|
|
14693
15013
|
const selectedLandingImages = Array.from({
|
|
14694
15014
|
length: numImages
|
|
14695
15015
|
}, (_, i) => ({
|
|
14696
15016
|
imageIndex: i,
|
|
14697
15017
|
variantIndex
|
|
14698
15018
|
}));
|
|
14699
|
-
const r = await api.postJson(`/v1/tasks/${encodeURIComponent(
|
|
14700
|
-
shopDomain:
|
|
15019
|
+
const r = await api.postJson(`/v1/tasks/${encodeURIComponent(parsed.taskId)}/publish/shopify`, {
|
|
15020
|
+
shopDomain: parsed.shopDomain,
|
|
14701
15021
|
selectedLandingImages,
|
|
14702
|
-
showVariantPicker:
|
|
14703
|
-
publicationMode:
|
|
14704
|
-
targetProductId:
|
|
15022
|
+
showVariantPicker: parsed.showVariantPicker,
|
|
15023
|
+
publicationMode: parsed.publicationMode,
|
|
15024
|
+
targetProductId: parsed.targetProductId,
|
|
15025
|
+
announcementBarEnabled: parsed.announcementBarEnabled,
|
|
15026
|
+
announcementBarText: parsed.announcementBarText
|
|
14705
15027
|
});
|
|
14706
15028
|
return renderShopifyPublishMarkdown(r);
|
|
14707
15029
|
} catch (e) {
|
|
14708
15030
|
throw translateError(e);
|
|
14709
15031
|
}
|
|
14710
15032
|
});
|
|
14711
|
-
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 }) => {
|
|
15033
|
+
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 }) => {
|
|
14712
15034
|
try {
|
|
14713
15035
|
const body = {
|
|
14714
15036
|
...rest
|
|
@@ -14736,7 +15058,7 @@ function registerPublishTools(server, api, opts = {}) {
|
|
|
14736
15058
|
throw translateError(e);
|
|
14737
15059
|
}
|
|
14738
15060
|
});
|
|
14739
|
-
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) => {
|
|
15061
|
+
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) => {
|
|
14740
15062
|
try {
|
|
14741
15063
|
const parsed = PublishCreativesToMetaInput.safeParse(rawInput);
|
|
14742
15064
|
if (!parsed.success) throw invalidInputFromZod(parsed.error);
|
|
@@ -14826,7 +15148,7 @@ function registerPublishTools(server, api, opts = {}) {
|
|
|
14826
15148
|
throw translateError(e);
|
|
14827
15149
|
}
|
|
14828
15150
|
});
|
|
14829
|
-
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 }) => {
|
|
15151
|
+
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 }) => {
|
|
14830
15152
|
try {
|
|
14831
15153
|
const body = {
|
|
14832
15154
|
...rest
|
|
@@ -14916,38 +15238,38 @@ __name(toTikTokCampaignConfig, "toTikTokCampaignConfig");
|
|
|
14916
15238
|
|
|
14917
15239
|
// src/tools/meta-upload.ts
|
|
14918
15240
|
import { registerAppTool } from "@modelcontextprotocol/ext-apps/server";
|
|
14919
|
-
import { z as
|
|
14920
|
-
var UploadSessionInputBase =
|
|
14921
|
-
mediaKind:
|
|
15241
|
+
import { z as z57 } from "zod";
|
|
15242
|
+
var UploadSessionInputBase = z57.object({
|
|
15243
|
+
mediaKind: z57.enum([
|
|
14922
15244
|
"image",
|
|
14923
15245
|
"video",
|
|
14924
15246
|
"mixed"
|
|
14925
15247
|
]).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."),
|
|
14926
|
-
assetRole:
|
|
15248
|
+
assetRole: z57.enum([
|
|
14927
15249
|
"media",
|
|
14928
15250
|
"thumbnail"
|
|
14929
15251
|
]).default("media").describe("Use media for the creative itself; thumbnail only for an image thumbnail."),
|
|
14930
|
-
uploadPurpose:
|
|
15252
|
+
uploadPurpose: z57.enum([
|
|
14931
15253
|
"product_reference",
|
|
14932
15254
|
"creative",
|
|
14933
15255
|
"generic"
|
|
14934
15256
|
]).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."),
|
|
14935
|
-
maxFiles:
|
|
15257
|
+
maxFiles: z57.number().int().min(1).max(10).optional().describe("Optional batch limit: image accepts 1-5, video exactly 1, and mixed 1-10.")
|
|
14936
15258
|
});
|
|
14937
|
-
var OpenAiFileReference =
|
|
14938
|
-
download_url:
|
|
14939
|
-
file_id:
|
|
14940
|
-
mime_type:
|
|
14941
|
-
file_name:
|
|
15259
|
+
var OpenAiFileReference = z57.object({
|
|
15260
|
+
download_url: z57.string().url().optional().describe("Optional host-provided download URL. The App prefers getFileDownloadUrl(file_id) and the server never fetches this URL."),
|
|
15261
|
+
file_id: z57.string().min(1),
|
|
15262
|
+
mime_type: z57.string().min(1).optional(),
|
|
15263
|
+
file_name: z57.string().min(1).optional()
|
|
14942
15264
|
});
|
|
14943
15265
|
var UploadSessionToolInputShape = {
|
|
14944
15266
|
...UploadSessionInputBase.shape,
|
|
14945
|
-
files:
|
|
15267
|
+
files: z57.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.")
|
|
14946
15268
|
};
|
|
14947
|
-
var UploadSessionToolInput =
|
|
15269
|
+
var UploadSessionToolInput = z57.object(UploadSessionToolInputShape).superRefine((value, ctx) => {
|
|
14948
15270
|
if (value.assetRole === "thumbnail" && value.mediaKind !== "image") {
|
|
14949
15271
|
ctx.addIssue({
|
|
14950
|
-
code:
|
|
15272
|
+
code: z57.ZodIssueCode.custom,
|
|
14951
15273
|
path: [
|
|
14952
15274
|
"mediaKind"
|
|
14953
15275
|
],
|
|
@@ -14956,7 +15278,7 @@ var UploadSessionToolInput = z56.object(UploadSessionToolInputShape).superRefine
|
|
|
14956
15278
|
}
|
|
14957
15279
|
if (value.mediaKind === "video" && value.maxFiles !== void 0 && value.maxFiles !== 1) {
|
|
14958
15280
|
ctx.addIssue({
|
|
14959
|
-
code:
|
|
15281
|
+
code: z57.ZodIssueCode.custom,
|
|
14960
15282
|
path: [
|
|
14961
15283
|
"maxFiles"
|
|
14962
15284
|
],
|
|
@@ -14965,7 +15287,7 @@ var UploadSessionToolInput = z56.object(UploadSessionToolInputShape).superRefine
|
|
|
14965
15287
|
}
|
|
14966
15288
|
if (value.mediaKind === "image" && (value.maxFiles ?? 1) > 5) {
|
|
14967
15289
|
ctx.addIssue({
|
|
14968
|
-
code:
|
|
15290
|
+
code: z57.ZodIssueCode.custom,
|
|
14969
15291
|
path: [
|
|
14970
15292
|
"maxFiles"
|
|
14971
15293
|
],
|
|
@@ -14973,38 +15295,38 @@ var UploadSessionToolInput = z56.object(UploadSessionToolInputShape).superRefine
|
|
|
14973
15295
|
});
|
|
14974
15296
|
}
|
|
14975
15297
|
});
|
|
14976
|
-
var UploadSessionResponse =
|
|
14977
|
-
sessionId:
|
|
14978
|
-
uploadPageUrl:
|
|
14979
|
-
mediaKind:
|
|
15298
|
+
var UploadSessionResponse = z57.object({
|
|
15299
|
+
sessionId: z57.string().uuid(),
|
|
15300
|
+
uploadPageUrl: z57.string().url(),
|
|
15301
|
+
mediaKind: z57.enum([
|
|
14980
15302
|
"image",
|
|
14981
15303
|
"video",
|
|
14982
15304
|
"mixed"
|
|
14983
15305
|
]),
|
|
14984
|
-
assetRole:
|
|
15306
|
+
assetRole: z57.enum([
|
|
14985
15307
|
"media",
|
|
14986
15308
|
"thumbnail"
|
|
14987
15309
|
]),
|
|
14988
|
-
maxFiles:
|
|
14989
|
-
expiresAt:
|
|
15310
|
+
maxFiles: z57.number().int().positive(),
|
|
15311
|
+
expiresAt: z57.string().datetime()
|
|
14990
15312
|
});
|
|
14991
|
-
var UploadStatusInput =
|
|
14992
|
-
sessionId:
|
|
15313
|
+
var UploadStatusInput = z57.object({
|
|
15314
|
+
sessionId: z57.string().uuid().describe("The sessionId returned by request_media_upload.")
|
|
14993
15315
|
});
|
|
14994
|
-
var UniversalUploadStatusResponse =
|
|
14995
|
-
|
|
14996
|
-
status:
|
|
14997
|
-
assets:
|
|
15316
|
+
var UniversalUploadStatusResponse = z57.discriminatedUnion("status", [
|
|
15317
|
+
z57.object({
|
|
15318
|
+
status: z57.literal("pending"),
|
|
15319
|
+
assets: z57.array(z57.never()).length(0)
|
|
14998
15320
|
}),
|
|
14999
|
-
|
|
15000
|
-
status:
|
|
15001
|
-
assets:
|
|
15002
|
-
assetId:
|
|
15003
|
-
mediaKind:
|
|
15321
|
+
z57.object({
|
|
15322
|
+
status: z57.literal("uploaded"),
|
|
15323
|
+
assets: z57.array(z57.object({
|
|
15324
|
+
assetId: z57.string().uuid(),
|
|
15325
|
+
mediaKind: z57.enum([
|
|
15004
15326
|
"image",
|
|
15005
15327
|
"video"
|
|
15006
15328
|
]),
|
|
15007
|
-
assetRole:
|
|
15329
|
+
assetRole: z57.enum([
|
|
15008
15330
|
"media",
|
|
15009
15331
|
"thumbnail"
|
|
15010
15332
|
])
|
|
@@ -15012,13 +15334,13 @@ var UniversalUploadStatusResponse = z56.discriminatedUnion("status", [
|
|
|
15012
15334
|
})
|
|
15013
15335
|
]);
|
|
15014
15336
|
var UniversalUploadStructuredResponse = UploadSessionResponse.extend({
|
|
15015
|
-
uploadPurpose:
|
|
15337
|
+
uploadPurpose: z57.enum([
|
|
15016
15338
|
"product_reference",
|
|
15017
15339
|
"creative",
|
|
15018
15340
|
"generic"
|
|
15019
15341
|
]),
|
|
15020
|
-
checkToolName:
|
|
15021
|
-
resolveEndpoint:
|
|
15342
|
+
checkToolName: z57.literal("check_media_upload"),
|
|
15343
|
+
resolveEndpoint: z57.string().url()
|
|
15022
15344
|
});
|
|
15023
15345
|
function contextualizeUploadPageUrl(uploadPageUrl, uploadPurpose) {
|
|
15024
15346
|
const url = new URL(uploadPageUrl);
|
|
@@ -15052,6 +15374,7 @@ function registerMediaUploadTools(server, api) {
|
|
|
15052
15374
|
title: "Subir archivos",
|
|
15053
15375
|
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.",
|
|
15054
15376
|
inputSchema: UploadSessionToolInputShape,
|
|
15377
|
+
annotations: CREATES_INTERNAL,
|
|
15055
15378
|
outputSchema: UniversalUploadStructuredResponse.shape,
|
|
15056
15379
|
_meta: {
|
|
15057
15380
|
ui: {
|
|
@@ -15092,6 +15415,7 @@ function registerMediaUploadTools(server, api) {
|
|
|
15092
15415
|
server.registerTool("check_media_upload", {
|
|
15093
15416
|
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.",
|
|
15094
15417
|
inputSchema: UploadStatusInput.shape,
|
|
15418
|
+
annotations: READS_INTERNAL,
|
|
15095
15419
|
_meta: {
|
|
15096
15420
|
"openai/widgetAccessible": true
|
|
15097
15421
|
}
|
|
@@ -15107,16 +15431,16 @@ function registerMediaUploadTools(server, api) {
|
|
|
15107
15431
|
__name(registerMediaUploadTools, "registerMediaUploadTools");
|
|
15108
15432
|
|
|
15109
15433
|
// src/tools/campaigns.ts
|
|
15110
|
-
import { z as
|
|
15111
|
-
var MetaStatusInput =
|
|
15112
|
-
campaignId:
|
|
15434
|
+
import { z as z58 } from "zod";
|
|
15435
|
+
var MetaStatusInput = z58.object({
|
|
15436
|
+
campaignId: z58.string().min(1).describe("Meta campaign ID returned by publish_to_meta.")
|
|
15113
15437
|
});
|
|
15114
|
-
var TikTokStatusInput =
|
|
15115
|
-
campaignId:
|
|
15116
|
-
advertiserId:
|
|
15438
|
+
var TikTokStatusInput = z58.object({
|
|
15439
|
+
campaignId: z58.string().min(1).describe("TikTok campaign ID returned by publish_to_tiktok."),
|
|
15440
|
+
advertiserId: z58.string().min(1).describe("The same advertiserId used to publish the campaign.")
|
|
15117
15441
|
});
|
|
15118
15442
|
function registerCampaignTools(server, api) {
|
|
15119
|
-
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) => {
|
|
15443
|
+
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) => {
|
|
15120
15444
|
try {
|
|
15121
15445
|
const r = await api.getJson(`/v1/facebook/campaigns/${encodeURIComponent(input.campaignId)}`);
|
|
15122
15446
|
return renderMetaStatusMarkdown(r);
|
|
@@ -15124,7 +15448,7 @@ function registerCampaignTools(server, api) {
|
|
|
15124
15448
|
throw translateError(e);
|
|
15125
15449
|
}
|
|
15126
15450
|
});
|
|
15127
|
-
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) => {
|
|
15451
|
+
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) => {
|
|
15128
15452
|
try {
|
|
15129
15453
|
const r = await api.getJson(`/v1/tiktok/campaigns/${encodeURIComponent(input.campaignId)}?advertiserId=${encodeURIComponent(input.advertiserId)}`);
|
|
15130
15454
|
return renderTikTokStatusMarkdown(r);
|
|
@@ -15210,10 +15534,10 @@ function renderTikTokStatusMarkdown(r) {
|
|
|
15210
15534
|
__name(renderTikTokStatusMarkdown, "renderTikTokStatusMarkdown");
|
|
15211
15535
|
|
|
15212
15536
|
// ../../packages/product-economics/src/product-costing.ts
|
|
15213
|
-
import { z as
|
|
15537
|
+
import { z as z60 } from "zod";
|
|
15214
15538
|
|
|
15215
15539
|
// ../../packages/product-economics/src/product-costing-countries.ts
|
|
15216
|
-
import { z as
|
|
15540
|
+
import { z as z59 } from "zod";
|
|
15217
15541
|
|
|
15218
15542
|
// ../../packages/product-economics/src/product-costing-shipping-references.json
|
|
15219
15543
|
var product_costing_shipping_references_default = {
|
|
@@ -15277,21 +15601,21 @@ var product_costing_shipping_references_default = {
|
|
|
15277
15601
|
};
|
|
15278
15602
|
|
|
15279
15603
|
// ../../packages/product-economics/src/product-costing-countries.ts
|
|
15280
|
-
var ShippingReferenceSchema =
|
|
15281
|
-
reference:
|
|
15282
|
-
min:
|
|
15283
|
-
max:
|
|
15284
|
-
updatedAt:
|
|
15285
|
-
confidence:
|
|
15604
|
+
var ShippingReferenceSchema = z59.object({
|
|
15605
|
+
reference: z59.number().positive(),
|
|
15606
|
+
min: z59.number().nonnegative(),
|
|
15607
|
+
max: z59.number().positive(),
|
|
15608
|
+
updatedAt: z59.string().date(),
|
|
15609
|
+
confidence: z59.enum([
|
|
15286
15610
|
"high",
|
|
15287
15611
|
"medium",
|
|
15288
15612
|
"medium-low"
|
|
15289
15613
|
]),
|
|
15290
|
-
sources:
|
|
15614
|
+
sources: z59.array(z59.string().url().startsWith("https://")).min(1).max(3)
|
|
15291
15615
|
}).superRefine(({ reference, min, max }, context) => {
|
|
15292
15616
|
if (min > reference || reference > max) {
|
|
15293
15617
|
context.addIssue({
|
|
15294
|
-
code:
|
|
15618
|
+
code: z59.ZodIssueCode.custom,
|
|
15295
15619
|
message: "shipping_reference_outside_range",
|
|
15296
15620
|
path: [
|
|
15297
15621
|
"reference"
|
|
@@ -15299,7 +15623,7 @@ var ShippingReferenceSchema = z58.object({
|
|
|
15299
15623
|
});
|
|
15300
15624
|
}
|
|
15301
15625
|
});
|
|
15302
|
-
var SHIPPING_REFERENCES =
|
|
15626
|
+
var SHIPPING_REFERENCES = z59.record(ShippingReferenceSchema).parse(product_costing_shipping_references_default);
|
|
15303
15627
|
var PRODUCT_COSTING_COUNTRY_CODES = Object.freeze(Object.keys(SHIPPING_REFERENCES));
|
|
15304
15628
|
var SPANISH_LOCALE_COUNTRIES = /* @__PURE__ */ new Set([
|
|
15305
15629
|
"MX",
|
|
@@ -15481,18 +15805,18 @@ function getProductCostingCountryName(country) {
|
|
|
15481
15805
|
__name(getProductCostingCountryName, "getProductCostingCountryName");
|
|
15482
15806
|
|
|
15483
15807
|
// ../../packages/product-economics/src/product-costing.ts
|
|
15484
|
-
var ProductCostingInputSchema =
|
|
15485
|
-
country:
|
|
15486
|
-
supplierCost:
|
|
15487
|
-
shippingCost:
|
|
15488
|
-
returnRate:
|
|
15489
|
-
cancellationRate:
|
|
15490
|
-
adsManagerCpa:
|
|
15491
|
-
desiredMargin:
|
|
15808
|
+
var ProductCostingInputSchema = z60.object({
|
|
15809
|
+
country: z60.string().refine(isProductCostingCountryCode, "unsupported_country"),
|
|
15810
|
+
supplierCost: z60.number().finite().max(Number.MAX_SAFE_INTEGER, "amount_too_large").positive("supplier_cost_positive"),
|
|
15811
|
+
shippingCost: z60.number().finite().max(Number.MAX_SAFE_INTEGER, "amount_too_large").nonnegative("shipping_cost_nonnegative"),
|
|
15812
|
+
returnRate: z60.number().finite().min(0, "rate_nonnegative").lt(1, "rate_below_one"),
|
|
15813
|
+
cancellationRate: z60.number().finite().min(0, "rate_nonnegative").lt(1, "rate_below_one"),
|
|
15814
|
+
adsManagerCpa: z60.number().finite().max(Number.MAX_SAFE_INTEGER, "amount_too_large").nonnegative("cpa_nonnegative"),
|
|
15815
|
+
desiredMargin: z60.number().finite().min(0, "margin_nonnegative").lt(1, "margin_below_one")
|
|
15492
15816
|
}).superRefine(({ returnRate, cancellationRate }, context) => {
|
|
15493
15817
|
if (returnRate + cancellationRate >= 1) {
|
|
15494
15818
|
context.addIssue({
|
|
15495
|
-
code:
|
|
15819
|
+
code: z60.ZodIssueCode.custom,
|
|
15496
15820
|
message: "delivery_rate_positive",
|
|
15497
15821
|
path: [
|
|
15498
15822
|
"cancellationRate"
|
|
@@ -15559,14 +15883,14 @@ function calculateProductCosting(rawInput) {
|
|
|
15559
15883
|
__name(calculateProductCosting, "calculateProductCosting");
|
|
15560
15884
|
|
|
15561
15885
|
// src/tools/product-costing.ts
|
|
15562
|
-
import { z as
|
|
15563
|
-
var optionalAmount = /* @__PURE__ */ __name((description) =>
|
|
15564
|
-
var optionalPercent = /* @__PURE__ */ __name((description) =>
|
|
15565
|
-
var ProductCostingToolInput =
|
|
15566
|
-
country:
|
|
15886
|
+
import { z as z61 } from "zod";
|
|
15887
|
+
var optionalAmount = /* @__PURE__ */ __name((description) => z61.number().finite().max(Number.MAX_SAFE_INTEGER).nonnegative().optional().describe(description), "optionalAmount");
|
|
15888
|
+
var optionalPercent = /* @__PURE__ */ __name((description) => z61.number().finite().min(0).lt(100).optional().describe(description), "optionalPercent");
|
|
15889
|
+
var ProductCostingToolInput = z61.object({
|
|
15890
|
+
country: z61.string().trim().toUpperCase().refine(isProductCostingCountryCode, {
|
|
15567
15891
|
message: "Pa\xEDs no soportado por ProductMaker."
|
|
15568
15892
|
}).optional().describe("C\xF3digo ISO-2 de cualquiera de los pa\xEDses disponibles en ProductMaker. Omite para Colombia."),
|
|
15569
|
-
supplierCost:
|
|
15893
|
+
supplierCost: z61.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."),
|
|
15570
15894
|
supplierCostCop: optionalAmount("Alias anterior para costo del proveedor en Colombia. Prefiere supplierCost."),
|
|
15571
15895
|
shippingCost: optionalAmount("Costo de un env\xEDo en la moneda del pa\xEDs. Omite para usar el valor gu\xEDa vigente."),
|
|
15572
15896
|
shippingCostCop: optionalAmount("Alias anterior para flete en Colombia. Prefiere shippingCost."),
|
|
@@ -15577,7 +15901,7 @@ var ProductCostingToolInput = z60.object({
|
|
|
15577
15901
|
desiredMarginPercent: optionalPercent("Margen de ganancia deseado. Ejemplo: 10 significa 10%. Omite para usar el valor gu\xEDa editable del pa\xEDs.")
|
|
15578
15902
|
});
|
|
15579
15903
|
function registerProductCostingTools(server) {
|
|
15580
|
-
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) => {
|
|
15904
|
+
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) => {
|
|
15581
15905
|
if (input.country !== void 0 && input.country !== "CO" && [
|
|
15582
15906
|
input.supplierCostCop,
|
|
15583
15907
|
input.shippingCostCop,
|
|
@@ -15698,7 +16022,7 @@ function formatPercent(value) {
|
|
|
15698
16022
|
__name(formatPercent, "formatPercent");
|
|
15699
16023
|
|
|
15700
16024
|
// src/tools/product-research.ts
|
|
15701
|
-
import { z as
|
|
16025
|
+
import { z as z62 } from "zod";
|
|
15702
16026
|
|
|
15703
16027
|
// src/tools/product-research-idempotency.ts
|
|
15704
16028
|
import { createHash as createHash3 } from "crypto";
|
|
@@ -15814,17 +16138,17 @@ var TERMINAL2 = /* @__PURE__ */ new Set([
|
|
|
15814
16138
|
]);
|
|
15815
16139
|
var POLL_INTERVAL_MS = 5e3;
|
|
15816
16140
|
var MAX_WAIT_SECONDS = 120;
|
|
15817
|
-
var FindInput =
|
|
16141
|
+
var FindInput = z62.object({
|
|
15818
16142
|
targetCountry: ResearchTargetCountryEnum.describe("Pa\xEDs objetivo. S\xF3lo CO, MX, PE, EC y CL tienen cobertura de investigaci\xF3n."),
|
|
15819
16143
|
categoryKey: ResearchCategoryEnum.default("auto").describe('Categor\xEDa a investigar. Usa "auto" para que la IA elija seg\xFAn el mercado.'),
|
|
15820
|
-
confirmed:
|
|
15821
|
-
waitSeconds:
|
|
16144
|
+
confirmed: z62.boolean().describe(`Ponlo en true SOLO despu\xE9s de que el usuario haya aceptado el cargo de ${PRICE_CREDITS} cr\xE9ditos. No lo asumas.`),
|
|
16145
|
+
waitSeconds: z62.number().int().min(0).max(MAX_WAIT_SECONDS).optional().describe("Segundos a esperar el reporte antes de devolver el runId. M\xE1ximo 120.")
|
|
15822
16146
|
});
|
|
15823
|
-
var GetInput =
|
|
15824
|
-
runId:
|
|
16147
|
+
var GetInput = z62.object({
|
|
16148
|
+
runId: z62.string().uuid().describe("ID del run devuelto por find_winning_products.")
|
|
15825
16149
|
});
|
|
15826
|
-
var ListInput =
|
|
15827
|
-
limit:
|
|
16150
|
+
var ListInput = z62.object({
|
|
16151
|
+
limit: z62.number().int().min(1).max(20).default(10)
|
|
15828
16152
|
});
|
|
15829
16153
|
function textResult(text) {
|
|
15830
16154
|
return {
|
|
@@ -15849,7 +16173,7 @@ async function pollUntilTerminal(api, runId, waitSeconds) {
|
|
|
15849
16173
|
}
|
|
15850
16174
|
__name(pollUntilTerminal, "pollUntilTerminal");
|
|
15851
16175
|
function registerProductResearchTools(server, api) {
|
|
15852
|
-
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) => {
|
|
16176
|
+
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) => {
|
|
15853
16177
|
const parsed = FindInput.parse(input);
|
|
15854
16178
|
if (!parsed.confirmed) {
|
|
15855
16179
|
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.`);
|
|
@@ -15883,7 +16207,7 @@ _Esta llamada no cobr\xF3 cr\xE9ditos: se reutiliz\xF3 una investigaci\xF3n reci
|
|
|
15883
16207
|
throw translateError(error);
|
|
15884
16208
|
}
|
|
15885
16209
|
});
|
|
15886
|
-
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) => {
|
|
16210
|
+
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) => {
|
|
15887
16211
|
const parsed = GetInput.parse(input);
|
|
15888
16212
|
try {
|
|
15889
16213
|
const run = await api.getJson(`/v1/product-research/runs/${parsed.runId}`);
|
|
@@ -15903,7 +16227,7 @@ _Esta llamada no cobr\xF3 cr\xE9ditos: se reutiliz\xF3 una investigaci\xF3n reci
|
|
|
15903
16227
|
throw translateError(error);
|
|
15904
16228
|
}
|
|
15905
16229
|
});
|
|
15906
|
-
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) => {
|
|
16230
|
+
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) => {
|
|
15907
16231
|
const parsed = ListInput.parse(input);
|
|
15908
16232
|
try {
|
|
15909
16233
|
const runs = await api.getJson(`/v1/product-research/runs?limit=${parsed.limit}`);
|
|
@@ -15916,14 +16240,14 @@ _Esta llamada no cobr\xF3 cr\xE9ditos: se reutiliz\xF3 una investigaci\xF3n reci
|
|
|
15916
16240
|
__name(registerProductResearchTools, "registerProductResearchTools");
|
|
15917
16241
|
|
|
15918
16242
|
// src/tools/dropshipping-projects.ts
|
|
15919
|
-
import { z as
|
|
15920
|
-
var ProjectId =
|
|
15921
|
-
var DropshippingProjectStatus =
|
|
16243
|
+
import { z as z63 } from "zod";
|
|
16244
|
+
var ProjectId = z63.string().uuid();
|
|
16245
|
+
var DropshippingProjectStatus = z63.enum([
|
|
15922
16246
|
"active",
|
|
15923
16247
|
"completed",
|
|
15924
16248
|
"archived"
|
|
15925
16249
|
]);
|
|
15926
|
-
var DropshippingProjectPhase =
|
|
16250
|
+
var DropshippingProjectPhase = z63.enum([
|
|
15927
16251
|
"research",
|
|
15928
16252
|
"costing",
|
|
15929
16253
|
"media",
|
|
@@ -15932,21 +16256,21 @@ var DropshippingProjectPhase = z62.enum([
|
|
|
15932
16256
|
"advertising",
|
|
15933
16257
|
"complete"
|
|
15934
16258
|
]);
|
|
15935
|
-
var DropshippingRewardMilestone =
|
|
16259
|
+
var DropshippingRewardMilestone = z63.enum([
|
|
15936
16260
|
"product_selected",
|
|
15937
16261
|
"costing_saved",
|
|
15938
16262
|
"product_media_ready",
|
|
15939
16263
|
"creatives_completed",
|
|
15940
16264
|
"campaign_created"
|
|
15941
16265
|
]);
|
|
15942
|
-
var TargetCountry =
|
|
16266
|
+
var TargetCountry = z63.enum([
|
|
15943
16267
|
"CO",
|
|
15944
16268
|
"MX",
|
|
15945
16269
|
"PE",
|
|
15946
16270
|
"EC",
|
|
15947
16271
|
"CL"
|
|
15948
16272
|
]);
|
|
15949
|
-
var ResearchCategory =
|
|
16273
|
+
var ResearchCategory = z63.enum([
|
|
15950
16274
|
"auto",
|
|
15951
16275
|
"automotive",
|
|
15952
16276
|
"kitchen",
|
|
@@ -15961,7 +16285,7 @@ var ResearchCategory = z62.enum([
|
|
|
15961
16285
|
"garden",
|
|
15962
16286
|
"cleaning"
|
|
15963
16287
|
]);
|
|
15964
|
-
var HttpUrl3 =
|
|
16288
|
+
var HttpUrl3 = z63.string().url().max(2e3).refine((value) => {
|
|
15965
16289
|
try {
|
|
15966
16290
|
return [
|
|
15967
16291
|
"http:",
|
|
@@ -15971,43 +16295,43 @@ var HttpUrl3 = z62.string().url().max(2e3).refine((value) => {
|
|
|
15971
16295
|
return false;
|
|
15972
16296
|
}
|
|
15973
16297
|
}, "URL must use HTTP(S)");
|
|
15974
|
-
var ResearchCandidateOrigin =
|
|
15975
|
-
type:
|
|
16298
|
+
var ResearchCandidateOrigin = z63.object({
|
|
16299
|
+
type: z63.literal("research_candidate"),
|
|
15976
16300
|
runId: ProjectId,
|
|
15977
|
-
candidateSourceId:
|
|
16301
|
+
candidateSourceId: z63.string().min(1).max(160)
|
|
15978
16302
|
});
|
|
15979
|
-
var TaskOrigin =
|
|
15980
|
-
type:
|
|
16303
|
+
var TaskOrigin = z63.object({
|
|
16304
|
+
type: z63.literal("task"),
|
|
15981
16305
|
taskId: ProjectId
|
|
15982
16306
|
});
|
|
15983
|
-
var ManualOrigin =
|
|
15984
|
-
type:
|
|
16307
|
+
var ManualOrigin = z63.object({
|
|
16308
|
+
type: z63.literal("manual"),
|
|
15985
16309
|
clientRequestId: ProjectId,
|
|
15986
|
-
country:
|
|
16310
|
+
country: z63.string().min(2).max(3).optional()
|
|
15987
16311
|
});
|
|
15988
|
-
var StartProjectInput =
|
|
15989
|
-
origin:
|
|
16312
|
+
var StartProjectInput = z63.object({
|
|
16313
|
+
origin: z63.discriminatedUnion("type", [
|
|
15990
16314
|
ResearchCandidateOrigin,
|
|
15991
16315
|
TaskOrigin,
|
|
15992
16316
|
ManualOrigin
|
|
15993
16317
|
]),
|
|
15994
|
-
productName:
|
|
16318
|
+
productName: z63.string().trim().min(1).max(240).optional(),
|
|
15995
16319
|
productUrl: HttpUrl3.optional(),
|
|
15996
|
-
country:
|
|
15997
|
-
languageCode:
|
|
16320
|
+
country: z63.string().min(2).max(3).optional(),
|
|
16321
|
+
languageCode: z63.string().min(2).max(16).optional()
|
|
15998
16322
|
}).strict();
|
|
15999
|
-
var CostProjectInput =
|
|
16323
|
+
var CostProjectInput = z63.object({
|
|
16000
16324
|
projectId: ProjectId,
|
|
16001
|
-
version:
|
|
16002
|
-
country:
|
|
16003
|
-
supplierCost:
|
|
16004
|
-
shippingCost:
|
|
16005
|
-
returnRate:
|
|
16006
|
-
cancellationRate:
|
|
16007
|
-
adsManagerCpa:
|
|
16008
|
-
desiredMargin:
|
|
16009
|
-
});
|
|
16010
|
-
var GuideInput =
|
|
16325
|
+
version: z63.number().int().positive(),
|
|
16326
|
+
country: z63.string().trim().min(2).max(3),
|
|
16327
|
+
supplierCost: z63.number().finite().positive(),
|
|
16328
|
+
shippingCost: z63.number().finite().nonnegative(),
|
|
16329
|
+
returnRate: z63.number().finite().min(0).lt(1),
|
|
16330
|
+
cancellationRate: z63.number().finite().min(0).lt(1),
|
|
16331
|
+
adsManagerCpa: z63.number().finite().nonnegative(),
|
|
16332
|
+
desiredMargin: z63.number().finite().min(0).lt(1)
|
|
16333
|
+
});
|
|
16334
|
+
var GuideInput = z63.object({
|
|
16011
16335
|
projectId: ProjectId.optional()
|
|
16012
16336
|
}).strict();
|
|
16013
16337
|
var NEXT_ACTION_KINDS = [
|
|
@@ -16025,29 +16349,29 @@ var NEXT_ACTION_KINDS = [
|
|
|
16025
16349
|
"repair_meta_campaign",
|
|
16026
16350
|
"review_paused_campaign"
|
|
16027
16351
|
];
|
|
16028
|
-
var DropshippingProjectNextAction =
|
|
16029
|
-
kind:
|
|
16030
|
-
href:
|
|
16352
|
+
var DropshippingProjectNextAction = z63.discriminatedUnion("kind", NEXT_ACTION_KINDS.map((kind) => z63.object({
|
|
16353
|
+
kind: z63.literal(kind),
|
|
16354
|
+
href: z63.string().min(1)
|
|
16031
16355
|
})));
|
|
16032
|
-
var CostingInputSchema =
|
|
16033
|
-
country:
|
|
16034
|
-
supplierCost:
|
|
16035
|
-
shippingCost:
|
|
16036
|
-
returnRate:
|
|
16037
|
-
cancellationRate:
|
|
16038
|
-
adsManagerCpa:
|
|
16039
|
-
desiredMargin:
|
|
16356
|
+
var CostingInputSchema = z63.object({
|
|
16357
|
+
country: z63.string().min(2).max(3),
|
|
16358
|
+
supplierCost: z63.number().finite().positive(),
|
|
16359
|
+
shippingCost: z63.number().finite().nonnegative(),
|
|
16360
|
+
returnRate: z63.number().finite().min(0).lt(1),
|
|
16361
|
+
cancellationRate: z63.number().finite().min(0).lt(1),
|
|
16362
|
+
adsManagerCpa: z63.number().finite().nonnegative(),
|
|
16363
|
+
desiredMargin: z63.number().finite().min(0).lt(1)
|
|
16040
16364
|
}).superRefine(({ returnRate, cancellationRate }, context) => {
|
|
16041
16365
|
if (returnRate + cancellationRate >= 1) context.addIssue({
|
|
16042
|
-
code:
|
|
16366
|
+
code: z63.ZodIssueCode.custom,
|
|
16043
16367
|
path: [
|
|
16044
16368
|
"cancellationRate"
|
|
16045
16369
|
]
|
|
16046
16370
|
});
|
|
16047
16371
|
});
|
|
16048
|
-
var FiniteCostingNumber =
|
|
16049
|
-
var CostingResultSchema =
|
|
16050
|
-
country:
|
|
16372
|
+
var FiniteCostingNumber = z63.number().finite();
|
|
16373
|
+
var CostingResultSchema = z63.object({
|
|
16374
|
+
country: z63.string().min(2).max(3),
|
|
16051
16375
|
deliveryRate: FiniteCostingNumber,
|
|
16052
16376
|
adjustedShippingCost: FiniteCostingNumber,
|
|
16053
16377
|
adjustedCpa: FiniteCostingNumber,
|
|
@@ -16060,44 +16384,44 @@ var CostingResultSchema = z62.object({
|
|
|
16060
16384
|
estimatedProfit: FiniteCostingNumber,
|
|
16061
16385
|
actualMargin: FiniteCostingNumber,
|
|
16062
16386
|
breakEvenAdsManagerCpa: FiniteCostingNumber,
|
|
16063
|
-
wasPriceAutoAdjusted:
|
|
16064
|
-
profitabilityStatus:
|
|
16065
|
-
});
|
|
16066
|
-
var RewardProgressSchema =
|
|
16067
|
-
campaignKey:
|
|
16068
|
-
totalCredits:
|
|
16069
|
-
earnedCredits:
|
|
16070
|
-
milestones:
|
|
16387
|
+
wasPriceAutoAdjusted: z63.boolean(),
|
|
16388
|
+
profitabilityStatus: z63.literal("healthy")
|
|
16389
|
+
});
|
|
16390
|
+
var RewardProgressSchema = z63.object({
|
|
16391
|
+
campaignKey: z63.literal("dropshipping-launch-v1"),
|
|
16392
|
+
totalCredits: z63.number().int().positive(),
|
|
16393
|
+
earnedCredits: z63.number().int().nonnegative(),
|
|
16394
|
+
milestones: z63.array(z63.object({
|
|
16071
16395
|
milestone: DropshippingRewardMilestone,
|
|
16072
|
-
credits:
|
|
16073
|
-
available:
|
|
16074
|
-
earned:
|
|
16396
|
+
credits: z63.number().int().positive(),
|
|
16397
|
+
available: z63.boolean().optional(),
|
|
16398
|
+
earned: z63.boolean(),
|
|
16075
16399
|
grantId: ProjectId.nullable(),
|
|
16076
|
-
grantedAt:
|
|
16400
|
+
grantedAt: z63.string().datetime().nullable()
|
|
16077
16401
|
})).length(DropshippingRewardMilestone.options.length)
|
|
16078
16402
|
});
|
|
16079
|
-
var ProjectResponseSchema =
|
|
16403
|
+
var ProjectResponseSchema = z63.object({
|
|
16080
16404
|
id: ProjectId,
|
|
16081
16405
|
taskId: ProjectId.nullable(),
|
|
16082
16406
|
status: DropshippingProjectStatus,
|
|
16083
|
-
originKey:
|
|
16407
|
+
originKey: z63.string().min(1),
|
|
16084
16408
|
researchRunId: ProjectId.nullable(),
|
|
16085
|
-
candidateSourceId:
|
|
16086
|
-
productName:
|
|
16409
|
+
candidateSourceId: z63.string().nullable(),
|
|
16410
|
+
productName: z63.string().nullable(),
|
|
16087
16411
|
productUrl: HttpUrl3.nullable(),
|
|
16088
16412
|
referenceImageUrl: HttpUrl3.nullable(),
|
|
16089
|
-
targetCountry:
|
|
16090
|
-
languageCode:
|
|
16091
|
-
hasReferenceImage:
|
|
16413
|
+
targetCountry: z63.string().nullable(),
|
|
16414
|
+
languageCode: z63.string().nullable(),
|
|
16415
|
+
hasReferenceImage: z63.boolean(),
|
|
16092
16416
|
costingInput: CostingInputSchema.nullable(),
|
|
16093
16417
|
costingResult: CostingResultSchema.nullable(),
|
|
16094
|
-
costingVersion:
|
|
16095
|
-
version:
|
|
16418
|
+
costingVersion: z63.number().int().positive().nullable(),
|
|
16419
|
+
version: z63.number().int().positive(),
|
|
16096
16420
|
phase: DropshippingProjectPhase,
|
|
16097
16421
|
nextAction: DropshippingProjectNextAction.nullable(),
|
|
16098
16422
|
rewards: RewardProgressSchema,
|
|
16099
|
-
createdAt:
|
|
16100
|
-
updatedAt:
|
|
16423
|
+
createdAt: z63.string().datetime(),
|
|
16424
|
+
updatedAt: z63.string().datetime()
|
|
16101
16425
|
}).passthrough();
|
|
16102
16426
|
var NEXT_ACTION_GUIDANCE = {
|
|
16103
16427
|
cost_product: "Llama `cost_dropshipping_project` con el costo real del proveedor. Nunca lo inventes: p\xEDdeselo al usuario.",
|
|
@@ -16146,28 +16470,38 @@ function projectText(raw) {
|
|
|
16146
16470
|
}
|
|
16147
16471
|
__name(projectText, "projectText");
|
|
16148
16472
|
function registerDropshippingProjectTools(server, api) {
|
|
16149
|
-
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) => {
|
|
16473
|
+
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) => {
|
|
16150
16474
|
try {
|
|
16151
16475
|
return projectText(await api.postJson("/v1/dropshipping-projects", input));
|
|
16152
16476
|
} catch (error) {
|
|
16153
16477
|
throw translateError(error);
|
|
16154
16478
|
}
|
|
16155
16479
|
});
|
|
16156
|
-
server.tool(
|
|
16157
|
-
|
|
16158
|
-
|
|
16159
|
-
|
|
16480
|
+
server.tool(
|
|
16481
|
+
"cost_dropshipping_project",
|
|
16482
|
+
"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.",
|
|
16483
|
+
CostProjectInput.shape,
|
|
16484
|
+
// Overwrites the costing inputs already saved on the project — the same
|
|
16485
|
+
// "previous values are gone" case as edit_task_draft, so destructive.
|
|
16486
|
+
// idempotentHint stays false despite the PUT: the `version` guard makes a
|
|
16487
|
+
// blind retry FAIL rather than replay, so a host should not auto-retry it.
|
|
16488
|
+
OVERWRITES_INTERNAL,
|
|
16489
|
+
async (input) => {
|
|
16490
|
+
try {
|
|
16491
|
+
if (input.returnRate + input.cancellationRate >= 1) {
|
|
16492
|
+
throw new Error("returnRate + cancellationRate debe ser menor que 1.");
|
|
16493
|
+
}
|
|
16494
|
+
const { projectId, version, ...costingInput } = input;
|
|
16495
|
+
return projectText(await api.putJson(`/v1/dropshipping-projects/${encodeURIComponent(projectId)}/costing`, {
|
|
16496
|
+
version,
|
|
16497
|
+
input: costingInput
|
|
16498
|
+
}));
|
|
16499
|
+
} catch (error) {
|
|
16500
|
+
throw translateError(error);
|
|
16160
16501
|
}
|
|
16161
|
-
const { projectId, version, ...costingInput } = input;
|
|
16162
|
-
return projectText(await api.putJson(`/v1/dropshipping-projects/${encodeURIComponent(projectId)}/costing`, {
|
|
16163
|
-
version,
|
|
16164
|
-
input: costingInput
|
|
16165
|
-
}));
|
|
16166
|
-
} catch (error) {
|
|
16167
|
-
throw translateError(error);
|
|
16168
16502
|
}
|
|
16169
|
-
|
|
16170
|
-
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) => {
|
|
16503
|
+
);
|
|
16504
|
+
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) => {
|
|
16171
16505
|
try {
|
|
16172
16506
|
if (!input.projectId) {
|
|
16173
16507
|
return asText({
|
|
@@ -16518,6 +16852,14 @@ function createApp(opts) {
|
|
|
16518
16852
|
ok: true
|
|
16519
16853
|
});
|
|
16520
16854
|
});
|
|
16855
|
+
app.get("/.well-known/openai-apps-challenge", (_req, res) => {
|
|
16856
|
+
const token = opts.appsChallengeToken?.trim();
|
|
16857
|
+
if (!token) {
|
|
16858
|
+
res.status(404).type("text/plain").send("Not found");
|
|
16859
|
+
return;
|
|
16860
|
+
}
|
|
16861
|
+
res.status(200).type("text/plain").send(token);
|
|
16862
|
+
});
|
|
16521
16863
|
app.get("/assets/:token/:filename?", rateLimit({
|
|
16522
16864
|
windowMs: 6e4,
|
|
16523
16865
|
max: 600,
|
|
@@ -16589,7 +16931,8 @@ async function startHttpServer(env = process.env) {
|
|
|
16589
16931
|
const app = createApp({
|
|
16590
16932
|
apiBaseUrl,
|
|
16591
16933
|
proxyBaseUrl,
|
|
16592
|
-
oauth
|
|
16934
|
+
oauth,
|
|
16935
|
+
appsChallengeToken: env.OPENAI_APPS_CHALLENGE_TOKEN ?? null
|
|
16593
16936
|
});
|
|
16594
16937
|
return await new Promise((resolve3, reject) => {
|
|
16595
16938
|
const server = app.listen(port, () => {
|