vidspotai-shared 1.0.100 → 1.0.101-dev.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (183) hide show
  1. package/lib/globals/aiModels/enums.d.ts +1 -0
  2. package/lib/globals/aiModels/enums.d.ts.map +1 -1
  3. package/lib/globals/aiModels/enums.js +3 -0
  4. package/lib/globals/aiModels/extendCapability.d.ts +26 -0
  5. package/lib/globals/aiModels/extendCapability.d.ts.map +1 -0
  6. package/lib/globals/aiModels/extendCapability.js +42 -0
  7. package/lib/globals/aiModels/index.d.ts +1 -0
  8. package/lib/globals/aiModels/index.d.ts.map +1 -1
  9. package/lib/globals/aiModels/index.js +1 -0
  10. package/lib/globals/aiModels/providers/google.d.ts.map +1 -1
  11. package/lib/globals/aiModels/providers/google.js +6 -9
  12. package/lib/globals/aiModels/providers/hostedAvatars.js +10 -10
  13. package/lib/globals/creditUnit.d.ts +20 -33
  14. package/lib/globals/creditUnit.d.ts.map +1 -1
  15. package/lib/globals/creditUnit.js +20 -33
  16. package/lib/globals/types.d.ts +34 -2
  17. package/lib/globals/types.d.ts.map +1 -1
  18. package/lib/globals/types.js +37 -0
  19. package/lib/models/demoVideoProject.model.d.ts +213 -0
  20. package/lib/models/demoVideoProject.model.d.ts.map +1 -1
  21. package/lib/models/index.d.ts +2 -0
  22. package/lib/models/index.d.ts.map +1 -1
  23. package/lib/models/index.js +2 -0
  24. package/lib/models/mediaUpload.model.d.ts +25 -0
  25. package/lib/models/mediaUpload.model.d.ts.map +1 -1
  26. package/lib/models/productConnection.model.d.ts +57 -0
  27. package/lib/models/productConnection.model.d.ts.map +1 -0
  28. package/lib/models/productConnection.model.js +7 -0
  29. package/lib/models/video.model.d.ts +11 -0
  30. package/lib/models/video.model.d.ts.map +1 -1
  31. package/lib/models/videoQaJob.model.d.ts +61 -0
  32. package/lib/models/videoQaJob.model.d.ts.map +1 -0
  33. package/lib/models/videoQaJob.model.js +16 -0
  34. package/lib/schemas/brandKit.schema.d.ts +3 -1
  35. package/lib/schemas/brandKit.schema.d.ts.map +1 -1
  36. package/lib/schemas/brandKit.schema.js +9 -0
  37. package/lib/schemas/brief.schema.d.ts +5 -4
  38. package/lib/schemas/brief.schema.d.ts.map +1 -1
  39. package/lib/schemas/brief.schema.js +23 -1
  40. package/lib/schemas/demoQuestion.schema.d.ts +24 -4
  41. package/lib/schemas/demoQuestion.schema.d.ts.map +1 -1
  42. package/lib/schemas/demoQuestion.schema.js +8 -2
  43. package/lib/schemas/demoSpec.schema.d.ts +40 -6
  44. package/lib/schemas/demoSpec.schema.d.ts.map +1 -1
  45. package/lib/schemas/demoSpec.schema.js +46 -1
  46. package/lib/schemas/project.schema.d.ts +33 -3
  47. package/lib/schemas/project.schema.d.ts.map +1 -1
  48. package/lib/schemas/project.schema.js +22 -0
  49. package/lib/schemas/social.schema.d.ts +1 -1
  50. package/lib/schemas/videoPlan.schema.d.ts +22 -20
  51. package/lib/schemas/videoPlan.schema.d.ts.map +1 -1
  52. package/lib/schemas/videoPlan.schema.js +19 -0
  53. package/lib/services/agent/editClassifier.d.ts +10 -10
  54. package/lib/services/agent/eval/types.d.ts +3 -2
  55. package/lib/services/agent/eval/types.d.ts.map +1 -1
  56. package/lib/services/agent/executor/core.d.ts.map +1 -1
  57. package/lib/services/agent/executor/core.js +11 -0
  58. package/lib/services/agent/planner/Planner.d.ts.map +1 -1
  59. package/lib/services/agent/planner/Planner.js +5 -0
  60. package/lib/services/agent/planner/overlayRegen.d.ts.map +1 -1
  61. package/lib/services/agent/planner/overlayRegen.js +5 -0
  62. package/lib/services/agent/planner/plannerMessages.d.ts.map +1 -1
  63. package/lib/services/agent/planner/plannerMessages.js +10 -1
  64. package/lib/services/agent/planner/scriptFirstPlanner.d.ts.map +1 -1
  65. package/lib/services/agent/planner/scriptFirstPlanner.js +2 -0
  66. package/lib/services/agent/regenCore.d.ts.map +1 -1
  67. package/lib/services/agent/regenCore.js +11 -1
  68. package/lib/services/agent/runHelpers.d.ts +8 -0
  69. package/lib/services/agent/runHelpers.d.ts.map +1 -1
  70. package/lib/services/agent/runHelpers.js +5 -1
  71. package/lib/services/agent/tools/chapterOutline.tool.d.ts +8 -0
  72. package/lib/services/agent/tools/chapterOutline.tool.d.ts.map +1 -1
  73. package/lib/services/agent/tools/chapterOutline.tool.js +21 -8
  74. package/lib/services/agent/tools/composeScene.tool.d.ts +32 -2
  75. package/lib/services/agent/tools/composeScene.tool.d.ts.map +1 -1
  76. package/lib/services/agent/tools/estimateCost.tool.d.ts +11 -1
  77. package/lib/services/agent/tools/estimateCost.tool.d.ts.map +1 -1
  78. package/lib/services/agent/tools/extendVideo.tool.d.ts +9 -3
  79. package/lib/services/agent/tools/extendVideo.tool.d.ts.map +1 -1
  80. package/lib/services/agent/tools/extendVideo.tool.js +12 -27
  81. package/lib/services/agent/tools/generateAvatarVideo.tool.d.ts +1 -1
  82. package/lib/services/agent/tools/generateScript.tool.d.ts +3 -2
  83. package/lib/services/agent/tools/generateScript.tool.d.ts.map +1 -1
  84. package/lib/services/agent/tools/generateScript.tool.js +16 -7
  85. package/lib/services/agent/tools/generateVoiceover.tool.d.ts +1 -1
  86. package/lib/services/agent/tools/planVideo.tool.d.ts +11 -8
  87. package/lib/services/agent/tools/planVideo.tool.d.ts.map +1 -1
  88. package/lib/services/agent/tools/render.tool.d.ts +11 -1
  89. package/lib/services/agent/tools/render.tool.d.ts.map +1 -1
  90. package/lib/services/agent/tools/searchStock.tool.d.ts +1 -1
  91. package/lib/services/agent/tools/searchUserLibrary.tool.d.ts +4 -4
  92. package/lib/services/aiGen/providers/alibaba/alibaba.d.ts +0 -6
  93. package/lib/services/aiGen/providers/alibaba/alibaba.d.ts.map +1 -1
  94. package/lib/services/aiGen/providers/alibaba/alibaba.js +5 -103
  95. package/lib/services/aiGen/providers/bytedance/bytedance.service.d.ts.map +1 -1
  96. package/lib/services/aiGen/providers/bytedance/bytedance.service.js +1 -9
  97. package/lib/services/aiGen/providers/google/google.service.d.ts.map +1 -1
  98. package/lib/services/aiGen/providers/google/google.service.js +26 -142
  99. package/lib/services/aiGen/providers/google/googleErrors.d.ts.map +1 -1
  100. package/lib/services/aiGen/providers/google/googleErrors.js +7 -17
  101. package/lib/services/aiGen/providers/kling/kling.service.d.ts.map +1 -1
  102. package/lib/services/aiGen/providers/kling/kling.service.js +22 -3
  103. package/lib/services/aiGen/providers/kling/klingStatus.d.ts +7 -0
  104. package/lib/services/aiGen/providers/kling/klingStatus.d.ts.map +1 -1
  105. package/lib/services/aiGen/providers/kling/klingStatus.js +41 -4
  106. package/lib/services/aiGen/providers/pixverse/pixverse.service.d.ts.map +1 -1
  107. package/lib/services/aiGen/providers/pixverse/pixverse.service.js +25 -0
  108. package/lib/services/aiGen/providers/types.d.ts +20 -0
  109. package/lib/services/aiGen/providers/types.d.ts.map +1 -1
  110. package/lib/services/avatarGen/avatarProviderFactory.d.ts.map +1 -1
  111. package/lib/services/avatarGen/avatarProviderFactory.js +3 -0
  112. package/lib/services/avatarGen/index.d.ts +1 -0
  113. package/lib/services/avatarGen/index.d.ts.map +1 -1
  114. package/lib/services/avatarGen/index.js +1 -0
  115. package/lib/services/avatarGen/providers/synthesiaAvatarProvider.d.ts +53 -0
  116. package/lib/services/avatarGen/providers/synthesiaAvatarProvider.d.ts.map +1 -0
  117. package/lib/services/avatarGen/providers/synthesiaAvatarProvider.js +176 -0
  118. package/lib/services/demo/demoAuthor.d.ts +7 -0
  119. package/lib/services/demo/demoAuthor.d.ts.map +1 -1
  120. package/lib/services/demo/demoAuthor.js +2 -0
  121. package/lib/services/demo/demoAuthorPrompt.d.ts +6 -0
  122. package/lib/services/demo/demoAuthorPrompt.d.ts.map +1 -1
  123. package/lib/services/demo/demoAuthorPrompt.js +24 -2
  124. package/lib/services/demo/demoCaptions.d.ts +69 -0
  125. package/lib/services/demo/demoCaptions.d.ts.map +1 -0
  126. package/lib/services/demo/demoCaptions.js +152 -0
  127. package/lib/services/demo/demoMusic.d.ts +8 -1
  128. package/lib/services/demo/demoMusic.d.ts.map +1 -1
  129. package/lib/services/demo/demoMusic.js +32 -16
  130. package/lib/services/demo/demoScriptPrompt.d.ts +2 -1
  131. package/lib/services/demo/demoScriptPrompt.d.ts.map +1 -1
  132. package/lib/services/demo/demoScriptPrompt.js +29 -2
  133. package/lib/services/demo/demoStockAssets.d.ts +29 -0
  134. package/lib/services/demo/demoStockAssets.d.ts.map +1 -0
  135. package/lib/services/demo/demoStockAssets.js +107 -0
  136. package/lib/services/demo/index.d.ts +3 -0
  137. package/lib/services/demo/index.d.ts.map +1 -1
  138. package/lib/services/demo/index.js +3 -0
  139. package/lib/services/demo/productConnection.service.d.ts +78 -0
  140. package/lib/services/demo/productConnection.service.d.ts.map +1 -0
  141. package/lib/services/demo/productConnection.service.js +125 -0
  142. package/lib/services/editor/planToProject.d.ts.map +1 -1
  143. package/lib/services/editor/planToProject.helpers.d.ts +1 -0
  144. package/lib/services/editor/planToProject.helpers.d.ts.map +1 -1
  145. package/lib/services/editor/planToProject.helpers.js +3 -0
  146. package/lib/services/editor/planToProject.js +39 -0
  147. package/lib/services/editor/projectToDesign.js +13 -1
  148. package/lib/services/firestore.service.d.ts +14 -0
  149. package/lib/services/firestore.service.d.ts.map +1 -1
  150. package/lib/services/firestore.service.js +16 -0
  151. package/lib/services/translation/index.d.ts +1 -0
  152. package/lib/services/translation/index.d.ts.map +1 -1
  153. package/lib/services/translation/index.js +8 -1
  154. package/lib/services/translation/outputLanguage.d.ts +75 -0
  155. package/lib/services/translation/outputLanguage.d.ts.map +1 -0
  156. package/lib/services/translation/outputLanguage.js +190 -0
  157. package/lib/services/translation/translation.service.d.ts +13 -2
  158. package/lib/services/translation/translation.service.d.ts.map +1 -1
  159. package/lib/services/translation/translation.service.js +73 -31
  160. package/lib/services/tts/index.d.ts +1 -0
  161. package/lib/services/tts/index.d.ts.map +1 -1
  162. package/lib/services/tts/index.js +1 -0
  163. package/lib/services/tts/providers/elevenlabs.service.d.ts +7 -0
  164. package/lib/services/tts/providers/elevenlabs.service.d.ts.map +1 -1
  165. package/lib/services/tts/providers/elevenlabs.service.js +69 -13
  166. package/lib/services/tts/types.d.ts +29 -0
  167. package/lib/services/tts/types.d.ts.map +1 -1
  168. package/lib/services/tts/wordTimings.d.ts +27 -0
  169. package/lib/services/tts/wordTimings.d.ts.map +1 -0
  170. package/lib/services/tts/wordTimings.js +57 -0
  171. package/lib/utils/billableDuration.d.ts +11 -0
  172. package/lib/utils/billableDuration.d.ts.map +1 -0
  173. package/lib/utils/billableDuration.js +91 -0
  174. package/lib/utils/index.d.ts +3 -0
  175. package/lib/utils/index.d.ts.map +1 -1
  176. package/lib/utils/index.js +3 -0
  177. package/lib/utils/mp4Duration.d.ts +38 -0
  178. package/lib/utils/mp4Duration.d.ts.map +1 -0
  179. package/lib/utils/mp4Duration.js +164 -0
  180. package/lib/utils/socialEntitlements.d.ts +51 -0
  181. package/lib/utils/socialEntitlements.d.ts.map +1 -0
  182. package/lib/utils/socialEntitlements.js +65 -0
  183. package/package.json +1 -1
@@ -4,7 +4,6 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.AlibabaService = void 0;
7
- exports.resolveDashScopeSize = resolveDashScopeSize;
8
7
  const axios_1 = __importDefault(require("axios"));
9
8
  const types_1 = require("../../../../globals/types");
10
9
  const baseAiGenProvider_service_1 = require("../baseAiGenProvider.service");
@@ -60,68 +59,6 @@ function isSyncMultimodalImageModel(modelId) {
60
59
  function isAsyncWanImageModel(modelId) {
61
60
  return modelId.startsWith("wan2.7-image") || modelId.startsWith("wan2.6-image");
62
61
  }
63
- /**
64
- * DashScope's `size` parameter is "WIDTH*HEIGHT" in PIXELS — it is NOT an
65
- * aspect-ratio string. We used to assign `parameters.size = params.aspectRatio`
66
- * directly, so every request with an aspect ratio shipped `size: "9:16"` and
67
- * came back `400 InvalidParameter` (observed in prod on qwen-image-2.0-pro,
68
- * 2026-07-26). The 400 body was also swallowed (see `request`), which is why it
69
- * only ever surfaced as the opaque "Request failed with status code 400".
70
- *
71
- * Two documented preset families, distinguished by their pixel budget:
72
- * - qwen-image-2.x → 2048×2048-class (square preset 2048*2048)
73
- * - everything else on this endpoint (max / plus / edit / wan2.7)
74
- * → 1328×1328-class (square preset 1328*1328)
75
- * Values below are the documented allowed presets, so they are guaranteed-valid
76
- * rather than inferred. Ratios with no published preset (2:3, 3:2) are computed
77
- * from the family's square-preset AREA and rounded to a multiple of 16, which
78
- * keeps them inside the documented total-pixel range.
79
- */
80
- const DASHSCOPE_SIZE_PRESETS = {
81
- // qwen-image-2.x series
82
- large: {
83
- "1:1": "2048*2048",
84
- "16:9": "2688*1536",
85
- "9:16": "1536*2688",
86
- "4:3": "2368*1728",
87
- "3:4": "1728*2368",
88
- },
89
- // qwen-image-max / -plus / -edit, wan2.7-image
90
- standard: {
91
- "1:1": "1328*1328",
92
- "16:9": "1664*928",
93
- "9:16": "928*1664",
94
- "4:3": "1472*1104",
95
- "3:4": "1104*1472",
96
- },
97
- };
98
- /** Round to the nearest multiple of 16 (DashScope dimensions are 16-aligned). */
99
- function round16(n) {
100
- return Math.max(16, Math.round(n / 16) * 16);
101
- }
102
- /**
103
- * Map a canonical "W:H" aspect ratio to a DashScope pixel `size` string.
104
- * Returns undefined for unparseable input so the caller simply omits `size`
105
- * and lets DashScope apply its own default (better than sending a bad value).
106
- */
107
- function resolveDashScopeSize(modelId, aspectRatio) {
108
- const family = modelId.startsWith("qwen-image-2") ? "large" : "standard";
109
- const preset = DASHSCOPE_SIZE_PRESETS[family][aspectRatio];
110
- if (preset)
111
- return preset;
112
- // No published preset for this ratio — derive one at the family's area.
113
- const [wStr, hStr] = aspectRatio.split(":");
114
- const w = Number(wStr);
115
- const h = Number(hStr);
116
- if (!Number.isFinite(w) || !Number.isFinite(h) || w <= 0 || h <= 0) {
117
- return undefined;
118
- }
119
- const squarePreset = DASHSCOPE_SIZE_PRESETS[family]["1:1"];
120
- const side = Number(squarePreset.split("*")[0]);
121
- const area = side * side;
122
- const ratio = w / h;
123
- return `${round16(Math.sqrt(area * ratio))}*${round16(Math.sqrt(area / ratio))}`;
124
- }
125
62
  class AlibabaService extends baseAiGenProvider_service_1.BaseAiGenProviderService {
126
63
  constructor() {
127
64
  super();
@@ -156,32 +93,8 @@ class AlibabaService extends baseAiGenProvider_service_1.BaseAiGenProviderServic
156
93
  timeout: this.timeout,
157
94
  data: method === "POST" ? body : undefined,
158
95
  };
159
- try {
160
- const res = await axios_1.default.request(config);
161
- return res.data;
162
- }
163
- catch (err) {
164
- // DashScope puts the real reason (code + message, e.g.
165
- // "InvalidParameter: size is invalid") in the RESPONSE BODY. Axios's own
166
- // message is only "Request failed with status code 400", so without this
167
- // the actual cause was lost — a prod 400 on qwen-image-2.0-pro was
168
- // undiagnosable until the body was surfaced (2026-07-26). Re-throw with
169
- // the provider's code/message appended so it reaches Loki/Slack and the
170
- // stored job errorMessage.
171
- const axiosErr = err;
172
- const data = axiosErr.response?.data;
173
- if (data && (data.code || data.message)) {
174
- logger_1.logger.error("Alibaba DashScope request failed", {
175
- url,
176
- status: axiosErr.response?.status,
177
- code: data.code,
178
- message: data.message,
179
- requestId: data.request_id,
180
- });
181
- throw new Error(`DashScope ${axiosErr.response?.status ?? ""} ${data.code ?? ""}: ${data.message ?? axiosErr.message ?? "request failed"}`.trim());
182
- }
183
- throw err;
184
- }
96
+ const res = await axios_1.default.request(config);
97
+ return res.data;
185
98
  }
186
99
  async generateVideo(params) {
187
100
  (0, helpers_2.validateParams)(params);
@@ -397,13 +310,8 @@ class AlibabaService extends baseAiGenProvider_service_1.BaseAiGenProviderServic
397
310
  parameters.prompt_extend = params.promptOptimizer;
398
311
  if (params.seed !== undefined)
399
312
  parameters.seed = params.seed;
400
- // `size` is WIDTH*HEIGHT pixels, never a ratio string — see
401
- // resolveDashScopeSize. An explicit imageSize (already in pixel form) wins.
402
- if (params.aspectRatio) {
403
- const size = resolveDashScopeSize(modelId, params.aspectRatio);
404
- if (size)
405
- parameters.size = size;
406
- }
313
+ if (params.aspectRatio)
314
+ parameters.size = params.aspectRatio;
407
315
  if (params.imageSize)
408
316
  parameters.size = params.imageSize;
409
317
  const result = await this.request({ model: modelId, input, parameters }, "POST", ENDPOINT_MULTIMODAL_SYNC, false);
@@ -424,13 +332,7 @@ class AlibabaService extends baseAiGenProvider_service_1.BaseAiGenProviderServic
424
332
  input.negative_prompt = params.negativePrompt;
425
333
  const parameters = {
426
334
  n: params.numImages ?? 1,
427
- // Same pixel-vs-ratio rule as the sync path — see resolveDashScopeSize.
428
- ...(params.aspectRatio
429
- ? (() => {
430
- const size = resolveDashScopeSize(modelId, params.aspectRatio);
431
- return size ? { size } : {};
432
- })()
433
- : {}),
335
+ ...(params.aspectRatio ? { size: params.aspectRatio } : {}),
434
336
  ...(params.imageSize ? { size: params.imageSize } : {}),
435
337
  ...(params.seed !== undefined ? { seed: params.seed } : {}),
436
338
  ...(params.watermark !== undefined ? { watermark: params.watermark } : {}),
@@ -1 +1 @@
1
- {"version":3,"file":"bytedance.service.d.ts","sourceRoot":"","sources":["../../../../../src/services/aiGen/providers/bytedance/bytedance.service.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,wBAAwB,EAAE,MAAM,8BAA8B,CAAC;AACxE,OAAO,EACL,iBAAiB,EACjB,qBAAqB,EACrB,qBAAqB,EACrB,iBAAiB,EACjB,iBAAiB,EAClB,MAAM,UAAU,CAAC;AAUlB,qBAAa,gBAAiB,SAAQ,wBAAwB;IAC5D,OAAO,CAAC,QAAQ,CAAC,OAAO,CACsD;IAI9E,OAAO,CAAC,cAAc;IAStB,OAAO,CAAC,WAAW;IAIb,aAAa,CACjB,MAAM,EAAE,qBAAqB,GAC5B,OAAO,CAAC,qBAAqB,CAAC;IAqM3B,gBAAgB,CAAC,EACrB,IAAI,EACJ,cAAc,EACd,cAAyB,GAC1B,EAAE,iBAAiB,GAAG,OAAO,CAAC,iBAAiB,CAAC;IA4HjD,aAAa,CAAC,MAAM,EAAE,iBAAiB,GAAG,MAAM;CAyEjD"}
1
+ {"version":3,"file":"bytedance.service.d.ts","sourceRoot":"","sources":["../../../../../src/services/aiGen/providers/bytedance/bytedance.service.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,wBAAwB,EAAE,MAAM,8BAA8B,CAAC;AACxE,OAAO,EACL,iBAAiB,EACjB,qBAAqB,EACrB,qBAAqB,EACrB,iBAAiB,EACjB,iBAAiB,EAClB,MAAM,UAAU,CAAC;AAUlB,qBAAa,gBAAiB,SAAQ,wBAAwB;IAC5D,OAAO,CAAC,QAAQ,CAAC,OAAO,CACsD;IAI9E,OAAO,CAAC,cAAc;IAStB,OAAO,CAAC,WAAW;IAIb,aAAa,CACjB,MAAM,EAAE,qBAAqB,GAC5B,OAAO,CAAC,qBAAqB,CAAC;IA4L3B,gBAAgB,CAAC,EACrB,IAAI,EACJ,cAAc,EACd,cAAyB,GAC1B,EAAE,iBAAiB,GAAG,OAAO,CAAC,iBAAiB,CAAC;IA4HjD,aAAa,CAAC,MAAM,EAAE,iBAAiB,GAAG,MAAM;CAyEjD"}
@@ -38,15 +38,7 @@ class ByteDanceService extends baseAiGenProvider_service_1.BaseAiGenProviderServ
38
38
  const modelId = modelConfig.modelId;
39
39
  const sanitizedPrompt = this.sanitizePrompt(params.prompt ?? "");
40
40
  if (!sanitizedPrompt) {
41
- // PROMPT_INVALID (not an uncoded throw): sanitizePrompt strips everything
42
- // outside ASCII+CJK, so an empty result means the prompt was written in a
43
- // script Seedance cannot accept (Cyrillic, Arabic, Devanagari, Thai, an
44
- // all-emoji prompt, …). That is deterministic user input — it can never
45
- // succeed on retry. Without a code, `isNonPagingCode(undefined)` is false
46
- // and the scene-failure handler logged it at `error`, which pages Slack
47
- // (see errorPaging.ts). PROMPT_INVALID is in NON_PAGING_ERROR_CODES, so
48
- // this now stays a warn while still failing the scene cleanly.
49
- 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);
41
+ throw new errors_1.UserFacingError("Your prompt could not be processed. Please use English or Chinese text and try again.");
50
42
  }
51
43
  const isV2 = this.isSeedance2(params.modelKey);
52
44
  const content = [
@@ -1 +1 @@
1
- {"version":3,"file":"google.service.d.ts","sourceRoot":"","sources":["../../../../../src/services/aiGen/providers/google/google.service.ts"],"names":[],"mappings":"AAiBA,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;AAkElB,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;IA2T3B,gBAAgB,CAAC,EACrB,IAAI,EACJ,cAAc,EACd,cAAyB,GAC1B,EAAE,iBAAiB,GAAG,OAAO,CAAC,iBAAiB,CAAC;IA8J3C,aAAa,CACjB,MAAM,EAAE,qBAAqB,GAC5B,OAAO,CAAC,qBAAqB,CAAC;YAiBnB,cAAc;IAyJ5B;;;;;;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":"AAiBA,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;AAkElB,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;IA0O3B,gBAAgB,CAAC,EACrB,IAAI,EACJ,cAAc,EACd,cAAyB,GAC1B,EAAE,iBAAiB,GAAG,OAAO,CAAC,iBAAiB,CAAC;IA8J3C,aAAa,CACjB,MAAM,EAAE,qBAAqB,GAC5B,OAAO,CAAC,qBAAqB,CAAC;YAiBnB,cAAc;IAwG5B;;;;;;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"}
@@ -197,54 +197,20 @@ class GoogleService extends baseAiGenProvider_service_1.BaseAiGenProviderService
197
197
  const modelConfig = aiModels_1.aiModelConfigs[params.modelKey];
198
198
  const modelId = modelConfig.modelId;
199
199
  const isVeo3_1 = VEO_3_1_MODELS.has(params.modelKey);
200
- // ── Veo generation mode ──────────────────────────────────────────────
201
- // Several Veo constraints are MODE-dependent (not just model-dependent),
202
- // so resolve the mode once and drive every guard below off it. Precedence
203
- // matches the request builder further down: video extension wins over an
204
- // input image (they are mutually exclusive in the Veo API).
205
- const isExtension = !!params.inputVideoUrl;
206
- const refCount = params.referenceImageUrls?.length ?? 0;
207
- const isImageDriven = !isExtension &&
208
- (!!params.inputImageUrl ||
209
- !!params.lastFrameImageUrl ||
210
- (isVeo3_1 && refCount > 0));
211
- // Gemini Veo cross-constraint: extension, first+last-frame interpolation
212
- // AND reference images ALL require durationSeconds=8 (per the Veo API
213
- // parameter table: `durationSeconds` "Must be '8' when using extension,
214
- // reference images or with 1080p and 4k resolutions"). Sending any other
215
- // duration returns INVALID_ARGUMENT 400 with no hint about which param
216
- // caused it. Surface a typed UserFacingError so the user/frontend can
217
- // correct the input instead of burning a provider call + opaque rejection.
218
- const needsDuration8 = isExtension ||
219
- !!params.lastFrameImageUrl ||
220
- (isVeo3_1 && refCount > 0);
200
+ // Gemini Veo cross-constraint: first+last-frame interpolation AND
201
+ // reference images BOTH require durationSeconds=8. Sending any other
202
+ // duration returns INVALID_ARGUMENT 400 "Your use case is currently not
203
+ // supported." with no hint about which param caused it. Surface a
204
+ // typed UserFacingError so the user/frontend can correct the input
205
+ // instead of burning a provider call + opaque rejection.
206
+ const needsDuration8 = !!params.lastFrameImageUrl ||
207
+ (isVeo3_1 && (params.referenceImageUrls?.length ?? 0) > 0);
221
208
  if (needsDuration8 && params.duration !== undefined && params.duration !== 8) {
222
- const constraint = isExtension
223
- ? "video extension"
224
- : params.lastFrameImageUrl
225
- ? "first-frame + last-frame interpolation"
226
- : "reference images";
227
- const remedy = isExtension
228
- ? "extend a shorter clip"
229
- : `remove the ${params.lastFrameImageUrl ? "last-frame image" : "reference images"}`;
209
+ const constraint = params.lastFrameImageUrl
210
+ ? "first-frame + last-frame interpolation"
211
+ : "reference images";
230
212
  throw new errors_1.UserFacingError(`Google Veo requires an 8-second duration when using ${constraint}. ` +
231
- `Please select 8s or ${remedy}.`, errors_1.USER_FACING_ERROR_CODES.CAPABILITY_MISMATCH);
232
- }
233
- // Extension output is capped at 720p ("`720p` only for extension" in the
234
- // same table). A 1080p/4k extend is a deterministic 400, so reject it here
235
- // rather than paying for the round-trip. NOT coerced to 720p on purpose:
236
- // resolution drives the credit price (cost.perResolution), so silently
237
- // downgrading the output would bill the user for a tier we didn't deliver.
238
- if (isExtension && params.resolution && params.resolution !== "720p") {
239
- throw new errors_1.UserFacingError(`Google Veo only supports 720p when extending a video (requested ${params.resolution}). ` +
240
- `Please select 720p to extend this clip.`, errors_1.USER_FACING_ERROR_CODES.CAPABILITY_MISMATCH);
241
- }
242
- // `lastFrame` is the END of an interpolation and is only meaningful with a
243
- // START frame — the API spec states it "Must be used in combination with
244
- // the `image` parameter." Sending it alone is a deterministic 400.
245
- if (params.lastFrameImageUrl && !params.inputImageUrl) {
246
- throw new errors_1.UserFacingError("Google Veo needs a starting image as well as a last-frame image to interpolate between them. " +
247
- "Please add a first-frame image or remove the last-frame image.", errors_1.USER_FACING_ERROR_CODES.CAPABILITY_MISMATCH);
213
+ `Please select 8s or remove the ${params.lastFrameImageUrl ? "last-frame image" : "reference images"}.`, errors_1.USER_FACING_ERROR_CODES.CAPABILITY_MISMATCH);
248
214
  }
249
215
  const request = {
250
216
  model: modelId,
@@ -262,25 +228,18 @@ class GoogleService extends baseAiGenProvider_service_1.BaseAiGenProviderService
262
228
  // carried a seed. Like `generateAudio`, it is a Vertex-AI-only field.
263
229
  // Restore this line if/when this client switches to Vertex (vertexai:true).
264
230
  // ...(params.seed !== undefined ? { seed: params.seed } : {}),
265
- // personGeneration on the Gemini API Veo backend is MODE-dependent, not
266
- // just model-dependent. Per the Veo API parameter table for Veo 3.1 /
267
- // 3.1 Fast / 3.1 Lite:
268
- // Text-to-video & extension "allow_all" ONLY
269
- // Image-to-video, interpolation, reference images "allow_adult" ONLY
270
- // The previous comment here claimed allow_all was the only valid value
271
- // for all of Veo 3.x; that is true only for the text/extension modes.
272
- // Forwarding allow_all on an image-driven request is a deterministic
273
- // 400. "dont_allow" is Veo-2-only legacy and is never valid on 3.x.
274
- //
275
- // So: forward only the value that is legal for THIS mode, and drop
276
- // anything else rather than hard-failing (the API default for each mode
277
- // is that mode's single legal value, so omitting is always safe — it is
278
- // what the overwhelming majority of our traffic already does).
279
- ...(params.personGeneration === "allow_all" ||
280
- params.personGeneration === "allow_adult"
281
- ? isImageDriven
282
- ? { personGeneration: "allow_adult" }
283
- : { personGeneration: "allow_all" }
231
+ // personGeneration on the Gemini API Veo backend: "allow_all" is the
232
+ // ONLY supported value for Veo 3.x (and it is also the default).
233
+ // "allow_adult" and "dont_allow" are Veo-2-only legacy values — sending
234
+ // either returns 400 "<value> for personGeneration is currently not
235
+ // supported", which is NOT matched by classifyGoogleApiError and so
236
+ // leaks to logger.error Slack while failing the job. Since allow_all
237
+ // is the default, forward the param only when it's explicitly allow_all
238
+ // (a no-op that documents intent); drop any other requested value
239
+ // rather than hard-fail. Restoring allow_adult/dont_allow would require
240
+ // a Veo-2 model or an allowlisted Vertex project.
241
+ ...(params.personGeneration === "allow_all"
242
+ ? { personGeneration: "allow_all" }
284
243
  : {}),
285
244
  // negativePrompt support is MODEL-SPECIFIC on the Gemini API: veo-3.1 and
286
245
  // veo-3.1-fast accept it, veo-3.1-lite returns 400 "negativePrompt isn't
@@ -364,36 +323,8 @@ class GoogleService extends baseAiGenProvider_service_1.BaseAiGenProviderService
364
323
  const keyFailure = (0, googleErrors_1.classifyGoogleKeyHealth)(err);
365
324
  // Not a fail-over-able failure (e.g. content rejection, bad params) →
366
325
  // don't burn other accounts on it; surface as-is.
367
- if (!keyFailure) {
368
- // Log WHAT WE SENT before rethrowing. Veo answers an unsupported
369
- // parameter combination with a generic 400 ("Unsupported video
370
- // generation request", "Your use case is currently not supported")
371
- // that names neither the offending field nor its value — so without
372
- // this line the Slack alert and the job doc are both dead ends, and
373
- // the combo can only be guessed at (prod 2026-07-26). Shape only: no
374
- // prompt text, no image bytes, no signed URLs.
375
- logger_1.logger.warn("Google Veo: request rejected — submitted shape", {
376
- modelKey: params.modelKey,
377
- modelId,
378
- aspectRatio: params.aspectRatio,
379
- resolution: params.resolution,
380
- durationSeconds: params.duration,
381
- mode: isExtension
382
- ? "extension"
383
- : isImageDriven
384
- ? "image-driven"
385
- : "text-to-video",
386
- hasInputImage: !!params.inputImageUrl,
387
- hasLastFrame: !!params.lastFrameImageUrl,
388
- referenceImageCount: refCount,
389
- personGeneration: params.personGeneration ?? null,
390
- hasNegativePrompt: !!params.negativePrompt,
391
- enhancePrompt: params.promptOptimizer ?? null,
392
- promptChars: params.prompt?.length ?? 0,
393
- error: err instanceof Error ? err.message.slice(0, 500) : String(err),
394
- });
326
+ if (!keyFailure)
395
327
  throw err;
396
- }
397
328
  // Observability: log the RAW provider error once so we can see whether
398
329
  // this was an RPM vs RPD limit and on which key (AI Studio's dashboard
399
330
  // lags real-time, so this is our authoritative signal — Ammar 2026-06-20).
@@ -612,11 +543,6 @@ class GoogleService extends baseAiGenProvider_service_1.BaseAiGenProviderService
612
543
  const isImagen = modelId.startsWith("imagen-");
613
544
  const isNanoBanana = modelId.startsWith("gemini-") && modelId.includes("image");
614
545
  const allBytes = [];
615
- // Safety/RAI block reasons harvested from the response when it comes back
616
- // WITHOUT image bytes. Google does not throw in this case — it returns a
617
- // 200 with the images filtered out — so the reason is only available here.
618
- // Mirrors the Veo video path (raiMediaFilteredReasons, above).
619
- const blockReasons = [];
620
546
  if (isImagen) {
621
547
  const resp = await this.ai.models.generateImages({
622
548
  model: modelId,
@@ -633,25 +559,13 @@ class GoogleService extends baseAiGenProvider_service_1.BaseAiGenProviderService
633
559
  });
634
560
  for (const gen of resp.generatedImages ?? []) {
635
561
  const b64 = gen.image?.imageBytes;
636
- if (!b64) {
637
- // An entry with no bytes carries the RAI filter reason instead.
638
- const reason = gen.raiFilteredReason;
639
- if (reason)
640
- blockReasons.push(reason);
562
+ if (!b64)
641
563
  continue;
642
- }
643
564
  allBytes.push({
644
565
  bytes: Buffer.from(b64, "base64"),
645
566
  mime: gen.image?.mimeType ?? "image/png",
646
567
  });
647
568
  }
648
- // Prompt-level rejection: Imagen filters the whole request before
649
- // producing any generatedImages entry, reporting it here instead.
650
- const promptReason = resp
651
- .positivePromptSafetyAttributes;
652
- if (!allBytes.length && promptReason && !blockReasons.length) {
653
- blockReasons.push("Prompt blocked by Google's safety filters");
654
- }
655
569
  }
656
570
  else if (isNanoBanana) {
657
571
  // Nano Banana (Gemini image-gen via generateContent) has no structured
@@ -693,41 +607,11 @@ class GoogleService extends baseAiGenProvider_service_1.BaseAiGenProviderService
693
607
  }
694
608
  }
695
609
  }
696
- // Gemini reports a safety stop two ways, neither of which throws:
697
- // promptFeedback.blockReason (whole prompt rejected → candidates empty)
698
- // and candidate.finishReason (generation halted: IMAGE_SAFETY,
699
- // PROHIBITED_CONTENT, RECITATION, SPII).
700
- const promptBlock = resp
701
- .promptFeedback?.blockReason;
702
- if (promptBlock)
703
- blockReasons.push(promptBlock);
704
- for (const cand of candidates) {
705
- const finish = cand.finishReason;
706
- if (finish && finish !== "STOP" && finish !== "MAX_TOKENS") {
707
- blockReasons.push(finish);
708
- }
709
- }
710
610
  }
711
611
  else {
712
612
  throw new Error(`Unsupported Google image modelId: ${modelId}`);
713
613
  }
714
614
  if (!allBytes.length) {
715
- // A 200 with zero images is almost always a safety/RAI block, not a bug.
716
- // Previously this threw a bare Error whose message wasn't JSON, so
717
- // classifyGoogleApiError (which JSON.parses the message) returned null and
718
- // the image queue logged it at `error` — paging Slack for what is really a
719
- // user-content outcome. Emit a typed CONTENT_POLICY_VIOLATION when we have
720
- // a block reason so it's logged at warn, and keep the reason for triage.
721
- if (blockReasons.length) {
722
- logger_1.logger.warn("Google image generation blocked by safety filters", {
723
- modelKey: params.modelKey,
724
- modelId,
725
- reasons: blockReasons,
726
- });
727
- throw new errors_1.UserFacingError("Your prompt was flagged by Google's safety filters. Please rephrase and try again.", errors_1.USER_FACING_ERROR_CODES.CONTENT_POLICY_VIOLATION);
728
- }
729
- // No block reason at all — genuinely unexpected; keep it loud so a real
730
- // platform/SDK regression isn't muted by the branch above.
731
615
  throw new Error("Google image generation returned no image bytes");
732
616
  }
733
617
  const bucket = (0, firebase_1.getBucket)();
@@ -1 +1 @@
1
- {"version":3,"file":"googleErrors.d.ts","sourceRoot":"","sources":["../../../../../src/services/aiGen/providers/google/googleErrors.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,eAAe,EAGhB,MAAM,0BAA0B,CAAC;AAElC;;;;;;;;;GASG;AACH,wBAAgB,sBAAsB,CAAC,GAAG,EAAE,GAAG,GAAG,eAAe,GAAG,IAAI,CA8JvE;AAED;;;+EAG+E;AAC/E,MAAM,WAAW,sBAAsB;IACrC,4DAA4D;IAC5D,IAAI,EAAE,SAAS,GAAG,MAAM,GAAG,OAAO,CAAC;IACnC,+EAA+E;IAC/E,MAAM,EAAE,MAAM,CAAC;IACf,mFAAmF;IACnF,UAAU,EAAE,MAAM,CAAC;CACpB;AAcD;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAgB,uBAAuB,CAAC,GAAG,EAAE,GAAG,GAAG,sBAAsB,GAAG,IAAI,CAuE/E"}
1
+ {"version":3,"file":"googleErrors.d.ts","sourceRoot":"","sources":["../../../../../src/services/aiGen/providers/google/googleErrors.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,eAAe,EAGhB,MAAM,0BAA0B,CAAC;AAElC;;;;;;;;;GASG;AACH,wBAAgB,sBAAsB,CAAC,GAAG,EAAE,GAAG,GAAG,eAAe,GAAG,IAAI,CAoJvE;AAED;;;+EAG+E;AAC/E,MAAM,WAAW,sBAAsB;IACrC,4DAA4D;IAC5D,IAAI,EAAE,SAAS,GAAG,MAAM,GAAG,OAAO,CAAC;IACnC,+EAA+E;IAC/E,MAAM,EAAE,MAAM,CAAC;IACf,mFAAmF;IACnF,UAAU,EAAE,MAAM,CAAC;CACpB;AAcD;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAgB,uBAAuB,CAAC,GAAG,EAAE,GAAG,GAAG,sBAAsB,GAAG,IAAI,CAuE/E"}
@@ -49,28 +49,18 @@ function classifyGoogleApiError(err) {
49
49
  if (httpCode === 14 || /high demand/i.test(msg)) {
50
50
  return new errors_1.UserFacingError((0, errors_1.friendlyMessageForCode)(errors_1.USER_FACING_ERROR_CODES.VIDEO_PROVIDER_HIGH_DEMAND), errors_1.USER_FACING_ERROR_CODES.VIDEO_PROVIDER_HIGH_DEMAND);
51
51
  }
52
- // INVALID_ARGUMENT 400 — Veo's "this request shape isn't supported for this
53
- // model variant" family. Two known phrasings, both meaning the same thing:
54
- // - "Your use case is currently not supported" (older)
55
- // - "Unsupported video generation request. Please check the documentation
56
- // for supported usage: …/gemini-api/docs/video" (seen in prod on
57
- // veo-3.1-fast, 2026-07-26 — recurring across many users)
52
+ // INVALID_ARGUMENT 400 — narrow match: only the specific "use case is
53
+ // currently not supported" string, which Veo returns when our request
54
+ // structure doesn't match the chosen model variant's capabilities.
58
55
  // The pre-call guards above (duration=8 for lastFrame/refs) should
59
56
  // prevent the known cases; if we still hit this it's a NEW combo we
60
57
  // haven't profiled — surface as CAPABILITY_MISMATCH so the user gets a
61
58
  // useful message, AND keep the raw provider text in the error so the
62
- // next entry in PROD_FIX_LOG can identify which combo broke.
63
- //
64
- // Why this matters beyond the message: an unclassified 400 is not a
65
- // UserFacingError, so the scene loop treats it as a candidate for the
66
- // transient-retry path and logs it at `error` (which is what pages Slack —
67
- // see errorPaging.ts). A deterministic 400 can never succeed on retry, so
68
- // classifying it here both stops the wasted re-submits and moves it to
69
- // `warn`, since CAPABILITY_MISMATCH is in NON_PAGING_ERROR_CODES.
70
- // Generic 400s (other INVALID_ARGUMENT variants) still surface as `error`
71
- // so a real platform bug isn't muted.
59
+ // next entry in PROD_FIX_LOG can identify which combo broke. Generic
60
+ // 400s (other INVALID_ARGUMENT variants) still surface as `error` so
61
+ // a real platform bug isn't muted.
72
62
  if ((status === "INVALID_ARGUMENT" || httpCode === 400) &&
73
- /use case is currently not supported|unsupported video generation request/i.test(msg)) {
63
+ /use case is currently not supported/i.test(msg)) {
74
64
  return new errors_1.UserFacingError(msg, errors_1.USER_FACING_ERROR_CODES.CAPABILITY_MISMATCH);
75
65
  }
76
66
  // Imagen + Nano-Banana surface Responsible-AI filter rejections as
@@ -1 +1 @@
1
- {"version":3,"file":"kling.service.d.ts","sourceRoot":"","sources":["../../../../../src/services/aiGen/providers/kling/kling.service.ts"],"names":[],"mappings":"AAUA,OAAO,EAAE,wBAAwB,EAAE,MAAM,8BAA8B,CAAC;AACxE,OAAO,EACL,iBAAiB,EACjB,qBAAqB,EACrB,qBAAqB,EACrB,iBAAiB,EACjB,iBAAiB,EAClB,MAAM,UAAU,CAAC;AAyBlB;;;;;;;;;;;;;;GAcG;AACH,MAAM,MAAM,aAAa,GAAG,KAAK,GAAG,QAAQ,CAAC;AAE7C;;;;;GAKG;AACH,qBAAa,YAAa,SAAQ,wBAAwB;IACxD,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAuC;IAC/D,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAW;IAC3C,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAU;IASxC,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAkD;IAC9E,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAmD;;YAWlE,OAAO;IAiCf,aAAa,CACjB,MAAM,EAAE,qBAAqB,GAC5B,OAAO,CAAC,qBAAqB,CAAC;IA0DjC;;;;;;;;OAQG;YACW,kBAAkB;IAgChC;;;;OAIG;YACW,WAAW;YA6BX,iBAAiB;YA6BjB,qBAAqB;YAgCrB,eAAe;YAkDf,mBAAmB;YAgCnB,uBAAuB;IAsI/B,gBAAgB,CAAC,MAAM,EAAE,iBAAiB,GAAG,OAAO,CAAC,iBAAiB,CAAC;IAiB7E;;;;;;;OAOG;IACH,OAAO,CAAC,gBAAgB;IAiBxB,cAAc,IAAI,MAAM;IAexB,aAAa,CAAC,MAAM,EAAE,iBAAiB,GAAG,MAAM;CAGjD"}
1
+ {"version":3,"file":"kling.service.d.ts","sourceRoot":"","sources":["../../../../../src/services/aiGen/providers/kling/kling.service.ts"],"names":[],"mappings":"AAcA,OAAO,EAAE,wBAAwB,EAAE,MAAM,8BAA8B,CAAC;AACxE,OAAO,EACL,iBAAiB,EACjB,qBAAqB,EACrB,qBAAqB,EACrB,iBAAiB,EACjB,iBAAiB,EAClB,MAAM,UAAU,CAAC;AAyBlB;;;;;;;;;;;;;;GAcG;AACH,MAAM,MAAM,aAAa,GAAG,KAAK,GAAG,QAAQ,CAAC;AAE7C;;;;;GAKG;AACH,qBAAa,YAAa,SAAQ,wBAAwB;IACxD,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAuC;IAC/D,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAW;IAC3C,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAU;IASxC,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAkD;IAC9E,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAmD;;YAWlE,OAAO;IAiCf,aAAa,CACjB,MAAM,EAAE,qBAAqB,GAC5B,OAAO,CAAC,qBAAqB,CAAC;IA+EjC;;;;;;;;OAQG;YACW,kBAAkB;IAgChC;;;;OAIG;YACW,WAAW;YA6BX,iBAAiB;YA6BjB,qBAAqB;YAgCrB,eAAe;YAkDf,mBAAmB;YAgCnB,uBAAuB;IAsI/B,gBAAgB,CAAC,MAAM,EAAE,iBAAiB,GAAG,OAAO,CAAC,iBAAiB,CAAC;IAiB7E;;;;;;;OAOG;IACH,OAAO,CAAC,gBAAgB;IAiBxB,cAAc,IAAI,MAAM;IAexB,aAAa,CAAC,MAAM,EAAE,iBAAiB,GAAG,MAAM;CAGjD"}
@@ -64,6 +64,28 @@ class KlingService extends baseAiGenProvider_service_1.BaseAiGenProviderService
64
64
  }
65
65
  async generateVideo(params) {
66
66
  (0, helpers_1.validateParams)(params);
67
+ // Extend is dispatched FIRST, and never silently downgrades.
68
+ //
69
+ // This check used to sit below the special-endpoint branches (avatar,
70
+ // lip-sync, motion-control, omni-video, turbo), so an extend request on any
71
+ // of those models fell into that model's own generate path and produced a
72
+ // BRAND-NEW clip while ignoring sourceTaskId — a silent wrong result, billed
73
+ // in full. kling-v3.0-turbo was the live example: it sits on the API 2.0
74
+ // /tasks surface, which has no video-extend equivalent at all.
75
+ //
76
+ // None of those endpoints accept a generic video-extend task, so the correct
77
+ // behaviour is to reject, not to reinterpret. The request schema already
78
+ // blocks this at the HTTP door; this is the provider-level backstop for the
79
+ // agent and any other in-process caller.
80
+ if (params.endpoint === "extend") {
81
+ if (!(0, aiModels_1.modelSupportsExtend)(params.modelKey)) {
82
+ throw new Error(`${params.modelKey} has no Kling video-extend endpoint — ` +
83
+ "avatar, lip-sync, motion-control, omni-video and v3.0-turbo tasks " +
84
+ "are not accepted by /v1/videos/video-extend. " +
85
+ `Extend-capable: ${[...aiModels_1.EXTEND_CAPABLE_MODELS].join(", ")}.`);
86
+ }
87
+ return this.extendVideo(params);
88
+ }
67
89
  if (params.modelKey === "kling-avatar") {
68
90
  return this.generateAvatarVideo(params);
69
91
  }
@@ -80,9 +102,6 @@ class KlingService extends baseAiGenProvider_service_1.BaseAiGenProviderService
80
102
  if (params.modelKey === "kling-v3.0-turbo") {
81
103
  return this.generateTurboVideo(params);
82
104
  }
83
- if (params.endpoint === "extend") {
84
- return this.extendVideo(params);
85
- }
86
105
  const modelId = aiModels_1.aiModelConfigs[params.modelKey]?.modelId;
87
106
  if (!modelId)
88
107
  throw new Error(`Unknown modelKey: ${params.modelKey}`);
@@ -6,6 +6,13 @@ import type { KlingAuthMode } from "./kling.service";
6
6
  * service's bound requestWithTimeoutRetry) plus the submit timeout, so they
7
7
  * reuse the JWT auth + IPv4-pinned agents without holding a class reference.
8
8
  */
9
+ /**
10
+ * Kling reports clip length as seconds-in-a-string ("5", "10.1"). Parse it
11
+ * defensively: a malformed or absent value must yield `undefined` (billing then
12
+ * falls back to the requested duration) rather than NaN, which would poison
13
+ * every downstream cost calculation. See VideoStatusResult.durationSec.
14
+ */
15
+ export declare function parseKlingDurationSec(raw: unknown): number | undefined;
9
16
  export interface KlingStatusDeps {
10
17
  /** Bound KlingService.requestWithTimeoutRetry. */
11
18
  request: <T>(endpoint: string, method: "POST" | "GET", body?: any, maxAttempts?: number, timeout?: number,
@@ -1 +1 @@
1
- {"version":3,"file":"klingStatus.d.ts","sourceRoot":"","sources":["../../../../../src/services/aiGen/providers/kling/klingStatus.ts"],"names":[],"mappings":"AAMA,OAAO,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,MAAM,UAAU,CAAC;AAChE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAGrD;;;;;GAKG;AACH,MAAM,WAAW,eAAe;IAC9B,kDAAkD;IAClD,OAAO,EAAE,CAAC,CAAC,EACT,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,MAAM,GAAG,KAAK,EACtB,IAAI,CAAC,EAAE,GAAG,EACV,WAAW,CAAC,EAAE,MAAM,EACpB,OAAO,CAAC,EAAE,MAAM;IAChB,4EAA4E;IAC5E,QAAQ,CAAC,EAAE,aAAa,KACrB,OAAO,CAAC,CAAC,CAAC,CAAC;IAChB,oFAAoF;IACpF,eAAe,EAAE,MAAM,CAAC;CACzB;AA4ED;;;;;;;GAOG;AACH,wBAAsB,qBAAqB,CACzC,IAAI,EAAE,eAAe,EACrB,EAAE,IAAI,EAAE,cAAc,EAAE,cAAyB,EAAE,EAAE,iBAAiB,GACrE,OAAO,CAAC,iBAAiB,CAAC,CAuD5B;AAED,wBAAsB,uBAAuB,CAC3C,IAAI,EAAE,eAAe,EACrB,EAAE,IAAI,EAAE,cAAc,EAAE,cAAyB,EAAE,EAAE,iBAAiB,GACrE,OAAO,CAAC,iBAAiB,CAAC,CAyE5B;AAED,wBAAsB,sBAAsB,CAC1C,IAAI,EAAE,eAAe,EACrB,EAAE,IAAI,EAAE,cAAc,EAAE,cAAyB,EAAE,EAAE,iBAAiB,GACrE,OAAO,CAAC,iBAAiB,CAAC,CAqG5B"}
1
+ {"version":3,"file":"klingStatus.d.ts","sourceRoot":"","sources":["../../../../../src/services/aiGen/providers/kling/klingStatus.ts"],"names":[],"mappings":"AAMA,OAAO,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,MAAM,UAAU,CAAC;AAChE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAGrD;;;;;GAKG;AACH;;;;;GAKG;AACH,wBAAgB,qBAAqB,CAAC,GAAG,EAAE,OAAO,GAAG,MAAM,GAAG,SAAS,CAItE;AAED,MAAM,WAAW,eAAe;IAC9B,kDAAkD;IAClD,OAAO,EAAE,CAAC,CAAC,EACT,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,MAAM,GAAG,KAAK,EACtB,IAAI,CAAC,EAAE,GAAG,EACV,WAAW,CAAC,EAAE,MAAM,EACpB,OAAO,CAAC,EAAE,MAAM;IAChB,4EAA4E;IAC5E,QAAQ,CAAC,EAAE,aAAa,KACrB,OAAO,CAAC,CAAC,CAAC,CAAC;IAChB,oFAAoF;IACpF,eAAe,EAAE,MAAM,CAAC;CACzB;AA4ED;;;;;;;GAOG;AACH,wBAAsB,qBAAqB,CACzC,IAAI,EAAE,eAAe,EACrB,EAAE,IAAI,EAAE,cAAc,EAAE,cAAyB,EAAE,EAAE,iBAAiB,GACrE,OAAO,CAAC,iBAAiB,CAAC,CA4D5B;AAED,wBAAsB,uBAAuB,CAC3C,IAAI,EAAE,eAAe,EACrB,EAAE,IAAI,EAAE,cAAc,EAAE,cAAyB,EAAE,EAAE,iBAAiB,GACrE,OAAO,CAAC,iBAAiB,CAAC,CAgF5B;AAED,wBAAsB,sBAAsB,CAC1C,IAAI,EAAE,eAAe,EACrB,EAAE,IAAI,EAAE,cAAc,EAAE,cAAyB,EAAE,EAAE,iBAAiB,GACrE,OAAO,CAAC,iBAAiB,CAAC,CA2G5B"}
@@ -3,6 +3,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.parseKlingDurationSec = parseKlingDurationSec;
6
7
  exports.checkTurboVideoStatus = checkTurboVideoStatus;
7
8
  exports.checkRegularVideoStatus = checkRegularVideoStatus;
8
9
  exports.checkAvatarVideoStatus = checkAvatarVideoStatus;
@@ -12,6 +13,24 @@ const https_1 = __importDefault(require("https"));
12
13
  const types_1 = require("../../../../globals/types");
13
14
  const firebase_1 = require("../../../../libs/firebase");
14
15
  const logger_1 = require("../../../../utils/logger");
16
+ /**
17
+ * Kling status polling + result stashing. Extracted from KlingService so the
18
+ * class stays a thin transport/dispatch layer. These take a `request` dep (the
19
+ * service's bound requestWithTimeoutRetry) plus the submit timeout, so they
20
+ * reuse the JWT auth + IPv4-pinned agents without holding a class reference.
21
+ */
22
+ /**
23
+ * Kling reports clip length as seconds-in-a-string ("5", "10.1"). Parse it
24
+ * defensively: a malformed or absent value must yield `undefined` (billing then
25
+ * falls back to the requested duration) rather than NaN, which would poison
26
+ * every downstream cost calculation. See VideoStatusResult.durationSec.
27
+ */
28
+ function parseKlingDurationSec(raw) {
29
+ if (typeof raw !== "string" && typeof raw !== "number")
30
+ return undefined;
31
+ const parsed = typeof raw === "number" ? raw : Number.parseFloat(raw);
32
+ return Number.isFinite(parsed) && parsed > 0 ? parsed : undefined;
33
+ }
15
34
  /**
16
35
  * Download a finished Kling video and re-host it in GCS, returning a signed
17
36
  * URL. Bounded retry + IPv4 pinning: Kling's CDN hostnames frequently resolve
@@ -102,7 +121,12 @@ async function checkTurboVideoStatus(deps, { task, outputFilename, outputFilePat
102
121
  };
103
122
  }
104
123
  if (taskData.status === "succeeded") {
105
- const videoUrl = taskData.outputs?.find((o) => o.type === "video")?.url;
124
+ const videoOutput = taskData.outputs?.find((o) => o.type === "video");
125
+ const videoUrl = videoOutput?.url;
126
+ // Turbo is priced `perSecond * duration` with NO duration table, and accepts
127
+ // any length 3–15s, so a requested-vs-delivered mismatch bills wrong at
128
+ // every second of drift rather than being absorbed by a {5,10} table.
129
+ const durationSec = parseKlingDurationSec(videoOutput?.duration);
106
130
  if (!videoUrl) {
107
131
  return {
108
132
  status: types_1.EVideoSceneStatus.FAILED,
@@ -110,7 +134,7 @@ async function checkTurboVideoStatus(deps, { task, outputFilename, outputFilePat
110
134
  };
111
135
  }
112
136
  const signedUrl = await stashVideoToGcs(videoUrl, outputFilePath, outputFilename, deps.generateTimeout, { task, surface: "turbo" });
113
- return { videoUrl: signedUrl, status: types_1.EVideoSceneStatus.COMPLETED };
137
+ return { videoUrl: signedUrl, status: types_1.EVideoSceneStatus.COMPLETED, durationSec };
114
138
  }
115
139
  if (taskData.status === "failed") {
116
140
  return {
@@ -154,7 +178,13 @@ async function checkRegularVideoStatus(deps, { task, outputFilename, outputFileP
154
178
  }
155
179
  logger_1.logger.info("Kling checkVideoStatus polled", { task, durationMs: Date.now() - startTime, status: result.data.task_status });
156
180
  if (result.data.task_status === "succeed") {
157
- const videoUrl = result.data.task_result?.videos?.[0]?.url;
181
+ const video = result.data.task_result?.videos?.[0];
182
+ const videoUrl = video?.url;
183
+ // Kling reports how long the clip ACTUALLY is; this was previously read for
184
+ // `.url` only and the length discarded. Carrying it out lets billing
185
+ // reconcile on the delivered length instead of the requested one — the fix
186
+ // for extend overcharging (see VideoStatusResult.durationSec).
187
+ const durationSec = parseKlingDurationSec(video?.duration);
158
188
  if (!videoUrl) {
159
189
  return {
160
190
  status: types_1.EVideoSceneStatus.FAILED,
@@ -177,6 +207,7 @@ async function checkRegularVideoStatus(deps, { task, outputFilename, outputFileP
177
207
  return {
178
208
  videoUrl: signedUrl,
179
209
  status: types_1.EVideoSceneStatus.COMPLETED,
210
+ durationSec,
180
211
  };
181
212
  }
182
213
  if (result.data.task_status === "failed") {
@@ -198,7 +229,12 @@ async function checkAvatarVideoStatus(deps, { task, outputFilename, outputFilePa
198
229
  };
199
230
  }
200
231
  if (result.data.task_status === "succeed") {
201
- const videoUrl = result.data.task_result?.videos?.[0]?.url;
232
+ const video = result.data.task_result?.videos?.[0];
233
+ const videoUrl = video?.url;
234
+ // Avatar/lip-sync length is driven by the driving AUDIO, not by a requested
235
+ // duration — and klingCredits bills it in 5s increments. Reporting the real
236
+ // length is what makes that calculation correct.
237
+ const durationSec = parseKlingDurationSec(video?.duration);
202
238
  if (!videoUrl) {
203
239
  return {
204
240
  status: types_1.EVideoSceneStatus.FAILED,
@@ -246,6 +282,7 @@ async function checkAvatarVideoStatus(deps, { task, outputFilename, outputFilePa
246
282
  return {
247
283
  videoUrl: signedUrl,
248
284
  status: types_1.EVideoSceneStatus.COMPLETED,
285
+ durationSec,
249
286
  };
250
287
  }
251
288
  catch (err) {
@@ -1 +1 @@
1
- {"version":3,"file":"pixverse.service.d.ts","sourceRoot":"","sources":["../../../../../src/services/aiGen/providers/pixverse/pixverse.service.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,wBAAwB,EAAE,MAAM,8BAA8B,CAAC;AACxE,OAAO,EACL,iBAAiB,EACjB,qBAAqB,EACrB,qBAAqB,EACrB,iBAAiB,EACjB,iBAAiB,EAClB,MAAM,UAAU,CAAC;AA0FlB,qBAAa,eAAgB,SAAQ,wBAAwB;IAC3D,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAkD;IAK1E;;;;;;OAMG;YACW,WAAW;IA6CnB,aAAa,CACjB,MAAM,EAAE,qBAAqB,GAC5B,OAAO,CAAC,qBAAqB,CAAC;IAgL3B,gBAAgB,CAAC,EACrB,IAAI,EACJ,cAAc,EACd,cAAyB,GAC1B,EAAE,iBAAiB,GAAG,OAAO,CAAC,iBAAiB,CAAC;IA0FjD,aAAa,CAAC,MAAM,EAAE,iBAAiB,GAAG,MAAM;CAkDjD"}
1
+ {"version":3,"file":"pixverse.service.d.ts","sourceRoot":"","sources":["../../../../../src/services/aiGen/providers/pixverse/pixverse.service.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,wBAAwB,EAAE,MAAM,8BAA8B,CAAC;AACxE,OAAO,EACL,iBAAiB,EACjB,qBAAqB,EACrB,qBAAqB,EACrB,iBAAiB,EACjB,iBAAiB,EAClB,MAAM,UAAU,CAAC;AA+FlB,qBAAa,eAAgB,SAAQ,wBAAwB;IAC3D,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAkD;IAK1E;;;;;;OAMG;YACW,WAAW;IA6CnB,aAAa,CACjB,MAAM,EAAE,qBAAqB,GAC5B,OAAO,CAAC,qBAAqB,CAAC;IAyL3B,gBAAgB,CAAC,EACrB,IAAI,EACJ,cAAc,EACd,cAAyB,GAC1B,EAAE,iBAAiB,GAAG,OAAO,CAAC,iBAAiB,CAAC;IA+GjD,aAAa,CAAC,MAAM,EAAE,iBAAiB,GAAG,MAAM;CAkDjD"}