vidspotai-shared 1.0.78 → 1.0.79
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/services/aiGen/providers/google/google.service.d.ts.map +1 -1
- package/lib/services/aiGen/providers/google/google.service.js +9 -0
- package/lib/services/aiGen/providers/openai/openai.service.d.ts.map +1 -1
- package/lib/services/aiGen/providers/openai/openai.service.js +27 -1
- package/lib/services/bullmq.service.d.ts.map +1 -1
- package/lib/services/bullmq.service.js +21 -1
- package/lib/services/redisOptions.d.ts +12 -0
- package/lib/services/redisOptions.d.ts.map +1 -1
- package/lib/services/redisOptions.js +34 -1
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"google.service.d.ts","sourceRoot":"","sources":["../../../../../src/services/aiGen/providers/google/google.service.ts"],"names":[],"mappings":"AAgBA,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;
|
|
1
|
+
{"version":3,"file":"google.service.d.ts","sourceRoot":"","sources":["../../../../../src/services/aiGen/providers/google/google.service.ts"],"names":[],"mappings":"AAgBA,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;AA6IlB,qBAAa,aAAc,SAAQ,wBAAwB;IACzD,OAAO,CAAC,EAAE,CAAc;IACxB,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,kBAAkB,CAAK;;IAO/C;;;;OAIG;YACW,kBAAkB;IA+B1B,aAAa,CACjB,MAAM,EAAE,qBAAqB,GAC5B,OAAO,CAAC,qBAAqB,CAAC;IAqF3B,gBAAgB,CAAC,EACrB,IAAI,EACJ,cAAc,EACd,cAAyB,GAC1B,EAAE,iBAAiB,GAAG,OAAO,CAAC,iBAAiB,CAAC;IA0F3C,aAAa,CACjB,MAAM,EAAE,qBAAqB,GAC5B,OAAO,CAAC,qBAAqB,CAAC;IAsGjC;;;;;;OAMG;IACG,aAAa,CACjB,MAAM,EAAE,qBAAqB,GAC5B,OAAO,CAAC,qBAAqB,CAAC;IAqEjC,aAAa,CAAC,EAAE,QAAQ,EAAE,QAAY,EAAE,UAAmB,EAAE,SAAiB,EAAE,SAAa,EAAE,SAAS,EAAE,EAAE,iBAAiB,GAAG,MAAM;CA8BvI"}
|
|
@@ -127,6 +127,15 @@ function classifyGoogleApiError(err) {
|
|
|
127
127
|
if (httpCode === 14 || /high demand/i.test(msg)) {
|
|
128
128
|
return new errors_1.UserFacingError(msg, errors_1.USER_FACING_ERROR_CODES.VIDEO_PROVIDER_HIGH_DEMAND);
|
|
129
129
|
}
|
|
130
|
+
// INVALID_ARGUMENT 400 — typically "Your use case is currently not
|
|
131
|
+
// supported" when the request mixes features the chosen Veo variant
|
|
132
|
+
// doesn't offer (e.g. referenceImages on -fast, lastFrame on -lite,
|
|
133
|
+
// video-extension with a non-Veo source URL). Not actionable as a
|
|
134
|
+
// platform bug — surface as a translatable capability mismatch so the
|
|
135
|
+
// user can switch model/feature combo instead of firing Slack.
|
|
136
|
+
if (status === "INVALID_ARGUMENT" || httpCode === 400) {
|
|
137
|
+
return new errors_1.UserFacingError(msg, errors_1.USER_FACING_ERROR_CODES.CAPABILITY_MISMATCH);
|
|
138
|
+
}
|
|
130
139
|
}
|
|
131
140
|
catch {
|
|
132
141
|
// Not JSON — fall through to non-JSON checks.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"openai.service.d.ts","sourceRoot":"","sources":["../../../../../src/services/aiGen/providers/openai/openai.service.ts"],"names":[],"mappings":"AAiBA,OAAO,EAAE,wBAAwB,EAAE,MAAM,8BAA8B,CAAC;AACxE,OAAO,EACL,iBAAiB,EACjB,qBAAqB,EACrB,qBAAqB,EACrB,oBAAoB,EACpB,oBAAoB,EACpB,qBAAqB,EACrB,qBAAqB,EACrB,iBAAiB,EACjB,iBAAiB,EAClB,MAAM,UAAU,CAAC;AAElB,qBAAa,aAAc,SAAQ,wBAAwB;IACzD,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAa;IACxC,OAAO,CAAC,MAAM,CAAS;;IAQjB,aAAa,CACjB,MAAM,EAAE,qBAAqB,GAC5B,OAAO,CAAC,qBAAqB,CAAC;
|
|
1
|
+
{"version":3,"file":"openai.service.d.ts","sourceRoot":"","sources":["../../../../../src/services/aiGen/providers/openai/openai.service.ts"],"names":[],"mappings":"AAiBA,OAAO,EAAE,wBAAwB,EAAE,MAAM,8BAA8B,CAAC;AACxE,OAAO,EACL,iBAAiB,EACjB,qBAAqB,EACrB,qBAAqB,EACrB,oBAAoB,EACpB,oBAAoB,EACpB,qBAAqB,EACrB,qBAAqB,EACrB,iBAAiB,EACjB,iBAAiB,EAClB,MAAM,UAAU,CAAC;AAElB,qBAAa,aAAc,SAAQ,wBAAwB;IACzD,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAa;IACxC,OAAO,CAAC,MAAM,CAAS;;IAQjB,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;IAsC3C,YAAY,CAChB,MAAM,EAAE,oBAAoB,GAC3B,OAAO,CAAC,oBAAoB,CAAC;IA2ChC;;;;OAIG;IACG,aAAa,CACjB,MAAM,EAAE,qBAAqB,GAC5B,OAAO,CAAC,qBAAqB,CAAC;IA6HjC,aAAa,CAAC,EACZ,QAAQ,EACR,UAAuB,EACvB,QAAY,EACZ,SAAiB,EACjB,SAAa,EACb,OAAO,GACR,EAAE,iBAAiB,GAAG,MAAM;CAoB9B"}
|
|
@@ -73,7 +73,33 @@ class OpenaiService extends baseAiGenProvider_service_1.BaseAiGenProviderService
|
|
|
73
73
|
const filename = (params.inputImageUrl.split("?")[0] ?? "reference").split("/").pop() || "reference.png";
|
|
74
74
|
request.input_reference = await (0, openai_1.toFile)(resp, filename);
|
|
75
75
|
}
|
|
76
|
-
|
|
76
|
+
let job;
|
|
77
|
+
try {
|
|
78
|
+
job = await this.client.videos.create(request);
|
|
79
|
+
}
|
|
80
|
+
catch (err) {
|
|
81
|
+
// OpenAI client surfaces 400s via APIError with `status`/`message`.
|
|
82
|
+
// The most common case on sora-2-pro right now:
|
|
83
|
+
// 400 Invalid type for 'input_reference': expected an object, but got a file instead.
|
|
84
|
+
// Either the API now requires `input_reference` to be an uploaded file
|
|
85
|
+
// reference object (file_id) rather than a multipart Uploadable, or it's
|
|
86
|
+
// a transient SDK<>endpoint mismatch. Either way, this is not an
|
|
87
|
+
// ops-actionable bug — surface as a translatable capability mismatch
|
|
88
|
+
// so the user can switch model / disable image-to-video instead of
|
|
89
|
+
// firing Slack on every attempt.
|
|
90
|
+
const status = err?.status ?? err?.response?.status;
|
|
91
|
+
const message = err?.message ?? String(err);
|
|
92
|
+
if (status === 400) {
|
|
93
|
+
throw new errors_1.UserFacingError(message, errors_1.USER_FACING_ERROR_CODES.CAPABILITY_MISMATCH);
|
|
94
|
+
}
|
|
95
|
+
if (status === 401 || status === 403) {
|
|
96
|
+
throw new errors_1.UserFacingError(message, errors_1.USER_FACING_ERROR_CODES.PROVIDER_AUTH_ERROR);
|
|
97
|
+
}
|
|
98
|
+
if (status === 429) {
|
|
99
|
+
throw new errors_1.UserFacingError(message, errors_1.USER_FACING_ERROR_CODES.VIDEO_PROVIDER_RATE_LIMITED);
|
|
100
|
+
}
|
|
101
|
+
throw err;
|
|
102
|
+
}
|
|
77
103
|
if (job.status === "failed") {
|
|
78
104
|
throw new Error(`OpenAI video generation failed: ${JSON.stringify(job.error)}`);
|
|
79
105
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"bullmq.service.d.ts","sourceRoot":"","sources":["../../src/services/bullmq.service.ts"],"names":[],"mappings":"AACA,OAAO,EAAiB,GAAG,EAAE,MAAM,QAAQ,CAAC;AAC5C,OAAgB,EAAwB,YAAY,EAAE,MAAM,SAAS,CAAC;AAItE,MAAM,MAAM,OAAO,GAAG,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;
|
|
1
|
+
{"version":3,"file":"bullmq.service.d.ts","sourceRoot":"","sources":["../../src/services/bullmq.service.ts"],"names":[],"mappings":"AACA,OAAO,EAAiB,GAAG,EAAE,MAAM,QAAQ,CAAC;AAC5C,OAAgB,EAAwB,YAAY,EAAE,MAAM,SAAS,CAAC;AAItE,MAAM,MAAM,OAAO,GAAG,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;AAc1C,UAAU,oBAAoB;IAC5B,YAAY,EAAE,YAAY,CAAC;IAC3B,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,cAAM,aAAa;IACjB,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAgB;IACvC,OAAO,CAAC,MAAM,CAAiC;IAC/C,OAAO,CAAC,OAAO,CAAgB;IAC/B,OAAO,CAAC,YAAY,CAAgB;IAIpC,OAAO,CAAC,UAAU,CAAC,CAAc;IACjC,OAAO,CAAC,WAAW,CAAS;IAC5B,OAAO,CAAC,WAAW,CAAS;IAC5B,OAAO,CAAC,YAAY,CAAS;IAE7B,OAAO;WAQO,WAAW,CAAC,OAAO,EAAE,oBAAoB;IAOvD;;;;;;;;;;;;;;;;;;;;;;OAsBG;IACH,OAAO,CAAC,aAAa;IAOrB,sDAAsD;IACzC,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAiBlC,gBAAgB;IACH,MAAM,CACjB,SAAS,EAAE,MAAM,EACjB,IAAI,EAAE,OAAO,EACb,IAAI,CAAC,EAAE;QAAE,KAAK,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE;IAUpC,YAAY,CACjB,UAAU,EAAE,MAAM,EAAE,EACpB,SAAS,EAAE,CAAC,GAAG,EAAE,GAAG,KAAK,OAAO,CAAC,IAAI,CAAC,EACtC,eAAe,CAAC,EAAE,MAAM,CACtB,MAAM,EACN;QACE,YAAY,CAAC,EAAE,MAAM,CAAC;QACtB,eAAe,CAAC,EAAE,MAAM,CAAC;QACzB,eAAe,CAAC,EAAE,MAAM,CAAC;QACzB,WAAW,CAAC,EAAE,MAAM,CAAC;KACtB,CACF;CAkHJ;AASD,eAAO,MAAM,MAAM,eAGjB,CAAC;AAEH,MAAM,MAAM,SAAS,GAAG,GAAG,CAAC"}
|
|
@@ -9,6 +9,17 @@ const bullmq_1 = require("bullmq");
|
|
|
9
9
|
const ioredis_1 = __importDefault(require("ioredis"));
|
|
10
10
|
const logger_1 = require("../utils/logger");
|
|
11
11
|
const redisOptions_1 = require("./redisOptions");
|
|
12
|
+
// Transient ioredis socket errors that BullMQ's worker `error` event surfaces
|
|
13
|
+
// verbatim. ioredis reconnects from these on its own — they're noise, not
|
|
14
|
+
// outages. Kept in sync with TRANSIENT_REDIS_ERROR_CODES in redisOptions.ts.
|
|
15
|
+
const TRANSIENT_WORKER_ERROR_CODES = new Set([
|
|
16
|
+
"ECONNRESET",
|
|
17
|
+
"ETIMEDOUT",
|
|
18
|
+
"ECONNREFUSED",
|
|
19
|
+
"EPIPE",
|
|
20
|
+
"ENOTFOUND",
|
|
21
|
+
"EAI_AGAIN",
|
|
22
|
+
]);
|
|
12
23
|
class BullMQService {
|
|
13
24
|
constructor({ redisOptions, concurrency = 10, }) {
|
|
14
25
|
this.queues = new Map();
|
|
@@ -132,9 +143,18 @@ class BullMQService {
|
|
|
132
143
|
});
|
|
133
144
|
// Worker-shell failure (Redis lost, malformed payload, processor
|
|
134
145
|
// import error, etc.). Without this handler these crashes are silent.
|
|
146
|
+
// Transient ioredis network blips (ECONNRESET/ETIMEDOUT/EPIPE) bubble
|
|
147
|
+
// through this same listener — ioredis recovers them automatically,
|
|
148
|
+
// so demote to `warn` to keep Slack quiet. Real shell failures (auth,
|
|
149
|
+
// parser, malformed payload) still surface at `error` → Slack.
|
|
135
150
|
worker.on("error", (err) => {
|
|
136
|
-
|
|
151
|
+
const code = err?.code;
|
|
152
|
+
const isTransient = !!code && TRANSIENT_WORKER_ERROR_CODES.has(code);
|
|
153
|
+
const logFn = isTransient ? logger_1.logger.warn.bind(logger_1.logger) : logger_1.logger.error.bind(logger_1.logger);
|
|
154
|
+
logFn(`BullMQ worker error`, {
|
|
137
155
|
queueName,
|
|
156
|
+
code,
|
|
157
|
+
transient: isTransient,
|
|
138
158
|
err: err?.stack ?? err?.message ?? String(err),
|
|
139
159
|
});
|
|
140
160
|
});
|
|
@@ -9,6 +9,18 @@ import type { RedisOptions } from "ioredis";
|
|
|
9
9
|
* - `retryStrategy` — exponential 200ms → 10s cap; infinite retries.
|
|
10
10
|
* - `reconnectOnError` — reconnect on READONLY (failover to a replica
|
|
11
11
|
* that has since become primary).
|
|
12
|
+
* - `keepAlive: 30_000` — send a TCP keep-alive probe every 30s. Without
|
|
13
|
+
* this, BullMQ's blocking worker connections (BRPOPLPUSH idle while
|
|
14
|
+
* waiting for jobs) get silently dropped by intermediate NATs / Redis
|
|
15
|
+
* Cloud's idle-connection killer after ~60-300s. The next read returns
|
|
16
|
+
* ECONNRESET / ETIMEDOUT and ioredis reconnects, but each drop fires
|
|
17
|
+
* `error` events on every queue's worker. Keep-alive prevents the
|
|
18
|
+
* silent-drop in the first place, which is the actual root cause of
|
|
19
|
+
* the recurring ECONNRESET / ETIMEDOUT flood we were seeing in Slack.
|
|
20
|
+
* - `connectTimeout: 15_000` — bounds new-connection establishment so a
|
|
21
|
+
* slow DNS / handshake fails fast and retryStrategy kicks in cleanly.
|
|
22
|
+
* - `noDelay: true` — disable Nagle so small commands (PING, BRPOPLPUSH
|
|
23
|
+
* wakeups) don't pile up waiting for an MTU's worth of data.
|
|
12
24
|
*/
|
|
13
25
|
export declare const sharedRedisOptions: RedisOptions;
|
|
14
26
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"redisOptions.d.ts","sourceRoot":"","sources":["../../src/services/redisOptions.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAG5C
|
|
1
|
+
{"version":3,"file":"redisOptions.d.ts","sourceRoot":"","sources":["../../src/services/redisOptions.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAG5C;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,eAAO,MAAM,kBAAkB,EAAE,YAShC,CAAC;AAkBF;;;;GAIG;AACH,wBAAgB,oBAAoB,CAClC,MAAM,EAAE;IAAE,EAAE,EAAE,CAAC,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,CAAC,GAAG,CAAC,EAAE,GAAG,EAAE,KAAK,IAAI,KAAK,IAAI,CAAA;CAAE,EAC/D,KAAK,EAAE,MAAM,GACZ,IAAI,CA2BN"}
|
|
@@ -13,14 +13,44 @@ const logger_1 = require("../utils/logger");
|
|
|
13
13
|
* - `retryStrategy` — exponential 200ms → 10s cap; infinite retries.
|
|
14
14
|
* - `reconnectOnError` — reconnect on READONLY (failover to a replica
|
|
15
15
|
* that has since become primary).
|
|
16
|
+
* - `keepAlive: 30_000` — send a TCP keep-alive probe every 30s. Without
|
|
17
|
+
* this, BullMQ's blocking worker connections (BRPOPLPUSH idle while
|
|
18
|
+
* waiting for jobs) get silently dropped by intermediate NATs / Redis
|
|
19
|
+
* Cloud's idle-connection killer after ~60-300s. The next read returns
|
|
20
|
+
* ECONNRESET / ETIMEDOUT and ioredis reconnects, but each drop fires
|
|
21
|
+
* `error` events on every queue's worker. Keep-alive prevents the
|
|
22
|
+
* silent-drop in the first place, which is the actual root cause of
|
|
23
|
+
* the recurring ECONNRESET / ETIMEDOUT flood we were seeing in Slack.
|
|
24
|
+
* - `connectTimeout: 15_000` — bounds new-connection establishment so a
|
|
25
|
+
* slow DNS / handshake fails fast and retryStrategy kicks in cleanly.
|
|
26
|
+
* - `noDelay: true` — disable Nagle so small commands (PING, BRPOPLPUSH
|
|
27
|
+
* wakeups) don't pile up waiting for an MTU's worth of data.
|
|
16
28
|
*/
|
|
17
29
|
exports.sharedRedisOptions = {
|
|
18
30
|
maxRetriesPerRequest: null,
|
|
19
31
|
enableReadyCheck: true,
|
|
20
32
|
enableOfflineQueue: true,
|
|
33
|
+
keepAlive: 30000,
|
|
34
|
+
connectTimeout: 15000,
|
|
35
|
+
noDelay: true,
|
|
21
36
|
retryStrategy: (times) => Math.min(200 * Math.pow(1.5, times), 10000),
|
|
22
37
|
reconnectOnError: (err) => err.message.includes("READONLY"),
|
|
23
38
|
};
|
|
39
|
+
/**
|
|
40
|
+
* ioredis auto-reconnects from these — they are operational noise during
|
|
41
|
+
* a Redis Cloud failover, a brief TCP reset, or a sleeping connection
|
|
42
|
+
* timing out. Logging them at `error` pages Slack every time the window
|
|
43
|
+
* expires; demote to `warn` so the Slack transport (error-only) ignores
|
|
44
|
+
* them. Real persistent outages still surface via BullMQ job timeouts.
|
|
45
|
+
*/
|
|
46
|
+
const TRANSIENT_REDIS_ERROR_CODES = new Set([
|
|
47
|
+
"ECONNRESET",
|
|
48
|
+
"ETIMEDOUT",
|
|
49
|
+
"ECONNREFUSED",
|
|
50
|
+
"EPIPE",
|
|
51
|
+
"ENOTFOUND",
|
|
52
|
+
"EAI_AGAIN",
|
|
53
|
+
]);
|
|
24
54
|
/**
|
|
25
55
|
* Attach observability + error-dedupe listeners to an ioredis client.
|
|
26
56
|
* Reduces log spam during prolonged outages (e.g. DNS NXDOMAIN, network
|
|
@@ -42,9 +72,12 @@ function attachRedisListeners(client, label) {
|
|
|
42
72
|
return;
|
|
43
73
|
lastErrCode = code;
|
|
44
74
|
lastErrAt = now;
|
|
45
|
-
|
|
75
|
+
const isTransient = !!err.code && TRANSIENT_REDIS_ERROR_CODES.has(err.code);
|
|
76
|
+
const logFn = isTransient ? logger_1.logger.warn.bind(logger_1.logger) : logger_1.logger.error.bind(logger_1.logger);
|
|
77
|
+
logFn("redis: connection error", {
|
|
46
78
|
label,
|
|
47
79
|
code,
|
|
80
|
+
transient: isTransient,
|
|
48
81
|
err: err?.stack ?? err?.message ?? String(err),
|
|
49
82
|
});
|
|
50
83
|
});
|