vidspotai-shared 1.0.115 → 1.0.117
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/libs/firebase.d.ts +11 -0
- package/lib/libs/firebase.d.ts.map +1 -1
- package/lib/libs/firebase.js +18 -0
- package/lib/libs/storage/MediaStorage.d.ts +14 -0
- package/lib/libs/storage/MediaStorage.d.ts.map +1 -1
- package/lib/libs/storage/cdnUrl.d.ts +26 -0
- package/lib/libs/storage/cdnUrl.d.ts.map +1 -1
- package/lib/libs/storage/cdnUrl.js +29 -0
- package/lib/services/agent/bibleImageVision.d.ts.map +1 -1
- package/lib/services/agent/bibleImageVision.js +2 -1
- package/lib/services/aiGen/providers/google/google.service.d.ts.map +1 -1
- package/lib/services/aiGen/providers/google/google.service.js +6 -1
- package/lib/services/aiGen/providers/google/googleFetch.d.ts.map +1 -1
- package/lib/services/aiGen/providers/google/googleFetch.js +2 -1
- package/lib/services/aiGen/providers/openai/openai.service.js +2 -2
- package/lib/services/aiGen/providers/pixverse/pixverse.service.d.ts.map +1 -1
- package/lib/services/aiGen/providers/pixverse/pixverse.service.js +6 -1
- package/lib/services/asr/assemblyai.service.d.ts.map +1 -1
- package/lib/services/asr/assemblyai.service.js +7 -1
- package/lib/services/asr/whisper.service.d.ts.map +1 -1
- package/lib/services/asr/whisper.service.js +5 -1
- package/lib/services/translation/translation.service.d.ts.map +1 -1
- package/lib/services/translation/translation.service.js +86 -56
- package/package.json +1 -1
package/lib/libs/firebase.d.ts
CHANGED
|
@@ -4,4 +4,15 @@ export declare const firestore: admin.firestore.Firestore;
|
|
|
4
4
|
export declare const FieldValue: typeof FirebaseFirestore.FieldValue;
|
|
5
5
|
export declare const storage: import("firebase-admin/lib/storage/storage").Storage;
|
|
6
6
|
export declare function getBucket(): import("@google-cloud/storage").Bucket;
|
|
7
|
+
/**
|
|
8
|
+
* OAuth2 access token for the service account backing this Admin app.
|
|
9
|
+
* Scoped by firebase-admin to include cloud-platform + identitytoolkit
|
|
10
|
+
* (see firebase-admin's credential-internal.js), so it's valid against any
|
|
11
|
+
* Google API the service account has IAM permission for — not just the
|
|
12
|
+
* Admin SDK's own wrapped calls. Use this instead of minting a token by hand
|
|
13
|
+
* when a check needs a raw REST call the Admin SDK doesn't expose (e.g. the
|
|
14
|
+
* Identity Toolkit admin/v2 config endpoint, which the Node SDK's
|
|
15
|
+
* ProjectConfig doesn't surface `authorizedDomains` on).
|
|
16
|
+
*/
|
|
17
|
+
export declare function getGoogleAccessToken(): Promise<string>;
|
|
7
18
|
//# sourceMappingURL=firebase.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"firebase.d.ts","sourceRoot":"","sources":["../../src/libs/firebase.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,gBAAgB,CAAC;AAwBxC,eAAO,MAAM,IAAI,6CAAe,CAAC;AACjC,eAAO,MAAM,SAAS,2BAAoB,CAAC;AAE3C,eAAO,MAAM,UAAU,qCAA6B,CAAC;AACrD,eAAO,MAAM,OAAO,sDAAkB,CAAC;AAEvC,wBAAgB,SAAS,2CAExB"}
|
|
1
|
+
{"version":3,"file":"firebase.d.ts","sourceRoot":"","sources":["../../src/libs/firebase.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,gBAAgB,CAAC;AAwBxC,eAAO,MAAM,IAAI,6CAAe,CAAC;AACjC,eAAO,MAAM,SAAS,2BAAoB,CAAC;AAE3C,eAAO,MAAM,UAAU,qCAA6B,CAAC;AACrD,eAAO,MAAM,OAAO,sDAAkB,CAAC;AAEvC,wBAAgB,SAAS,2CAExB;AAED;;;;;;;;;GASG;AACH,wBAAsB,oBAAoB,IAAI,OAAO,CAAC,MAAM,CAAC,CAM5D"}
|
package/lib/libs/firebase.js
CHANGED
|
@@ -35,6 +35,7 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
36
|
exports.storage = exports.FieldValue = exports.firestore = exports.auth = void 0;
|
|
37
37
|
exports.getBucket = getBucket;
|
|
38
|
+
exports.getGoogleAccessToken = getGoogleAccessToken;
|
|
38
39
|
const admin = __importStar(require("firebase-admin"));
|
|
39
40
|
const dotenv = __importStar(require("dotenv"));
|
|
40
41
|
dotenv.config();
|
|
@@ -64,3 +65,20 @@ exports.storage = admin.storage();
|
|
|
64
65
|
function getBucket() {
|
|
65
66
|
return exports.storage.bucket();
|
|
66
67
|
}
|
|
68
|
+
/**
|
|
69
|
+
* OAuth2 access token for the service account backing this Admin app.
|
|
70
|
+
* Scoped by firebase-admin to include cloud-platform + identitytoolkit
|
|
71
|
+
* (see firebase-admin's credential-internal.js), so it's valid against any
|
|
72
|
+
* Google API the service account has IAM permission for — not just the
|
|
73
|
+
* Admin SDK's own wrapped calls. Use this instead of minting a token by hand
|
|
74
|
+
* when a check needs a raw REST call the Admin SDK doesn't expose (e.g. the
|
|
75
|
+
* Identity Toolkit admin/v2 config endpoint, which the Node SDK's
|
|
76
|
+
* ProjectConfig doesn't surface `authorizedDomains` on).
|
|
77
|
+
*/
|
|
78
|
+
async function getGoogleAccessToken() {
|
|
79
|
+
const token = await admin.app().options.credential?.getAccessToken();
|
|
80
|
+
if (!token?.access_token) {
|
|
81
|
+
throw new Error("Failed to obtain a Google access token from the Firebase Admin credential");
|
|
82
|
+
}
|
|
83
|
+
return token.access_token;
|
|
84
|
+
}
|
|
@@ -35,6 +35,20 @@
|
|
|
35
35
|
* `mirrorFromReadable` below for a best-effort R2 copy. Those still write
|
|
36
36
|
* real bytes to GCS today — converting them is a separate, larger effort
|
|
37
37
|
* (see the notes doc §9).
|
|
38
|
+
*
|
|
39
|
+
* ⚠️ RE-DOWNLOADING a URL this returned, anywhere else in the backend (a
|
|
40
|
+
* provider re-fetching a bible reference image, a pipeline step re-fetching a
|
|
41
|
+
* scene clip to post-process it, ASR re-fetching an audio URL, handing a URL
|
|
42
|
+
* to an external API that dereferences it server-side, etc): NEVER call raw
|
|
43
|
+
* `fetch()`/`axios.get()` on it. Since the flip above, it's GCS-shaped but
|
|
44
|
+
* R2-backed, so a direct fetch against storage.googleapis.com 404s — it only
|
|
45
|
+
* resolves through the CDN Worker's R2 read-through. Use `fetchOwnMedia()`
|
|
46
|
+
* (drop-in `fetch()` replacement) when you're doing the fetching yourself, or
|
|
47
|
+
* `toCdnUrl(url, { visibility: "private" })` when you're just embedding the
|
|
48
|
+
* URL string into a payload for someone else to fetch — both in ./cdnUrl,
|
|
49
|
+
* both safe no-ops on a genuinely external URL. This is not hypothetical:
|
|
50
|
+
* ~10 independent call sites had each rolled their own raw download and
|
|
51
|
+
* broke in production the same day this flipped (see notes doc §9).
|
|
38
52
|
*/
|
|
39
53
|
import type { Readable } from "stream";
|
|
40
54
|
import type { IStorageBackend } from "./StorageBackend";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"MediaStorage.d.ts","sourceRoot":"","sources":["../../../src/libs/storage/MediaStorage.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"MediaStorage.d.ts","sourceRoot":"","sources":["../../../src/libs/storage/MediaStorage.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmDG;AACH,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,QAAQ,CAAC;AAGvC,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAC;AAGxD;;;;GAIG;AACH,wBAAgB,gBAAgB,IAAI,OAAO,CAE1C;AAED,qBAAa,YAAY;IACvB,iEAAiE;IACjE,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAkB;IACxC,0EAA0E;IAC1E,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAkB;gBAEhC,KAAK,EAAE,eAAe,EAAE,SAAS,EAAE,eAAe;IAK9D;;;;;;OAMG;IACG,GAAG,CACP,UAAU,EAAE,MAAM,EAClB,IAAI,EAAE,MAAM,EACZ,WAAW,EAAE,MAAM,GAClB,OAAO,CAAC,MAAM,CAAC;IAKlB;;;;;OAKG;IACG,WAAW,CACf,UAAU,EAAE,MAAM,EAClB,SAAS,EAAE,MAAM,EACjB,WAAW,EAAE,MAAM,GAClB,OAAO,CAAC;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC;IAMzC;;;;;;;OAOG;IACG,WAAW,CACf,UAAU,EAAE,MAAM,EAClB,IAAI,EAAE,MAAM,EACZ,WAAW,EAAE,MAAM,GAClB,OAAO,CAAC,IAAI,CAAC;IAQhB;;;;;OAKG;IACG,cAAc,CAClB,UAAU,EAAE,MAAM,EAClB,SAAS,EAAE,MAAM,EACjB,WAAW,EAAE,MAAM,GAClB,OAAO,CAAC,IAAI,CAAC;IAQhB;;;;;;OAMG;IACG,kBAAkB,CACtB,UAAU,EAAE,MAAM,EAClB,MAAM,EAAE,QAAQ,EAChB,WAAW,EAAE,MAAM,EACnB,IAAI,EAAE,MAAM,GACX,OAAO,CAAC,IAAI,CAAC;IAShB,iDAAiD;IACjD,aAAa,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAIlD;;;;;;;;OAQG;IACG,MAAM,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAI/C;;;;;OAKG;YACW,gBAAgB;CAa/B;AAED,8EAA8E;AAC9E,eAAO,MAAM,YAAY,cAAsD,CAAC"}
|
|
@@ -24,4 +24,30 @@ export type MediaVisibility = "public" | "private";
|
|
|
24
24
|
export declare function toCdnUrl(url: string, opts?: {
|
|
25
25
|
visibility?: MediaVisibility;
|
|
26
26
|
}): string;
|
|
27
|
+
/**
|
|
28
|
+
* The sanctioned way to RE-DOWNLOAD a URL this backend itself minted via
|
|
29
|
+
* `mediaStorage.put()`/`putFromFile()`, from anywhere in the backend (a
|
|
30
|
+
* provider re-fetching a bible reference image, a pipeline step re-fetching a
|
|
31
|
+
* scene clip to post-process it, etc).
|
|
32
|
+
*
|
|
33
|
+
* Why this exists: since the Stage 2 R2-primary flip (see MediaStorage.ts's
|
|
34
|
+
* class doc), those URLs are GCS-signed-SHAPED but R2-BACKED — the object no
|
|
35
|
+
* longer exists at the literal storage.googleapis.com address, only under the
|
|
36
|
+
* CDN Worker's R2 read-through. A plain `fetch(url)` on one 404s. This
|
|
37
|
+
* wrapper is the fix, applied once, here — use it instead of a raw `fetch()`
|
|
38
|
+
* so a future call site can't reintroduce the same bug by forgetting to
|
|
39
|
+
* rewrite the URL first (which is exactly how this broke: ~10 independent
|
|
40
|
+
* fetch/axios call sites had each rolled their own download, see
|
|
41
|
+
* notes/R2_MEDIA_MIGRATION_PLAN.md §9).
|
|
42
|
+
*
|
|
43
|
+
* Safe no-op passthrough for a genuinely external URL (AI provider output,
|
|
44
|
+
* stock API, etc) — `toCdnUrl` only rewrites our own host prefixes.
|
|
45
|
+
*
|
|
46
|
+
* Not applicable when WE aren't the one fetching — e.g. handing a URL to an
|
|
47
|
+
* external API that dereferences it server-side (AssemblyAI's `audio_url`,
|
|
48
|
+
* Veo's `video.uri`, PixVerse's `image_url`). Those need the same rewrite,
|
|
49
|
+
* but via `toCdnUrl()` directly on the string embedded in the request, since
|
|
50
|
+
* there's no local `fetch()` call to wrap.
|
|
51
|
+
*/
|
|
52
|
+
export declare function fetchOwnMedia(url: string, init?: RequestInit): Promise<Response>;
|
|
27
53
|
//# sourceMappingURL=cdnUrl.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cdnUrl.d.ts","sourceRoot":"","sources":["../../../src/libs/storage/cdnUrl.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAeH,MAAM,MAAM,eAAe,GAAG,QAAQ,GAAG,SAAS,CAAC;AAEnD;;;;;;;;GAQG;AACH,wBAAgB,QAAQ,CACtB,GAAG,EAAE,MAAM,EACX,IAAI,CAAC,EAAE;IAAE,UAAU,CAAC,EAAE,eAAe,CAAA;CAAE,GACtC,MAAM,CAeR"}
|
|
1
|
+
{"version":3,"file":"cdnUrl.d.ts","sourceRoot":"","sources":["../../../src/libs/storage/cdnUrl.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAeH,MAAM,MAAM,eAAe,GAAG,QAAQ,GAAG,SAAS,CAAC;AAEnD;;;;;;;;GAQG;AACH,wBAAgB,QAAQ,CACtB,GAAG,EAAE,MAAM,EACX,IAAI,CAAC,EAAE;IAAE,UAAU,CAAC,EAAE,eAAe,CAAA;CAAE,GACtC,MAAM,CAeR;AAED;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,wBAAgB,aAAa,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,QAAQ,CAAC,CAEhF"}
|
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
*/
|
|
15
15
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
16
16
|
exports.toCdnUrl = toCdnUrl;
|
|
17
|
+
exports.fetchOwnMedia = fetchOwnMedia;
|
|
17
18
|
const FIREBASE_STORAGE_HOST = "https://firebasestorage.googleapis.com";
|
|
18
19
|
const GCS_HOST = "https://storage.googleapis.com";
|
|
19
20
|
const BUCKET = "vidspot-firebase.firebasestorage.app";
|
|
@@ -46,3 +47,31 @@ function toCdnUrl(url, opts) {
|
|
|
46
47
|
}
|
|
47
48
|
return url;
|
|
48
49
|
}
|
|
50
|
+
/**
|
|
51
|
+
* The sanctioned way to RE-DOWNLOAD a URL this backend itself minted via
|
|
52
|
+
* `mediaStorage.put()`/`putFromFile()`, from anywhere in the backend (a
|
|
53
|
+
* provider re-fetching a bible reference image, a pipeline step re-fetching a
|
|
54
|
+
* scene clip to post-process it, etc).
|
|
55
|
+
*
|
|
56
|
+
* Why this exists: since the Stage 2 R2-primary flip (see MediaStorage.ts's
|
|
57
|
+
* class doc), those URLs are GCS-signed-SHAPED but R2-BACKED — the object no
|
|
58
|
+
* longer exists at the literal storage.googleapis.com address, only under the
|
|
59
|
+
* CDN Worker's R2 read-through. A plain `fetch(url)` on one 404s. This
|
|
60
|
+
* wrapper is the fix, applied once, here — use it instead of a raw `fetch()`
|
|
61
|
+
* so a future call site can't reintroduce the same bug by forgetting to
|
|
62
|
+
* rewrite the URL first (which is exactly how this broke: ~10 independent
|
|
63
|
+
* fetch/axios call sites had each rolled their own download, see
|
|
64
|
+
* notes/R2_MEDIA_MIGRATION_PLAN.md §9).
|
|
65
|
+
*
|
|
66
|
+
* Safe no-op passthrough for a genuinely external URL (AI provider output,
|
|
67
|
+
* stock API, etc) — `toCdnUrl` only rewrites our own host prefixes.
|
|
68
|
+
*
|
|
69
|
+
* Not applicable when WE aren't the one fetching — e.g. handing a URL to an
|
|
70
|
+
* external API that dereferences it server-side (AssemblyAI's `audio_url`,
|
|
71
|
+
* Veo's `video.uri`, PixVerse's `image_url`). Those need the same rewrite,
|
|
72
|
+
* but via `toCdnUrl()` directly on the string embedded in the request, since
|
|
73
|
+
* there's no local `fetch()` call to wrap.
|
|
74
|
+
*/
|
|
75
|
+
function fetchOwnMedia(url, init) {
|
|
76
|
+
return fetch(toCdnUrl(url, { visibility: "private" }), init);
|
|
77
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"bibleImageVision.d.ts","sourceRoot":"","sources":["../../../src/services/agent/bibleImageVision.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;
|
|
1
|
+
{"version":3,"file":"bibleImageVision.d.ts","sourceRoot":"","sources":["../../../src/services/agent/bibleImageVision.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AAKH,OAAO,KAAK,EACV,gBAAgB,EAEjB,MAAM,gCAAgC,CAAC;AAExC,MAAM,WAAW,sBAAsB;IACrC,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,kCAAkC;IAClC,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AA4BD;;;;;GAKG;AACH,wBAAsB,mBAAmB,CACvC,KAAK,EAAE,gBAAgB,EACvB,MAAM,EAAE,sBAAsB,GAC7B,OAAO,CAAC,gBAAgB,CAAC,CA2C3B"}
|
|
@@ -32,6 +32,7 @@
|
|
|
32
32
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
33
33
|
exports.runBibleImageVision = runBibleImageVision;
|
|
34
34
|
const genai_1 = require("@google/genai");
|
|
35
|
+
const cdnUrl_1 = require("../../libs/storage/cdnUrl");
|
|
35
36
|
const logger_1 = require("../../utils/logger");
|
|
36
37
|
const DEFAULT_MODEL = "gemini-2.5-flash";
|
|
37
38
|
/**
|
|
@@ -122,7 +123,7 @@ async function describeImage(ai, model, imageUrl, prompt, timeoutMs) {
|
|
|
122
123
|
let bytes;
|
|
123
124
|
let mimeType;
|
|
124
125
|
try {
|
|
125
|
-
const r = await
|
|
126
|
+
const r = await (0, cdnUrl_1.fetchOwnMedia)(imageUrl, { signal: controller.signal });
|
|
126
127
|
if (!r.ok) {
|
|
127
128
|
throw new Error(`fetch ${r.status} ${r.statusText}`);
|
|
128
129
|
}
|
|
@@ -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;
|
|
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;IAmK3C,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"}
|
|
@@ -318,7 +318,12 @@ class GoogleService extends baseAiGenProvider_service_1.BaseAiGenProviderService
|
|
|
318
318
|
};
|
|
319
319
|
// Video extension takes precedence over image (mutually exclusive in Veo API).
|
|
320
320
|
if (params.inputVideoUrl) {
|
|
321
|
-
|
|
321
|
+
// Veo's own servers dereference this URI directly — unlike
|
|
322
|
+
// fetchAsImage() below, WE never fetch it ourselves, so the URL string
|
|
323
|
+
// itself must already resolve publicly. Our own mediaStorage URLs are
|
|
324
|
+
// GCS-shaped but R2-backed post Stage-2-flip, so route through the CDN
|
|
325
|
+
// Worker's R2 read-through (no-op for a genuinely external URL).
|
|
326
|
+
request.video = { uri: (0, storage_1.toCdnUrl)(params.inputVideoUrl, { visibility: "private" }) };
|
|
322
327
|
}
|
|
323
328
|
else if (params.inputImageUrl) {
|
|
324
329
|
request.image = await this.withTransientRetry("input-image fetch", () => (0, googleFetch_1.fetchAsImage)(params.inputImageUrl));
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"googleFetch.d.ts","sourceRoot":"","sources":["../../../../../src/services/aiGen/providers/google/googleFetch.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"googleFetch.d.ts","sourceRoot":"","sources":["../../../../../src/services/aiGen/providers/google/googleFetch.ts"],"names":[],"mappings":"AAmCA,wBAAgB,iBAAiB,CAAC,WAAW,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,CAiB1E;AAED,wBAAgB,qBAAqB,CAAC,GAAG,EAAE,GAAG,GAAG,OAAO,CAOvD;AAED,wBAAgB,cAAc,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAKlD;AAED,wBAAsB,YAAY,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,MAAM,CAAA;CAAE,CAAC,CAyBjG"}
|
|
@@ -5,6 +5,7 @@ exports.isTransientFetchError = isTransientFetchError;
|
|
|
5
5
|
exports.inferImageMime = inferImageMime;
|
|
6
6
|
exports.fetchAsImage = fetchAsImage;
|
|
7
7
|
const errors_1 = require("../../../../utils/errors");
|
|
8
|
+
const cdnUrl_1 = require("../../../../libs/storage/cdnUrl");
|
|
8
9
|
/**
|
|
9
10
|
* Network + image helpers for the Google provider: transient-error detection
|
|
10
11
|
* (for withTransientRetry) and downloading an input image as Veo-compatible
|
|
@@ -73,7 +74,7 @@ function inferImageMime(url) {
|
|
|
73
74
|
return "image/png";
|
|
74
75
|
}
|
|
75
76
|
async function fetchAsImage(url) {
|
|
76
|
-
const resp = await
|
|
77
|
+
const resp = await (0, cdnUrl_1.fetchOwnMedia)(url);
|
|
77
78
|
// fetch() does NOT throw on 4xx/5xx — surface the HTTP error so we don't
|
|
78
79
|
// encode an HTML error page as image bytes (Veo silently rejects them).
|
|
79
80
|
if (!resp.ok) {
|
|
@@ -237,7 +237,7 @@ class OpenaiService extends baseAiGenProvider_service_1.BaseAiGenProviderService
|
|
|
237
237
|
let resp;
|
|
238
238
|
if (isEdit) {
|
|
239
239
|
const images = await Promise.all(refs.map(async (url) => {
|
|
240
|
-
const r = await
|
|
240
|
+
const r = await (0, storage_1.fetchOwnMedia)(url);
|
|
241
241
|
if (!r.ok) {
|
|
242
242
|
logger_1.logger.error("openai: ref fetch failed", {
|
|
243
243
|
url,
|
|
@@ -251,7 +251,7 @@ class OpenaiService extends baseAiGenProvider_service_1.BaseAiGenProviderService
|
|
|
251
251
|
}));
|
|
252
252
|
const mask = params.maskImageUrl
|
|
253
253
|
? await (async () => {
|
|
254
|
-
const r = await
|
|
254
|
+
const r = await (0, storage_1.fetchOwnMedia)(params.maskImageUrl);
|
|
255
255
|
if (!r.ok) {
|
|
256
256
|
logger_1.logger.error("openai: mask fetch failed", {
|
|
257
257
|
url: params.maskImageUrl,
|
|
@@ -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;AAgGlB,qBAAa,eAAgB,SAAQ,wBAAwB;IAC3D,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAkD;IAK1E;;;;;;OAMG;YACW,WAAW;
|
|
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;AAgGlB,qBAAa,eAAgB,SAAQ,wBAAwB;IAC3D,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAkD;IAK1E;;;;;;OAMG;YACW,WAAW;IAkDnB,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;IAoHjD,aAAa,CAAC,MAAM,EAAE,iBAAiB,GAAG,MAAM;CAkDjD"}
|
|
@@ -95,7 +95,12 @@ class PixVerseService extends baseAiGenProvider_service_1.BaseAiGenProviderServi
|
|
|
95
95
|
*/
|
|
96
96
|
async uploadImage(imageUrl, traceId) {
|
|
97
97
|
const form = new FormData();
|
|
98
|
-
|
|
98
|
+
// PixVerse's own servers dereference this URL directly — we never fetch
|
|
99
|
+
// it ourselves, so the URL string itself must already resolve publicly.
|
|
100
|
+
// Our own mediaStorage URLs are GCS-shaped but R2-backed post
|
|
101
|
+
// Stage-2-flip, so route through the CDN Worker (no-op for a genuinely
|
|
102
|
+
// external/user-uploaded URL).
|
|
103
|
+
form.append("image_url", (0, storage_1.toCdnUrl)(imageUrl, { visibility: "private" }));
|
|
99
104
|
return (0, transientRetry_1.withTransientRetry)("pixverse:uploadImage", async () => {
|
|
100
105
|
const resp = await fetch(`https://app-api.pixverse.ai/openapi/v2/image/upload`, {
|
|
101
106
|
method: "POST",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"assemblyai.service.d.ts","sourceRoot":"","sources":["../../../src/services/asr/assemblyai.service.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"assemblyai.service.d.ts","sourceRoot":"","sources":["../../../src/services/asr/assemblyai.service.ts"],"names":[],"mappings":"AAKA;;;;;;;;GAQG;AACH,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,EAAE,MAAM,CAAC;IACjB,kFAAkF;IAClF,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,uDAAuD;IACvD,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,iDAAiD;IACjD,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,uCAAuC;IACvC,cAAc,CAAC,EAAE,OAAO,CAAC;CAC1B;AAED,MAAM,WAAW,gBAAgB;IAC/B,EAAE,EAAE,MAAM,CAAC;IACX,MAAM,EAAE,WAAW,GAAG,QAAQ,GAAG,YAAY,GAAG,OAAO,CAAC;IACxD,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,0DAA0D;IAC1D,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,0CAA0C;IAC1C,KAAK,CAAC,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAC;QAAC,UAAU,EAAE,MAAM,CAAC;QAAC,OAAO,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAClG,sDAAsD;IACtD,UAAU,CAAC,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAC;QAAC,UAAU,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACtG,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED,qBAAa,iBAAiB;IAC5B,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAmC;IAC3D,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAS;;YASlB,OAAO;IAyBrB,yEAAyE;IACnE,MAAM,CAAC,MAAM,EAAE,gBAAgB,GAAG,OAAO,CAAC;QAAE,EAAE,EAAE,MAAM,CAAA;KAAE,CAAC;IAgBzD,WAAW,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,gBAAgB,CAAC;IAaxD;;;;OAIG;IACG,UAAU,CACd,MAAM,EAAE,gBAAgB,EACxB,EAAE,SAAmB,EAAE,UAAiB,EAAE,GAAE;QAAE,SAAS,CAAC,EAAE,MAAM,CAAC;QAAC,UAAU,CAAC,EAAE,MAAM,CAAA;KAAO,GAC3F,OAAO,CAAC,gBAAgB,CAAC;IAc5B;;;;;OAKG;IACH,aAAa,CAAC,gBAAgB,EAAE,MAAM,EAAE,aAAa,UAAQ,GAAG,MAAM;CAMvE"}
|
|
@@ -5,6 +5,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
exports.AssemblyAIService = void 0;
|
|
7
7
|
const axios_1 = __importDefault(require("axios"));
|
|
8
|
+
const cdnUrl_1 = require("../../libs/storage/cdnUrl");
|
|
8
9
|
const logger_1 = require("../../utils/logger");
|
|
9
10
|
const helpers_1 = require("../../utils/helpers");
|
|
10
11
|
class AssemblyAIService {
|
|
@@ -36,7 +37,12 @@ class AssemblyAIService {
|
|
|
36
37
|
/** Submit a transcription job. Returns the transcript id immediately. */
|
|
37
38
|
async submit(params) {
|
|
38
39
|
const body = {
|
|
39
|
-
|
|
40
|
+
// AssemblyAI's own servers dereference this URL directly — we never
|
|
41
|
+
// fetch it ourselves, so the URL string itself must already resolve
|
|
42
|
+
// publicly. Our own mediaStorage URLs are GCS-shaped but R2-backed
|
|
43
|
+
// post Stage-2-flip, so route through the CDN Worker (no-op for a
|
|
44
|
+
// genuinely external/user-uploaded URL).
|
|
45
|
+
audio_url: (0, cdnUrl_1.toCdnUrl)(params.audioUrl, { visibility: "private" }),
|
|
40
46
|
...(params.languageCode ? { language_code: params.languageCode } : { language_detection: true }),
|
|
41
47
|
...(params.speakerLabels ? { speaker_labels: true } : {}),
|
|
42
48
|
...(params.redactPii ? { redact_pii: true, redact_pii_policies: ["us_social_security_number", "credit_card_number", "email_address", "phone_number"] } : {}),
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"whisper.service.d.ts","sourceRoot":"","sources":["../../../src/services/asr/whisper.service.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"whisper.service.d.ts","sourceRoot":"","sources":["../../../src/services/asr/whisper.service.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AA2B/E,qBAAa,iBAAiB;IAC5B,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAS;;IAShC;;;;OAIG;IACG,UAAU,CAAC,MAAM,EAAE,gBAAgB,GAAG,OAAO,CAAC,gBAAgB,CAAC;IA8DrE;;;;OAIG;IACH,aAAa,CAAC,gBAAgB,EAAE,MAAM,GAAG,MAAM;CAIhD"}
|
|
@@ -39,6 +39,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
39
39
|
exports.WhisperAsrService = void 0;
|
|
40
40
|
const axios_1 = __importDefault(require("axios"));
|
|
41
41
|
const openai_1 = __importStar(require("openai"));
|
|
42
|
+
const cdnUrl_1 = require("../../libs/storage/cdnUrl");
|
|
42
43
|
const logger_1 = require("../../utils/logger");
|
|
43
44
|
const helpers_1 = require("../../utils/helpers");
|
|
44
45
|
/**
|
|
@@ -83,7 +84,10 @@ class WhisperAsrService {
|
|
|
83
84
|
if (params.redactPii) {
|
|
84
85
|
logger_1.logger.warn("Whisper fallback requested with redactPii=true — Whisper has no native PII redaction, returning unredacted transcript");
|
|
85
86
|
}
|
|
86
|
-
|
|
87
|
+
// Our own mediaStorage URLs are GCS-shaped but R2-backed post
|
|
88
|
+
// Stage-2-flip — must go through the CDN Worker's R2 read-through
|
|
89
|
+
// rather than being fetched directly against storage.googleapis.com.
|
|
90
|
+
const { data: audioBuf, headers } = await axios_1.default.get((0, cdnUrl_1.toCdnUrl)(params.audioUrl, { visibility: "private" }), {
|
|
87
91
|
responseType: "arraybuffer",
|
|
88
92
|
timeout: DOWNLOAD_TIMEOUT_MS,
|
|
89
93
|
maxContentLength: WHISPER_MAX_BYTES,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"translation.service.d.ts","sourceRoot":"","sources":["../../../src/services/translation/translation.service.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,cAAc,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAGlE;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAEH,eAAO,MAAM,kBAAkB,uBAAuB,CAAC;
|
|
1
|
+
{"version":3,"file":"translation.service.d.ts","sourceRoot":"","sources":["../../../src/services/translation/translation.service.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,cAAc,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAGlE;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAEH,eAAO,MAAM,kBAAkB,uBAAuB,CAAC;AAwIvD;;;;GAIG;AACH,iBAAe,SAAS,CACtB,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,cAAc,EACpB,EAAE,GAAE,cAAkC,EACtC,OAAO,GAAE,IAAI,GAAG,IAAW,GAC1B,OAAO,CAAC,MAAM,CAAC,CAsCjB;AAuDD;;;;;;;;;;;;;;GAcG;AACH,iBAAe,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,CAAC,CA8ChE;AAOD,eAAO,MAAM,kBAAkB;;;CAG9B,CAAC;AAGF,qDAAqD;AACrD,eAAO,MAAM,qBAAqB,kBAAY,CAAC;AAC/C,0DAA0D;AAC1D,eAAO,MAAM,UAAU,uBAAiB,CAAC"}
|
|
@@ -78,9 +78,18 @@ const PRIMARY_MAX_ATTEMPTS = 2;
|
|
|
78
78
|
// two layers compounded into an unbounded stall — a Vietnamese request was
|
|
79
79
|
// measured at 48s in prod (2026-07-26): ~12s burning both primary attempts,
|
|
80
80
|
// then ~36s waiting on a slow generateText that nothing was bounding. Cap the
|
|
81
|
-
// fallback so the worst case is primary (~12.2s) + fallback (
|
|
82
|
-
// caller gets a definite answer instead of holding the request open.
|
|
83
|
-
|
|
81
|
+
// fallback so the worst case is primary (~12.2s) + fallback (~16s) ≈ 28s and
|
|
82
|
+
// the caller gets a definite answer instead of holding the request open.
|
|
83
|
+
//
|
|
84
|
+
// 2026-09-22: a single un-retried 15s shot then ALSO started losing — both
|
|
85
|
+
// primary AND fallback failed in prod for `id`→`en` (Slack: "translation
|
|
86
|
+
// failed on BOTH primary and LLM fallback"), at ~27.2s total, matching this
|
|
87
|
+
// budget almost exactly. One bad connection with zero recovery chance was
|
|
88
|
+
// enough to take the whole fallback down. Split the same total budget into
|
|
89
|
+
// two shorter attempts so a transient hiccup (dropped connection, slow TLS
|
|
90
|
+
// handshake) gets a second, fresh try instead of being fatal.
|
|
91
|
+
const FALLBACK_ATTEMPT_TIMEOUT_MS = 8000;
|
|
92
|
+
const FALLBACK_MAX_ATTEMPTS = 2;
|
|
84
93
|
// gpt-4o-mini: cheap (~$0.15/1M in, $0.60/1M out → fractions of a cent per
|
|
85
94
|
// prompt), reliable, and independent of the Google video quota. Swap here if a
|
|
86
95
|
// cheaper effective model becomes available.
|
|
@@ -104,12 +113,27 @@ async function callPrimaryTranslate(text, from, to, version) {
|
|
|
104
113
|
}
|
|
105
114
|
return translated;
|
|
106
115
|
}
|
|
107
|
-
/**
|
|
108
|
-
*
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
116
|
+
/** Races `promise` against a timeout; the loser is abandoned (no AbortSignal
|
|
117
|
+
* on this path), but the caller is unblocked — which is the point. */
|
|
118
|
+
async function withTimeout(promise, ms, label) {
|
|
119
|
+
let timer;
|
|
120
|
+
try {
|
|
121
|
+
return await Promise.race([
|
|
122
|
+
promise,
|
|
123
|
+
new Promise((_, reject) => {
|
|
124
|
+
timer = setTimeout(() => reject(new Error(`${label} timed out after ${ms}ms`)), ms);
|
|
125
|
+
}),
|
|
126
|
+
]);
|
|
127
|
+
}
|
|
128
|
+
finally {
|
|
129
|
+
if (timer)
|
|
130
|
+
clearTimeout(timer);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
/** One un-retried LLM translate attempt. Lazy-imports the provider factory so
|
|
134
|
+
* this module stays light (utils/helpers re-exports it) and to avoid an
|
|
135
|
+
* import cycle with the aiGen layer. */
|
|
136
|
+
async function callLlmTranslateOnce(text, from, to) {
|
|
113
137
|
const { getAiGenProviderService } = await Promise.resolve().then(() => __importStar(require("../aiGen/aiGenFactory.service")));
|
|
114
138
|
const service = getAiGenProviderService(FALLBACK_MODEL);
|
|
115
139
|
const target = languageLabel(to);
|
|
@@ -117,25 +141,13 @@ async function callLlmTranslate(text, from, to) {
|
|
|
117
141
|
`Preserve meaning, tone, names, numbers, hashtags, emojis and line breaks. ` +
|
|
118
142
|
`Output ONLY the translated text — no quotes, no language labels, no commentary. ` +
|
|
119
143
|
`If the text is already in ${target}, return it unchanged.`;
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
{ role: "system", content: system },
|
|
128
|
-
{ role: "user", content: text },
|
|
129
|
-
],
|
|
130
|
-
modelKey: FALLBACK_MODEL,
|
|
131
|
-
options: { temperature: 0 },
|
|
132
|
-
}),
|
|
133
|
-
new Promise((_, reject) => {
|
|
134
|
-
timer = setTimeout(() => reject(new Error(`LLM fallback timed out after ${FALLBACK_TIMEOUT_MS}ms`)), FALLBACK_TIMEOUT_MS);
|
|
135
|
-
}),
|
|
136
|
-
]).finally(() => {
|
|
137
|
-
if (timer)
|
|
138
|
-
clearTimeout(timer);
|
|
144
|
+
const { text: out } = await service.generateText({
|
|
145
|
+
input: [
|
|
146
|
+
{ role: "system", content: system },
|
|
147
|
+
{ role: "user", content: text },
|
|
148
|
+
],
|
|
149
|
+
modelKey: FALLBACK_MODEL,
|
|
150
|
+
options: { temperature: 0 },
|
|
139
151
|
});
|
|
140
152
|
const trimmed = (out ?? "").trim();
|
|
141
153
|
if (!trimmed || trimmed === "No response") {
|
|
@@ -143,6 +155,20 @@ async function callLlmTranslate(text, from, to) {
|
|
|
143
155
|
}
|
|
144
156
|
return trimmed;
|
|
145
157
|
}
|
|
158
|
+
/** FALLBACK: translate via a cheap LLM, with a real retry — see the
|
|
159
|
+
* FALLBACK_ATTEMPT_TIMEOUT_MS comment above for why one shot wasn't enough. */
|
|
160
|
+
async function callLlmTranslate(text, from, to) {
|
|
161
|
+
let lastErr;
|
|
162
|
+
for (let attempt = 1; attempt <= FALLBACK_MAX_ATTEMPTS; attempt++) {
|
|
163
|
+
try {
|
|
164
|
+
return await withTimeout(callLlmTranslateOnce(text, from, to), FALLBACK_ATTEMPT_TIMEOUT_MS, "LLM fallback");
|
|
165
|
+
}
|
|
166
|
+
catch (err) {
|
|
167
|
+
lastErr = err;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
throw lastErr;
|
|
171
|
+
}
|
|
146
172
|
/**
|
|
147
173
|
* Translate `text` from → to (default English). Primary CF first (with a couple
|
|
148
174
|
* of quick retries), then a cheap LLM. Throws Error(TRANSLATION_FAILED) only if
|
|
@@ -188,40 +214,23 @@ async function translate(text, from, to = types_1.ELANGUAGE_CODE.en, version = "
|
|
|
188
214
|
throw e;
|
|
189
215
|
}
|
|
190
216
|
}
|
|
191
|
-
/**
|
|
192
|
-
|
|
193
|
-
* the bare ISO 639-1 code. Throws if the reply doesn't look like a code.
|
|
194
|
-
*/
|
|
195
|
-
async function callLlmDetect(text) {
|
|
217
|
+
/** One un-retried LLM detect attempt. */
|
|
218
|
+
async function callLlmDetectOnce(text) {
|
|
196
219
|
const { getAiGenProviderService } = await Promise.resolve().then(() => __importStar(require("../aiGen/aiGenFactory.service")));
|
|
197
220
|
const service = getAiGenProviderService(FALLBACK_MODEL);
|
|
198
221
|
const system = "You are a language identification engine. Reply with ONLY the ISO 639-1 " +
|
|
199
222
|
"code of the language the user's text is written in — two lowercase " +
|
|
200
223
|
'letters, nothing else (e.g. "es", "ja", "ar"). Use "zh-cn" or "zh-tw" ' +
|
|
201
224
|
"for Chinese. No punctuation, no explanation.";
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
// keeps this cheap when a long-form brief triggers the fallback.
|
|
212
|
-
input: [
|
|
213
|
-
{ role: "system", content: system },
|
|
214
|
-
{ role: "user", content: text.slice(0, 1000) },
|
|
215
|
-
],
|
|
216
|
-
modelKey: FALLBACK_MODEL,
|
|
217
|
-
options: { temperature: 0 },
|
|
218
|
-
}),
|
|
219
|
-
new Promise((_, reject) => {
|
|
220
|
-
timer = setTimeout(() => reject(new Error(`LLM detect timed out after ${FALLBACK_TIMEOUT_MS}ms`)), FALLBACK_TIMEOUT_MS);
|
|
221
|
-
}),
|
|
222
|
-
]).finally(() => {
|
|
223
|
-
if (timer)
|
|
224
|
-
clearTimeout(timer);
|
|
225
|
+
const { text: out } = await service.generateText({
|
|
226
|
+
// Only the opening slice matters for identification, and capping it
|
|
227
|
+
// keeps this cheap when a long-form brief triggers the fallback.
|
|
228
|
+
input: [
|
|
229
|
+
{ role: "system", content: system },
|
|
230
|
+
{ role: "user", content: text.slice(0, 1000) },
|
|
231
|
+
],
|
|
232
|
+
modelKey: FALLBACK_MODEL,
|
|
233
|
+
options: { temperature: 0 },
|
|
225
234
|
});
|
|
226
235
|
const code = (out ?? "").trim().toLowerCase().replace(/[^a-z-]/g, "");
|
|
227
236
|
if (!/^[a-z]{2}(-[a-z]{2})?$/.test(code)) {
|
|
@@ -229,6 +238,27 @@ async function callLlmDetect(text) {
|
|
|
229
238
|
}
|
|
230
239
|
return code;
|
|
231
240
|
}
|
|
241
|
+
/**
|
|
242
|
+
* FALLBACK: detect via the same cheap LLM `translate` falls back to, with the
|
|
243
|
+
* same retry treatment as callLlmTranslate. Returns the bare ISO 639-1 code.
|
|
244
|
+
*
|
|
245
|
+
* This is called synchronously from POST /v1/video/generate before the
|
|
246
|
+
* BullMQ enqueue, so an unbounded generateText here stalls the HTTP request
|
|
247
|
+
* all the way to the Cloud Run 300s ceiling instead of failing over to the
|
|
248
|
+
* "default to en" path below — hence the same per-attempt bound as translate.
|
|
249
|
+
*/
|
|
250
|
+
async function callLlmDetect(text) {
|
|
251
|
+
let lastErr;
|
|
252
|
+
for (let attempt = 1; attempt <= FALLBACK_MAX_ATTEMPTS; attempt++) {
|
|
253
|
+
try {
|
|
254
|
+
return await withTimeout(callLlmDetectOnce(text), FALLBACK_ATTEMPT_TIMEOUT_MS, "LLM detect");
|
|
255
|
+
}
|
|
256
|
+
catch (err) {
|
|
257
|
+
lastErr = err;
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
throw lastErr;
|
|
261
|
+
}
|
|
232
262
|
/**
|
|
233
263
|
* Detect the language of `text`. Primary CF → cheap LLM → fail OPEN to English.
|
|
234
264
|
*
|