vidspotai-shared 1.0.124 → 1.0.126
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/globals/aiModels/enums.d.ts +38 -0
- package/lib/globals/aiModels/enums.d.ts.map +1 -1
- package/lib/globals/aiModels/enums.js +49 -1
- package/lib/globals/aiModels/index.d.ts.map +1 -1
- package/lib/globals/aiModels/index.js +2 -0
- package/lib/globals/aiModels/providers/alibaba.d.ts.map +1 -1
- package/lib/globals/aiModels/providers/alibaba.js +460 -53
- package/lib/globals/aiModels/providers/bytedance.d.ts.map +1 -1
- package/lib/globals/aiModels/providers/bytedance.js +221 -39
- package/lib/globals/aiModels/providers/google.d.ts.map +1 -1
- package/lib/globals/aiModels/providers/google.js +178 -32
- package/lib/globals/aiModels/providers/kling.d.ts.map +1 -1
- package/lib/globals/aiModels/providers/kling.js +86 -12
- package/lib/globals/aiModels/providers/minimax.d.ts.map +1 -1
- package/lib/globals/aiModels/providers/minimax.js +77 -0
- package/lib/globals/aiModels/providers/openai.d.ts.map +1 -1
- package/lib/globals/aiModels/providers/openai.js +12 -0
- package/lib/globals/aiModels/providers/pixverse.d.ts.map +1 -1
- package/lib/globals/aiModels/providers/pixverse.js +101 -28
- package/lib/globals/aiModels/providers/runway.d.ts.map +1 -1
- package/lib/globals/aiModels/providers/runway.js +450 -24
- package/lib/globals/aiModels/providers/xai.d.ts +4 -0
- package/lib/globals/aiModels/providers/xai.d.ts.map +1 -0
- package/lib/globals/aiModels/providers/xai.js +72 -0
- package/lib/globals/aiModels/tierHelpers.d.ts +10 -0
- package/lib/globals/aiModels/tierHelpers.d.ts.map +1 -1
- package/lib/globals/aiModels/tierHelpers.js +48 -7
- package/lib/globals/aiModels/types.d.ts +21 -0
- package/lib/globals/aiModels/types.d.ts.map +1 -1
- package/lib/models/image.model.d.ts +24 -0
- package/lib/models/image.model.d.ts.map +1 -1
- package/lib/models/image.model.js +12 -0
- package/lib/models/index.d.ts +1 -0
- package/lib/models/index.d.ts.map +1 -1
- package/lib/models/index.js +1 -0
- package/lib/models/klingElement.model.d.ts +52 -0
- package/lib/models/klingElement.model.d.ts.map +1 -0
- package/lib/models/klingElement.model.js +24 -0
- package/lib/models/user.model.d.ts +7 -1
- package/lib/models/user.model.d.ts.map +1 -1
- package/lib/models/video.model.d.ts +11 -0
- package/lib/models/video.model.d.ts.map +1 -1
- package/lib/schemas/videoPlan.schema.d.ts +88 -0
- package/lib/schemas/videoPlan.schema.d.ts.map +1 -1
- package/lib/schemas/videoPlan.schema.js +27 -1
- package/lib/services/agent/executor/visual.d.ts.map +1 -1
- package/lib/services/agent/executor/visual.js +40 -2
- package/lib/services/agent/mentionedRefs.d.ts.map +1 -1
- package/lib/services/agent/mentionedRefs.js +4 -1
- package/lib/services/agent/planner/structuralRules.d.ts.map +1 -1
- package/lib/services/agent/planner/structuralRules.js +8 -3
- package/lib/services/agent/poster/posterAgent.schema.d.ts +3 -3
- package/lib/services/agent/providerFallback/chains.d.ts +25 -0
- package/lib/services/agent/providerFallback/chains.d.ts.map +1 -1
- package/lib/services/agent/providerFallback/chains.js +59 -5
- package/lib/services/agent/repair/strategies/llmRepair.strategy.d.ts.map +1 -1
- package/lib/services/agent/repair/strategies/llmRepair.strategy.js +2 -0
- package/lib/services/agent/tools/animateImage.tool.d.ts +31 -0
- package/lib/services/agent/tools/animateImage.tool.d.ts.map +1 -1
- package/lib/services/agent/tools/animateImage.tool.js +10 -3
- package/lib/services/agent/tools/generateImage.tool.d.ts +8 -4
- package/lib/services/agent/tools/generateImage.tool.d.ts.map +1 -1
- package/lib/services/agent/tools/generateImage.tool.js +24 -7
- package/lib/services/agent/tools/generateVideo.tool.d.ts +32 -0
- package/lib/services/agent/tools/generateVideo.tool.d.ts.map +1 -1
- package/lib/services/agent/tools/generateVideo.tool.js +37 -9
- package/lib/services/agent/tools/planVideo.tool.d.ts +19 -0
- package/lib/services/agent/tools/planVideo.tool.d.ts.map +1 -1
- package/lib/services/aiGen/aiGenFactory.service.d.ts.map +1 -1
- package/lib/services/aiGen/aiGenFactory.service.js +40 -0
- package/lib/services/aiGen/canonicalAdapters/cameraControl.types.d.ts +6 -3
- package/lib/services/aiGen/canonicalAdapters/cameraControl.types.d.ts.map +1 -1
- package/lib/services/aiGen/canonicalAdapters/cameraControl.types.js +35 -0
- package/lib/services/aiGen/canonicalAdapters/cameraDirection.d.ts +36 -0
- package/lib/services/aiGen/canonicalAdapters/cameraDirection.d.ts.map +1 -0
- package/lib/services/aiGen/canonicalAdapters/cameraDirection.js +168 -0
- package/lib/services/aiGen/canonicalAdapters/index.d.ts +1 -0
- package/lib/services/aiGen/canonicalAdapters/index.d.ts.map +1 -1
- package/lib/services/aiGen/canonicalAdapters/index.js +1 -0
- package/lib/services/aiGen/canonicalAdapters/multiShot.types.d.ts +3 -2
- package/lib/services/aiGen/canonicalAdapters/multiShot.types.d.ts.map +1 -1
- package/lib/services/aiGen/helpers.d.ts +40 -0
- package/lib/services/aiGen/helpers.d.ts.map +1 -1
- package/lib/services/aiGen/helpers.js +48 -2
- package/lib/services/aiGen/index.d.ts +1 -0
- package/lib/services/aiGen/index.d.ts.map +1 -1
- package/lib/services/aiGen/index.js +3 -0
- package/lib/services/aiGen/providers/alibaba/alibaba.d.ts +13 -1
- package/lib/services/aiGen/providers/alibaba/alibaba.d.ts.map +1 -1
- package/lib/services/aiGen/providers/alibaba/alibaba.js +313 -33
- package/lib/services/aiGen/providers/bytedance/bytedance.service.d.ts +29 -1
- package/lib/services/aiGen/providers/bytedance/bytedance.service.d.ts.map +1 -1
- package/lib/services/aiGen/providers/bytedance/bytedance.service.js +371 -54
- package/lib/services/aiGen/providers/google/google.service.d.ts +16 -0
- package/lib/services/aiGen/providers/google/google.service.d.ts.map +1 -1
- package/lib/services/aiGen/providers/google/google.service.js +372 -116
- package/lib/services/aiGen/providers/google/googleKeyPool.d.ts +6 -0
- package/lib/services/aiGen/providers/google/googleKeyPool.d.ts.map +1 -1
- package/lib/services/aiGen/providers/google/googleKeyPool.js +8 -0
- package/lib/services/aiGen/providers/kling/kling.service.d.ts +17 -1
- package/lib/services/aiGen/providers/kling/kling.service.d.ts.map +1 -1
- package/lib/services/aiGen/providers/kling/kling.service.js +59 -3
- package/lib/services/aiGen/providers/kling/klingCredits.d.ts +1 -1
- package/lib/services/aiGen/providers/kling/klingCredits.d.ts.map +1 -1
- package/lib/services/aiGen/providers/kling/klingCredits.js +19 -1
- package/lib/services/aiGen/providers/kling/klingRequests.d.ts +37 -7
- package/lib/services/aiGen/providers/kling/klingRequests.d.ts.map +1 -1
- package/lib/services/aiGen/providers/kling/klingRequests.js +191 -13
- package/lib/services/aiGen/providers/kling/types.d.ts +78 -0
- package/lib/services/aiGen/providers/kling/types.d.ts.map +1 -1
- package/lib/services/aiGen/providers/minimax/minimax.service.d.ts +11 -2
- package/lib/services/aiGen/providers/minimax/minimax.service.d.ts.map +1 -1
- package/lib/services/aiGen/providers/minimax/minimax.service.js +168 -2
- package/lib/services/aiGen/providers/minimax/types.d.ts +63 -0
- package/lib/services/aiGen/providers/minimax/types.d.ts.map +1 -1
- package/lib/services/aiGen/providers/pixverse/pixverse.service.d.ts +12 -0
- package/lib/services/aiGen/providers/pixverse/pixverse.service.d.ts.map +1 -1
- package/lib/services/aiGen/providers/pixverse/pixverse.service.js +131 -17
- package/lib/services/aiGen/providers/runway/hostedModels.d.ts +84 -0
- package/lib/services/aiGen/providers/runway/hostedModels.d.ts.map +1 -0
- package/lib/services/aiGen/providers/runway/hostedModels.js +266 -0
- package/lib/services/aiGen/providers/runway/index.d.ts +1 -0
- package/lib/services/aiGen/providers/runway/index.d.ts.map +1 -1
- package/lib/services/aiGen/providers/runway/index.js +1 -0
- package/lib/services/aiGen/providers/runway/runway.service.d.ts +1 -1
- package/lib/services/aiGen/providers/runway/runway.service.d.ts.map +1 -1
- package/lib/services/aiGen/providers/runway/runway.service.js +118 -21
- package/lib/services/aiGen/providers/runway/types.d.ts +18 -3
- package/lib/services/aiGen/providers/runway/types.d.ts.map +1 -1
- package/lib/services/aiGen/providers/runway/types.js +31 -3
- package/lib/services/aiGen/providers/types.d.ts +55 -0
- package/lib/services/aiGen/providers/types.d.ts.map +1 -1
- package/lib/services/aiGen/providers/xai/xai.service.d.ts +19 -0
- package/lib/services/aiGen/providers/xai/xai.service.d.ts.map +1 -0
- package/lib/services/aiGen/providers/xai/xai.service.js +205 -0
- package/lib/services/firestore.service.d.ts +4 -0
- package/lib/services/firestore.service.d.ts.map +1 -1
- package/lib/services/firestore.service.js +5 -0
- package/lib/services/index.d.ts +2 -0
- package/lib/services/index.d.ts.map +1 -1
- package/lib/services/index.js +2 -0
- package/lib/services/klingElements/klingElementLibrary.service.d.ts +37 -0
- package/lib/services/klingElements/klingElementLibrary.service.d.ts.map +1 -0
- package/lib/services/klingElements/klingElementLibrary.service.js +179 -0
- package/lib/services/trialGuard/index.d.ts +2 -0
- package/lib/services/trialGuard/index.d.ts.map +1 -0
- package/lib/services/trialGuard/index.js +17 -0
- package/lib/services/trialGuard/trialSignals.d.ts +88 -0
- package/lib/services/trialGuard/trialSignals.d.ts.map +1 -0
- package/lib/services/trialGuard/trialSignals.js +160 -0
- package/lib/utils/helpers.d.ts.map +1 -1
- package/lib/utils/helpers.js +5 -1
- package/package.json +2 -1
|
@@ -11,6 +11,7 @@ const aiModels_1 = require("../../../../globals/aiModels");
|
|
|
11
11
|
const types_1 = require("../../../../globals/types");
|
|
12
12
|
const firebase_1 = require("../../../../libs/firebase");
|
|
13
13
|
const storage_1 = require("../../../../libs/storage");
|
|
14
|
+
const mp4Duration_1 = require("../../../../utils/mp4Duration");
|
|
14
15
|
const helpers_1 = require("../../../../utils/helpers");
|
|
15
16
|
const logger_1 = require("../../../../utils/logger");
|
|
16
17
|
const errors_1 = require("../../../../utils/errors");
|
|
@@ -81,6 +82,31 @@ async function mp4IsComplete(path) {
|
|
|
81
82
|
return (buf.includes(Buffer.from("ftyp", "ascii")) &&
|
|
82
83
|
buf.includes(Buffer.from("moov", "ascii")));
|
|
83
84
|
}
|
|
85
|
+
// ── Gemini Omni (Interactions API) ─────────────────────────────────────────
|
|
86
|
+
// Omni is not a Veo `generateVideos` model: it is served by the Interactions
|
|
87
|
+
// API, which the pinned @google/genai (1.23) predates, so it is called over
|
|
88
|
+
// REST. Docs: ai.google.dev/gemini-api/docs/omni + /api/interactions-api.
|
|
89
|
+
const INTERACTIONS_BASE = "https://generativelanguage.googleapis.com/v1beta/interactions";
|
|
90
|
+
/** Task prefix that routes checkVideoStatus to the Omni poller (Veo operation
|
|
91
|
+
* names are `models/…/operations/…`, so the two can't collide). */
|
|
92
|
+
const OMNI_TASK_PREFIX = "interactions/";
|
|
93
|
+
function isOmniModelId(modelId) {
|
|
94
|
+
return modelId.startsWith("gemini-omni");
|
|
95
|
+
}
|
|
96
|
+
/** Throw a Google REST error body verbatim as the Error message — the same
|
|
97
|
+
* JSON shape the SDK's ApiError carries, so classifyGoogleApiError /
|
|
98
|
+
* classifyGoogleKeyHealth work unchanged. */
|
|
99
|
+
async function omniFetch(url, apiKey, init) {
|
|
100
|
+
const res = await fetch(url, {
|
|
101
|
+
...init,
|
|
102
|
+
headers: { "Content-Type": "application/json", "x-goog-api-key": apiKey },
|
|
103
|
+
});
|
|
104
|
+
const text = await res.text();
|
|
105
|
+
if (!res.ok) {
|
|
106
|
+
throw new Error(text || JSON.stringify({ error: { code: res.status, message: res.statusText } }));
|
|
107
|
+
}
|
|
108
|
+
return JSON.parse(text);
|
|
109
|
+
}
|
|
84
110
|
const VEO_3_1_MODELS = new Set([
|
|
85
111
|
aiModels_1.EVideoGenModels.GOOGLE_VEO_3_1,
|
|
86
112
|
aiModels_1.EVideoGenModels.GOOGLE_VEO_3_1_FAST,
|
|
@@ -209,6 +235,8 @@ class GoogleService extends baseAiGenProvider_service_1.BaseAiGenProviderService
|
|
|
209
235
|
(0, helpers_2.validateParams)(params);
|
|
210
236
|
const modelConfig = aiModels_1.aiModelConfigs[params.modelKey];
|
|
211
237
|
const modelId = modelConfig.modelId;
|
|
238
|
+
if (isOmniModelId(modelId))
|
|
239
|
+
return this.generateOmniVideo(params, modelId);
|
|
212
240
|
const isVeo3_1 = VEO_3_1_MODELS.has(params.modelKey);
|
|
213
241
|
// ── Veo generation mode ──────────────────────────────────────────────
|
|
214
242
|
// Several Veo constraints are MODE-dependent (not just model-dependent),
|
|
@@ -475,11 +503,157 @@ class GoogleService extends baseAiGenProvider_service_1.BaseAiGenProviderService
|
|
|
475
503
|
// covers the no-key-left case) — surface the last error.
|
|
476
504
|
throw lastErr ?? new Error("Failed to initiate video generation task");
|
|
477
505
|
}
|
|
506
|
+
/**
|
|
507
|
+
* Gemini Omni submit (Interactions API, `background: true`). Input mapping per
|
|
508
|
+
* the Omni guide: one image = image-to-video, two images = first/last-frame
|
|
509
|
+
* interpolation, several images = subject references. Omni has no
|
|
510
|
+
* negativePrompt / seed / duration-seconds params, so a negative prompt and
|
|
511
|
+
* the requested length are folded into the prompt text (the guide's own
|
|
512
|
+
* advice for negatives: "Do not do X").
|
|
513
|
+
*/
|
|
514
|
+
async generateOmniVideo(params, modelId) {
|
|
515
|
+
if (params.lastFrameImageUrl && !params.inputImageUrl) {
|
|
516
|
+
throw new errors_1.UserFacingError("Gemini Omni needs a starting image as well as a last-frame image to interpolate between them. " +
|
|
517
|
+
"Please add a first-frame image or remove the last-frame image.", errors_1.USER_FACING_ERROR_CODES.CAPABILITY_MISMATCH);
|
|
518
|
+
}
|
|
519
|
+
// Frames and subject references are both plain image parts to Omni; mixing
|
|
520
|
+
// them is undocumented and would make two frames + refs ambiguous, so
|
|
521
|
+
// explicit frames win and references are dropped (logged).
|
|
522
|
+
const frameUrls = params.inputImageUrl
|
|
523
|
+
? [params.inputImageUrl, ...(params.lastFrameImageUrl ? [params.lastFrameImageUrl] : [])]
|
|
524
|
+
: [];
|
|
525
|
+
const refUrls = (params.referenceImageUrls ?? []).slice(0, 3);
|
|
526
|
+
if (frameUrls.length && refUrls.length) {
|
|
527
|
+
logger_1.logger.warn("Gemini Omni: frame image(s) and reference images both supplied — using frames only", {
|
|
528
|
+
modelKey: params.modelKey,
|
|
529
|
+
referenceImageCount: refUrls.length,
|
|
530
|
+
});
|
|
531
|
+
}
|
|
532
|
+
const imageUrls = frameUrls.length ? frameUrls : refUrls;
|
|
533
|
+
const images = await Promise.all(imageUrls.map((url) => this.withTransientRetry("omni image fetch", () => (0, googleFetch_1.fetchAsImage)(url))));
|
|
534
|
+
const textParts = [params.prompt];
|
|
535
|
+
if (params.duration)
|
|
536
|
+
textParts.push(`The video is about ${params.duration} seconds long.`);
|
|
537
|
+
if (params.negativePrompt)
|
|
538
|
+
textParts.push(`Do not include: ${params.negativePrompt}.`);
|
|
539
|
+
const text = textParts.join("\n\n");
|
|
540
|
+
// `task` is only set where the image count alone is ambiguous (the guide
|
|
541
|
+
// recommends letting the model infer otherwise — the field adds constraints).
|
|
542
|
+
const task = !frameUrls.length && refUrls.length ? "reference_to_video" : undefined;
|
|
543
|
+
const body = JSON.stringify({
|
|
544
|
+
model: modelId,
|
|
545
|
+
input: images.length
|
|
546
|
+
? [
|
|
547
|
+
...images.map((img) => ({ type: "image", data: img.imageBytes, mime_type: img.mimeType })),
|
|
548
|
+
{ type: "text", text },
|
|
549
|
+
]
|
|
550
|
+
: text,
|
|
551
|
+
response_format: {
|
|
552
|
+
type: "video",
|
|
553
|
+
...(params.aspectRatio ? { aspect_ratio: params.aspectRatio } : {}),
|
|
554
|
+
...(params.resolution ? { resolution: params.resolution } : {}),
|
|
555
|
+
// URI delivery: the guide's advice for anything over ~4 MB (>720p), and
|
|
556
|
+
// it keeps multi-MB base64 out of every status poll.
|
|
557
|
+
delivery: "uri",
|
|
558
|
+
},
|
|
559
|
+
...(task ? { generation_config: { video_config: { task } } } : {}),
|
|
560
|
+
// Async: returns an interaction id immediately; checkVideoStatus polls it.
|
|
561
|
+
// (Requires store=true, the default.)
|
|
562
|
+
background: true,
|
|
563
|
+
});
|
|
564
|
+
const modelConfig = aiModels_1.aiModelConfigs[params.modelKey];
|
|
565
|
+
const keyEntry = await this.keyPool.pickForSubmit(modelId, modelConfig.requestPerMin ?? 0, modelConfig.requestPerDay ?? 0);
|
|
566
|
+
try {
|
|
567
|
+
const interaction = await this.withTransientRetry("interactions.create", () => omniFetch(INTERACTIONS_BASE, keyEntry.apiKey, { method: "POST", body }));
|
|
568
|
+
if (!interaction?.id)
|
|
569
|
+
throw new Error("Failed to initiate Gemini Omni interaction");
|
|
570
|
+
return {
|
|
571
|
+
task: (0, googleApiKeys_1.encodeVeoTask)(keyEntry.id, `${OMNI_TASK_PREFIX}${interaction.id}`, this.keyPool.size),
|
|
572
|
+
status: types_1.EVideoSceneStatus.TRIGGERED,
|
|
573
|
+
};
|
|
574
|
+
}
|
|
575
|
+
catch (err) {
|
|
576
|
+
const keyFailure = (0, googleErrors_1.classifyGoogleKeyHealth)(err);
|
|
577
|
+
if (keyFailure && keyFailure.kind !== "quota") {
|
|
578
|
+
await this.keyPool.markDepleted(keyEntry.id, keyFailure.ttlSeconds, keyFailure.reason);
|
|
579
|
+
}
|
|
580
|
+
logger_1.logger.warn("Gemini Omni: request rejected — submitted shape", {
|
|
581
|
+
modelKey: params.modelKey,
|
|
582
|
+
modelId,
|
|
583
|
+
keyId: keyEntry.id,
|
|
584
|
+
aspectRatio: params.aspectRatio,
|
|
585
|
+
resolution: params.resolution,
|
|
586
|
+
duration: params.duration,
|
|
587
|
+
frameCount: frameUrls.length,
|
|
588
|
+
referenceImageCount: frameUrls.length ? 0 : refUrls.length,
|
|
589
|
+
task: task ?? null,
|
|
590
|
+
promptChars: text.length,
|
|
591
|
+
error: err instanceof Error ? err.message.slice(0, 500) : String(err),
|
|
592
|
+
});
|
|
593
|
+
throw err;
|
|
594
|
+
}
|
|
595
|
+
}
|
|
596
|
+
async checkOmniStatus(keyId, client, interactionId, outputFilename, outputFilePath) {
|
|
597
|
+
const apiKey = this.keyPool.apiKeyById(keyId);
|
|
598
|
+
const interaction = await this.withTransientRetry("interactions.get", () => omniFetch(`${INTERACTIONS_BASE}/${encodeURIComponent(interactionId)}`, apiKey));
|
|
599
|
+
if (interaction.status === "in_progress")
|
|
600
|
+
return { status: types_1.EVideoSceneStatus.PENDING };
|
|
601
|
+
if (interaction.status !== "completed") {
|
|
602
|
+
const apiErr = interaction.errors?.[0] ?? interaction.error;
|
|
603
|
+
const classified = apiErr ? (0, googleErrors_1.classifyGoogleApiError)({ message: JSON.stringify({ error: apiErr }) }) : null;
|
|
604
|
+
return {
|
|
605
|
+
status: types_1.EVideoSceneStatus.FAILED,
|
|
606
|
+
errorMessage: classified
|
|
607
|
+
? JSON.stringify(classified.toJSON())
|
|
608
|
+
: `Gemini Omni interaction ${interaction.status}` +
|
|
609
|
+
(apiErr?.message ? `: ${apiErr.message}` : ""),
|
|
610
|
+
};
|
|
611
|
+
}
|
|
612
|
+
const outputs = (interaction.steps ?? []).filter((s) => s.type === "model_output");
|
|
613
|
+
const contents = outputs.flatMap((s) => s.content ?? []);
|
|
614
|
+
const videoPart = contents.find((c) => c.type === "video" && (c.data || c.uri));
|
|
615
|
+
logger_1.logger.info("Gemini Omni: interaction completed", {
|
|
616
|
+
interactionId,
|
|
617
|
+
outputTokens: interaction.usage?.total_output_tokens ?? null,
|
|
618
|
+
delivery: videoPart?.data ? "inline" : videoPart?.uri ? "uri" : "none",
|
|
619
|
+
});
|
|
620
|
+
if (!videoPart) {
|
|
621
|
+
// Omni answers a blocked/refused prompt with text instead of a video.
|
|
622
|
+
// Surface it as a user-input error (sceneMonitor demotes the log level).
|
|
623
|
+
const reply = contents.filter((c) => c.type === "text" && c.text).map((c) => c.text).join(" ").trim();
|
|
624
|
+
return {
|
|
625
|
+
status: types_1.EVideoSceneStatus.FAILED,
|
|
626
|
+
errorMessage: `user_input_error: ${reply.slice(0, 500) || "Gemini Omni returned no video"}`,
|
|
627
|
+
};
|
|
628
|
+
}
|
|
629
|
+
// GET returns the video inline even for `delivery: "uri"` (Omni guide), so
|
|
630
|
+
// this branch is a fallback. It bills the requested duration, since the
|
|
631
|
+
// shared Veo persist path doesn't measure the file.
|
|
632
|
+
if (videoPart.uri) {
|
|
633
|
+
return this.persistFilesApiVideo(client, { uri: videoPart.uri, mimeType: videoPart.mime_type ?? "video/mp4" }, outputFilename, outputFilePath);
|
|
634
|
+
}
|
|
635
|
+
// Inline delivery (the API may still inline small outputs).
|
|
636
|
+
const bytes = Buffer.from(videoPart.data, "base64");
|
|
637
|
+
if (!bytes.includes(Buffer.from("ftyp", "ascii")) || !bytes.includes(Buffer.from("moov", "ascii"))) {
|
|
638
|
+
return {
|
|
639
|
+
status: types_1.EVideoSceneStatus.FAILED,
|
|
640
|
+
errorMessage: "Generated video failed integrity validation (incomplete inline payload).",
|
|
641
|
+
};
|
|
642
|
+
}
|
|
643
|
+
const videoUrl = await storage_1.mediaStorage.put(`${outputFilePath}/${outputFilename}.mp4`, bytes, videoPart.mime_type ?? "video/mp4");
|
|
644
|
+
// Omni picks its own length (3–10s; the requested duration is only a prompt
|
|
645
|
+
// hint), so report the delivered length and finalization bills that.
|
|
646
|
+
const durationSec = (0, mp4Duration_1.readMp4DurationSec)(bytes);
|
|
647
|
+
return { videoUrl, status: types_1.EVideoSceneStatus.COMPLETED, ...(durationSec !== undefined ? { durationSec } : {}) };
|
|
648
|
+
}
|
|
478
649
|
async checkVideoStatus({ task, outputFilename, outputFilePath = "videos", }) {
|
|
479
650
|
// Re-select the key that submitted this task (Veo operations are
|
|
480
651
|
// project-scoped). Un-tagged / unknown ids fall back to the legacy client.
|
|
481
652
|
const { keyId, operationName } = (0, googleApiKeys_1.decodeVeoTask)(task);
|
|
482
653
|
const client = this.keyPool.clientById(keyId) ?? this.ai;
|
|
654
|
+
if (operationName.startsWith(OMNI_TASK_PREFIX)) {
|
|
655
|
+
return this.checkOmniStatus(keyId, client, operationName.slice(OMNI_TASK_PREFIX.length), outputFilename, outputFilePath);
|
|
656
|
+
}
|
|
483
657
|
const operation = new genai_1.GenerateVideosOperation();
|
|
484
658
|
operation.name = operationName;
|
|
485
659
|
const result = await this.withTransientRetry("getVideosOperation", () => client.operations.getVideosOperation({ operation }));
|
|
@@ -524,94 +698,102 @@ class GoogleService extends baseAiGenProvider_service_1.BaseAiGenProviderService
|
|
|
524
698
|
};
|
|
525
699
|
}
|
|
526
700
|
const video = result.response?.generatedVideos?.[0]?.video;
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
filePath,
|
|
575
|
-
attempt,
|
|
576
|
-
localSize,
|
|
577
|
-
});
|
|
701
|
+
return this.persistFilesApiVideo(client, video, outputFilename, outputFilePath);
|
|
702
|
+
}
|
|
703
|
+
return { status: types_1.EVideoSceneStatus.PENDING };
|
|
704
|
+
}
|
|
705
|
+
/**
|
|
706
|
+
* Download a Files-API video (Veo output, or Gemini Omni `delivery:"uri"`
|
|
707
|
+
* output) with the key that produced it and persist it GCS-primary with the
|
|
708
|
+
* truncation guards below. Returns the scene's terminal status.
|
|
709
|
+
*/
|
|
710
|
+
async persistFilesApiVideo(client, video, outputFilename, outputFilePath) {
|
|
711
|
+
const localPath = `${outputFilename}.mp4`; // use /tmp for cloud functions
|
|
712
|
+
const filePath = `${outputFilePath}/${outputFilename}.mp4`;
|
|
713
|
+
const file = (0, firebase_1.getBucket)().file(filePath);
|
|
714
|
+
// Download with the SAME key that submitted the task. The Veo output is
|
|
715
|
+
// stored via the Files API in the issuing key's GCP project, so it is
|
|
716
|
+
// project-scoped exactly like the operation — the default `this.ai`
|
|
717
|
+
// (legacy GOOGLE_API_KEY) client can only read files from its own
|
|
718
|
+
// project. With the multi-key pool, the drain-first key (k0) lives in a
|
|
719
|
+
// separate project, so downloading its videos via `this.ai` would 403/404.
|
|
720
|
+
// Use the re-selected `client` (falls back to `this.ai` for un-tagged
|
|
721
|
+
// legacy tasks, which were submitted on GOOGLE_API_KEY anyway).
|
|
722
|
+
// Download → validate-complete → upload, retried as a unit. The Files API
|
|
723
|
+
// downloader intermittently resolves before the clip is fully flushed to
|
|
724
|
+
// disk; uploading then truncates the scene at a chunk boundary (commonly
|
|
725
|
+
// exactly 4 MiB), dropping the trailing `moov` atom and yielding a corrupt
|
|
726
|
+
// MP4 that ffprobe rejects downstream (poster/watermark "moov atom not
|
|
727
|
+
// found", and a broken deliverable). Guard with (1) a stable-size wait so
|
|
728
|
+
// we only read a fully-written file, (2) a structural ftyp+moov check, and
|
|
729
|
+
// (3) a stored-vs-local size match after upload. Any failure re-runs the
|
|
730
|
+
// whole download instead of shipping corruption.
|
|
731
|
+
//
|
|
732
|
+
// GCS-PRIMARY (not mediaStorage.put): the size-verified integrity guard
|
|
733
|
+
// above needs GCS's own writeFromFile + getMetadata size readback: R2
|
|
734
|
+
// gets a best-effort mirror via mediaStorage.mirrorFromFile once the
|
|
735
|
+
// GCS copy is confirmed intact. See notes/R2_MEDIA_MIGRATION_PLAN.md §10.
|
|
736
|
+
const MAX_FETCH_ATTEMPTS = 3;
|
|
737
|
+
let uploaded = false;
|
|
738
|
+
for (let attempt = 1; attempt <= MAX_FETCH_ATTEMPTS && !uploaded; attempt++) {
|
|
739
|
+
await this.withTransientRetry("files.download", () => client.files.download({ file: video, downloadPath: localPath }));
|
|
740
|
+
const localSize = await waitForStableFile(localPath, 30000, 500);
|
|
741
|
+
if (await mp4IsComplete(localPath)) {
|
|
742
|
+
await (0, promises_2.pipeline)((0, fs_1.createReadStream)(localPath), file.createWriteStream({ contentType: "video/mp4" }));
|
|
743
|
+
const [meta] = await file.getMetadata();
|
|
744
|
+
const storedSize = Number(meta.size);
|
|
745
|
+
if (Number.isFinite(storedSize) && storedSize === localSize) {
|
|
746
|
+
uploaded = true;
|
|
747
|
+
break; // faithful, complete upload
|
|
578
748
|
}
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
status: types_1.EVideoSceneStatus.FAILED,
|
|
586
|
-
errorMessage: "Generated video failed integrity validation (incomplete/truncated download).",
|
|
587
|
-
};
|
|
749
|
+
logger_1.logger.warn("google: scene upload size mismatch — retrying", {
|
|
750
|
+
filePath,
|
|
751
|
+
attempt,
|
|
752
|
+
localSize,
|
|
753
|
+
storedSize,
|
|
754
|
+
});
|
|
588
755
|
}
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
// (zero-egress delivery) — the GCS streaming write above stays the guarded
|
|
595
|
-
// system-of-record; this only runs when R2_WRITE_ENABLED. Before cleanup.
|
|
596
|
-
await storage_1.mediaStorage.mirrorFromFile(filePath, localPath, "video/mp4");
|
|
597
|
-
// Optionally clean up local file
|
|
598
|
-
await promises_1.default
|
|
599
|
-
.unlink(localPath)
|
|
600
|
-
.then(() => {
|
|
601
|
-
// console.log("Local file deleted:", localPath);
|
|
602
|
-
})
|
|
603
|
-
.catch((err) => {
|
|
604
|
-
logger_1.logger.warn("google: local file cleanup failed", {
|
|
605
|
-
localPath,
|
|
606
|
-
err: err?.stack ?? err?.message ?? String(err),
|
|
756
|
+
else {
|
|
757
|
+
logger_1.logger.warn("google: downloaded scene missing moov atom — retrying", {
|
|
758
|
+
filePath,
|
|
759
|
+
attempt,
|
|
760
|
+
localSize,
|
|
607
761
|
});
|
|
608
|
-
}
|
|
762
|
+
}
|
|
763
|
+
// Failed validation — discard the partial file before the next attempt.
|
|
764
|
+
await promises_1.default.unlink(localPath).catch(() => { });
|
|
765
|
+
}
|
|
766
|
+
if (!uploaded) {
|
|
767
|
+
logger_1.logger.error("google: scene download/upload failed integrity validation — giving up", { filePath });
|
|
609
768
|
return {
|
|
610
|
-
|
|
611
|
-
|
|
769
|
+
status: types_1.EVideoSceneStatus.FAILED,
|
|
770
|
+
errorMessage: "Generated video failed integrity validation (incomplete/truncated download).",
|
|
612
771
|
};
|
|
613
772
|
}
|
|
614
|
-
|
|
773
|
+
const [signedUrl] = await file.getSignedUrl({
|
|
774
|
+
action: "read",
|
|
775
|
+
expires: "03-09-2491",
|
|
776
|
+
});
|
|
777
|
+
// mediaStorage: best-effort R2 mirror of the integrity-validated local clip
|
|
778
|
+
// (zero-egress delivery) — the GCS streaming write above stays the guarded
|
|
779
|
+
// system-of-record; this only runs when R2_WRITE_ENABLED. Before cleanup.
|
|
780
|
+
await storage_1.mediaStorage.mirrorFromFile(filePath, localPath, "video/mp4");
|
|
781
|
+
// Optionally clean up local file
|
|
782
|
+
await promises_1.default
|
|
783
|
+
.unlink(localPath)
|
|
784
|
+
.then(() => {
|
|
785
|
+
// console.log("Local file deleted:", localPath);
|
|
786
|
+
})
|
|
787
|
+
.catch((err) => {
|
|
788
|
+
logger_1.logger.warn("google: local file cleanup failed", {
|
|
789
|
+
localPath,
|
|
790
|
+
err: err?.stack ?? err?.message ?? String(err),
|
|
791
|
+
});
|
|
792
|
+
});
|
|
793
|
+
return {
|
|
794
|
+
videoUrl: signedUrl,
|
|
795
|
+
status: types_1.EVideoSceneStatus.COMPLETED,
|
|
796
|
+
};
|
|
615
797
|
}
|
|
616
798
|
async generateImage(params) {
|
|
617
799
|
try {
|
|
@@ -654,6 +836,9 @@ class GoogleService extends baseAiGenProvider_service_1.BaseAiGenProviderService
|
|
|
654
836
|
// 200 with the images filtered out — so the reason is only available here.
|
|
655
837
|
// Mirrors the Veo video path (raiMediaFilteredReasons, above).
|
|
656
838
|
const blockReasons = [];
|
|
839
|
+
// Nano Banana declining with a text-only answer instead of an image —
|
|
840
|
+
// see the 2026-09-30 comment below where this is populated.
|
|
841
|
+
const textDeclines = [];
|
|
657
842
|
if (isImagen) {
|
|
658
843
|
const resp = await this.ai.models.generateImages({
|
|
659
844
|
model: modelId,
|
|
@@ -698,12 +883,28 @@ class GoogleService extends baseAiGenProvider_service_1.BaseAiGenProviderService
|
|
|
698
883
|
// Prefix the requested ratio so the model produces an output that at
|
|
699
884
|
// least matches orientation (Gemini still won't honor exact pixel
|
|
700
885
|
// dimensions, but landscape vs portrait is reliable).
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
886
|
+
//
|
|
887
|
+
// 2026-09-29: the Gemini 3.x image models take a structured
|
|
888
|
+
// `imageConfig { aspectRatio, imageSize }` (ai.google.dev image-generation
|
|
889
|
+
// "Aspect ratios and image size"). When the model declares the requested
|
|
890
|
+
// ratio we send it structurally and skip the prompt hint; the hint stays
|
|
891
|
+
// the fallback for a ratio the model's allowedValues don't list.
|
|
892
|
+
const declaredAspects = modelConfig.fields?.aspectRatio
|
|
893
|
+
?.allowedValues;
|
|
894
|
+
const structuredAspect = params.aspectRatio && declaredAspects?.includes(params.aspectRatio) ? params.aspectRatio : undefined;
|
|
895
|
+
// imageSize was declared + PRICED per size (nano-banana-pro) but never sent,
|
|
896
|
+
// so a 4K request billed $0.24 and returned the 1K default. "auto_2K" is
|
|
897
|
+
// the picker's legacy default → 2K.
|
|
898
|
+
const declaredSizes = modelConfig.fields?.imageSize
|
|
899
|
+
?.allowedValues;
|
|
900
|
+
const requestedSize = params.imageSize === "auto_2K" ? "2K" : params.imageSize;
|
|
901
|
+
const structuredSize = requestedSize && declaredSizes?.includes(requestedSize) ? requestedSize : undefined;
|
|
902
|
+
// Orientation is derived from the ratio itself (a fixed portrait list
|
|
903
|
+
// mislabeled 2:3 / 1:4 / 1:8 as landscape).
|
|
904
|
+
const [aw, ah] = (params.aspectRatio ?? "").split(":").map(Number);
|
|
905
|
+
const orientation = aw && ah ? (aw < ah ? "vertical portrait" : aw === ah ? "square" : "horizontal landscape") : "";
|
|
906
|
+
const aspectHint = params.aspectRatio && !structuredAspect
|
|
907
|
+
? `Generate the image with a ${params.aspectRatio} aspect ratio${orientation ? ` (${orientation} orientation)` : ""}. `
|
|
707
908
|
: "";
|
|
708
909
|
const parts = [{ text: `${aspectHint}${params.prompt}` }];
|
|
709
910
|
const refs = [
|
|
@@ -714,34 +915,71 @@ class GoogleService extends baseAiGenProvider_service_1.BaseAiGenProviderService
|
|
|
714
915
|
const ref = await (0, googleFetch_1.fetchAsImage)(url);
|
|
715
916
|
parts.push({ inlineData: { mimeType: ref.mimeType, data: ref.imageBytes } });
|
|
716
917
|
}
|
|
717
|
-
|
|
918
|
+
// The pinned SDK's ImageConfig type predates `imageSize`, but its request
|
|
919
|
+
// transformer copies `imageConfig` through verbatim, so the field reaches
|
|
920
|
+
// the API. (Newer SDKs type it: `imageSize?: string`.)
|
|
921
|
+
const imageConfig = {
|
|
922
|
+
...(structuredAspect ? { aspectRatio: structuredAspect } : {}),
|
|
923
|
+
...(structuredSize ? { imageSize: structuredSize } : {}),
|
|
924
|
+
};
|
|
925
|
+
// generateContent returns ONE image per call, but the route bills
|
|
926
|
+
// numImages (1–8) up front with no shortfall reconciliation — so issue one
|
|
927
|
+
// call per requested image instead of charging N× for a single output.
|
|
928
|
+
const responses = await Promise.all(Array.from({ length: Math.max(1, params.numImages ?? 1) }, () => this.ai.models.generateContent({
|
|
718
929
|
model: modelId,
|
|
719
930
|
contents: [{ role: "user", parts }],
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
for (const
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
931
|
+
...(Object.keys(imageConfig).length ? { config: { imageConfig } } : {}),
|
|
932
|
+
})));
|
|
933
|
+
for (const resp of responses) {
|
|
934
|
+
const candidates = resp.candidates ?? [];
|
|
935
|
+
let gotImageThisResponse = false;
|
|
936
|
+
for (const cand of candidates) {
|
|
937
|
+
for (const part of cand.content?.parts ?? []) {
|
|
938
|
+
const inline = part.inlineData;
|
|
939
|
+
if (inline?.data) {
|
|
940
|
+
allBytes.push({
|
|
941
|
+
bytes: Buffer.from(inline.data, "base64"),
|
|
942
|
+
mime: inline.mimeType ?? "image/png",
|
|
943
|
+
});
|
|
944
|
+
gotImageThisResponse = true;
|
|
945
|
+
}
|
|
730
946
|
}
|
|
731
947
|
}
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
948
|
+
// Gemini reports a safety stop two ways, neither of which throws:
|
|
949
|
+
// promptFeedback.blockReason (whole prompt rejected → candidates empty)
|
|
950
|
+
// and candidate.finishReason (generation halted: IMAGE_SAFETY,
|
|
951
|
+
// PROHIBITED_CONTENT, RECITATION, SPII).
|
|
952
|
+
const promptBlock = resp
|
|
953
|
+
.promptFeedback?.blockReason;
|
|
954
|
+
if (promptBlock)
|
|
955
|
+
blockReasons.push(promptBlock);
|
|
956
|
+
for (const cand of candidates) {
|
|
957
|
+
const finish = cand.finishReason;
|
|
958
|
+
if (finish && finish !== "STOP" && finish !== "MAX_TOKENS") {
|
|
959
|
+
blockReasons.push(finish);
|
|
960
|
+
}
|
|
961
|
+
}
|
|
962
|
+
// 2026-09-30: gemini-3.1-flash-image (Nano Banana 2) refuses some
|
|
963
|
+
// requests with finishReason "STOP" and NO promptFeedback block — it
|
|
964
|
+
// just answers with a TEXT part instead of an image. Verified by
|
|
965
|
+
// replaying prod job 9NyH60DhQD5wZiS6UenP (an edit asking to undress
|
|
966
|
+
// a person in an uploaded photo): candidates[0] = one text part, in
|
|
967
|
+
// the user's language ("Saya tidak bisa mengubah gambar ... karena
|
|
968
|
+
// adanya pelanggaran kebijakan terkait konten seksual"),
|
|
969
|
+
// finishReason STOP, no promptFeedback. Neither block signal above
|
|
970
|
+
// fires, so this used to fall through to the generic "no image
|
|
971
|
+
// bytes" Error and page Slack on every refusal. Harvest the model's
|
|
972
|
+
// text as the reason. It is logged (warn) for triage but never shown
|
|
973
|
+
// to the user — it is model-language-specific free text.
|
|
974
|
+
if (!gotImageThisResponse) {
|
|
975
|
+
const declineText = candidates
|
|
976
|
+
.flatMap((cand) => cand.content?.parts ?? [])
|
|
977
|
+
.map((part) => part.text)
|
|
978
|
+
.filter((text) => Boolean(text && text.trim()))
|
|
979
|
+
.join(" ")
|
|
980
|
+
.trim();
|
|
981
|
+
if (declineText)
|
|
982
|
+
textDeclines.push(declineText);
|
|
745
983
|
}
|
|
746
984
|
}
|
|
747
985
|
}
|
|
@@ -763,8 +1001,20 @@ class GoogleService extends baseAiGenProvider_service_1.BaseAiGenProviderService
|
|
|
763
1001
|
});
|
|
764
1002
|
throw new errors_1.UserFacingError("Your prompt was flagged by Google's safety filters. Please rephrase and try again.", errors_1.USER_FACING_ERROR_CODES.CONTENT_POLICY_VIOLATION);
|
|
765
1003
|
}
|
|
766
|
-
|
|
767
|
-
|
|
1004
|
+
if (textDeclines.length) {
|
|
1005
|
+
logger_1.logger.warn("Google image generation declined with text instead of an image", {
|
|
1006
|
+
modelKey: params.modelKey,
|
|
1007
|
+
modelId,
|
|
1008
|
+
declines: textDeclines,
|
|
1009
|
+
});
|
|
1010
|
+
// A text-only STOP from an image model is a refusal (policy), same
|
|
1011
|
+
// outcome as the safety-filter branch above, so same code: the
|
|
1012
|
+
// frontend already maps it to "flagged by the model's safety filters".
|
|
1013
|
+
throw new errors_1.UserFacingError("Your prompt was flagged by Google's safety filters. Please rephrase and try again.", errors_1.USER_FACING_ERROR_CODES.CONTENT_POLICY_VIOLATION);
|
|
1014
|
+
}
|
|
1015
|
+
// No block reason and no text at all — genuinely unexpected; keep it
|
|
1016
|
+
// loud so a real platform/SDK regression isn't muted by the branches
|
|
1017
|
+
// above.
|
|
768
1018
|
throw new Error("Google image generation returned no image bytes");
|
|
769
1019
|
}
|
|
770
1020
|
const ts = Date.now();
|
|
@@ -805,8 +1055,14 @@ class GoogleService extends baseAiGenProvider_service_1.BaseAiGenProviderService
|
|
|
805
1055
|
// Image-gen per-image cost keyed by output size (nano-banana-pro). Falls
|
|
806
1056
|
// back to `cost.fixed` if `imageSize` isn't supplied by the caller.
|
|
807
1057
|
if (cost?.perImageSize) {
|
|
808
|
-
|
|
809
|
-
|
|
1058
|
+
// No size → nothing is sent and the model renders its default (1K), which
|
|
1059
|
+
// `cost.fixed` prices. "auto_2K" is normalized to "2K" exactly as
|
|
1060
|
+
// _generateImage does, so we bill the size actually requested.
|
|
1061
|
+
// REMOVED FOR v2 — kept for reference: defaulted to 2K, overbilling
|
|
1062
|
+
// size-less requests on models whose 1K and 2K prices differ.
|
|
1063
|
+
// const key = imageSize ?? "2K";
|
|
1064
|
+
const key = imageSize === "auto_2K" ? "2K" : imageSize;
|
|
1065
|
+
const perImage = (key !== undefined ? cost.perImageSize[key] : undefined) ?? cost.fixed ?? 0;
|
|
810
1066
|
return (0, helpers_1.getCreditsFromCost)(perImage * Math.max(1, numImages), applyFloor, creditFloor);
|
|
811
1067
|
}
|
|
812
1068
|
// Image-gen flat per-image cost (Imagen / Nano Banana).
|
|
@@ -39,6 +39,12 @@ declare class GoogleKeyPool {
|
|
|
39
39
|
get primaryClient(): GoogleGenAI;
|
|
40
40
|
/** Resolve a tagged task's key id back to its client (undefined if unknown). */
|
|
41
41
|
clientById(id: string | undefined): GoogleGenAI | undefined;
|
|
42
|
+
/**
|
|
43
|
+
* Raw API key for a tagged task's key id, for REST paths the pinned SDK
|
|
44
|
+
* doesn't wrap (the Interactions API behind Gemini Omni). Untagged/unknown
|
|
45
|
+
* ids resolve to the primary key, which is where legacy tasks were issued.
|
|
46
|
+
*/
|
|
47
|
+
apiKeyById(id: string | undefined): string;
|
|
42
48
|
/**
|
|
43
49
|
* Pick the key that should serve a new submission. Walks keys in priority
|
|
44
50
|
* order and returns the first whose per-key minute + day budgets still have
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"googleKeyPool.d.ts","sourceRoot":"","sources":["../../../../../src/services/aiGen/providers/google/googleKeyPool.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAG5C,OAAO,EACL,iBAAiB,EAKlB,MAAM,iBAAiB,CAAC;AAEzB;;;;;;;;;;;;;;;;GAgBG;AAEH,UAAU,QAAQ;IAChB,EAAE,EAAE,MAAM,CAAC;IACX,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,WAAW,CAAC;IACpB,6DAA6D;IAC7D,IAAI,EAAE,MAAM,CAAC;CACd;AAiDD,cAAM,aAAa;IACjB,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAa;;IA4BrC,IAAI,IAAI,IAAI,MAAM,CAEjB;IAED,8EAA8E;IAC9E,IAAI,MAAM,IAAI,MAAM,EAAE,CAErB;IAUD,OAAO,CAAC,WAAW;IAInB,kFAAkF;IAC5E,YAAY,CAAC,EAAE,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAkBjF,qFAAqF;YACvE,WAAW;IAiBzB,mDAAmD;IACnD,IAAI,aAAa,IAAI,WAAW,CAE/B;IAED,gFAAgF;IAChF,UAAU,CAAC,EAAE,EAAE,MAAM,GAAG,SAAS,GAAG,WAAW,GAAG,SAAS;IAK3D;;;;;;;;OAQG;IACG,aAAa,CACjB,OAAO,EAAE,MAAM,EACf,WAAW,EAAE,MAAM,EACnB,WAAW,EAAE,MAAM,EACnB,IAAI,CAAC,EAAE,GAAG,CAAC,MAAM,CAAC,GACjB,OAAO,CAAC,QAAQ,CAAC;IA4DpB,8EAA8E;YAChE,OAAO;CAUtB;AAID,sCAAsC;AACtC,wBAAgB,gBAAgB,IAAI,aAAa,CAGhD;AAED,OAAO,EAAE,iBAAiB,EAAE,CAAC;AAC7B,YAAY,EAAE,aAAa,EAAE,CAAC"}
|
|
1
|
+
{"version":3,"file":"googleKeyPool.d.ts","sourceRoot":"","sources":["../../../../../src/services/aiGen/providers/google/googleKeyPool.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAG5C,OAAO,EACL,iBAAiB,EAKlB,MAAM,iBAAiB,CAAC;AAEzB;;;;;;;;;;;;;;;;GAgBG;AAEH,UAAU,QAAQ;IAChB,EAAE,EAAE,MAAM,CAAC;IACX,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,WAAW,CAAC;IACpB,6DAA6D;IAC7D,IAAI,EAAE,MAAM,CAAC;CACd;AAiDD,cAAM,aAAa;IACjB,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAa;;IA4BrC,IAAI,IAAI,IAAI,MAAM,CAEjB;IAED,8EAA8E;IAC9E,IAAI,MAAM,IAAI,MAAM,EAAE,CAErB;IAUD,OAAO,CAAC,WAAW;IAInB,kFAAkF;IAC5E,YAAY,CAAC,EAAE,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAkBjF,qFAAqF;YACvE,WAAW;IAiBzB,mDAAmD;IACnD,IAAI,aAAa,IAAI,WAAW,CAE/B;IAED,gFAAgF;IAChF,UAAU,CAAC,EAAE,EAAE,MAAM,GAAG,SAAS,GAAG,WAAW,GAAG,SAAS;IAK3D;;;;OAIG;IACH,UAAU,CAAC,EAAE,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM;IAI1C;;;;;;;;OAQG;IACG,aAAa,CACjB,OAAO,EAAE,MAAM,EACf,WAAW,EAAE,MAAM,EACnB,WAAW,EAAE,MAAM,EACnB,IAAI,CAAC,EAAE,GAAG,CAAC,MAAM,CAAC,GACjB,OAAO,CAAC,QAAQ,CAAC;IA4DpB,8EAA8E;YAChE,OAAO;CAUtB;AAID,sCAAsC;AACtC,wBAAgB,gBAAgB,IAAI,aAAa,CAGhD;AAED,OAAO,EAAE,iBAAiB,EAAE,CAAC;AAC7B,YAAY,EAAE,aAAa,EAAE,CAAC"}
|
|
@@ -141,6 +141,14 @@ class GoogleKeyPool {
|
|
|
141
141
|
return undefined;
|
|
142
142
|
return this.entries.find((e) => e.id === id)?.client;
|
|
143
143
|
}
|
|
144
|
+
/**
|
|
145
|
+
* Raw API key for a tagged task's key id, for REST paths the pinned SDK
|
|
146
|
+
* doesn't wrap (the Interactions API behind Gemini Omni). Untagged/unknown
|
|
147
|
+
* ids resolve to the primary key, which is where legacy tasks were issued.
|
|
148
|
+
*/
|
|
149
|
+
apiKeyById(id) {
|
|
150
|
+
return (id && this.entries.find((e) => e.id === id)?.apiKey) || this.entries[0].apiKey;
|
|
151
|
+
}
|
|
144
152
|
/**
|
|
145
153
|
* Pick the key that should serve a new submission. Walks keys in priority
|
|
146
154
|
* order and returns the first whose per-key minute + day budgets still have
|