vidspotai-shared 1.0.124 → 1.0.126
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/lib/globals/aiModels/enums.d.ts +38 -0
- package/lib/globals/aiModels/enums.d.ts.map +1 -1
- package/lib/globals/aiModels/enums.js +49 -1
- package/lib/globals/aiModels/index.d.ts.map +1 -1
- package/lib/globals/aiModels/index.js +2 -0
- package/lib/globals/aiModels/providers/alibaba.d.ts.map +1 -1
- package/lib/globals/aiModels/providers/alibaba.js +460 -53
- package/lib/globals/aiModels/providers/bytedance.d.ts.map +1 -1
- package/lib/globals/aiModels/providers/bytedance.js +221 -39
- package/lib/globals/aiModels/providers/google.d.ts.map +1 -1
- package/lib/globals/aiModels/providers/google.js +178 -32
- package/lib/globals/aiModels/providers/kling.d.ts.map +1 -1
- package/lib/globals/aiModels/providers/kling.js +86 -12
- package/lib/globals/aiModels/providers/minimax.d.ts.map +1 -1
- package/lib/globals/aiModels/providers/minimax.js +77 -0
- package/lib/globals/aiModels/providers/openai.d.ts.map +1 -1
- package/lib/globals/aiModels/providers/openai.js +12 -0
- package/lib/globals/aiModels/providers/pixverse.d.ts.map +1 -1
- package/lib/globals/aiModels/providers/pixverse.js +101 -28
- package/lib/globals/aiModels/providers/runway.d.ts.map +1 -1
- package/lib/globals/aiModels/providers/runway.js +450 -24
- package/lib/globals/aiModels/providers/xai.d.ts +4 -0
- package/lib/globals/aiModels/providers/xai.d.ts.map +1 -0
- package/lib/globals/aiModels/providers/xai.js +72 -0
- package/lib/globals/aiModels/tierHelpers.d.ts +10 -0
- package/lib/globals/aiModels/tierHelpers.d.ts.map +1 -1
- package/lib/globals/aiModels/tierHelpers.js +48 -7
- package/lib/globals/aiModels/types.d.ts +21 -0
- package/lib/globals/aiModels/types.d.ts.map +1 -1
- package/lib/models/image.model.d.ts +24 -0
- package/lib/models/image.model.d.ts.map +1 -1
- package/lib/models/image.model.js +12 -0
- package/lib/models/index.d.ts +1 -0
- package/lib/models/index.d.ts.map +1 -1
- package/lib/models/index.js +1 -0
- package/lib/models/klingElement.model.d.ts +52 -0
- package/lib/models/klingElement.model.d.ts.map +1 -0
- package/lib/models/klingElement.model.js +24 -0
- package/lib/models/user.model.d.ts +7 -1
- package/lib/models/user.model.d.ts.map +1 -1
- package/lib/models/video.model.d.ts +11 -0
- package/lib/models/video.model.d.ts.map +1 -1
- package/lib/schemas/videoPlan.schema.d.ts +88 -0
- package/lib/schemas/videoPlan.schema.d.ts.map +1 -1
- package/lib/schemas/videoPlan.schema.js +27 -1
- package/lib/services/agent/executor/visual.d.ts.map +1 -1
- package/lib/services/agent/executor/visual.js +40 -2
- package/lib/services/agent/mentionedRefs.d.ts.map +1 -1
- package/lib/services/agent/mentionedRefs.js +4 -1
- package/lib/services/agent/planner/structuralRules.d.ts.map +1 -1
- package/lib/services/agent/planner/structuralRules.js +8 -3
- package/lib/services/agent/poster/posterAgent.schema.d.ts +3 -3
- package/lib/services/agent/providerFallback/chains.d.ts +25 -0
- package/lib/services/agent/providerFallback/chains.d.ts.map +1 -1
- package/lib/services/agent/providerFallback/chains.js +59 -5
- package/lib/services/agent/repair/strategies/llmRepair.strategy.d.ts.map +1 -1
- package/lib/services/agent/repair/strategies/llmRepair.strategy.js +2 -0
- package/lib/services/agent/tools/animateImage.tool.d.ts +31 -0
- package/lib/services/agent/tools/animateImage.tool.d.ts.map +1 -1
- package/lib/services/agent/tools/animateImage.tool.js +10 -3
- package/lib/services/agent/tools/generateImage.tool.d.ts +8 -4
- package/lib/services/agent/tools/generateImage.tool.d.ts.map +1 -1
- package/lib/services/agent/tools/generateImage.tool.js +24 -7
- package/lib/services/agent/tools/generateVideo.tool.d.ts +32 -0
- package/lib/services/agent/tools/generateVideo.tool.d.ts.map +1 -1
- package/lib/services/agent/tools/generateVideo.tool.js +37 -9
- package/lib/services/agent/tools/planVideo.tool.d.ts +19 -0
- package/lib/services/agent/tools/planVideo.tool.d.ts.map +1 -1
- package/lib/services/aiGen/aiGenFactory.service.d.ts.map +1 -1
- package/lib/services/aiGen/aiGenFactory.service.js +40 -0
- package/lib/services/aiGen/canonicalAdapters/cameraControl.types.d.ts +6 -3
- package/lib/services/aiGen/canonicalAdapters/cameraControl.types.d.ts.map +1 -1
- package/lib/services/aiGen/canonicalAdapters/cameraControl.types.js +35 -0
- package/lib/services/aiGen/canonicalAdapters/cameraDirection.d.ts +36 -0
- package/lib/services/aiGen/canonicalAdapters/cameraDirection.d.ts.map +1 -0
- package/lib/services/aiGen/canonicalAdapters/cameraDirection.js +168 -0
- package/lib/services/aiGen/canonicalAdapters/index.d.ts +1 -0
- package/lib/services/aiGen/canonicalAdapters/index.d.ts.map +1 -1
- package/lib/services/aiGen/canonicalAdapters/index.js +1 -0
- package/lib/services/aiGen/canonicalAdapters/multiShot.types.d.ts +3 -2
- package/lib/services/aiGen/canonicalAdapters/multiShot.types.d.ts.map +1 -1
- package/lib/services/aiGen/helpers.d.ts +40 -0
- package/lib/services/aiGen/helpers.d.ts.map +1 -1
- package/lib/services/aiGen/helpers.js +48 -2
- package/lib/services/aiGen/index.d.ts +1 -0
- package/lib/services/aiGen/index.d.ts.map +1 -1
- package/lib/services/aiGen/index.js +3 -0
- package/lib/services/aiGen/providers/alibaba/alibaba.d.ts +13 -1
- package/lib/services/aiGen/providers/alibaba/alibaba.d.ts.map +1 -1
- package/lib/services/aiGen/providers/alibaba/alibaba.js +313 -33
- package/lib/services/aiGen/providers/bytedance/bytedance.service.d.ts +29 -1
- package/lib/services/aiGen/providers/bytedance/bytedance.service.d.ts.map +1 -1
- package/lib/services/aiGen/providers/bytedance/bytedance.service.js +371 -54
- package/lib/services/aiGen/providers/google/google.service.d.ts +16 -0
- package/lib/services/aiGen/providers/google/google.service.d.ts.map +1 -1
- package/lib/services/aiGen/providers/google/google.service.js +372 -116
- package/lib/services/aiGen/providers/google/googleKeyPool.d.ts +6 -0
- package/lib/services/aiGen/providers/google/googleKeyPool.d.ts.map +1 -1
- package/lib/services/aiGen/providers/google/googleKeyPool.js +8 -0
- package/lib/services/aiGen/providers/kling/kling.service.d.ts +17 -1
- package/lib/services/aiGen/providers/kling/kling.service.d.ts.map +1 -1
- package/lib/services/aiGen/providers/kling/kling.service.js +59 -3
- package/lib/services/aiGen/providers/kling/klingCredits.d.ts +1 -1
- package/lib/services/aiGen/providers/kling/klingCredits.d.ts.map +1 -1
- package/lib/services/aiGen/providers/kling/klingCredits.js +19 -1
- package/lib/services/aiGen/providers/kling/klingRequests.d.ts +37 -7
- package/lib/services/aiGen/providers/kling/klingRequests.d.ts.map +1 -1
- package/lib/services/aiGen/providers/kling/klingRequests.js +191 -13
- package/lib/services/aiGen/providers/kling/types.d.ts +78 -0
- package/lib/services/aiGen/providers/kling/types.d.ts.map +1 -1
- package/lib/services/aiGen/providers/minimax/minimax.service.d.ts +11 -2
- package/lib/services/aiGen/providers/minimax/minimax.service.d.ts.map +1 -1
- package/lib/services/aiGen/providers/minimax/minimax.service.js +168 -2
- package/lib/services/aiGen/providers/minimax/types.d.ts +63 -0
- package/lib/services/aiGen/providers/minimax/types.d.ts.map +1 -1
- package/lib/services/aiGen/providers/pixverse/pixverse.service.d.ts +12 -0
- package/lib/services/aiGen/providers/pixverse/pixverse.service.d.ts.map +1 -1
- package/lib/services/aiGen/providers/pixverse/pixverse.service.js +131 -17
- package/lib/services/aiGen/providers/runway/hostedModels.d.ts +84 -0
- package/lib/services/aiGen/providers/runway/hostedModels.d.ts.map +1 -0
- package/lib/services/aiGen/providers/runway/hostedModels.js +266 -0
- package/lib/services/aiGen/providers/runway/index.d.ts +1 -0
- package/lib/services/aiGen/providers/runway/index.d.ts.map +1 -1
- package/lib/services/aiGen/providers/runway/index.js +1 -0
- package/lib/services/aiGen/providers/runway/runway.service.d.ts +1 -1
- package/lib/services/aiGen/providers/runway/runway.service.d.ts.map +1 -1
- package/lib/services/aiGen/providers/runway/runway.service.js +118 -21
- package/lib/services/aiGen/providers/runway/types.d.ts +18 -3
- package/lib/services/aiGen/providers/runway/types.d.ts.map +1 -1
- package/lib/services/aiGen/providers/runway/types.js +31 -3
- package/lib/services/aiGen/providers/types.d.ts +55 -0
- package/lib/services/aiGen/providers/types.d.ts.map +1 -1
- package/lib/services/aiGen/providers/xai/xai.service.d.ts +19 -0
- package/lib/services/aiGen/providers/xai/xai.service.d.ts.map +1 -0
- package/lib/services/aiGen/providers/xai/xai.service.js +205 -0
- package/lib/services/firestore.service.d.ts +4 -0
- package/lib/services/firestore.service.d.ts.map +1 -1
- package/lib/services/firestore.service.js +5 -0
- package/lib/services/index.d.ts +2 -0
- package/lib/services/index.d.ts.map +1 -1
- package/lib/services/index.js +2 -0
- package/lib/services/klingElements/klingElementLibrary.service.d.ts +37 -0
- package/lib/services/klingElements/klingElementLibrary.service.d.ts.map +1 -0
- package/lib/services/klingElements/klingElementLibrary.service.js +179 -0
- package/lib/services/trialGuard/index.d.ts +2 -0
- package/lib/services/trialGuard/index.d.ts.map +1 -0
- package/lib/services/trialGuard/index.js +17 -0
- package/lib/services/trialGuard/trialSignals.d.ts +88 -0
- package/lib/services/trialGuard/trialSignals.d.ts.map +1 -0
- package/lib/services/trialGuard/trialSignals.js +160 -0
- package/lib/utils/helpers.d.ts.map +1 -1
- package/lib/utils/helpers.js +5 -1
- package/package.json +2 -1
|
@@ -4,6 +4,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
exports.ByteDanceService = void 0;
|
|
7
|
+
exports.seedreamSize = seedreamSize;
|
|
7
8
|
// bytedance.service.ts
|
|
8
9
|
const baseAiGenProvider_service_1 = require("../baseAiGenProvider.service");
|
|
9
10
|
const helpers_1 = require("../../helpers");
|
|
@@ -11,26 +12,57 @@ const aiModels_1 = require("../../../../globals/aiModels");
|
|
|
11
12
|
const types_1 = require("../../../../globals/types");
|
|
12
13
|
const firebase_1 = require("../../../../libs/firebase");
|
|
13
14
|
const storage_1 = require("../../../../libs/storage");
|
|
15
|
+
const image_model_1 = require("../../../../models/image.model");
|
|
14
16
|
const helpers_2 = require("../../../../utils/helpers");
|
|
15
17
|
const errors_1 = require("../../../../utils/errors");
|
|
16
18
|
const logger_1 = require("../../../../utils/logger");
|
|
17
19
|
const crypto_1 = __importDefault(require("crypto"));
|
|
20
|
+
const SEEDREAM_URL = "https://ark.ap-southeast.bytepluses.com/api/v3/images/generations";
|
|
21
|
+
/** Long-edge-equivalent of each `size` tier (tier² = target pixel count). */
|
|
22
|
+
const SEEDREAM_TIER_EDGE = {
|
|
23
|
+
"1K": 1024,
|
|
24
|
+
"1.5K": 1536,
|
|
25
|
+
"2K": 2048,
|
|
26
|
+
"3K": 3072,
|
|
27
|
+
"4K": 4096,
|
|
28
|
+
};
|
|
29
|
+
/**
|
|
30
|
+
* Explicit "WxH" for a ratio at a size tier: tier² pixels in the requested
|
|
31
|
+
* shape, floored to multiples of 16. Every tier's pixel count sits inside the
|
|
32
|
+
* range the models that offer it accept (e.g. 5.0 lite: [2560×1440, 4096²]),
|
|
33
|
+
* and flooring never drops below it, so any ratio in [1/16, 16] is valid —
|
|
34
|
+
* including ones a model's aspect list doesn't name (the agent's 4:5).
|
|
35
|
+
*/
|
|
36
|
+
function seedreamSize(aspectRatio, tier) {
|
|
37
|
+
const edge = SEEDREAM_TIER_EDGE[tier];
|
|
38
|
+
const [w, h] = aspectRatio.split(":").map(Number);
|
|
39
|
+
if (!edge || !w || !h || w / h > 16 || h / w > 16)
|
|
40
|
+
return undefined;
|
|
41
|
+
const pixels = edge * edge;
|
|
42
|
+
const width = Math.floor(Math.sqrt((pixels * w) / h) / 16) * 16;
|
|
43
|
+
const height = Math.floor(Math.sqrt((pixels * h) / w) / 16) * 16;
|
|
44
|
+
return `${width}x${height}`;
|
|
45
|
+
}
|
|
18
46
|
class ByteDanceService extends baseAiGenProvider_service_1.BaseAiGenProviderService {
|
|
19
47
|
constructor() {
|
|
20
48
|
super(...arguments);
|
|
21
49
|
this.baseUrl = "https://ark.ap-southeast.bytepluses.com/api/v3/contents/generations/tasks";
|
|
22
50
|
}
|
|
23
|
-
//
|
|
24
|
-
//
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
51
|
+
// Seedance 1.x only accepts ASCII printable characters and CJK (Chinese)
|
|
52
|
+
// characters: strip everything else (emojis, en-dashes, special Unicode,
|
|
53
|
+
// etc.) and collapse whitespace. The 2.x series takes prompts in 11 (2.5) /
|
|
54
|
+
// 7 (2.0) languages, including Arabic and Thai, so there we strip only emoji
|
|
55
|
+
// and control characters. Unsupported languages are translated upstream
|
|
56
|
+
// (supportedPromptLanguages → generate.controller).
|
|
57
|
+
sanitizePrompt(text, isV2) {
|
|
58
|
+
const stripped = isV2
|
|
59
|
+
? text.replace(/[\p{Extended_Pictographic}\p{Cc}\u{FE0F}\u{200D}]/gu, " ")
|
|
60
|
+
: text.replace(/[^\x20-\x7E一-鿿㐀-䶿]/g, " ");
|
|
61
|
+
return stripped.replace(/\s+/g, " ").trim();
|
|
30
62
|
}
|
|
31
63
|
// Seedance 2.x (Dreamina — 2.0, 2.0-fast, 2.5) use a new multimodal content
|
|
32
64
|
// schema with `role` tags on each non-text item, a `safety_identifier`, and
|
|
33
|
-
// no `service_tier`.
|
|
65
|
+
// no `service_tier`. 1.x takes `role` only for first+last-frame requests.
|
|
34
66
|
// Matches any "bytedance-seedance-2." key so 2.5 (and future 2.x variants)
|
|
35
67
|
// inherit the same request shape and video-input billing as 2.0.
|
|
36
68
|
isSeedance2(modelKey) {
|
|
@@ -40,57 +72,91 @@ class ByteDanceService extends baseAiGenProvider_service_1.BaseAiGenProviderServ
|
|
|
40
72
|
(0, helpers_1.validateParams)(params);
|
|
41
73
|
const modelConfig = aiModels_1.aiModelConfigs[params.modelKey];
|
|
42
74
|
const modelId = modelConfig.modelId;
|
|
43
|
-
const
|
|
75
|
+
const isV2 = this.isSeedance2(params.modelKey);
|
|
76
|
+
const sanitizedPrompt = this.sanitizePrompt(params.prompt ?? "", isV2);
|
|
44
77
|
if (!sanitizedPrompt) {
|
|
45
|
-
// PROMPT_INVALID (not an uncoded throw):
|
|
46
|
-
//
|
|
47
|
-
//
|
|
48
|
-
//
|
|
49
|
-
//
|
|
50
|
-
//
|
|
51
|
-
//
|
|
52
|
-
//
|
|
78
|
+
// PROMPT_INVALID (not an uncoded throw): an empty result means the prompt
|
|
79
|
+
// was written in a script this Seedance model cannot accept (1.x: anything
|
|
80
|
+
// outside ASCII+CJK; any model: an all-emoji prompt). That is
|
|
81
|
+
// deterministic user input — it can never succeed on retry. Without a
|
|
82
|
+
// code, `isNonPagingCode(undefined)` is false and the scene-failure
|
|
83
|
+
// handler logged it at `error`, which pages Slack (see errorPaging.ts).
|
|
84
|
+
// PROMPT_INVALID is in NON_PAGING_ERROR_CODES, so this stays a warn
|
|
85
|
+
// while still failing the scene cleanly.
|
|
53
86
|
throw new errors_1.UserFacingError("Your prompt could not be processed. Please use English or Chinese text and try again.", errors_1.USER_FACING_ERROR_CODES.PROMPT_INVALID);
|
|
54
87
|
}
|
|
55
|
-
const
|
|
88
|
+
const fields = modelConfig.fields;
|
|
56
89
|
const content = [
|
|
57
90
|
{
|
|
58
91
|
type: "text",
|
|
59
92
|
text: sanitizedPrompt,
|
|
60
93
|
},
|
|
61
94
|
];
|
|
62
|
-
//
|
|
63
|
-
//
|
|
64
|
-
//
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
95
|
+
// Omni reference-to-video (2.x only): reference images / videos / audio,
|
|
96
|
+
// each tagged with its role. Per docs.byteplus.com/en/docs/ModelArk/1520757
|
|
97
|
+
// first-frame, first+last-frame and omni-reference are mutually exclusive
|
|
98
|
+
// scenarios. `inputVideoUrl` / `inputAudioUrl` are the single-asset form
|
|
99
|
+
// the video routes send; the `reference*Urls` lists come from the agent.
|
|
100
|
+
const cap = (field, urls) => {
|
|
101
|
+
const max = fields[field]?.max;
|
|
102
|
+
return typeof max === "number" ? urls.slice(0, max) : urls;
|
|
103
|
+
};
|
|
104
|
+
const refVideos = isV2
|
|
105
|
+
? cap("referenceVideoUrls", [
|
|
106
|
+
...(params.inputVideoUrl ? [params.inputVideoUrl] : []),
|
|
107
|
+
...(params.referenceVideoUrls ?? []),
|
|
108
|
+
])
|
|
109
|
+
: [];
|
|
110
|
+
const refAudios = isV2
|
|
111
|
+
? cap("referenceAudioUrls", [
|
|
112
|
+
...(params.inputAudioUrl ? [params.inputAudioUrl] : []),
|
|
113
|
+
...(params.referenceAudioUrls ?? []),
|
|
114
|
+
])
|
|
115
|
+
: [];
|
|
116
|
+
const refImagesIn = isV2 ? (params.referenceImageUrls ?? []) : [];
|
|
117
|
+
const isOmni = refVideos.length + refAudios.length + refImagesIn.length > 0;
|
|
118
|
+
if (isOmni) {
|
|
119
|
+
// An input image alongside references can't be a first frame — it joins
|
|
120
|
+
// the reference images (the prompt can still say "@Image1 as the first
|
|
121
|
+
// frame", which is the documented workaround).
|
|
122
|
+
const refImages = cap("referenceImageUrls", [
|
|
123
|
+
...(params.inputImageUrl ? [params.inputImageUrl] : []),
|
|
124
|
+
...refImagesIn,
|
|
125
|
+
]);
|
|
126
|
+
if (params.lastFrameImageUrl) {
|
|
127
|
+
logger_1.logger.warn("Seedance: last frame dropped — not combinable with omni references", {
|
|
128
|
+
modelKey: params.modelKey,
|
|
84
129
|
});
|
|
85
130
|
}
|
|
86
|
-
|
|
87
|
-
content.push({
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
131
|
+
for (const url of refImages) {
|
|
132
|
+
content.push({ type: "image_url", image_url: { url }, role: "reference_image" });
|
|
133
|
+
}
|
|
134
|
+
for (const url of refVideos) {
|
|
135
|
+
content.push({ type: "video_url", video_url: { url }, role: "reference_video" });
|
|
136
|
+
}
|
|
137
|
+
for (const url of refAudios) {
|
|
138
|
+
content.push({ type: "audio_url", audio_url: { url }, role: "reference_audio" });
|
|
92
139
|
}
|
|
93
140
|
}
|
|
141
|
+
else if (params.inputImageUrl) {
|
|
142
|
+
// First frame, optionally with a last frame (2.x, 1.5 pro, 1.0 pro —
|
|
143
|
+
// the configs that declare lastFrameImageUrl; validateParams rejects it
|
|
144
|
+
// elsewhere). With two images the role is required on every model; with
|
|
145
|
+
// one it is optional, and we keep the 1.x request role-free as before.
|
|
146
|
+
const lastFrame = params.lastFrameImageUrl && "lastFrameImageUrl" in fields
|
|
147
|
+
? params.lastFrameImageUrl
|
|
148
|
+
: undefined;
|
|
149
|
+
const tagRoles = isV2 || !!lastFrame;
|
|
150
|
+
content.push({
|
|
151
|
+
type: "image_url",
|
|
152
|
+
image_url: { url: params.inputImageUrl },
|
|
153
|
+
...(tagRoles ? { role: "first_frame" } : {}),
|
|
154
|
+
});
|
|
155
|
+
if (lastFrame) {
|
|
156
|
+
content.push({ type: "image_url", image_url: { url: lastFrame }, role: "last_frame" });
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
const isFrameMode = !isOmni && !!params.inputImageUrl;
|
|
94
160
|
const body = {
|
|
95
161
|
model: modelId,
|
|
96
162
|
content,
|
|
@@ -100,22 +166,31 @@ class ByteDanceService extends baseAiGenProvider_service_1.BaseAiGenProviderServ
|
|
|
100
166
|
watermark: false,
|
|
101
167
|
execution_expires_after: 172800, // 48h
|
|
102
168
|
};
|
|
103
|
-
// For first-frame / first-last-frame generation
|
|
104
|
-
//
|
|
105
|
-
//
|
|
106
|
-
//
|
|
107
|
-
if (!
|
|
169
|
+
// For first-frame / first-last-frame generation, ByteDance derives the
|
|
170
|
+
// output ratio from the image itself and rejects an explicit `ratio`
|
|
171
|
+
// (InvalidParameter.TaskTypeConstraint, prod 2026-08-11). Text-to-video
|
|
172
|
+
// and omni reference-to-video take the requested aspect ratio.
|
|
173
|
+
if (!isFrameMode) {
|
|
108
174
|
body.ratio = params.aspectRatio; // e.g. 16:9
|
|
109
175
|
}
|
|
110
|
-
if (!
|
|
176
|
+
if (!isV2) {
|
|
111
177
|
body.service_tier = "default";
|
|
112
178
|
}
|
|
179
|
+
// `seed` and `camera_fixed` are 1.x-only per the docs; the configs that
|
|
180
|
+
// accept them declare the field. camera_fixed is unsupported with
|
|
181
|
+
// reference images (1.x has none, but keep the guard with the rule).
|
|
182
|
+
if (typeof params.seed === "number" && "seed" in fields) {
|
|
183
|
+
body.seed = params.seed;
|
|
184
|
+
}
|
|
185
|
+
if (params.cameraFixed && "cameraFixed" in fields && !isOmni) {
|
|
186
|
+
body.camera_fixed = true;
|
|
187
|
+
}
|
|
113
188
|
// Byteplus KYC commitment K2 (deadline 2026-07-01): every Seedance 2.0
|
|
114
189
|
// request must carry a stable per-user safety_identifier so Byteplus can
|
|
115
190
|
// attribute downstream policy violations to a single end-user. We hash the
|
|
116
191
|
// Firebase userId (sha256, 16 hex chars) — stable per user, but never
|
|
117
192
|
// exposes the raw UID to the provider.
|
|
118
|
-
if (
|
|
193
|
+
if (isV2 && params.userId) {
|
|
119
194
|
body.safety_identifier = crypto_1.default
|
|
120
195
|
.createHash("sha256")
|
|
121
196
|
.update(params.userId)
|
|
@@ -294,6 +369,231 @@ class ByteDanceService extends baseAiGenProvider_service_1.BaseAiGenProviderServ
|
|
|
294
369
|
status: types_1.EVideoSceneStatus.COMPLETED,
|
|
295
370
|
};
|
|
296
371
|
}
|
|
372
|
+
/**
|
|
373
|
+
* Seedream text-to-image / image-to-image. Three modes, each gated on the
|
|
374
|
+
* model declaring the field:
|
|
375
|
+
* - default: one request per output image, run in parallel, so numImages
|
|
376
|
+
* is exact.
|
|
377
|
+
* - `imageSet` (5.0 lite / 4.x): one `sequential_image_generation: "auto"`
|
|
378
|
+
* request returning a set of related images, at most numImages (the model
|
|
379
|
+
* picks the count; the worker refunds the unused share).
|
|
380
|
+
* - `layerDecomposition` (5.0 pro / flash): one input image → base image +
|
|
381
|
+
* up to 16 transparent PNG layers, returned with their stacking order and
|
|
382
|
+
* bounding boxes.
|
|
383
|
+
*/
|
|
384
|
+
async generateImage(params) {
|
|
385
|
+
const modelConfig = aiModels_1.aiModelConfigs[params.modelKey];
|
|
386
|
+
if (!modelConfig?.modelId)
|
|
387
|
+
throw new Error(`Unknown image modelKey: ${params.modelKey}`);
|
|
388
|
+
const fields = modelConfig.fields;
|
|
389
|
+
const refs = [
|
|
390
|
+
...(params.inputImageUrl ? [params.inputImageUrl] : []),
|
|
391
|
+
...(params.inputImageUrls ?? []),
|
|
392
|
+
];
|
|
393
|
+
const maxRefs = fields.inputImageUrls?.max ?? 0;
|
|
394
|
+
if (refs.length > maxRefs) {
|
|
395
|
+
logger_1.logger.warn("seedream: reference images capped", { modelKey: params.modelKey, given: refs.length, max: maxRefs });
|
|
396
|
+
}
|
|
397
|
+
const images = refs.slice(0, maxRefs);
|
|
398
|
+
const body = {
|
|
399
|
+
model: modelConfig.modelId,
|
|
400
|
+
prompt: params.prompt,
|
|
401
|
+
response_format: "url",
|
|
402
|
+
watermark: params.watermark ?? false,
|
|
403
|
+
};
|
|
404
|
+
const promptModes = fields.promptMode?.allowedValues;
|
|
405
|
+
if (params.promptMode && promptModes?.includes(params.promptMode)) {
|
|
406
|
+
body.optimize_prompt_options = { mode: params.promptMode };
|
|
407
|
+
}
|
|
408
|
+
const tiers = (fields.imageSize?.allowedValues ?? []);
|
|
409
|
+
const tier = params.imageSize && tiers.includes(params.imageSize)
|
|
410
|
+
? params.imageSize
|
|
411
|
+
: fields.imageSize?.default;
|
|
412
|
+
if (params.layerDecomposition) {
|
|
413
|
+
if (!fields.layerDecomposition) {
|
|
414
|
+
throw new errors_1.UserFacingError("This model can't split an image into layers. Use Seedream 5.0 Pro or Flash.", errors_1.USER_FACING_ERROR_CODES.CAPABILITY_MISMATCH);
|
|
415
|
+
}
|
|
416
|
+
if (images.length !== 1) {
|
|
417
|
+
throw new errors_1.UserFacingError("Layer decomposition needs exactly one input image.", errors_1.USER_FACING_ERROR_CODES.CAPABILITY_MISMATCH);
|
|
418
|
+
}
|
|
419
|
+
// Tiers only (no WxH); without one, `auto` keeps the input's size.
|
|
420
|
+
body.layer_decomposition = true;
|
|
421
|
+
body.image = images[0];
|
|
422
|
+
body.size = params.imageSize && tiers.includes(params.imageSize) ? params.imageSize : "auto";
|
|
423
|
+
// output_format applies to the base image; layers are always png.
|
|
424
|
+
const formats = fields.outputFormat?.allowedValues;
|
|
425
|
+
if (params.outputFormat && formats?.includes(params.outputFormat))
|
|
426
|
+
body.output_format = params.outputFormat;
|
|
427
|
+
const { items, billed } = await this.requestSeedream(body, true);
|
|
428
|
+
const result = await this.storeSeedreamLayers(items.slice(0, image_model_1.SEEDREAM_MAX_LAYER_OUTPUTS));
|
|
429
|
+
// Pricing says "billed per successfully generated image" and the API
|
|
430
|
+
// docs' decomposition example reports usage.generated_images = base +
|
|
431
|
+
// layers. Settle on the provider's count either way, and log it so the
|
|
432
|
+
// first invoice can be checked against it.
|
|
433
|
+
logger_1.logger.info("seedream: layer decomposition usage", {
|
|
434
|
+
modelKey: params.modelKey,
|
|
435
|
+
outputs: items.length,
|
|
436
|
+
billed,
|
|
437
|
+
});
|
|
438
|
+
return billed !== undefined ? { ...result, billedOutputs: billed } : result;
|
|
439
|
+
}
|
|
440
|
+
if (params.dimensions && /^\d+x\d+$/.test(params.dimensions)) {
|
|
441
|
+
body.size = params.dimensions;
|
|
442
|
+
}
|
|
443
|
+
else if (tier) {
|
|
444
|
+
// No ratio (e.g. a plain edit) → the bare tier, which keeps the input's shape.
|
|
445
|
+
body.size = (params.aspectRatio && seedreamSize(params.aspectRatio, tier)) ?? tier;
|
|
446
|
+
}
|
|
447
|
+
if (images.length === 1)
|
|
448
|
+
body.image = images[0];
|
|
449
|
+
else if (images.length > 1)
|
|
450
|
+
body.image = images;
|
|
451
|
+
const formats = fields.outputFormat?.allowedValues;
|
|
452
|
+
const backgrounds = fields.background?.allowedValues;
|
|
453
|
+
// `background: transparent` works only as an edit of exactly one image
|
|
454
|
+
// that has alpha, and forces png.
|
|
455
|
+
const transparent = params.background === "transparent" && !!backgrounds?.includes("transparent") && images.length === 1;
|
|
456
|
+
if (transparent) {
|
|
457
|
+
body.background = "transparent";
|
|
458
|
+
body.output_format = "png";
|
|
459
|
+
}
|
|
460
|
+
else if (params.outputFormat && formats?.includes(params.outputFormat)) {
|
|
461
|
+
body.output_format = params.outputFormat;
|
|
462
|
+
}
|
|
463
|
+
const count = Math.max(1, Math.min(params.numImages ?? 1, 8));
|
|
464
|
+
let sources;
|
|
465
|
+
let billed;
|
|
466
|
+
if (params.imageSet && fields.imageSet && count > 1) {
|
|
467
|
+
// Inputs + outputs ≤ 15 per request.
|
|
468
|
+
body.sequential_image_generation = "auto";
|
|
469
|
+
body.sequential_image_generation_options = { max_images: Math.max(1, Math.min(count, 15 - images.length)) };
|
|
470
|
+
({ items: sources, billed } = await this.requestSeedream(body, images.length > 0));
|
|
471
|
+
}
|
|
472
|
+
else {
|
|
473
|
+
const results = await Promise.all(Array.from({ length: count }, () => this.requestSeedream(body, images.length > 0)));
|
|
474
|
+
sources = results.flatMap((r) => r.items);
|
|
475
|
+
billed = results.every((r) => r.billed !== undefined)
|
|
476
|
+
? results.reduce((sum, r) => sum + (r.billed ?? 0), 0)
|
|
477
|
+
: undefined;
|
|
478
|
+
}
|
|
479
|
+
const ts = Date.now();
|
|
480
|
+
const urls = [];
|
|
481
|
+
for (let i = 0; i < sources.length; i++) {
|
|
482
|
+
const item = sources[i];
|
|
483
|
+
let bytes;
|
|
484
|
+
let mime = item.output_format === "png" ? "image/png" : "image/jpeg";
|
|
485
|
+
if (item.b64_json)
|
|
486
|
+
bytes = Buffer.from(item.b64_json, "base64");
|
|
487
|
+
else if (item.url) {
|
|
488
|
+
// BytePlus URLs expire after 24h — copy the bytes now.
|
|
489
|
+
const r = await fetch(item.url);
|
|
490
|
+
if (!r.ok)
|
|
491
|
+
throw new Error(`seedream image download ${r.status}`);
|
|
492
|
+
bytes = Buffer.from(await r.arrayBuffer());
|
|
493
|
+
mime = r.headers.get("content-type")?.split(";")[0] || mime;
|
|
494
|
+
}
|
|
495
|
+
else
|
|
496
|
+
continue;
|
|
497
|
+
const ext = mime === "image/png" ? "png" : mime === "image/webp" ? "webp" : "jpg";
|
|
498
|
+
const path = `images/bytedance/${ts}-${crypto_1.default.randomBytes(3).toString("hex")}-${i}.${ext}`;
|
|
499
|
+
urls.push(await storage_1.mediaStorage.put(path, bytes, mime));
|
|
500
|
+
}
|
|
501
|
+
if (!urls.length)
|
|
502
|
+
throw new Error("seedream returned no images");
|
|
503
|
+
const [width, height] = (sources[0]?.size ?? "").split("x").map(Number);
|
|
504
|
+
return {
|
|
505
|
+
imageUrl: urls[0],
|
|
506
|
+
imageUrls: urls.length > 1 ? urls : undefined,
|
|
507
|
+
...(width && height ? { width, height } : {}),
|
|
508
|
+
...(billed !== undefined ? { billedOutputs: billed } : {}),
|
|
509
|
+
};
|
|
510
|
+
}
|
|
511
|
+
/** Copy a layer decomposition's outputs into our storage, base image first. */
|
|
512
|
+
async storeSeedreamLayers(data) {
|
|
513
|
+
const ts = Date.now();
|
|
514
|
+
const sorted = [...data].sort((a, b) => (a.z_index ?? 0) - (b.z_index ?? 0));
|
|
515
|
+
const layers = [];
|
|
516
|
+
for (let i = 0; i < sorted.length; i++) {
|
|
517
|
+
const item = sorted[i];
|
|
518
|
+
let bytes;
|
|
519
|
+
let mime = item.output_format === "jpeg" ? "image/jpeg" : "image/png";
|
|
520
|
+
if (item.b64_json)
|
|
521
|
+
bytes = Buffer.from(item.b64_json, "base64");
|
|
522
|
+
else if (item.url) {
|
|
523
|
+
const r = await fetch(item.url);
|
|
524
|
+
if (!r.ok)
|
|
525
|
+
throw new Error(`seedream layer download ${r.status}`);
|
|
526
|
+
bytes = Buffer.from(await r.arrayBuffer());
|
|
527
|
+
mime = r.headers.get("content-type")?.split(";")[0] || mime;
|
|
528
|
+
}
|
|
529
|
+
else
|
|
530
|
+
continue;
|
|
531
|
+
const ext = mime === "image/jpeg" ? "jpg" : mime === "image/webp" ? "webp" : "png";
|
|
532
|
+
const path = `images/bytedance/${ts}-${crypto_1.default.randomBytes(3).toString("hex")}-layer${i}.${ext}`;
|
|
533
|
+
const bbox = item.bounding_box?.normalized;
|
|
534
|
+
layers.push({
|
|
535
|
+
url: await storage_1.mediaStorage.put(path, bytes, mime),
|
|
536
|
+
zIndex: item.z_index ?? i,
|
|
537
|
+
...(item.name ? { name: item.name } : {}),
|
|
538
|
+
...(item.description ? { description: item.description } : {}),
|
|
539
|
+
...(bbox?.length === 4 ? { bbox: [bbox[0], bbox[1], bbox[2], bbox[3]] } : {}),
|
|
540
|
+
});
|
|
541
|
+
}
|
|
542
|
+
if (!layers.length)
|
|
543
|
+
throw new Error("seedream layer decomposition returned no images");
|
|
544
|
+
const [width, height] = (sorted[0]?.size ?? "").split("x").map(Number);
|
|
545
|
+
return {
|
|
546
|
+
imageUrl: layers[0].url,
|
|
547
|
+
imageUrls: layers.map((l) => l.url),
|
|
548
|
+
layers,
|
|
549
|
+
...(width && height ? { width, height } : {}),
|
|
550
|
+
};
|
|
551
|
+
}
|
|
552
|
+
/**
|
|
553
|
+
* One /images/generations call → its successful items (failed ones aren't
|
|
554
|
+
* billed) plus `usage.generated_images`, the count BytePlus bills.
|
|
555
|
+
*/
|
|
556
|
+
async requestSeedream(body, isEdit) {
|
|
557
|
+
const resp = await fetch(SEEDREAM_URL, {
|
|
558
|
+
method: "POST",
|
|
559
|
+
headers: {
|
|
560
|
+
"Content-Type": "application/json",
|
|
561
|
+
Authorization: `Bearer ${process.env.BYTEDANCE_API_KEY}`,
|
|
562
|
+
},
|
|
563
|
+
body: JSON.stringify(body),
|
|
564
|
+
});
|
|
565
|
+
const text = await resp.text();
|
|
566
|
+
let json = {};
|
|
567
|
+
try {
|
|
568
|
+
json = JSON.parse(text);
|
|
569
|
+
}
|
|
570
|
+
catch {
|
|
571
|
+
/* non-JSON body — handled below */
|
|
572
|
+
}
|
|
573
|
+
const data = json.data ?? [];
|
|
574
|
+
const ok = data.filter((d) => d.url || d.b64_json);
|
|
575
|
+
if (resp.ok && ok.length) {
|
|
576
|
+
const billed = json.usage?.generated_images;
|
|
577
|
+
return { items: ok, billed: typeof billed === "number" ? billed : undefined };
|
|
578
|
+
}
|
|
579
|
+
const err = json.error ?? data.find((d) => d.error)?.error;
|
|
580
|
+
const code = err?.code ?? "";
|
|
581
|
+
const message = err?.message ?? text;
|
|
582
|
+
if (/SensitiveContentDetected/.test(code)) {
|
|
583
|
+
throw new errors_1.UserFacingError(code.startsWith("InputImage")
|
|
584
|
+
? "Your input image contains sensitive content and cannot be processed. Please use a different image."
|
|
585
|
+
: "Your prompt or the generated image violates the content policy. Please modify your prompt and try again.", code.startsWith("InputImage")
|
|
586
|
+
? errors_1.USER_FACING_ERROR_CODES.INPUT_IMAGE_REJECTED
|
|
587
|
+
: errors_1.USER_FACING_ERROR_CODES.CONTENT_POLICY_VIOLATION);
|
|
588
|
+
}
|
|
589
|
+
if (resp.status === 400 && isEdit && /image/i.test(`${code} ${json.error?.param ?? ""} ${message}`)) {
|
|
590
|
+
throw new errors_1.UserFacingError(message, errors_1.USER_FACING_ERROR_CODES.INPUT_IMAGE_REJECTED);
|
|
591
|
+
}
|
|
592
|
+
if (resp.status === 400) {
|
|
593
|
+
throw new errors_1.UserFacingError(message, errors_1.USER_FACING_ERROR_CODES.CAPABILITY_MISMATCH);
|
|
594
|
+
}
|
|
595
|
+
throw new Error(`seedream images failed (${resp.status}): ${code} ${message}`);
|
|
596
|
+
}
|
|
297
597
|
getCreditUsed(params) {
|
|
298
598
|
const modelConfig = aiModels_1.aiModelConfigs[params.modelKey];
|
|
299
599
|
// Image-gen — flat per-image cost (Seedream). Must be handled before the
|
|
@@ -302,7 +602,24 @@ class ByteDanceService extends baseAiGenProvider_service_1.BaseAiGenProviderServ
|
|
|
302
602
|
// image credit floor passed by the image controller.
|
|
303
603
|
if (modelConfig?.cost?.fixed !== undefined &&
|
|
304
604
|
modelConfig.type?.some((t) => t.endsWith("-to-image"))) {
|
|
305
|
-
|
|
605
|
+
const cost = modelConfig.cost;
|
|
606
|
+
const tier = params.imageSize ?? modelConfig.fields.imageSize?.default;
|
|
607
|
+
const perImage = (tier ? cost.perImageSize?.[tier] : undefined) ?? cost.fixed;
|
|
608
|
+
// Layer decomposition bills each output (base + layers) and the count is
|
|
609
|
+
// the model's choice, so estimate the maximum; the image worker refunds
|
|
610
|
+
// the unused share, settling on the provider's usage.generated_images
|
|
611
|
+
// (see generateImage). API docs' decomposition example bills base + layers.
|
|
612
|
+
const images = params.layerDecomposition
|
|
613
|
+
? image_model_1.SEEDREAM_MAX_LAYER_OUTPUTS
|
|
614
|
+
: Math.max(1, Math.min(params.numImages ?? 1, 8));
|
|
615
|
+
// 5.0 pro bills reference images after the first. Normal mode sends one
|
|
616
|
+
// request per output, so refs are billed once per image; an image set or
|
|
617
|
+
// a decomposition is a single request.
|
|
618
|
+
const extraRefs = Math.max(0, (params.numInputImages ?? 0) - 1);
|
|
619
|
+
const refCost = (cost.perExtraInputImage ?? 0) * extraRefs;
|
|
620
|
+
const singleRequest = params.layerDecomposition || params.imageSet;
|
|
621
|
+
const total = singleRequest ? perImage * images + refCost : (perImage + refCost) * images;
|
|
622
|
+
return (0, helpers_2.getCreditsFromCost)(total, !params.multiClip, params.creditFloor);
|
|
306
623
|
}
|
|
307
624
|
// Seedance 2.0 prices on (resolution × whether the request includes a
|
|
308
625
|
// video input), not on (audio toggle). Pick the matching per-1K rate.
|
|
@@ -30,7 +30,23 @@ export declare class GoogleService extends BaseAiGenProviderService {
|
|
|
30
30
|
*/
|
|
31
31
|
private withTransientRetry;
|
|
32
32
|
generateVideo(params: VideoGenerationParams): Promise<VideoGenerationResult>;
|
|
33
|
+
/**
|
|
34
|
+
* Gemini Omni submit (Interactions API, `background: true`). Input mapping per
|
|
35
|
+
* the Omni guide: one image = image-to-video, two images = first/last-frame
|
|
36
|
+
* interpolation, several images = subject references. Omni has no
|
|
37
|
+
* negativePrompt / seed / duration-seconds params, so a negative prompt and
|
|
38
|
+
* the requested length are folded into the prompt text (the guide's own
|
|
39
|
+
* advice for negatives: "Do not do X").
|
|
40
|
+
*/
|
|
41
|
+
private generateOmniVideo;
|
|
42
|
+
private checkOmniStatus;
|
|
33
43
|
checkVideoStatus({ task, outputFilename, outputFilePath, }: VideoStatusParams): Promise<VideoStatusResult>;
|
|
44
|
+
/**
|
|
45
|
+
* Download a Files-API video (Veo output, or Gemini Omni `delivery:"uri"`
|
|
46
|
+
* output) with the key that produced it and persist it GCS-primary with the
|
|
47
|
+
* truncation guards below. Returns the scene's terminal status.
|
|
48
|
+
*/
|
|
49
|
+
private persistFilesApiVideo;
|
|
34
50
|
generateImage(params: ImageGenerationParams): Promise<ImageGenerationResult>;
|
|
35
51
|
private _generateImage;
|
|
36
52
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"google.service.d.ts","sourceRoot":"","sources":["../../../../../src/services/aiGen/providers/google/google.service.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"google.service.d.ts","sourceRoot":"","sources":["../../../../../src/services/aiGen/providers/google/google.service.ts"],"names":[],"mappings":"AAqBA,OAAO,EAAE,wBAAwB,EAAE,MAAM,8BAA8B,CAAC;AACxE,OAAO,EACL,iBAAiB,EACjB,qBAAqB,EACrB,qBAAqB,EACrB,qBAAqB,EACrB,qBAAqB,EACrB,oBAAoB,EACpB,oBAAoB,EACpB,qBAAqB,EACrB,qBAAqB,EACrB,iBAAiB,EACjB,iBAAiB,EAClB,MAAM,UAAU,CAAC;AAuIlB,qBAAa,aAAc,SAAQ,wBAAwB;IAKzD,OAAO,CAAC,EAAE,CAAc;IACxB,OAAO,CAAC,OAAO,CAAgB;IAC/B,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,kBAAkB,CAAK;;IAQ/C;;;;;;;;;OASG;IACG,YAAY,CAChB,MAAM,EAAE,oBAAoB,GAC3B,OAAO,CAAC,oBAAoB,CAAC;IAoChC;;;;;OAKG;IACH,OAAO,CAAC,eAAe;IAoCvB;;;;OAIG;YACW,kBAAkB;IAsC1B,aAAa,CACjB,MAAM,EAAE,qBAAqB,GAC5B,OAAO,CAAC,qBAAqB,CAAC;IAiUjC;;;;;;;OAOG;YACW,iBAAiB;YAoGjB,eAAe;IA4EvB,gBAAgB,CAAC,EACrB,IAAI,EACJ,cAAc,EACd,cAAyB,GAC1B,EAAE,iBAAiB,GAAG,OAAO,CAAC,iBAAiB,CAAC;IAyEjD;;;;OAIG;YACW,oBAAoB;IAkH5B,aAAa,CACjB,MAAM,EAAE,qBAAqB,GAC5B,OAAO,CAAC,qBAAqB,CAAC;YAiBnB,cAAc;IAkP5B;;;;;;OAMG;IACH;;;;OAIG;IACG,aAAa,CACjB,MAAM,EAAE,qBAAqB,GAC5B,OAAO,CAAC,qBAAqB,CAAC;IAIjC,aAAa,CAAC,EAAE,QAAQ,EAAE,QAAY,EAAE,UAAmB,EAAE,SAAiB,EAAE,SAAa,EAAE,SAAS,EAAE,WAAW,EAAE,EAAE,iBAAiB,GAAG,MAAM;CAoCpJ"}
|