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.
Files changed (153) hide show
  1. package/lib/globals/aiModels/enums.d.ts +38 -0
  2. package/lib/globals/aiModels/enums.d.ts.map +1 -1
  3. package/lib/globals/aiModels/enums.js +49 -1
  4. package/lib/globals/aiModels/index.d.ts.map +1 -1
  5. package/lib/globals/aiModels/index.js +2 -0
  6. package/lib/globals/aiModels/providers/alibaba.d.ts.map +1 -1
  7. package/lib/globals/aiModels/providers/alibaba.js +460 -53
  8. package/lib/globals/aiModels/providers/bytedance.d.ts.map +1 -1
  9. package/lib/globals/aiModels/providers/bytedance.js +221 -39
  10. package/lib/globals/aiModels/providers/google.d.ts.map +1 -1
  11. package/lib/globals/aiModels/providers/google.js +178 -32
  12. package/lib/globals/aiModels/providers/kling.d.ts.map +1 -1
  13. package/lib/globals/aiModels/providers/kling.js +86 -12
  14. package/lib/globals/aiModels/providers/minimax.d.ts.map +1 -1
  15. package/lib/globals/aiModels/providers/minimax.js +77 -0
  16. package/lib/globals/aiModels/providers/openai.d.ts.map +1 -1
  17. package/lib/globals/aiModels/providers/openai.js +12 -0
  18. package/lib/globals/aiModels/providers/pixverse.d.ts.map +1 -1
  19. package/lib/globals/aiModels/providers/pixverse.js +101 -28
  20. package/lib/globals/aiModels/providers/runway.d.ts.map +1 -1
  21. package/lib/globals/aiModels/providers/runway.js +450 -24
  22. package/lib/globals/aiModels/providers/xai.d.ts +4 -0
  23. package/lib/globals/aiModels/providers/xai.d.ts.map +1 -0
  24. package/lib/globals/aiModels/providers/xai.js +72 -0
  25. package/lib/globals/aiModels/tierHelpers.d.ts +10 -0
  26. package/lib/globals/aiModels/tierHelpers.d.ts.map +1 -1
  27. package/lib/globals/aiModels/tierHelpers.js +48 -7
  28. package/lib/globals/aiModels/types.d.ts +21 -0
  29. package/lib/globals/aiModels/types.d.ts.map +1 -1
  30. package/lib/models/image.model.d.ts +24 -0
  31. package/lib/models/image.model.d.ts.map +1 -1
  32. package/lib/models/image.model.js +12 -0
  33. package/lib/models/index.d.ts +1 -0
  34. package/lib/models/index.d.ts.map +1 -1
  35. package/lib/models/index.js +1 -0
  36. package/lib/models/klingElement.model.d.ts +52 -0
  37. package/lib/models/klingElement.model.d.ts.map +1 -0
  38. package/lib/models/klingElement.model.js +24 -0
  39. package/lib/models/user.model.d.ts +7 -1
  40. package/lib/models/user.model.d.ts.map +1 -1
  41. package/lib/models/video.model.d.ts +11 -0
  42. package/lib/models/video.model.d.ts.map +1 -1
  43. package/lib/schemas/videoPlan.schema.d.ts +88 -0
  44. package/lib/schemas/videoPlan.schema.d.ts.map +1 -1
  45. package/lib/schemas/videoPlan.schema.js +27 -1
  46. package/lib/services/agent/executor/visual.d.ts.map +1 -1
  47. package/lib/services/agent/executor/visual.js +40 -2
  48. package/lib/services/agent/mentionedRefs.d.ts.map +1 -1
  49. package/lib/services/agent/mentionedRefs.js +4 -1
  50. package/lib/services/agent/planner/structuralRules.d.ts.map +1 -1
  51. package/lib/services/agent/planner/structuralRules.js +8 -3
  52. package/lib/services/agent/poster/posterAgent.schema.d.ts +3 -3
  53. package/lib/services/agent/providerFallback/chains.d.ts +25 -0
  54. package/lib/services/agent/providerFallback/chains.d.ts.map +1 -1
  55. package/lib/services/agent/providerFallback/chains.js +59 -5
  56. package/lib/services/agent/repair/strategies/llmRepair.strategy.d.ts.map +1 -1
  57. package/lib/services/agent/repair/strategies/llmRepair.strategy.js +2 -0
  58. package/lib/services/agent/tools/animateImage.tool.d.ts +31 -0
  59. package/lib/services/agent/tools/animateImage.tool.d.ts.map +1 -1
  60. package/lib/services/agent/tools/animateImage.tool.js +10 -3
  61. package/lib/services/agent/tools/generateImage.tool.d.ts +8 -4
  62. package/lib/services/agent/tools/generateImage.tool.d.ts.map +1 -1
  63. package/lib/services/agent/tools/generateImage.tool.js +24 -7
  64. package/lib/services/agent/tools/generateVideo.tool.d.ts +32 -0
  65. package/lib/services/agent/tools/generateVideo.tool.d.ts.map +1 -1
  66. package/lib/services/agent/tools/generateVideo.tool.js +37 -9
  67. package/lib/services/agent/tools/planVideo.tool.d.ts +19 -0
  68. package/lib/services/agent/tools/planVideo.tool.d.ts.map +1 -1
  69. package/lib/services/aiGen/aiGenFactory.service.d.ts.map +1 -1
  70. package/lib/services/aiGen/aiGenFactory.service.js +40 -0
  71. package/lib/services/aiGen/canonicalAdapters/cameraControl.types.d.ts +6 -3
  72. package/lib/services/aiGen/canonicalAdapters/cameraControl.types.d.ts.map +1 -1
  73. package/lib/services/aiGen/canonicalAdapters/cameraControl.types.js +35 -0
  74. package/lib/services/aiGen/canonicalAdapters/cameraDirection.d.ts +36 -0
  75. package/lib/services/aiGen/canonicalAdapters/cameraDirection.d.ts.map +1 -0
  76. package/lib/services/aiGen/canonicalAdapters/cameraDirection.js +168 -0
  77. package/lib/services/aiGen/canonicalAdapters/index.d.ts +1 -0
  78. package/lib/services/aiGen/canonicalAdapters/index.d.ts.map +1 -1
  79. package/lib/services/aiGen/canonicalAdapters/index.js +1 -0
  80. package/lib/services/aiGen/canonicalAdapters/multiShot.types.d.ts +3 -2
  81. package/lib/services/aiGen/canonicalAdapters/multiShot.types.d.ts.map +1 -1
  82. package/lib/services/aiGen/helpers.d.ts +40 -0
  83. package/lib/services/aiGen/helpers.d.ts.map +1 -1
  84. package/lib/services/aiGen/helpers.js +48 -2
  85. package/lib/services/aiGen/index.d.ts +1 -0
  86. package/lib/services/aiGen/index.d.ts.map +1 -1
  87. package/lib/services/aiGen/index.js +3 -0
  88. package/lib/services/aiGen/providers/alibaba/alibaba.d.ts +13 -1
  89. package/lib/services/aiGen/providers/alibaba/alibaba.d.ts.map +1 -1
  90. package/lib/services/aiGen/providers/alibaba/alibaba.js +313 -33
  91. package/lib/services/aiGen/providers/bytedance/bytedance.service.d.ts +29 -1
  92. package/lib/services/aiGen/providers/bytedance/bytedance.service.d.ts.map +1 -1
  93. package/lib/services/aiGen/providers/bytedance/bytedance.service.js +371 -54
  94. package/lib/services/aiGen/providers/google/google.service.d.ts +16 -0
  95. package/lib/services/aiGen/providers/google/google.service.d.ts.map +1 -1
  96. package/lib/services/aiGen/providers/google/google.service.js +372 -116
  97. package/lib/services/aiGen/providers/google/googleKeyPool.d.ts +6 -0
  98. package/lib/services/aiGen/providers/google/googleKeyPool.d.ts.map +1 -1
  99. package/lib/services/aiGen/providers/google/googleKeyPool.js +8 -0
  100. package/lib/services/aiGen/providers/kling/kling.service.d.ts +17 -1
  101. package/lib/services/aiGen/providers/kling/kling.service.d.ts.map +1 -1
  102. package/lib/services/aiGen/providers/kling/kling.service.js +59 -3
  103. package/lib/services/aiGen/providers/kling/klingCredits.d.ts +1 -1
  104. package/lib/services/aiGen/providers/kling/klingCredits.d.ts.map +1 -1
  105. package/lib/services/aiGen/providers/kling/klingCredits.js +19 -1
  106. package/lib/services/aiGen/providers/kling/klingRequests.d.ts +37 -7
  107. package/lib/services/aiGen/providers/kling/klingRequests.d.ts.map +1 -1
  108. package/lib/services/aiGen/providers/kling/klingRequests.js +191 -13
  109. package/lib/services/aiGen/providers/kling/types.d.ts +78 -0
  110. package/lib/services/aiGen/providers/kling/types.d.ts.map +1 -1
  111. package/lib/services/aiGen/providers/minimax/minimax.service.d.ts +11 -2
  112. package/lib/services/aiGen/providers/minimax/minimax.service.d.ts.map +1 -1
  113. package/lib/services/aiGen/providers/minimax/minimax.service.js +168 -2
  114. package/lib/services/aiGen/providers/minimax/types.d.ts +63 -0
  115. package/lib/services/aiGen/providers/minimax/types.d.ts.map +1 -1
  116. package/lib/services/aiGen/providers/pixverse/pixverse.service.d.ts +12 -0
  117. package/lib/services/aiGen/providers/pixverse/pixverse.service.d.ts.map +1 -1
  118. package/lib/services/aiGen/providers/pixverse/pixverse.service.js +131 -17
  119. package/lib/services/aiGen/providers/runway/hostedModels.d.ts +84 -0
  120. package/lib/services/aiGen/providers/runway/hostedModels.d.ts.map +1 -0
  121. package/lib/services/aiGen/providers/runway/hostedModels.js +266 -0
  122. package/lib/services/aiGen/providers/runway/index.d.ts +1 -0
  123. package/lib/services/aiGen/providers/runway/index.d.ts.map +1 -1
  124. package/lib/services/aiGen/providers/runway/index.js +1 -0
  125. package/lib/services/aiGen/providers/runway/runway.service.d.ts +1 -1
  126. package/lib/services/aiGen/providers/runway/runway.service.d.ts.map +1 -1
  127. package/lib/services/aiGen/providers/runway/runway.service.js +118 -21
  128. package/lib/services/aiGen/providers/runway/types.d.ts +18 -3
  129. package/lib/services/aiGen/providers/runway/types.d.ts.map +1 -1
  130. package/lib/services/aiGen/providers/runway/types.js +31 -3
  131. package/lib/services/aiGen/providers/types.d.ts +55 -0
  132. package/lib/services/aiGen/providers/types.d.ts.map +1 -1
  133. package/lib/services/aiGen/providers/xai/xai.service.d.ts +19 -0
  134. package/lib/services/aiGen/providers/xai/xai.service.d.ts.map +1 -0
  135. package/lib/services/aiGen/providers/xai/xai.service.js +205 -0
  136. package/lib/services/firestore.service.d.ts +4 -0
  137. package/lib/services/firestore.service.d.ts.map +1 -1
  138. package/lib/services/firestore.service.js +5 -0
  139. package/lib/services/index.d.ts +2 -0
  140. package/lib/services/index.d.ts.map +1 -1
  141. package/lib/services/index.js +2 -0
  142. package/lib/services/klingElements/klingElementLibrary.service.d.ts +37 -0
  143. package/lib/services/klingElements/klingElementLibrary.service.d.ts.map +1 -0
  144. package/lib/services/klingElements/klingElementLibrary.service.js +179 -0
  145. package/lib/services/trialGuard/index.d.ts +2 -0
  146. package/lib/services/trialGuard/index.d.ts.map +1 -0
  147. package/lib/services/trialGuard/index.js +17 -0
  148. package/lib/services/trialGuard/trialSignals.d.ts +88 -0
  149. package/lib/services/trialGuard/trialSignals.d.ts.map +1 -0
  150. package/lib/services/trialGuard/trialSignals.js +160 -0
  151. package/lib/utils/helpers.d.ts.map +1 -1
  152. package/lib/utils/helpers.js +5 -1
  153. 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
- // Bytedance Seedance only accepts ASCII printable characters and CJK (Chinese) characters.
24
- // Strip everything else (emojis, en-dashes, special Unicode, etc.) and collapse whitespace.
25
- sanitizePrompt(text) {
26
- return text
27
- .replace(/[^\x20-\x7E一-鿿㐀-䶿]/g, " ")
28
- .replace(/\s+/g, " ")
29
- .trim();
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`. Earlier Seedance models (1.x) reject the `role` field.
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 sanitizedPrompt = this.sanitizePrompt(params.prompt ?? "");
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): sanitizePrompt strips everything
46
- // outside ASCII+CJK, so an empty result means the prompt was written in a
47
- // script Seedance cannot accept (Cyrillic, Arabic, Devanagari, Thai, an
48
- // all-emoji prompt, …). That is deterministic user input — it can never
49
- // succeed on retry. Without a code, `isNonPagingCode(undefined)` is false
50
- // and the scene-failure handler logged it at `error`, which pages Slack
51
- // (see errorPaging.ts). PROMPT_INVALID is in NON_PAGING_ERROR_CODES, so
52
- // this now stays a warn while still failing the scene cleanly.
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 isV2 = this.isSeedance2(params.modelKey);
88
+ const fields = modelConfig.fields;
56
89
  const content = [
57
90
  {
58
91
  type: "text",
59
92
  text: sanitizedPrompt,
60
93
  },
61
94
  ];
62
- // Image input. Seedance 2.0 expects role="first_frame" for I2V (or
63
- // "reference_image" when combined with a video/audio reference). Earlier
64
- // Seedance models reject `role` and treat any image as the first frame.
65
- if (params.inputImageUrl) {
66
- const item = {
67
- type: "image_url",
68
- image_url: { url: params.inputImageUrl },
69
- };
70
- if (isV2) {
71
- // If a video reference is also present this is a multimodal reference,
72
- // not first-frame I2V, so tag as reference_image.
73
- item.role = params.inputVideoUrl ? "reference_image" : "first_frame";
74
- }
75
- content.push(item);
76
- }
77
- // Seedance 2.0 multimodal reference: video and/or audio inputs.
78
- if (isV2) {
79
- if (params.inputVideoUrl) {
80
- content.push({
81
- type: "video_url",
82
- video_url: { url: params.inputVideoUrl },
83
- role: "reference_video",
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
- if (params.inputAudioUrl) {
87
- content.push({
88
- type: "audio_url",
89
- audio_url: { url: params.inputAudioUrl },
90
- role: "reference_audio",
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 (an input image present),
104
- // ByteDance derives the output ratio from the image itself and rejects an
105
- // explicit `ratio` (InvalidParameter.TaskTypeConstraint, prod 2026-08-11).
106
- // Only pure text-to-video sends the requested aspect ratio.
107
- if (!params.inputImageUrl) {
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 (!this.isSeedance2(params.modelKey)) {
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 (this.isSeedance2(params.modelKey) && params.userId) {
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
- return (0, helpers_2.getCreditsFromCost)(modelConfig.cost.fixed * Math.max(1, params.numImages ?? 1), !params.multiClip, params.creditFloor);
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":"AAkBA,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;AA+ElB,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;IAgU3B,gBAAgB,CAAC,EACrB,IAAI,EACJ,cAAc,EACd,cAAyB,GAC1B,EAAE,iBAAiB,GAAG,OAAO,CAAC,iBAAiB,CAAC;IAwK3C,aAAa,CACjB,MAAM,EAAE,qBAAqB,GAC5B,OAAO,CAAC,qBAAqB,CAAC;YAiBnB,cAAc;IAqK5B;;;;;;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;CA8BpJ"}
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"}