vidspotai-shared 1.0.95 → 1.0.97

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 (143) hide show
  1. package/lib/globals/aiModels/enums.d.ts +3 -0
  2. package/lib/globals/aiModels/enums.d.ts.map +1 -1
  3. package/lib/globals/aiModels/enums.js +9 -0
  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/hostedAvatars.d.ts +22 -0
  7. package/lib/globals/aiModels/providers/hostedAvatars.d.ts.map +1 -0
  8. package/lib/globals/aiModels/providers/hostedAvatars.js +129 -0
  9. package/lib/globals/aiModels/providers/kling.d.ts.map +1 -1
  10. package/lib/globals/aiModels/providers/kling.js +85 -1
  11. package/lib/globals/creditUnit.d.ts +29 -0
  12. package/lib/globals/creditUnit.d.ts.map +1 -1
  13. package/lib/globals/creditUnit.js +30 -1
  14. package/lib/globals/types.d.ts +58 -1
  15. package/lib/globals/types.d.ts.map +1 -1
  16. package/lib/globals/types.js +61 -1
  17. package/lib/models/demoVideoProject.model.d.ts +244 -0
  18. package/lib/models/demoVideoProject.model.d.ts.map +1 -0
  19. package/lib/models/demoVideoProject.model.js +2 -0
  20. package/lib/models/githubConnection.model.d.ts +35 -0
  21. package/lib/models/githubConnection.model.d.ts.map +1 -0
  22. package/lib/models/githubConnection.model.js +2 -0
  23. package/lib/models/index.d.ts +3 -0
  24. package/lib/models/index.d.ts.map +1 -1
  25. package/lib/models/index.js +3 -0
  26. package/lib/models/outreach.model.d.ts +175 -0
  27. package/lib/models/outreach.model.d.ts.map +1 -0
  28. package/lib/models/outreach.model.js +2 -0
  29. package/lib/models/video.model.d.ts +4 -0
  30. package/lib/models/video.model.d.ts.map +1 -1
  31. package/lib/schemas/brandKit.schema.d.ts +4 -0
  32. package/lib/schemas/brandKit.schema.d.ts.map +1 -1
  33. package/lib/schemas/brandKit.schema.js +10 -0
  34. package/lib/schemas/demoQuestion.schema.d.ts +286 -0
  35. package/lib/schemas/demoQuestion.schema.d.ts.map +1 -0
  36. package/lib/schemas/demoQuestion.schema.js +210 -0
  37. package/lib/schemas/demoSpec.schema.d.ts +109 -6
  38. package/lib/schemas/demoSpec.schema.d.ts.map +1 -1
  39. package/lib/schemas/demoSpec.schema.js +95 -7
  40. package/lib/schemas/index.d.ts +1 -0
  41. package/lib/schemas/index.d.ts.map +1 -1
  42. package/lib/schemas/index.js +1 -0
  43. package/lib/services/agent/providerFallback/chains.d.ts.map +1 -1
  44. package/lib/services/agent/providerFallback/chains.js +9 -1
  45. package/lib/services/agent/taskPoller.d.ts.map +1 -1
  46. package/lib/services/agent/taskPoller.js +17 -6
  47. package/lib/services/agent/tools/generateAvatarVideo.tool.d.ts +21 -9
  48. package/lib/services/agent/tools/generateAvatarVideo.tool.d.ts.map +1 -1
  49. package/lib/services/agent/tools/generateAvatarVideo.tool.js +135 -39
  50. package/lib/services/aiGen/aiGenFactory.service.d.ts +1 -1
  51. package/lib/services/aiGen/aiGenFactory.service.d.ts.map +1 -1
  52. package/lib/services/aiGen/aiGenFactory.service.js +11 -2
  53. package/lib/services/aiGen/providers/kling/kling.service.d.ts +36 -1
  54. package/lib/services/aiGen/providers/kling/kling.service.d.ts.map +1 -1
  55. package/lib/services/aiGen/providers/kling/kling.service.js +62 -5
  56. package/lib/services/aiGen/providers/kling/klingCredits.d.ts.map +1 -1
  57. package/lib/services/aiGen/providers/kling/klingCredits.js +12 -0
  58. package/lib/services/aiGen/providers/kling/klingRequests.d.ts +14 -1
  59. package/lib/services/aiGen/providers/kling/klingRequests.d.ts.map +1 -1
  60. package/lib/services/aiGen/providers/kling/klingRequests.js +44 -0
  61. package/lib/services/aiGen/providers/kling/klingStatus.d.ts +13 -1
  62. package/lib/services/aiGen/providers/kling/klingStatus.d.ts.map +1 -1
  63. package/lib/services/aiGen/providers/kling/klingStatus.js +111 -0
  64. package/lib/services/aiGen/providers/kling/types.d.ts +71 -0
  65. package/lib/services/aiGen/providers/kling/types.d.ts.map +1 -1
  66. package/lib/services/avatarGen/IAvatarVideoProvider.d.ts +136 -0
  67. package/lib/services/avatarGen/IAvatarVideoProvider.d.ts.map +1 -0
  68. package/lib/services/avatarGen/IAvatarVideoProvider.js +42 -0
  69. package/lib/services/avatarGen/avatarProviderFactory.d.ts +18 -0
  70. package/lib/services/avatarGen/avatarProviderFactory.d.ts.map +1 -0
  71. package/lib/services/avatarGen/avatarProviderFactory.js +54 -0
  72. package/lib/services/avatarGen/index.d.ts +6 -0
  73. package/lib/services/avatarGen/index.d.ts.map +1 -0
  74. package/lib/services/avatarGen/index.js +21 -0
  75. package/lib/services/avatarGen/providers/heygenAvatarProvider.d.ts +45 -0
  76. package/lib/services/avatarGen/providers/heygenAvatarProvider.d.ts.map +1 -0
  77. package/lib/services/avatarGen/providers/heygenAvatarProvider.js +190 -0
  78. package/lib/services/avatarGen/providers/klingAvatarProvider.d.ts +32 -0
  79. package/lib/services/avatarGen/providers/klingAvatarProvider.d.ts.map +1 -0
  80. package/lib/services/avatarGen/providers/klingAvatarProvider.js +69 -0
  81. package/lib/services/avatarGen/providers/tavusAvatarProvider.d.ts +56 -0
  82. package/lib/services/avatarGen/providers/tavusAvatarProvider.d.ts.map +1 -0
  83. package/lib/services/avatarGen/providers/tavusAvatarProvider.js +183 -0
  84. package/lib/services/demo/demoAuthor.d.ts +45 -0
  85. package/lib/services/demo/demoAuthor.d.ts.map +1 -0
  86. package/lib/services/demo/demoAuthor.js +75 -0
  87. package/lib/services/demo/demoAuthorNormalize.d.ts +49 -0
  88. package/lib/services/demo/demoAuthorNormalize.d.ts.map +1 -0
  89. package/lib/services/demo/demoAuthorNormalize.js +327 -0
  90. package/lib/services/demo/demoAuthorPrompt.d.ts +102 -0
  91. package/lib/services/demo/demoAuthorPrompt.d.ts.map +1 -0
  92. package/lib/services/demo/demoAuthorPrompt.js +243 -0
  93. package/lib/services/demo/demoCredits.d.ts +20 -0
  94. package/lib/services/demo/demoCredits.d.ts.map +1 -0
  95. package/lib/services/demo/demoCredits.js +51 -0
  96. package/lib/services/demo/demoMediaGuard.d.ts +43 -0
  97. package/lib/services/demo/demoMediaGuard.d.ts.map +1 -0
  98. package/lib/services/demo/demoMediaGuard.js +207 -0
  99. package/lib/services/demo/demoMusic.d.ts +13 -0
  100. package/lib/services/demo/demoMusic.d.ts.map +1 -0
  101. package/lib/services/demo/demoMusic.js +93 -0
  102. package/lib/services/demo/demoPrefill.d.ts +76 -0
  103. package/lib/services/demo/demoPrefill.d.ts.map +1 -0
  104. package/lib/services/demo/demoPrefill.js +435 -0
  105. package/lib/services/demo/demoRevisePrompt.d.ts +17 -0
  106. package/lib/services/demo/demoRevisePrompt.d.ts.map +1 -0
  107. package/lib/services/demo/demoRevisePrompt.js +85 -0
  108. package/lib/services/demo/demoReviser.d.ts +63 -0
  109. package/lib/services/demo/demoReviser.d.ts.map +1 -0
  110. package/lib/services/demo/demoReviser.js +81 -0
  111. package/lib/services/demo/demoScriptPrompt.d.ts +63 -0
  112. package/lib/services/demo/demoScriptPrompt.d.ts.map +1 -0
  113. package/lib/services/demo/demoScriptPrompt.js +234 -0
  114. package/lib/services/demo/demoScriptWriter.d.ts +65 -0
  115. package/lib/services/demo/demoScriptWriter.d.ts.map +1 -0
  116. package/lib/services/demo/demoScriptWriter.js +211 -0
  117. package/lib/services/demo/demoTriage.d.ts +65 -0
  118. package/lib/services/demo/demoTriage.d.ts.map +1 -0
  119. package/lib/services/demo/demoTriage.js +130 -0
  120. package/lib/services/demo/githubOAuth.d.ts +78 -0
  121. package/lib/services/demo/githubOAuth.d.ts.map +1 -0
  122. package/lib/services/demo/githubOAuth.js +209 -0
  123. package/lib/services/demo/githubOAuthState.d.ts +10 -0
  124. package/lib/services/demo/githubOAuthState.d.ts.map +1 -0
  125. package/lib/services/demo/githubOAuthState.js +63 -0
  126. package/lib/services/demo/index.d.ts +24 -0
  127. package/lib/services/demo/index.d.ts.map +1 -0
  128. package/lib/services/demo/index.js +39 -0
  129. package/lib/services/firestore.service.d.ts +11 -0
  130. package/lib/services/firestore.service.d.ts.map +1 -1
  131. package/lib/services/firestore.service.js +24 -0
  132. package/lib/services/index.d.ts +2 -0
  133. package/lib/services/index.d.ts.map +1 -1
  134. package/lib/services/index.js +2 -0
  135. package/lib/services/tts/providers/elevenlabs.service.d.ts.map +1 -1
  136. package/lib/services/tts/providers/elevenlabs.service.js +16 -8
  137. package/lib/utils/index.d.ts +1 -0
  138. package/lib/utils/index.d.ts.map +1 -1
  139. package/lib/utils/index.js +1 -0
  140. package/lib/utils/outreachTemplate.d.ts +46 -0
  141. package/lib/utils/outreachTemplate.d.ts.map +1 -0
  142. package/lib/utils/outreachTemplate.js +174 -0
  143. package/package.json +1 -1
@@ -0,0 +1,190 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.HeyGenAvatarProvider = void 0;
7
+ const axios_1 = __importDefault(require("axios"));
8
+ const aiModels_1 = require("../../../globals/aiModels");
9
+ const types_1 = require("../../../globals/types");
10
+ const logger_1 = require("../../../utils/logger");
11
+ const types_2 = require("../../aiGen/types");
12
+ const IAvatarVideoProvider_1 = require("../IAvatarVideoProvider");
13
+ /**
14
+ * HeyGen avatar engine — a HOSTED engine (the caller picks a HeyGen-hosted
15
+ * avatar + voice and supplies a script; HeyGen does its own TTS + lipsync).
16
+ *
17
+ * Contract (HeyGen API, verified 2026-06-26 against developers.heygen.com +
18
+ * the published OpenAPI spec; v2 endpoints supported through 2026-10-31):
19
+ * • submit : POST /v2/video/generate → { data: { video_id } }
20
+ * • status : GET /v1/video_status.get?video_id=… → { data: { status, video_url } }
21
+ * • avatars: GET /v2/avatars → { data: { avatars: [...] } }
22
+ * • voices : GET /v2/voices → { data: { voices: [...] } }
23
+ * • auth : X-Api-Key header
24
+ *
25
+ * This is a STANDALONE class (not an adapter over an existing service) because
26
+ * HeyGen has no representation in the generic video factory — avatar gen is its
27
+ * own concern (see IAvatarVideoProvider). Transport (auth + timeout + retry) is
28
+ * owned here, mirroring the per-provider pattern KlingService uses.
29
+ *
30
+ * Capabilities: inputMode "hosted", nativeTts true (HeyGen synthesizes), no
31
+ * multi-scene split (one render per script). It also exposes the hosted catalog
32
+ * (ISupportsHostedCatalog) so the UI / agent can list pickable avatars + voices.
33
+ */
34
+ class HeyGenAvatarProvider {
35
+ constructor(modelKey = aiModels_1.EVideoGenModels.HEYGEN_AVATAR) {
36
+ this.modelKey = modelKey;
37
+ this.engine = IAvatarVideoProvider_1.EAvatarEngine.HEYGEN;
38
+ this.capabilities = HeyGenAvatarProvider.CAPABILITIES;
39
+ this.baseUrl = "https://api.heygen.com";
40
+ this.submitTimeout = 120000; // 2 min — submit can be slow under load
41
+ this.statusTimeout = 60000; // 1 min — status should be fast
42
+ if (!process.env.HEYGEN_API_KEY) {
43
+ throw new Error("Missing HEYGEN_API_KEY in environment variables");
44
+ }
45
+ this.apiKey = process.env.HEYGEN_API_KEY;
46
+ }
47
+ async generate(req) {
48
+ if (req.mode !== "hosted") {
49
+ throw new Error(`HeyGenAvatarProvider only supports hosted requests (got "${req.mode}")`);
50
+ }
51
+ // HeyGen renders a 16:9 720p by default; render mode bumps to 1080p on pro.
52
+ const dimension = req.renderMode === types_2.EVideoMode.PROFESSIONAL
53
+ ? { width: 1920, height: 1080 }
54
+ : { width: 1280, height: 720 };
55
+ const body = {
56
+ video_inputs: [
57
+ {
58
+ character: {
59
+ type: "avatar",
60
+ avatar_id: req.hostedAvatarId,
61
+ avatar_style: "normal",
62
+ },
63
+ voice: {
64
+ type: "text",
65
+ input_text: req.script,
66
+ voice_id: req.hostedVoiceId,
67
+ },
68
+ },
69
+ ],
70
+ dimension,
71
+ };
72
+ const data = await this.request("/v2/video/generate", "POST", body, this.submitTimeout);
73
+ const videoId = data?.data?.video_id;
74
+ if (!videoId) {
75
+ throw new Error(`HeyGen submit returned no video_id: ${JSON.stringify(data?.error ?? data)}`);
76
+ }
77
+ return { providerTaskId: videoId };
78
+ }
79
+ async checkStatus(query) {
80
+ const data = await this.requestWithRetry(`/v1/video_status.get?video_id=${encodeURIComponent(query.providerTaskId)}`, "GET", undefined, this.statusTimeout);
81
+ const providerStatus = data?.data?.status;
82
+ // HeyGen status enum: pending | waiting | processing | completed | failed.
83
+ if (providerStatus === "completed") {
84
+ return {
85
+ status: types_1.EVideoSceneStatus.COMPLETED,
86
+ videoUrl: data?.data?.video_url,
87
+ };
88
+ }
89
+ if (providerStatus === "failed") {
90
+ return {
91
+ status: types_1.EVideoSceneStatus.FAILED,
92
+ errorMessage: data?.data?.error?.message ?? data?.message ?? "HeyGen render failed",
93
+ };
94
+ }
95
+ return { status: types_1.EVideoSceneStatus.PENDING };
96
+ }
97
+ // ─── ISupportsHostedCatalog ───────────────────────────────────────────────
98
+ async listAvatars() {
99
+ const data = await this.requestWithRetry("/v2/avatars", "GET", undefined, this.statusTimeout);
100
+ return (data?.data?.avatars ?? []).map((a) => ({
101
+ id: a.avatar_id,
102
+ name: a.avatar_name ?? a.avatar_id,
103
+ previewUrl: a.preview_image_url,
104
+ }));
105
+ }
106
+ async listVoices() {
107
+ const data = await this.requestWithRetry("/v2/voices", "GET", undefined, this.statusTimeout);
108
+ return (data?.data?.voices ?? []).map((v) => ({
109
+ id: v.voice_id,
110
+ name: v.name ?? v.voice_id,
111
+ gender: this.normalizeGender(v.gender),
112
+ accent: v.language,
113
+ previewUrl: v.preview_audio,
114
+ }));
115
+ }
116
+ // ─── Transport ────────────────────────────────────────────────────────────
117
+ normalizeGender(g) {
118
+ const lower = (g ?? "").toLowerCase();
119
+ if (lower === "male")
120
+ return "male";
121
+ if (lower === "female")
122
+ return "female";
123
+ return "neutral";
124
+ }
125
+ async request(endpoint, method, body, timeout = this.submitTimeout) {
126
+ const config = {
127
+ method,
128
+ url: `${this.baseUrl}${endpoint}`,
129
+ headers: {
130
+ "X-Api-Key": this.apiKey,
131
+ "Content-Type": "application/json",
132
+ },
133
+ timeout,
134
+ data: body,
135
+ };
136
+ const response = await axios_1.default.request(config);
137
+ if (response.status < 200 || response.status >= 300) {
138
+ throw new Error(`HeyGen API Error: ${response.status} ${response.statusText} — ${JSON.stringify(response.data)}`);
139
+ }
140
+ return response.data;
141
+ }
142
+ /** Retries transient network/timeout/429 failures with a small backoff. */
143
+ async requestWithRetry(endpoint, method, body, timeout = this.statusTimeout, maxAttempts = 4) {
144
+ for (let attempt = 1; attempt <= maxAttempts; attempt++) {
145
+ try {
146
+ return await this.request(endpoint, method, body, timeout);
147
+ }
148
+ catch (err) {
149
+ const e = err;
150
+ const httpStatus = e.response?.status;
151
+ const isTimeout = e.code === "ECONNABORTED" || (e.message?.includes("timeout") ?? false);
152
+ const isNetworkError = e.code === "ECONNRESET" ||
153
+ e.code === "ECONNREFUSED" ||
154
+ e.code === "ENOTFOUND" ||
155
+ e.code === "ERR_NETWORK";
156
+ const isRateLimit = httpStatus === 429;
157
+ const isRetryable = isTimeout || isNetworkError || isRateLimit;
158
+ if (!isRetryable || attempt === maxAttempts) {
159
+ logger_1.logger.error("HeyGen request failed (non-retryable or max attempts)", {
160
+ endpoint,
161
+ attempt,
162
+ maxAttempts,
163
+ httpStatus,
164
+ errorCode: e.code,
165
+ errorMessage: e.message,
166
+ });
167
+ throw err;
168
+ }
169
+ const backoffMs = isRateLimit ? 10000 : 1000;
170
+ logger_1.logger.warn("HeyGen request failed — retrying", {
171
+ endpoint,
172
+ attempt,
173
+ maxAttempts,
174
+ httpStatus,
175
+ backoffMs,
176
+ });
177
+ await new Promise((r) => setTimeout(r, backoffMs));
178
+ }
179
+ }
180
+ throw new Error("Unreachable");
181
+ }
182
+ }
183
+ exports.HeyGenAvatarProvider = HeyGenAvatarProvider;
184
+ /** Static so callers can branch on capability without constructing (which
185
+ * would require HEYGEN_API_KEY just to read the input mode). */
186
+ HeyGenAvatarProvider.CAPABILITIES = {
187
+ inputMode: "hosted",
188
+ nativeTts: true,
189
+ multiSceneSplit: false,
190
+ };
@@ -0,0 +1,32 @@
1
+ import { EVideoGenModels } from "../../../globals/aiModels";
2
+ import { AvatarCapabilities, AvatarGenerateRequest, AvatarStatusQuery, AvatarStatusResult, AvatarSubmitResult, EAvatarEngine, IAvatarVideoProvider } from "../IAvatarVideoProvider";
3
+ /**
4
+ * Kling avatar engine, behind the IAvatarVideoProvider seam.
5
+ *
6
+ * This is intentionally a THIN ADAPTER over the existing `KlingService`, NOT a
7
+ * reimplementation: Kling's avatar endpoint shares the same JWT auth, IPv4-pinned
8
+ * agents, timeout/retry and status-stash transport as regular Kling video, so the
9
+ * avatar submit/poll logic stays exactly where it already lives and is proven
10
+ * (kling.service.ts `generateAvatarVideo` + klingStatus.ts `checkAvatarVideoStatus`).
11
+ * Migrating callers onto this class changes the seam, not Kling's behavior — that
12
+ * byte-for-byte preservation is the Phase 0a regression gate.
13
+ *
14
+ * Kling is "byo-media": caller supplies a face image; WE supply the audio (TTS is
15
+ * run by the orchestrator), so `nativeTts` is false. Long audio is split into
16
+ * scenes and stitched, so `multiSceneSplit` is true. It exposes no hosted catalog,
17
+ * replica training or dubbing — those optional capability interfaces are simply
18
+ * not implemented (ISP).
19
+ */
20
+ export declare class KlingAvatarProvider implements IAvatarVideoProvider {
21
+ private readonly modelKey;
22
+ /** Static so callers can branch on capability without constructing (which
23
+ * would require Kling API keys just to read the input mode). */
24
+ static readonly CAPABILITIES: AvatarCapabilities;
25
+ readonly engine = EAvatarEngine.KLING;
26
+ readonly capabilities: AvatarCapabilities;
27
+ private readonly kling;
28
+ constructor(modelKey?: EVideoGenModels);
29
+ generate(req: AvatarGenerateRequest): Promise<AvatarSubmitResult>;
30
+ checkStatus(query: AvatarStatusQuery): Promise<AvatarStatusResult>;
31
+ }
32
+ //# sourceMappingURL=klingAvatarProvider.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"klingAvatarProvider.d.ts","sourceRoot":"","sources":["../../../../src/services/avatarGen/providers/klingAvatarProvider.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,2BAA2B,CAAC;AAE5D,OAAO,EACL,kBAAkB,EAClB,qBAAqB,EACrB,iBAAiB,EACjB,kBAAkB,EAClB,kBAAkB,EAClB,aAAa,EACb,oBAAoB,EACrB,MAAM,yBAAyB,CAAC;AAIjC;;;;;;;;;;;;;;;;GAgBG;AACH,qBAAa,mBAAoB,YAAW,oBAAoB;IAe5D,OAAO,CAAC,QAAQ,CAAC,QAAQ;IAd3B;qEACiE;IACjE,MAAM,CAAC,QAAQ,CAAC,YAAY,EAAE,kBAAkB,CAI9C;IAEF,QAAQ,CAAC,MAAM,uBAAuB;IACtC,QAAQ,CAAC,YAAY,qBAAoC;IAEzD,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAe;gBAGlB,QAAQ,GAAE,eAA8C;IAKrE,QAAQ,CAAC,GAAG,EAAE,qBAAqB,GAAG,OAAO,CAAC,kBAAkB,CAAC;IAqBjE,WAAW,CAAC,KAAK,EAAE,iBAAiB,GAAG,OAAO,CAAC,kBAAkB,CAAC;CAczE"}
@@ -0,0 +1,69 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.KlingAvatarProvider = void 0;
4
+ const aiModels_1 = require("../../../globals/aiModels");
5
+ const kling_service_1 = require("../../aiGen/providers/kling/kling.service");
6
+ const IAvatarVideoProvider_1 = require("../IAvatarVideoProvider");
7
+ const DEFAULT_OUTPUT_PATH = "avatar-videos";
8
+ /**
9
+ * Kling avatar engine, behind the IAvatarVideoProvider seam.
10
+ *
11
+ * This is intentionally a THIN ADAPTER over the existing `KlingService`, NOT a
12
+ * reimplementation: Kling's avatar endpoint shares the same JWT auth, IPv4-pinned
13
+ * agents, timeout/retry and status-stash transport as regular Kling video, so the
14
+ * avatar submit/poll logic stays exactly where it already lives and is proven
15
+ * (kling.service.ts `generateAvatarVideo` + klingStatus.ts `checkAvatarVideoStatus`).
16
+ * Migrating callers onto this class changes the seam, not Kling's behavior — that
17
+ * byte-for-byte preservation is the Phase 0a regression gate.
18
+ *
19
+ * Kling is "byo-media": caller supplies a face image; WE supply the audio (TTS is
20
+ * run by the orchestrator), so `nativeTts` is false. Long audio is split into
21
+ * scenes and stitched, so `multiSceneSplit` is true. It exposes no hosted catalog,
22
+ * replica training or dubbing — those optional capability interfaces are simply
23
+ * not implemented (ISP).
24
+ */
25
+ class KlingAvatarProvider {
26
+ constructor(modelKey = aiModels_1.EVideoGenModels.KLING_AVATAR) {
27
+ this.modelKey = modelKey;
28
+ this.engine = IAvatarVideoProvider_1.EAvatarEngine.KLING;
29
+ this.capabilities = KlingAvatarProvider.CAPABILITIES;
30
+ this.kling = new kling_service_1.KlingService();
31
+ }
32
+ async generate(req) {
33
+ if (req.mode !== "byo-media") {
34
+ throw new Error(`KlingAvatarProvider only supports byo-media requests (got "${req.mode}")`);
35
+ }
36
+ // Delegates to the existing Kling avatar path (routes on modelKey ===
37
+ // "kling-avatar" inside generateVideo, before any type-based dispatch — so
38
+ // `type` is intentionally omitted, matching the prior call site).
39
+ const result = await this.kling.generateVideo({
40
+ modelKey: this.modelKey,
41
+ inputImageUrl: req.faceImageUrl,
42
+ inputAudioUrl: req.audioUrl,
43
+ prompt: req.prompt,
44
+ mode: req.renderMode,
45
+ });
46
+ return { providerTaskId: result.task };
47
+ }
48
+ async checkStatus(query) {
49
+ const result = await this.kling.checkVideoStatus({
50
+ task: query.providerTaskId,
51
+ outputFilename: query.outputFilename,
52
+ outputFilePath: query.outputFilePath ?? DEFAULT_OUTPUT_PATH,
53
+ modelKey: this.modelKey,
54
+ });
55
+ return {
56
+ status: result.status,
57
+ videoUrl: result.videoUrl,
58
+ errorMessage: result.errorMessage,
59
+ };
60
+ }
61
+ }
62
+ exports.KlingAvatarProvider = KlingAvatarProvider;
63
+ /** Static so callers can branch on capability without constructing (which
64
+ * would require Kling API keys just to read the input mode). */
65
+ KlingAvatarProvider.CAPABILITIES = {
66
+ inputMode: "byo-media",
67
+ nativeTts: false,
68
+ multiSceneSplit: true,
69
+ };
@@ -0,0 +1,56 @@
1
+ import { EVideoGenModels } from "../../../globals/aiModels";
2
+ import { AvatarCapabilities, AvatarGenerateRequest, AvatarOption, AvatarStatusQuery, AvatarStatusResult, AvatarSubmitResult, ConsentRecord, EAvatarEngine, IAvatarVideoProvider, ISupportsHostedCatalog, ISupportsReplica, VoiceOption } from "../IAvatarVideoProvider";
3
+ /**
4
+ * Tavus avatar engine — a HOSTED engine driven by a trained "replica"
5
+ * (Tavus's term for a likeness model). The caller supplies a replica_id + a
6
+ * script; Tavus synthesizes the voice (the replica's cloned voice) and lipsyncs
7
+ * natively. Tavus's differentiator is multilingual / dubbing — see the dubbing
8
+ * note at the bottom of this class.
9
+ *
10
+ * Contract (Tavus API, verified 2026-06-26 against docs.tavus.io):
11
+ * • submit : POST /v2/videos { replica_id, script, video_name? }
12
+ * → { video_id, status, hosted_url }
13
+ * • status : GET /v2/videos/{id} → { status: queued|generating|ready|
14
+ * deleted|error, download_url,
15
+ * stream_url, hosted_url, status_details }
16
+ * • replicas: POST /v2/replicas { train_video_url, consent_video_url?,
17
+ * replica_name, callback_url? }
18
+ * → { replica_id, status }
19
+ * GET /v2/replicas → { data: [{ replica_id, replica_name,
20
+ * status, thumbnail_video_url }] }
21
+ * • auth : x-api-key header
22
+ *
23
+ * For Tavus the hosted "avatar" IS the replica, so a hosted request maps
24
+ * `hostedAvatarId` → `replica_id` (`hostedVoiceId` is unused; the replica owns
25
+ * its voice). Standalone class — Tavus has no place in the generic video
26
+ * factory; avatar gen is its own concern (see IAvatarVideoProvider).
27
+ */
28
+ export declare class TavusAvatarProvider implements IAvatarVideoProvider, ISupportsHostedCatalog, ISupportsReplica {
29
+ private readonly modelKey;
30
+ /** Static so callers can branch on capability without constructing (which
31
+ * would require TAVUS_API_KEY just to read the input mode). */
32
+ static readonly CAPABILITIES: AvatarCapabilities;
33
+ readonly engine = EAvatarEngine.TAVUS;
34
+ readonly capabilities: AvatarCapabilities;
35
+ private readonly baseUrl;
36
+ private readonly apiKey;
37
+ private readonly submitTimeout;
38
+ private readonly statusTimeout;
39
+ constructor(modelKey?: EVideoGenModels);
40
+ generate(req: AvatarGenerateRequest): Promise<AvatarSubmitResult>;
41
+ checkStatus(query: AvatarStatusQuery): Promise<AvatarStatusResult>;
42
+ createReplica(trainingMediaUrl: string, consent: ConsentRecord): Promise<{
43
+ replicaId: string;
44
+ }>;
45
+ /** For Tavus the catalog of "avatars" is the account's trained replicas. */
46
+ listAvatars(): Promise<AvatarOption[]>;
47
+ /**
48
+ * Tavus voices are bound to the replica (its cloned voice), so there is no
49
+ * standalone voice catalog to pick from — return empty. Hosted requests leave
50
+ * `hostedVoiceId` unused for Tavus.
51
+ */
52
+ listVoices(): Promise<VoiceOption[]>;
53
+ private request;
54
+ private requestWithRetry;
55
+ }
56
+ //# sourceMappingURL=tavusAvatarProvider.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tavusAvatarProvider.d.ts","sourceRoot":"","sources":["../../../../src/services/avatarGen/providers/tavusAvatarProvider.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,eAAe,EAAE,MAAM,2BAA2B,CAAC;AAG5D,OAAO,EACL,kBAAkB,EAClB,qBAAqB,EACrB,YAAY,EACZ,iBAAiB,EACjB,kBAAkB,EAClB,kBAAkB,EAClB,aAAa,EACb,aAAa,EACb,oBAAoB,EACpB,sBAAsB,EACtB,gBAAgB,EAChB,WAAW,EACZ,MAAM,yBAAyB,CAAC;AAEjC;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,qBAAa,mBACX,YAAW,oBAAoB,EAAE,sBAAsB,EAAE,gBAAgB;IAmBvE,OAAO,CAAC,QAAQ,CAAC,QAAQ;IAjB3B;oEACgE;IAChE,MAAM,CAAC,QAAQ,CAAC,YAAY,EAAE,kBAAkB,CAI9C;IAEF,QAAQ,CAAC,MAAM,uBAAuB;IACtC,QAAQ,CAAC,YAAY,qBAAoC;IAEzD,OAAO,CAAC,QAAQ,CAAC,OAAO,CAA0B;IAClD,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAS;IAChC,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAW;IACzC,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAU;gBAGrB,QAAQ,GAAE,eAA8C;IAQrE,QAAQ,CAAC,GAAG,EAAE,qBAAqB,GAAG,OAAO,CAAC,kBAAkB,CAAC;IA4BjE,WAAW,CAAC,KAAK,EAAE,iBAAiB,GAAG,OAAO,CAAC,kBAAkB,CAAC;IAiClE,aAAa,CACjB,gBAAgB,EAAE,MAAM,EACxB,OAAO,EAAE,aAAa,GACrB,OAAO,CAAC;QAAE,SAAS,EAAE,MAAM,CAAA;KAAE,CAAC;IA2BjC,4EAA4E;IACtE,WAAW,IAAI,OAAO,CAAC,YAAY,EAAE,CAAC;IAgB5C;;;;OAIG;IACG,UAAU,IAAI,OAAO,CAAC,WAAW,EAAE,CAAC;YAM5B,OAAO;YA0BP,gBAAgB;CAiE/B"}
@@ -0,0 +1,183 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.TavusAvatarProvider = void 0;
7
+ const axios_1 = __importDefault(require("axios"));
8
+ const aiModels_1 = require("../../../globals/aiModels");
9
+ const types_1 = require("../../../globals/types");
10
+ const logger_1 = require("../../../utils/logger");
11
+ const IAvatarVideoProvider_1 = require("../IAvatarVideoProvider");
12
+ /**
13
+ * Tavus avatar engine — a HOSTED engine driven by a trained "replica"
14
+ * (Tavus's term for a likeness model). The caller supplies a replica_id + a
15
+ * script; Tavus synthesizes the voice (the replica's cloned voice) and lipsyncs
16
+ * natively. Tavus's differentiator is multilingual / dubbing — see the dubbing
17
+ * note at the bottom of this class.
18
+ *
19
+ * Contract (Tavus API, verified 2026-06-26 against docs.tavus.io):
20
+ * • submit : POST /v2/videos { replica_id, script, video_name? }
21
+ * → { video_id, status, hosted_url }
22
+ * • status : GET /v2/videos/{id} → { status: queued|generating|ready|
23
+ * deleted|error, download_url,
24
+ * stream_url, hosted_url, status_details }
25
+ * • replicas: POST /v2/replicas { train_video_url, consent_video_url?,
26
+ * replica_name, callback_url? }
27
+ * → { replica_id, status }
28
+ * GET /v2/replicas → { data: [{ replica_id, replica_name,
29
+ * status, thumbnail_video_url }] }
30
+ * • auth : x-api-key header
31
+ *
32
+ * For Tavus the hosted "avatar" IS the replica, so a hosted request maps
33
+ * `hostedAvatarId` → `replica_id` (`hostedVoiceId` is unused; the replica owns
34
+ * its voice). Standalone class — Tavus has no place in the generic video
35
+ * factory; avatar gen is its own concern (see IAvatarVideoProvider).
36
+ */
37
+ class TavusAvatarProvider {
38
+ constructor(modelKey = aiModels_1.EVideoGenModels.TAVUS_AVATAR) {
39
+ this.modelKey = modelKey;
40
+ this.engine = IAvatarVideoProvider_1.EAvatarEngine.TAVUS;
41
+ this.capabilities = TavusAvatarProvider.CAPABILITIES;
42
+ this.baseUrl = "https://tavusapi.com";
43
+ this.submitTimeout = 120000;
44
+ this.statusTimeout = 60000;
45
+ if (!process.env.TAVUS_API_KEY) {
46
+ throw new Error("Missing TAVUS_API_KEY in environment variables");
47
+ }
48
+ this.apiKey = process.env.TAVUS_API_KEY;
49
+ }
50
+ async generate(req) {
51
+ if (req.mode !== "hosted") {
52
+ throw new Error(`TavusAvatarProvider only supports hosted requests (got "${req.mode}")`);
53
+ }
54
+ const body = {
55
+ replica_id: req.hostedAvatarId, // Tavus: the hosted avatar IS the replica
56
+ script: req.script,
57
+ video_name: `vidspot-${req.hostedAvatarId}-${req.script.slice(0, 24)}`,
58
+ };
59
+ const data = await this.request("/v2/videos", "POST", body, this.submitTimeout);
60
+ const videoId = data?.video_id;
61
+ if (!videoId) {
62
+ throw new Error(`Tavus submit returned no video_id: ${JSON.stringify(data?.message ?? data)}`);
63
+ }
64
+ return { providerTaskId: videoId };
65
+ }
66
+ async checkStatus(query) {
67
+ const data = await this.requestWithRetry(`/v2/videos/${encodeURIComponent(query.providerTaskId)}`, "GET", undefined, this.statusTimeout);
68
+ // Tavus status enum: queued | generating | ready | deleted | error.
69
+ if (data?.status === "ready") {
70
+ return {
71
+ status: types_1.EVideoSceneStatus.COMPLETED,
72
+ // Prefer the direct download URL; fall back to stream/hosted.
73
+ videoUrl: data.download_url ?? data.stream_url ?? data.hosted_url,
74
+ };
75
+ }
76
+ if (data?.status === "error" || data?.status === "deleted") {
77
+ return {
78
+ status: types_1.EVideoSceneStatus.FAILED,
79
+ errorMessage: data.status_details ?? `Tavus render ${data.status}`,
80
+ };
81
+ }
82
+ return { status: types_1.EVideoSceneStatus.PENDING };
83
+ }
84
+ // ─── ISupportsReplica ─────────────────────────────────────────────────────
85
+ async createReplica(trainingMediaUrl, consent) {
86
+ const body = {
87
+ train_video_url: trainingMediaUrl,
88
+ replica_name: `vidspot-${consent.userId}-${consent.capturedAt}`,
89
+ // Tavus accepts a separate consent clip; we pass the same training media
90
+ // when no dedicated consent video exists (the statement is recorded in
91
+ // our own ConsentRecord audit trail regardless).
92
+ consent_video_url: trainingMediaUrl,
93
+ };
94
+ const data = await this.request("/v2/replicas", "POST", body, this.submitTimeout);
95
+ if (!data?.replica_id) {
96
+ throw new Error(`Tavus createReplica returned no replica_id: ${JSON.stringify(data)}`);
97
+ }
98
+ return { replicaId: data.replica_id };
99
+ }
100
+ // ─── ISupportsHostedCatalog ───────────────────────────────────────────────
101
+ /** For Tavus the catalog of "avatars" is the account's trained replicas. */
102
+ async listAvatars() {
103
+ const data = await this.requestWithRetry("/v2/replicas", "GET", undefined, this.statusTimeout);
104
+ return (data?.data ?? []).map((r) => ({
105
+ id: r.replica_id,
106
+ name: r.replica_name ?? r.replica_id,
107
+ previewUrl: r.thumbnail_video_url,
108
+ }));
109
+ }
110
+ /**
111
+ * Tavus voices are bound to the replica (its cloned voice), so there is no
112
+ * standalone voice catalog to pick from — return empty. Hosted requests leave
113
+ * `hostedVoiceId` unused for Tavus.
114
+ */
115
+ async listVoices() {
116
+ return [];
117
+ }
118
+ // ─── Transport ────────────────────────────────────────────────────────────
119
+ async request(endpoint, method, body, timeout = this.submitTimeout) {
120
+ const config = {
121
+ method,
122
+ url: `${this.baseUrl}${endpoint}`,
123
+ headers: {
124
+ "x-api-key": this.apiKey,
125
+ "Content-Type": "application/json",
126
+ },
127
+ timeout,
128
+ data: body,
129
+ };
130
+ const response = await axios_1.default.request(config);
131
+ if (response.status < 200 || response.status >= 300) {
132
+ throw new Error(`Tavus API Error: ${response.status} ${response.statusText} — ${JSON.stringify(response.data)}`);
133
+ }
134
+ return response.data;
135
+ }
136
+ async requestWithRetry(endpoint, method, body, timeout = this.statusTimeout, maxAttempts = 4) {
137
+ for (let attempt = 1; attempt <= maxAttempts; attempt++) {
138
+ try {
139
+ return await this.request(endpoint, method, body, timeout);
140
+ }
141
+ catch (err) {
142
+ const e = err;
143
+ const httpStatus = e.response?.status;
144
+ const isTimeout = e.code === "ECONNABORTED" || (e.message?.includes("timeout") ?? false);
145
+ const isNetworkError = e.code === "ECONNRESET" ||
146
+ e.code === "ECONNREFUSED" ||
147
+ e.code === "ENOTFOUND" ||
148
+ e.code === "ERR_NETWORK";
149
+ const isRateLimit = httpStatus === 429;
150
+ const isRetryable = isTimeout || isNetworkError || isRateLimit;
151
+ if (!isRetryable || attempt === maxAttempts) {
152
+ logger_1.logger.error("Tavus request failed (non-retryable or max attempts)", {
153
+ endpoint,
154
+ attempt,
155
+ maxAttempts,
156
+ httpStatus,
157
+ errorCode: e.code,
158
+ errorMessage: e.message,
159
+ });
160
+ throw err;
161
+ }
162
+ const backoffMs = isRateLimit ? 10000 : 1000;
163
+ logger_1.logger.warn("Tavus request failed — retrying", {
164
+ endpoint,
165
+ attempt,
166
+ maxAttempts,
167
+ httpStatus,
168
+ backoffMs,
169
+ });
170
+ await new Promise((r) => setTimeout(r, backoffMs));
171
+ }
172
+ }
173
+ throw new Error("Unreachable");
174
+ }
175
+ }
176
+ exports.TavusAvatarProvider = TavusAvatarProvider;
177
+ /** Static so callers can branch on capability without constructing (which
178
+ * would require TAVUS_API_KEY just to read the input mode). */
179
+ TavusAvatarProvider.CAPABILITIES = {
180
+ inputMode: "hosted",
181
+ nativeTts: true,
182
+ multiSceneSplit: false,
183
+ };
@@ -0,0 +1,45 @@
1
+ import { LlmCaller } from "../agent/llmCaller";
2
+ import { ModelChoice, ModelRouter } from "../agent/modelRouter";
3
+ import { DemoAuthorInput } from "./demoAuthorPrompt";
4
+ import { NormalizeResult } from "./demoAuthorNormalize";
5
+ export interface DemoAuthorDeps {
6
+ /** LLM caller. Defaults to the host-wired dispatcher. */
7
+ llm?: LlmCaller;
8
+ /** Model router. Defaults to DEFAULT_MODEL_ROUTING. */
9
+ router?: ModelRouter;
10
+ /** Route the planner to the premium tier (Opus) for higher-fidelity authoring. */
11
+ premium?: boolean;
12
+ /** Sampling temperature. Creative-but-structured default. */
13
+ temperature?: number;
14
+ /** Max output tokens — a full DemoSpec with rich scenes can be large. */
15
+ maxTokens?: number;
16
+ /**
17
+ * Allow the planner to propose Layer-B `custom` scenes (with an `intent`
18
+ * brief). Threaded from the worker's `DEMO_CODEGEN_ENABLED` gate; default
19
+ * false, so the author never emits a `custom` scene the pipeline can't fill.
20
+ */
21
+ allowCustomScenes?: boolean;
22
+ /**
23
+ * Allow the planner to commission AI-generated media (`generatedAssets` +
24
+ * `gen://` refs). Default true; the worker passes false only under the
25
+ * DEMO_GEN_ASSETS_DISABLED kill-switch.
26
+ */
27
+ allowGeneratedAssets?: boolean;
28
+ }
29
+ export interface DemoAuthorResult extends NormalizeResult {
30
+ /** Model that produced the spec (primary or fallback). */
31
+ modelUsed: ModelChoice;
32
+ usedFallback?: boolean;
33
+ usage?: {
34
+ promptTokens?: number;
35
+ completionTokens?: number;
36
+ };
37
+ }
38
+ /**
39
+ * Author a render-ready DemoSpec from inputs. Pure orchestration: build the
40
+ * prompt, attach screenshots as vision, call `.structured(DemoSpec)`, then
41
+ * normalize. Throws if the caller is unconfigured or the model returns an
42
+ * unparseable / scene-less spec (the normalize pass is the last guard).
43
+ */
44
+ export declare function authorDemoSpec(input: DemoAuthorInput, deps?: DemoAuthorDeps): Promise<DemoAuthorResult>;
45
+ //# sourceMappingURL=demoAuthor.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"demoAuthor.d.ts","sourceRoot":"","sources":["../../../src/services/demo/demoAuthor.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAC;AAE/C,OAAO,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AAChE,OAAO,EACL,eAAe,EAGhB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAAE,eAAe,EAAqB,MAAM,uBAAuB,CAAC;AAqB3E,MAAM,WAAW,cAAc;IAC7B,yDAAyD;IACzD,GAAG,CAAC,EAAE,SAAS,CAAC;IAChB,uDAAuD;IACvD,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB,kFAAkF;IAClF,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,6DAA6D;IAC7D,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,yEAAyE;IACzE,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;;;;OAIG;IACH,iBAAiB,CAAC,EAAE,OAAO,CAAC;IAC5B;;;;OAIG;IACH,oBAAoB,CAAC,EAAE,OAAO,CAAC;CAChC;AAED,MAAM,WAAW,gBAAiB,SAAQ,eAAe;IACvD,0DAA0D;IAC1D,SAAS,EAAE,WAAW,CAAC;IACvB,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,KAAK,CAAC,EAAE;QAAE,YAAY,CAAC,EAAE,MAAM,CAAC;QAAC,gBAAgB,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;CAC9D;AAED;;;;;GAKG;AACH,wBAAsB,cAAc,CAClC,KAAK,EAAE,eAAe,EACtB,IAAI,GAAE,cAAmB,GACxB,OAAO,CAAC,gBAAgB,CAAC,CAgD3B"}