fix: retry vip98k while staged media propagates
This commit is contained in:
parent
01b3f6eec1
commit
c69ddb1bb3
@ -13,6 +13,7 @@ import { VIP98K_BASE_URL, normalizeVip98kBaseUrl } from "@/lib/ai/vip98k-models"
|
|||||||
import { shouldResolveVip98kResultDelivery } from "@/lib/ai/vip98k-result-delivery";
|
import { shouldResolveVip98kResultDelivery } from "@/lib/ai/vip98k-result-delivery";
|
||||||
import {
|
import {
|
||||||
getStorageObjectKeyFromPublicUrl,
|
getStorageObjectKeyFromPublicUrl,
|
||||||
|
getVip98kStagedMediaRetryDelay,
|
||||||
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";
|
||||||
@ -499,6 +500,14 @@ function getUrlHost(value: string) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function isStagedReferenceUrl(value: string) {
|
||||||
|
try {
|
||||||
|
return new URL(value).pathname.startsWith(`/${REFERENCE_STAGING_KEY_PREFIX}`);
|
||||||
|
} catch {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async function downloadReferenceImage(mediaUrl: string) {
|
async function downloadReferenceImage(mediaUrl: string) {
|
||||||
let lastError: unknown;
|
let lastError: unknown;
|
||||||
for (let attempt = 1; attempt <= REFERENCE_DOWNLOAD_ATTEMPTS; attempt += 1) {
|
for (let attempt = 1; attempt <= REFERENCE_DOWNLOAD_ATTEMPTS; attempt += 1) {
|
||||||
@ -791,25 +800,55 @@ async function submitTask(params: {
|
|||||||
const baseURL = normalizeVip98kBaseUrl(params.baseURL);
|
const baseURL = normalizeVip98kBaseUrl(params.baseURL);
|
||||||
const path = String(params.path || "/api/external/task/generate").trim();
|
const path = String(params.path || "/api/external/task/generate").trim();
|
||||||
if (!path.startsWith("/")) throw new Error("VIP98K invocation path is invalid");
|
if (!path.startsWith("/")) throw new Error("VIP98K invocation path is invalid");
|
||||||
const response = await fetch(`${baseURL}${path}`, {
|
const stagedReferences = Array.isArray(params.body.file_urls)
|
||||||
method: String(params.method || "POST").toUpperCase(),
|
? params.body.file_urls.filter(
|
||||||
headers: {
|
(value): value is string =>
|
||||||
Authorization: `Bearer ${params.apiKey}`,
|
typeof value === "string" && isStagedReferenceUrl(value),
|
||||||
"Content-Type": params.contentType || "application/json",
|
)
|
||||||
},
|
: [];
|
||||||
body: JSON.stringify(params.body),
|
const maxAttempts = stagedReferences.length > 0 ? 3 : 1;
|
||||||
signal: AbortSignal.timeout(120_000),
|
|
||||||
});
|
for (let attempt = 1; attempt <= maxAttempts; attempt += 1) {
|
||||||
const payload = await parseJsonResponse(response);
|
const response = await fetch(`${baseURL}${path}`, {
|
||||||
if (!response.ok) {
|
method: String(params.method || "POST").toUpperCase(),
|
||||||
throw new Error(`${extractErrorMessage(payload)} (HTTP ${response.status})`);
|
headers: {
|
||||||
|
Authorization: `Bearer ${params.apiKey}`,
|
||||||
|
"Content-Type": params.contentType || "application/json",
|
||||||
|
},
|
||||||
|
body: JSON.stringify(params.body),
|
||||||
|
signal: AbortSignal.timeout(120_000),
|
||||||
|
});
|
||||||
|
const payload = await parseJsonResponse(response);
|
||||||
|
if (response.ok) {
|
||||||
|
return {
|
||||||
|
payload,
|
||||||
|
taskId: extractTaskId(payload),
|
||||||
|
resultPath: extractResultPath(payload),
|
||||||
|
outputs: collectOutputUrls(payload),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const errorMessage = extractErrorMessage(payload);
|
||||||
|
const retryDelayMs = getVip98kStagedMediaRetryDelay({
|
||||||
|
status: response.status,
|
||||||
|
message: errorMessage,
|
||||||
|
attempt,
|
||||||
|
maxAttempts,
|
||||||
|
stagedReferenceCount: stagedReferences.length,
|
||||||
|
});
|
||||||
|
if (retryDelayMs !== null) {
|
||||||
|
logger.warn("staged reference not ready for provider; retrying submission", {
|
||||||
|
attempt,
|
||||||
|
retryDelayMs,
|
||||||
|
stagedReferenceCount: stagedReferences.length,
|
||||||
|
});
|
||||||
|
await delay(retryDelayMs);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
throw new Error(`${errorMessage} (HTTP ${response.status})`);
|
||||||
}
|
}
|
||||||
return {
|
|
||||||
payload,
|
throw new Error("VIP98K request failed after media readiness retries");
|
||||||
taskId: extractTaskId(payload),
|
|
||||||
resultPath: extractResultPath(payload),
|
|
||||||
outputs: collectOutputUrls(payload),
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export type Vip98kExternalTaskSubmission = {
|
export type Vip98kExternalTaskSubmission = {
|
||||||
|
|||||||
@ -1,3 +1,5 @@
|
|||||||
|
import { isMediaInputFailure } from "@/lib/ai/runtime/request-failure-classification";
|
||||||
|
|
||||||
function parseUrl(value: string) {
|
function parseUrl(value: string) {
|
||||||
try {
|
try {
|
||||||
return new URL(String(value || "").trim());
|
return new URL(String(value || "").trim());
|
||||||
@ -49,3 +51,21 @@ export function getStorageObjectKeyFromPublicUrl(
|
|||||||
return encodedKey;
|
return encodedKey;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getVip98kStagedMediaRetryDelay(params: {
|
||||||
|
status: number;
|
||||||
|
message: string;
|
||||||
|
attempt: number;
|
||||||
|
maxAttempts: number;
|
||||||
|
stagedReferenceCount: number;
|
||||||
|
}) {
|
||||||
|
if (
|
||||||
|
params.status !== 400 ||
|
||||||
|
params.stagedReferenceCount < 1 ||
|
||||||
|
params.attempt >= params.maxAttempts ||
|
||||||
|
!isMediaInputFailure(params.message)
|
||||||
|
) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return params.attempt * 20_000;
|
||||||
|
}
|
||||||
|
|||||||
@ -9,8 +9,11 @@ import {
|
|||||||
buildVip98kModelPresets,
|
buildVip98kModelPresets,
|
||||||
isVip98kImageModelVisible,
|
isVip98kImageModelVisible,
|
||||||
} from "@/lib/ai/vip98k-models";
|
} from "@/lib/ai/vip98k-models";
|
||||||
import { shouldStageVip98kReferenceUrl } from "@/lib/ai/vip98k-reference-staging";
|
import {
|
||||||
import { getStorageObjectKeyFromPublicUrl } from "@/lib/ai/vip98k-reference-staging";
|
getStorageObjectKeyFromPublicUrl,
|
||||||
|
getVip98kStagedMediaRetryDelay,
|
||||||
|
shouldStageVip98kReferenceUrl,
|
||||||
|
} from "@/lib/ai/vip98k-reference-staging";
|
||||||
import { shouldResolveVip98kResultDelivery } from "@/lib/ai/vip98k-result-delivery";
|
import { shouldResolveVip98kResultDelivery } from "@/lib/ai/vip98k-result-delivery";
|
||||||
|
|
||||||
const textToImageInvocation: Vip98kTaskInvocation = {
|
const textToImageInvocation: Vip98kTaskInvocation = {
|
||||||
@ -25,7 +28,7 @@ const textToImageInvocation: Vip98kTaskInvocation = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
test.describe("VIP98K reference-image routing", () => {
|
test.describe("VIP98K reference-image routing", () => {
|
||||||
test("stages references from the configured default storage into backup storage", () => {
|
test("identifies default-storage references that need provider staging", () => {
|
||||||
expect(
|
expect(
|
||||||
shouldStageVip98kReferenceUrl({
|
shouldStageVip98kReferenceUrl({
|
||||||
mediaUrl: "https://default.example/uploads/reference.jpg",
|
mediaUrl: "https://default.example/uploads/reference.jpg",
|
||||||
@ -55,6 +58,27 @@ test.describe("VIP98K reference-image routing", () => {
|
|||||||
).toBe("folder name/reference.jpg");
|
).toBe("folder name/reference.jpg");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("retries transient media rejection while a staged URL propagates", () => {
|
||||||
|
expect(
|
||||||
|
getVip98kStagedMediaRetryDelay({
|
||||||
|
status: 400,
|
||||||
|
message: "The media request is invalid.",
|
||||||
|
attempt: 1,
|
||||||
|
maxAttempts: 3,
|
||||||
|
stagedReferenceCount: 2,
|
||||||
|
}),
|
||||||
|
).toBe(20_000);
|
||||||
|
expect(
|
||||||
|
getVip98kStagedMediaRetryDelay({
|
||||||
|
status: 400,
|
||||||
|
message: "The media request is invalid.",
|
||||||
|
attempt: 3,
|
||||||
|
maxAttempts: 3,
|
||||||
|
stagedReferenceCount: 2,
|
||||||
|
}),
|
||||||
|
).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