vidspotai-shared 1.0.112 → 1.0.113

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.
@@ -1,2 +1,4 @@
1
1
  export * from "./firebase";
2
+ export * from "./r2KeyMap";
3
+ export * from "./mediaStore";
2
4
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/libs/index.ts"],"names":[],"mappings":"AAAA,cAAc,YAAY,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/libs/index.ts"],"names":[],"mappings":"AAAA,cAAc,YAAY,CAAC;AAC3B,cAAc,YAAY,CAAC;AAC3B,cAAc,cAAc,CAAC"}
package/lib/libs/index.js CHANGED
@@ -15,3 +15,5 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
15
15
  };
16
16
  Object.defineProperty(exports, "__esModule", { value: true });
17
17
  __exportStar(require("./firebase"), exports);
18
+ __exportStar(require("./r2KeyMap"), exports);
19
+ __exportStar(require("./mediaStore"), exports);
@@ -0,0 +1,35 @@
1
+ /**
2
+ * Media store — GCS system-of-record + optional zero-egress R2 mirror.
3
+ * ====================================================================
4
+ *
5
+ * `putMedia()` is the single write path new media should flow through. It:
6
+ * 1. writes the bytes to GCS (unchanged system-of-record + pipeline source),
7
+ * 2. mirror-writes them to Cloudflare R2 at the clean `toR2Key()` key IF
8
+ * `R2_WRITE_ENABLED=true` (best-effort — a mirror failure never breaks the
9
+ * pipeline; GCS already succeeded), and
10
+ * 3. returns the same far-future GCS signed URL the call sites already use, so
11
+ * stored URLs / Firestore records don't change at all.
12
+ *
13
+ * Delivery then flips to free egress automatically: the `cdn.vidspotai.com`
14
+ * Worker is R2-first, so once an object is in R2 it's served from R2; until then
15
+ * the Worker falls back to GCS. See notes/R2_MEDIA_MIGRATION_PLAN.md (Phase 2).
16
+ *
17
+ * The R2 SDK is imported lazily (dynamic import) so it stays out of the module
18
+ * import graph — the shared package is already heavy at import time and this
19
+ * must not add to Cloud Function cold start (see
20
+ * memory/project_shared_lazy_load.md).
21
+ */
22
+ /** R2 mirror-writes are opt-in; off = today's GCS-only behavior, exactly. */
23
+ export declare function isR2WriteEnabled(): boolean;
24
+ /**
25
+ * Write media to GCS (+ optional R2 mirror) and return a far-future signed read
26
+ * URL. Drop-in for the ubiquitous
27
+ * `file.save(buffer, { contentType })` + `getSignedUrl({ expires: "03-09-2491" })`
28
+ * pair.
29
+ *
30
+ * @param objectPath canonical GCS object key, e.g. "final_videos/<id>.mp4"
31
+ * @param body the bytes to store
32
+ * @param contentType MIME type, e.g. "video/mp4"
33
+ */
34
+ export declare function putMedia(objectPath: string, body: Buffer, contentType: string): Promise<string>;
35
+ //# sourceMappingURL=mediaStore.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mediaStore.d.ts","sourceRoot":"","sources":["../../src/libs/mediaStore.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAaH,6EAA6E;AAC7E,wBAAgB,gBAAgB,IAAI,OAAO,CAE1C;AAsED;;;;;;;;;GASG;AACH,wBAAsB,QAAQ,CAC5B,UAAU,EAAE,MAAM,EAClB,IAAI,EAAE,MAAM,EACZ,WAAW,EAAE,MAAM,GAClB,OAAO,CAAC,MAAM,CAAC,CAajB"}
@@ -0,0 +1,148 @@
1
+ "use strict";
2
+ /**
3
+ * Media store — GCS system-of-record + optional zero-egress R2 mirror.
4
+ * ====================================================================
5
+ *
6
+ * `putMedia()` is the single write path new media should flow through. It:
7
+ * 1. writes the bytes to GCS (unchanged system-of-record + pipeline source),
8
+ * 2. mirror-writes them to Cloudflare R2 at the clean `toR2Key()` key IF
9
+ * `R2_WRITE_ENABLED=true` (best-effort — a mirror failure never breaks the
10
+ * pipeline; GCS already succeeded), and
11
+ * 3. returns the same far-future GCS signed URL the call sites already use, so
12
+ * stored URLs / Firestore records don't change at all.
13
+ *
14
+ * Delivery then flips to free egress automatically: the `cdn.vidspotai.com`
15
+ * Worker is R2-first, so once an object is in R2 it's served from R2; until then
16
+ * the Worker falls back to GCS. See notes/R2_MEDIA_MIGRATION_PLAN.md (Phase 2).
17
+ *
18
+ * The R2 SDK is imported lazily (dynamic import) so it stays out of the module
19
+ * import graph — the shared package is already heavy at import time and this
20
+ * must not add to Cloud Function cold start (see
21
+ * memory/project_shared_lazy_load.md).
22
+ */
23
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
24
+ if (k2 === undefined) k2 = k;
25
+ var desc = Object.getOwnPropertyDescriptor(m, k);
26
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
27
+ desc = { enumerable: true, get: function() { return m[k]; } };
28
+ }
29
+ Object.defineProperty(o, k2, desc);
30
+ }) : (function(o, m, k, k2) {
31
+ if (k2 === undefined) k2 = k;
32
+ o[k2] = m[k];
33
+ }));
34
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
35
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
36
+ }) : function(o, v) {
37
+ o["default"] = v;
38
+ });
39
+ var __importStar = (this && this.__importStar) || (function () {
40
+ var ownKeys = function(o) {
41
+ ownKeys = Object.getOwnPropertyNames || function (o) {
42
+ var ar = [];
43
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
44
+ return ar;
45
+ };
46
+ return ownKeys(o);
47
+ };
48
+ return function (mod) {
49
+ if (mod && mod.__esModule) return mod;
50
+ var result = {};
51
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
52
+ __setModuleDefault(result, mod);
53
+ return result;
54
+ };
55
+ })();
56
+ Object.defineProperty(exports, "__esModule", { value: true });
57
+ exports.isR2WriteEnabled = isR2WriteEnabled;
58
+ exports.putMedia = putMedia;
59
+ const firebase_1 = require("./firebase");
60
+ const r2KeyMap_1 = require("./r2KeyMap");
61
+ const logger_1 = require("../utils/logger");
62
+ // Far-future expiry — matches the permanent bearer-token URLs used everywhere
63
+ // in the codebase (getSignedUrl expires "03-09-2491").
64
+ const PERMANENT_EXPIRY = "03-09-2491";
65
+ const R2_BUCKET = process.env.R2_BUCKET || "vidspot-media";
66
+ /** R2 mirror-writes are opt-in; off = today's GCS-only behavior, exactly. */
67
+ function isR2WriteEnabled() {
68
+ return process.env.R2_WRITE_ENABLED === "true";
69
+ }
70
+ function r2Config() {
71
+ const accessKeyId = process.env.CLOUDFLARE_R2_ACCESS_KEY_ID || process.env.R2_ACCESS_KEY_ID;
72
+ const secretAccessKey = process.env.CLOUDFLARE_R2_SECRET_ACCESS_KEY ||
73
+ process.env.R2_SECRET_ACCESS_KEY;
74
+ const endpoint = process.env.CLOUDFLARE_R2_S3_ENDPOINT || process.env.R2_S3_ENDPOINT;
75
+ return { accessKeyId, secretAccessKey, endpoint };
76
+ }
77
+ // Lazily-built singleton S3 client (only when the first mirror actually runs).
78
+ let s3Client = null;
79
+ let s3Init = null;
80
+ async function getS3() {
81
+ if (s3Client)
82
+ return s3Client;
83
+ if (s3Init)
84
+ return s3Init;
85
+ s3Init = (async () => {
86
+ const { accessKeyId, secretAccessKey, endpoint } = r2Config();
87
+ if (!accessKeyId || !secretAccessKey || !endpoint) {
88
+ logger_1.logger.warn("mediaStore: R2_WRITE_ENABLED but R2 creds/endpoint missing — skipping mirror");
89
+ return null;
90
+ }
91
+ const { S3Client: Client } = await Promise.resolve().then(() => __importStar(require("@aws-sdk/client-s3")));
92
+ s3Client = new Client({
93
+ region: "auto",
94
+ endpoint,
95
+ credentials: { accessKeyId, secretAccessKey },
96
+ });
97
+ return s3Client;
98
+ })();
99
+ return s3Init;
100
+ }
101
+ /**
102
+ * Best-effort mirror of an object into R2. Never throws — a mirror failure is
103
+ * logged and swallowed so the (already-succeeded) GCS write remains the truth.
104
+ */
105
+ async function mirrorToR2(objectPath, body, contentType) {
106
+ try {
107
+ const client = await getS3();
108
+ if (!client)
109
+ return;
110
+ const { PutObjectCommand } = await Promise.resolve().then(() => __importStar(require("@aws-sdk/client-s3")));
111
+ const r2Key = (0, r2KeyMap_1.toR2Key)(objectPath);
112
+ await client.send(new PutObjectCommand({
113
+ Bucket: R2_BUCKET,
114
+ Key: r2Key,
115
+ Body: body,
116
+ ContentType: contentType,
117
+ }));
118
+ logger_1.logger.info("mediaStore: mirrored to R2", { objectPath, r2Key });
119
+ }
120
+ catch (err) {
121
+ logger_1.logger.error("mediaStore: R2 mirror failed (GCS write still stands)", {
122
+ objectPath,
123
+ err: err instanceof Error ? err.stack ?? err.message : String(err),
124
+ });
125
+ }
126
+ }
127
+ /**
128
+ * Write media to GCS (+ optional R2 mirror) and return a far-future signed read
129
+ * URL. Drop-in for the ubiquitous
130
+ * `file.save(buffer, { contentType })` + `getSignedUrl({ expires: "03-09-2491" })`
131
+ * pair.
132
+ *
133
+ * @param objectPath canonical GCS object key, e.g. "final_videos/<id>.mp4"
134
+ * @param body the bytes to store
135
+ * @param contentType MIME type, e.g. "video/mp4"
136
+ */
137
+ async function putMedia(objectPath, body, contentType) {
138
+ const file = (0, firebase_1.getBucket)().file(objectPath);
139
+ await file.save(body, { contentType });
140
+ if (isR2WriteEnabled()) {
141
+ await mirrorToR2(objectPath, body, contentType);
142
+ }
143
+ const [signedUrl] = await file.getSignedUrl({
144
+ action: "read",
145
+ expires: PERMANENT_EXPIRY,
146
+ });
147
+ return signedUrl;
148
+ }
@@ -0,0 +1,40 @@
1
+ /**
2
+ * Canonical GCS → R2 key mapping — BACKEND TS PORT.
3
+ * =================================================
4
+ *
5
+ * This is the TypeScript twin of the CDN Worker's single source of truth
6
+ * (`vidspot-frontend/infra/cloudflare-media-cdn/r2-key-map.mjs`). The two files
7
+ * MUST stay vector-for-vector identical: the Worker maps an incoming request to
8
+ * an R2 key with `toR2Key()`, and the backend write-through / bulk migration
9
+ * writes each object to R2 at `toR2Key()` — if they disagree, a migrated object
10
+ * would be stored at one key and looked up at another.
11
+ *
12
+ * A parity check lives in `scripts/verify-r2-keymap-parity.mjs` (run it after
13
+ * editing either file). See `notes/R2_STRUCTURE_AND_MAPPING.md` for the taxonomy.
14
+ *
15
+ * Pure, dependency-free. No I/O.
16
+ */
17
+ /**
18
+ * Reserved R2 top-level namespaces (documentation + bootstrap use). Keep in sync
19
+ * with the `.mjs` twin.
20
+ *
21
+ * NOTE: no `videos/web/` — the old single "web rendition" is superseded by the
22
+ * per-video multi-quality layout, where the 720p tier lives at
23
+ * `videos/final/{jobId}/sd.mp4` (see notes/R2_MULTI_QUALITY_VARIANTS_PLAN.md).
24
+ * Per-namespace self-documenting blurbs (R2_NAMESPACE_DOCS) live only in the
25
+ * `.mjs` twin, since only the frontend bootstrap script writes them to R2.
26
+ */
27
+ export declare const R2_NAMESPACES: string[];
28
+ /**
29
+ * Map a canonical GCS object key to its clean R2 key.
30
+ *
31
+ * TOTAL by construction: an unmapped prefix falls through to the identity, so
32
+ * nothing is ever dropped or misrouted — a forgotten prefix simply keeps its
33
+ * original key in R2 (and the Worker still finds it, since it calls this same
34
+ * function).
35
+ *
36
+ * @param key e.g. "final_videos/abc.mp4"
37
+ * @returns e.g. "videos/final/abc.mp4"
38
+ */
39
+ export declare function toR2Key(key: string): string;
40
+ //# sourceMappingURL=r2KeyMap.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"r2KeyMap.d.ts","sourceRoot":"","sources":["../../src/libs/r2KeyMap.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAoDH;;;;;;;;;GASG;AACH,eAAO,MAAM,aAAa,EAAE,MAAM,EAejC,CAAC;AAEF;;;;;;;;;;GAUG;AACH,wBAAgB,OAAO,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAS3C"}
@@ -0,0 +1,110 @@
1
+ "use strict";
2
+ /**
3
+ * Canonical GCS → R2 key mapping — BACKEND TS PORT.
4
+ * =================================================
5
+ *
6
+ * This is the TypeScript twin of the CDN Worker's single source of truth
7
+ * (`vidspot-frontend/infra/cloudflare-media-cdn/r2-key-map.mjs`). The two files
8
+ * MUST stay vector-for-vector identical: the Worker maps an incoming request to
9
+ * an R2 key with `toR2Key()`, and the backend write-through / bulk migration
10
+ * writes each object to R2 at `toR2Key()` — if they disagree, a migrated object
11
+ * would be stored at one key and looked up at another.
12
+ *
13
+ * A parity check lives in `scripts/verify-r2-keymap-parity.mjs` (run it after
14
+ * editing either file). See `notes/R2_STRUCTURE_AND_MAPPING.md` for the taxonomy.
15
+ *
16
+ * Pure, dependency-free. No I/O.
17
+ */
18
+ Object.defineProperty(exports, "__esModule", { value: true });
19
+ exports.R2_NAMESPACES = void 0;
20
+ exports.toR2Key = toR2Key;
21
+ /**
22
+ * Ordered prefix-rewrite rules. First match wins, so more-specific prefixes
23
+ * (e.g. `avatar-audio-preview/` before `avatar-audio/`) come first. Each rule
24
+ * swaps a legacy top-level prefix for its clean role-based home; the remainder
25
+ * of the key is preserved verbatim unless a `reshape` is given.
26
+ */
27
+ const RULES = [
28
+ // --- generated video, by role -----------------------------------------
29
+ { from: "final_videos/", to: "videos/final/" },
30
+ { from: "watermarked_videos/", to: "videos/watermarked/" },
31
+ {
32
+ // videos/<jobId>_scene<n>.mp4 -> videos/scenes/<jobId>/scene<n>.mp4
33
+ from: "videos/",
34
+ to: "videos/scenes/",
35
+ reshape(rest) {
36
+ const m = /^(.+)_scene(\d+)(\.[A-Za-z0-9]+)$/.exec(rest);
37
+ return m ? `${m[1]}/scene${m[2]}${m[3]}` : rest;
38
+ },
39
+ },
40
+ // NOTE: `social_variants/` (reframeVideo output) is intentionally NOT mapped
41
+ // yet — it falls through to identity, which is CONSISTENT in both this file
42
+ // and the deployed Worker `.mjs`. To give it a clean home (`videos/social/`)
43
+ // later, add the rule to BOTH maps + redeploy the Worker in one change so the
44
+ // migrate-side and lookup-side never disagree.
45
+ // --- already clean role names: keep as-is (identity) -------------------
46
+ { from: "posters/", to: "posters/" },
47
+ { from: "images/", to: "images/" },
48
+ // --- user source uploads ----------------------------------------------
49
+ { from: "media-uploads/", to: "uploads/" },
50
+ // --- avatars (longer prefix FIRST) ------------------------------------
51
+ { from: "avatar-audio-preview/", to: "avatars/audio-preview/" },
52
+ { from: "avatar-videos/", to: "avatars/video/" },
53
+ { from: "avatar-audio/", to: "avatars/audio/" },
54
+ // --- agent workspace --------------------------------------------------
55
+ { from: "agent-projects/", to: "agent/projects/" },
56
+ { from: "agent_renders/", to: "agent/renders/" },
57
+ // --- public showcase gallery ------------------------------------------
58
+ { from: "sample_videos/", to: "gallery/" },
59
+ // --- internal demo pipeline -------------------------------------------
60
+ { from: "demo_inputs/", to: "demo/inputs/" },
61
+ { from: "demo_renders/", to: "demo/renders/" },
62
+ ];
63
+ /**
64
+ * Reserved R2 top-level namespaces (documentation + bootstrap use). Keep in sync
65
+ * with the `.mjs` twin.
66
+ *
67
+ * NOTE: no `videos/web/` — the old single "web rendition" is superseded by the
68
+ * per-video multi-quality layout, where the 720p tier lives at
69
+ * `videos/final/{jobId}/sd.mp4` (see notes/R2_MULTI_QUALITY_VARIANTS_PLAN.md).
70
+ * Per-namespace self-documenting blurbs (R2_NAMESPACE_DOCS) live only in the
71
+ * `.mjs` twin, since only the frontend bootstrap script writes them to R2.
72
+ */
73
+ exports.R2_NAMESPACES = [
74
+ "videos/final/",
75
+ "videos/watermarked/",
76
+ "videos/scenes/",
77
+ "posters/",
78
+ "images/",
79
+ "uploads/",
80
+ "avatars/video/",
81
+ "avatars/audio/",
82
+ "avatars/audio-preview/",
83
+ "agent/projects/",
84
+ "agent/renders/",
85
+ "gallery/",
86
+ "demo/inputs/",
87
+ "demo/renders/",
88
+ ];
89
+ /**
90
+ * Map a canonical GCS object key to its clean R2 key.
91
+ *
92
+ * TOTAL by construction: an unmapped prefix falls through to the identity, so
93
+ * nothing is ever dropped or misrouted — a forgotten prefix simply keeps its
94
+ * original key in R2 (and the Worker still finds it, since it calls this same
95
+ * function).
96
+ *
97
+ * @param key e.g. "final_videos/abc.mp4"
98
+ * @returns e.g. "videos/final/abc.mp4"
99
+ */
100
+ function toR2Key(key) {
101
+ if (!key)
102
+ return key;
103
+ for (const rule of RULES) {
104
+ if (key.startsWith(rule.from)) {
105
+ const rest = key.slice(rule.from.length);
106
+ return rule.to + (rule.reshape ? rule.reshape(rest) : rest);
107
+ }
108
+ }
109
+ return key; // safety net — identity for anything unmapped
110
+ }
@@ -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;AAyElB,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;IAsK5B;;;;;;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;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;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;IAuK5B;;;;;;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"}
@@ -27,6 +27,12 @@ const googleKeyPool_1 = require("./googleKeyPool");
27
27
  // of round-tripping the API and burning a refund cycle. See _generateImage.
28
28
  const DEPRECATED_GOOGLE_MODEL_IDS = new Set([
29
29
  "imagen-4.0-generate-001", // discontinued 2026-08-19; Google's error suggests gemini-3.1-flash-image
30
+ // 2026-09-14: confirmed via a live models.list call against our API key that
31
+ // Google has now pulled the ENTIRE imagen-4.0-* family (ultra + fast too,
32
+ // not just the base variant above) — none of them appear in the response
33
+ // anymore, only the gemini-*-image ("Nano Banana") models remain.
34
+ "imagen-4.0-ultra-generate-001",
35
+ "imagen-4.0-fast-generate-001",
30
36
  ]);
31
37
  /**
32
38
  * Wait until a file exists AND its on-disk size has stopped growing, returning
@@ -615,12 +621,13 @@ class GoogleService extends baseAiGenProvider_service_1.BaseAiGenProviderService
615
621
  const modelId = modelConfig?.modelId;
616
622
  if (!modelId)
617
623
  throw new Error(`Unknown image modelKey: ${params.modelKey}`);
618
- // Google discontinued imagen-4.0-generate-001 (confirmed via live 404s
619
- // starting 2026-08-19: "no longer available ... use models/gemini-3.1-flash-image").
620
- // Every call was failing 100% of the time, burning a full round-trip +
621
- // credit-refund cycle for nothing. Fail fast instead. Ultra/Fast variants
622
- // (imagen-4.0-ultra-generate-001 / -fast-generate-001) are unaffected —
623
- // only this exact model ID was pulled.
624
+ // Google discontinued the entire imagen-4.0-* family (confirmed via live
625
+ // 404s starting 2026-08-19 for the base model, and via a live models.list
626
+ // call on 2026-09-14 confirming ultra + fast are gone too — none of the
627
+ // three appear in the API's model list anymore, only gemini-*-image
628
+ // ("Nano Banana") remains). Every call was failing 100% of the time,
629
+ // burning a full round-trip + credit-refund cycle for nothing. Fail fast
630
+ // instead.
624
631
  if (DEPRECATED_GOOGLE_MODEL_IDS.has(modelId)) {
625
632
  throw new errors_1.UserFacingError(`Model "${modelId}" has been discontinued by Google. Please pick a different image model.`, errors_1.USER_FACING_ERROR_CODES.CAPABILITY_MISMATCH);
626
633
  }
@@ -1 +1 @@
1
- {"version":3,"file":"minimax.service.d.ts","sourceRoot":"","sources":["../../../../../src/services/aiGen/providers/minimax/minimax.service.ts"],"names":[],"mappings":"AAMA,OAAO,EAAE,wBAAwB,EAAE,MAAM,8BAA8B,CAAC;AACxE,OAAO,EACL,iBAAiB,EACjB,qBAAqB,EACrB,qBAAqB,EACrB,qBAAqB,EACrB,qBAAqB,EACrB,qBAAqB,EACrB,qBAAqB,EACrB,iBAAiB,EACjB,iBAAiB,EAClB,MAAM,UAAU,CAAC;AAUlB;;;GAGG;AAGH,eAAO,MAAM,+BAA+B,uBAAuB,CAAC;AA+GpE,qBAAa,cAAe,SAAQ,wBAAwB;IAC1D,OAAO,CAAC,QAAQ,CAAC,OAAO,CAA4B;IAEpD,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAS;;YAUlB,OAAO;IAuDf,aAAa,CACjB,MAAM,EAAE,qBAAqB,GAC5B,OAAO,CAAC,qBAAqB,CAAC;IAgE3B,gBAAgB,CAAC,EACrB,IAAI,EACJ,cAAc,EACd,cAAyB,GAC1B,EAAE,iBAAiB,GAAG,OAAO,CAAC,iBAAiB,CAAC;IA6EjD;;;;OAIG;IACG,aAAa,CACjB,MAAM,EAAE,qBAAqB,GAC5B,OAAO,CAAC,qBAAqB,CAAC;IAuDjC;;;;;OAKG;IACG,aAAa,CACjB,MAAM,EAAE,qBAAqB,GAC5B,OAAO,CAAC,qBAAqB,CAAC;IA+CjC,aAAa,CAAC,EACZ,QAAQ,EACR,UAAmB,EACnB,QAAY,EACZ,SAAiB,EACjB,SAAa,EACb,WAAW,GACZ,EAAE,iBAAiB,GAAG,MAAM;CAgC9B"}
1
+ {"version":3,"file":"minimax.service.d.ts","sourceRoot":"","sources":["../../../../../src/services/aiGen/providers/minimax/minimax.service.ts"],"names":[],"mappings":"AAMA,OAAO,EAAE,wBAAwB,EAAE,MAAM,8BAA8B,CAAC;AACxE,OAAO,EACL,iBAAiB,EACjB,qBAAqB,EACrB,qBAAqB,EACrB,qBAAqB,EACrB,qBAAqB,EACrB,qBAAqB,EACrB,qBAAqB,EACrB,iBAAiB,EACjB,iBAAiB,EAClB,MAAM,UAAU,CAAC;AAUlB;;;GAGG;AAGH,eAAO,MAAM,+BAA+B,uBAAuB,CAAC;AAuHpE,qBAAa,cAAe,SAAQ,wBAAwB;IAC1D,OAAO,CAAC,QAAQ,CAAC,OAAO,CAA4B;IAEpD,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAS;;YAUlB,OAAO;IAuDf,aAAa,CACjB,MAAM,EAAE,qBAAqB,GAC5B,OAAO,CAAC,qBAAqB,CAAC;IAgE3B,gBAAgB,CAAC,EACrB,IAAI,EACJ,cAAc,EACd,cAAyB,GAC1B,EAAE,iBAAiB,GAAG,OAAO,CAAC,iBAAiB,CAAC;IA6EjD;;;;OAIG;IACG,aAAa,CACjB,MAAM,EAAE,qBAAqB,GAC5B,OAAO,CAAC,qBAAqB,CAAC;IAuDjC;;;;;OAKG;IACG,aAAa,CACjB,MAAM,EAAE,qBAAqB,GAC5B,OAAO,CAAC,qBAAqB,CAAC;IA+CjC,aAAa,CAAC,EACZ,QAAQ,EACR,UAAmB,EACnB,QAAY,EACZ,SAAiB,EACjB,SAAa,EACb,WAAW,GACZ,EAAE,iBAAiB,GAAG,MAAM;CAgC9B"}
@@ -71,6 +71,14 @@ function classifyMinimaxError(code, msg) {
71
71
  // 1004 = authentication failed — a bad/revoked MiniMax key (deploy/config
72
72
  // fault). Must page: PROVIDER_AUTH_ERROR is not in the non-paging set.
73
73
  return { message: "Authorization failed for the video generation service. Please contact support.", code: errors_1.USER_FACING_ERROR_CODES.PROVIDER_AUTH_ERROR, isUserInput: false };
74
+ case 2153:
75
+ // 2153 = Music API withdrawn from our account tier ("no longer available
76
+ // to new users; existing paying customers can continue"). Confirmed live
77
+ // 2026-09-14 — every music_generation call fails 100%. Not user-fixable
78
+ // (no prompt change helps) and not a transient outage — an account/billing
79
+ // decision (upgrade the MiniMax plan, or move music-gen to another
80
+ // provider). Must page: isUserInput:false, same bucket as 1008/1004.
81
+ return { message: "Music generation is temporarily unavailable on our current plan with this provider. You haven't been charged — please try again later.", code: errors_1.USER_FACING_ERROR_CODES.CAPABILITY_MISMATCH, isUserInput: false };
74
82
  default:
75
83
  return { message: msg || `Generation failed (code ${code})`, isUserInput: false };
76
84
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vidspotai-shared",
3
- "version": "1.0.112",
3
+ "version": "1.0.113",
4
4
  "main": "lib/index.js",
5
5
  "types": "lib/index.d.ts",
6
6
  "exports": {
@@ -15,6 +15,7 @@
15
15
  ],
16
16
  "dependencies": {
17
17
  "@anthropic-ai/sdk": "^0.98.0",
18
+ "@aws-sdk/client-s3": "^3.1119.0",
18
19
  "@google-cloud/storage": "*",
19
20
  "@google/genai": "^1.22.0",
20
21
  "@googleapis/sheets": "^13.0.1",