fix: retry vip98k reconciliation responses
This commit is contained in:
parent
002ab8b98d
commit
faf3a2dbb2
@ -15,6 +15,7 @@ import {
|
||||
addVip98kReferenceRetryToken,
|
||||
getStorageObjectKeyFromPublicUrl,
|
||||
getVip98kStagedMediaRetryDelay,
|
||||
getVip98kSubmissionRetryDelay,
|
||||
shouldStageVip98kReferenceUrl,
|
||||
} from "@/lib/ai/vip98k-reference-staging";
|
||||
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 REFERENCE_STAGING_CACHE_MS = 60_000;
|
||||
const REFERENCE_STAGING_KEY_PREFIX = "uploads/vip98k-";
|
||||
const MAX_SUBMISSION_ATTEMPTS = 3;
|
||||
|
||||
type ReferenceStagingConfig = {
|
||||
sourcePublicUrl: string;
|
||||
@ -807,11 +809,12 @@ async function submitTask(params: {
|
||||
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) {
|
||||
const requestBody =
|
||||
attempt === 1 || !Array.isArray(params.body.file_urls)
|
||||
!refreshStagedReferences || !Array.isArray(params.body.file_urls)
|
||||
? params.body
|
||||
: {
|
||||
...params.body,
|
||||
@ -844,20 +847,41 @@ async function submitTask(params: {
|
||||
}
|
||||
|
||||
const errorMessage = extractErrorMessage(payload);
|
||||
const retryDelayMs = getVip98kStagedMediaRetryDelay({
|
||||
const stagedMediaRetryDelayMs = getVip98kStagedMediaRetryDelay({
|
||||
status: response.status,
|
||||
message: errorMessage,
|
||||
attempt,
|
||||
maxAttempts,
|
||||
stagedReferenceCount: stagedReferences.length,
|
||||
});
|
||||
if (retryDelayMs !== null) {
|
||||
if (stagedMediaRetryDelayMs !== null) {
|
||||
logger.warn("staged reference not ready for provider; retrying submission", {
|
||||
attempt,
|
||||
retryDelayMs,
|
||||
retryDelayMs: stagedMediaRetryDelayMs,
|
||||
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;
|
||||
}
|
||||
throw new Error(`${errorMessage} (HTTP ${response.status})`);
|
||||
@ -1312,7 +1336,9 @@ export function createVip98k({ apiKey, baseURL }: Vip98kConfig) {
|
||||
const activeModelId = String(options.overrideModelId || modelId || "").trim();
|
||||
if (!activeModelId) throw new Error("VIP98K model ID is not configured");
|
||||
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([
|
||||
Promise.all(
|
||||
imageUrls.map((url) => ensurePublicMediaUrl(url, "image")),
|
||||
|
||||
@ -70,6 +70,28 @@ export function getVip98kStagedMediaRetryDelay(params: {
|
||||
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) {
|
||||
const parsed = parseUrl(mediaUrl);
|
||||
if (!parsed) return mediaUrl;
|
||||
|
||||
@ -13,6 +13,7 @@ import {
|
||||
addVip98kReferenceRetryToken,
|
||||
getStorageObjectKeyFromPublicUrl,
|
||||
getVip98kStagedMediaRetryDelay,
|
||||
getVip98kSubmissionRetryDelay,
|
||||
shouldStageVip98kReferenceUrl,
|
||||
} from "@/lib/ai/vip98k-reference-staging";
|
||||
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", () => {
|
||||
expect(
|
||||
isVip98kImageModelVisible({
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user