fix: retry vip98k reconciliation responses
This commit is contained in:
parent
002ab8b98d
commit
faf3a2dbb2
@ -15,6 +15,7 @@ import {
|
|||||||
addVip98kReferenceRetryToken,
|
addVip98kReferenceRetryToken,
|
||||||
getStorageObjectKeyFromPublicUrl,
|
getStorageObjectKeyFromPublicUrl,
|
||||||
getVip98kStagedMediaRetryDelay,
|
getVip98kStagedMediaRetryDelay,
|
||||||
|
getVip98kSubmissionRetryDelay,
|
||||||
shouldStageVip98kReferenceUrl,
|
shouldStageVip98kReferenceUrl,
|
||||||
} from "@/lib/ai/vip98k-reference-staging";
|
} from "@/lib/ai/vip98k-reference-staging";
|
||||||
import { createLogger } from "@/lib/observability/logger";
|
import { createLogger } from "@/lib/observability/logger";
|
||||||
@ -67,6 +68,7 @@ const REFERENCE_DOWNLOAD_TIMEOUT_MS = 120_000;
|
|||||||
const MAX_REFERENCE_DOWNLOAD_BYTES = 40 * 1024 * 1024;
|
const MAX_REFERENCE_DOWNLOAD_BYTES = 40 * 1024 * 1024;
|
||||||
const REFERENCE_STAGING_CACHE_MS = 60_000;
|
const REFERENCE_STAGING_CACHE_MS = 60_000;
|
||||||
const REFERENCE_STAGING_KEY_PREFIX = "uploads/vip98k-";
|
const REFERENCE_STAGING_KEY_PREFIX = "uploads/vip98k-";
|
||||||
|
const MAX_SUBMISSION_ATTEMPTS = 3;
|
||||||
|
|
||||||
type ReferenceStagingConfig = {
|
type ReferenceStagingConfig = {
|
||||||
sourcePublicUrl: string;
|
sourcePublicUrl: string;
|
||||||
@ -807,11 +809,12 @@ async function submitTask(params: {
|
|||||||
typeof value === "string" && isStagedReferenceUrl(value),
|
typeof value === "string" && isStagedReferenceUrl(value),
|
||||||
)
|
)
|
||||||
: [];
|
: [];
|
||||||
const maxAttempts = stagedReferences.length > 0 ? 3 : 1;
|
const maxAttempts = MAX_SUBMISSION_ATTEMPTS;
|
||||||
|
let refreshStagedReferences = false;
|
||||||
|
|
||||||
for (let attempt = 1; attempt <= maxAttempts; attempt += 1) {
|
for (let attempt = 1; attempt <= maxAttempts; attempt += 1) {
|
||||||
const requestBody =
|
const requestBody =
|
||||||
attempt === 1 || !Array.isArray(params.body.file_urls)
|
!refreshStagedReferences || !Array.isArray(params.body.file_urls)
|
||||||
? params.body
|
? params.body
|
||||||
: {
|
: {
|
||||||
...params.body,
|
...params.body,
|
||||||
@ -844,20 +847,41 @@ async function submitTask(params: {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const errorMessage = extractErrorMessage(payload);
|
const errorMessage = extractErrorMessage(payload);
|
||||||
const retryDelayMs = getVip98kStagedMediaRetryDelay({
|
const stagedMediaRetryDelayMs = getVip98kStagedMediaRetryDelay({
|
||||||
status: response.status,
|
status: response.status,
|
||||||
message: errorMessage,
|
message: errorMessage,
|
||||||
attempt,
|
attempt,
|
||||||
maxAttempts,
|
maxAttempts,
|
||||||
stagedReferenceCount: stagedReferences.length,
|
stagedReferenceCount: stagedReferences.length,
|
||||||
});
|
});
|
||||||
if (retryDelayMs !== null) {
|
if (stagedMediaRetryDelayMs !== null) {
|
||||||
logger.warn("staged reference not ready for provider; retrying submission", {
|
logger.warn("staged reference not ready for provider; retrying submission", {
|
||||||
attempt,
|
attempt,
|
||||||
retryDelayMs,
|
retryDelayMs: stagedMediaRetryDelayMs,
|
||||||
stagedReferenceCount: stagedReferences.length,
|
stagedReferenceCount: stagedReferences.length,
|
||||||
});
|
});
|
||||||
await delay(retryDelayMs);
|
refreshStagedReferences = true;
|
||||||
|
await delay(stagedMediaRetryDelayMs);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const retryAfterSeconds = Number(response.headers.get("retry-after"));
|
||||||
|
const reconciliationRetryDelayMs = getVip98kSubmissionRetryDelay({
|
||||||
|
status: response.status,
|
||||||
|
message: errorMessage,
|
||||||
|
attempt,
|
||||||
|
maxAttempts,
|
||||||
|
retryAfterMs: Number.isFinite(retryAfterSeconds)
|
||||||
|
? retryAfterSeconds * 1000
|
||||||
|
: null,
|
||||||
|
});
|
||||||
|
if (reconciliationRetryDelayMs !== null) {
|
||||||
|
logger.warn("provider is reconciling task submission; retrying", {
|
||||||
|
attempt,
|
||||||
|
retryDelayMs: reconciliationRetryDelayMs,
|
||||||
|
});
|
||||||
|
refreshStagedReferences = false;
|
||||||
|
await delay(reconciliationRetryDelayMs);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
throw new Error(`${errorMessage} (HTTP ${response.status})`);
|
throw new Error(`${errorMessage} (HTTP ${response.status})`);
|
||||||
@ -1312,7 +1336,9 @@ export function createVip98k({ apiKey, baseURL }: Vip98kConfig) {
|
|||||||
const activeModelId = String(options.overrideModelId || modelId || "").trim();
|
const activeModelId = String(options.overrideModelId || modelId || "").trim();
|
||||||
if (!activeModelId) throw new Error("VIP98K model ID is not configured");
|
if (!activeModelId) throw new Error("VIP98K model ID is not configured");
|
||||||
const activeKind = inferKind(activeModelId, runtimeConfig);
|
const activeKind = inferKind(activeModelId, runtimeConfig);
|
||||||
const { prompt, imageUrls, videoUrls } = extractPromptAndMedia(options.messages);
|
const { prompt, imageUrls, videoUrls } = extractPromptAndMedia(
|
||||||
|
options.messages,
|
||||||
|
);
|
||||||
const [publicImageUrls, publicVideoUrls] = await Promise.all([
|
const [publicImageUrls, publicVideoUrls] = await Promise.all([
|
||||||
Promise.all(
|
Promise.all(
|
||||||
imageUrls.map((url) => ensurePublicMediaUrl(url, "image")),
|
imageUrls.map((url) => ensurePublicMediaUrl(url, "image")),
|
||||||
|
|||||||
@ -70,6 +70,28 @@ export function getVip98kStagedMediaRetryDelay(params: {
|
|||||||
return params.attempt * 20_000;
|
return params.attempt * 20_000;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getVip98kSubmissionRetryDelay(params: {
|
||||||
|
status: number;
|
||||||
|
message: string;
|
||||||
|
attempt: number;
|
||||||
|
maxAttempts: number;
|
||||||
|
retryAfterMs?: number | null;
|
||||||
|
}) {
|
||||||
|
if (
|
||||||
|
params.status !== 503 ||
|
||||||
|
params.attempt >= params.maxAttempts ||
|
||||||
|
!/pending\s+safe\s+reconciliation/i.test(params.message)
|
||||||
|
) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const providerDelay = Number(params.retryAfterMs);
|
||||||
|
if (Number.isFinite(providerDelay) && providerDelay > 0) {
|
||||||
|
return Math.min(Math.max(providerDelay, 1_000), 60_000);
|
||||||
|
}
|
||||||
|
return Math.min(params.attempt * 15_000, 30_000);
|
||||||
|
}
|
||||||
|
|
||||||
export function addVip98kReferenceRetryToken(mediaUrl: string, token: string) {
|
export function addVip98kReferenceRetryToken(mediaUrl: string, token: string) {
|
||||||
const parsed = parseUrl(mediaUrl);
|
const parsed = parseUrl(mediaUrl);
|
||||||
if (!parsed) return mediaUrl;
|
if (!parsed) return mediaUrl;
|
||||||
|
|||||||
@ -13,6 +13,7 @@ import {
|
|||||||
addVip98kReferenceRetryToken,
|
addVip98kReferenceRetryToken,
|
||||||
getStorageObjectKeyFromPublicUrl,
|
getStorageObjectKeyFromPublicUrl,
|
||||||
getVip98kStagedMediaRetryDelay,
|
getVip98kStagedMediaRetryDelay,
|
||||||
|
getVip98kSubmissionRetryDelay,
|
||||||
shouldStageVip98kReferenceUrl,
|
shouldStageVip98kReferenceUrl,
|
||||||
} from "@/lib/ai/vip98k-reference-staging";
|
} from "@/lib/ai/vip98k-reference-staging";
|
||||||
import { shouldResolveVip98kResultDelivery } from "@/lib/ai/vip98k-result-delivery";
|
import { shouldResolveVip98kResultDelivery } from "@/lib/ai/vip98k-result-delivery";
|
||||||
@ -88,6 +89,34 @@ test.describe("VIP98K reference-image routing", () => {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("retries provider reconciliation without treating it as a media failure", () => {
|
||||||
|
expect(
|
||||||
|
getVip98kSubmissionRetryDelay({
|
||||||
|
status: 503,
|
||||||
|
message: "The media request is pending safe reconciliation.",
|
||||||
|
attempt: 1,
|
||||||
|
maxAttempts: 3,
|
||||||
|
}),
|
||||||
|
).toBe(15_000);
|
||||||
|
expect(
|
||||||
|
getVip98kSubmissionRetryDelay({
|
||||||
|
status: 503,
|
||||||
|
message: "pending safe reconciliation",
|
||||||
|
attempt: 2,
|
||||||
|
maxAttempts: 3,
|
||||||
|
retryAfterMs: 8_000,
|
||||||
|
}),
|
||||||
|
).toBe(8_000);
|
||||||
|
expect(
|
||||||
|
getVip98kSubmissionRetryDelay({
|
||||||
|
status: 503,
|
||||||
|
message: "pending safe reconciliation",
|
||||||
|
attempt: 3,
|
||||||
|
maxAttempts: 3,
|
||||||
|
}),
|
||||||
|
).toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
test("shows the newly published Nano Banana Pro image editor", () => {
|
test("shows the newly published Nano Banana Pro image editor", () => {
|
||||||
expect(
|
expect(
|
||||||
isVip98kImageModelVisible({
|
isVip98kImageModelVisible({
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user