fix(world): fallback status polling from failed external task result

This commit is contained in:
134 2026-08-19 13:48:07 +08:00
parent e601f8079f
commit e05b42942d

View File

@ -730,185 +730,208 @@ export async function GET(request: Request, { params }: RouteParams) {
});
providerForLog = config.providerId || null;
const modelAliasConfig = await getThreeDModelAliasConfigForRuntime();
const persistedAssetPayload =
let persistedAssetPayload =
getObjectValue(persistedWorldAsset?.assetPayload) || {};
const shouldUseExternalTaskResultApi =
let shouldUseExternalTaskResultApi =
isWorldLabsExternalTaskApi(config.baseUrl) &&
persistedAssetPayload.externalTaskApi !== false &&
persistedAssetPayload.marbleApi !== true;
if (shouldUseExternalTaskResultApi) {
const externalPayload = await worldLabsRequest<
WorldLabsExternalTaskResponse<WorldLabsExternalTaskResultData>
>(config, {
method: "GET",
path: `/api/external/task/result?taskId=${encodeURIComponent(normalizedTaskId)}`,
timeoutMs: resolveWorldLabsStatusTimeoutMs(),
});
const externalData =
unwrapExternalTaskData<WorldLabsExternalTaskResultData>(externalPayload);
const externalStatusText = getStringValue(externalData.status) || "running";
const externalStatus = normalizeExternalTaskStatus(
externalStatusText,
externalData.done === true
);
const externalAssetPayloadBase = {
...persistedAssetPayload,
providerId: config.providerId || null,
providerName: config.providerName || null,
externalTaskApi: true,
externalTaskStatus: externalStatusText,
requestId: getStringValue(externalPayload.requestId, externalPayload.request_id),
};
if (externalStatus === "running") {
return NextResponse.json({
success: true,
data: {
taskId: normalizedTaskId,
type: "world",
status: "running",
progress: null,
output: externalData,
assetPayload: externalAssetPayloadBase,
},
try {
const externalPayload = await worldLabsRequest<
WorldLabsExternalTaskResponse<WorldLabsExternalTaskResultData>
>(config, {
method: "GET",
path: `/api/external/task/result?taskId=${encodeURIComponent(normalizedTaskId)}`,
timeoutMs: resolveWorldLabsStatusTimeoutMs(),
});
}
if (externalStatus === "failed") {
const errorMessage = extractExternalTaskErrorMessage(
externalData,
externalPayload
const externalData =
unwrapExternalTaskData<WorldLabsExternalTaskResultData>(externalPayload);
const externalStatusText = getStringValue(externalData.status) || "running";
const externalStatus = normalizeExternalTaskStatus(
externalStatusText,
externalData.done === true
);
const externalAssetPayloadBase = {
...persistedAssetPayload,
providerId: config.providerId || null,
providerName: config.providerName || null,
externalTaskApi: true,
externalTaskStatus: externalStatusText,
requestId: getStringValue(externalPayload.requestId, externalPayload.request_id),
};
if (externalStatus === "running") {
return NextResponse.json({
success: true,
data: {
taskId: normalizedTaskId,
type: "world",
status: "running",
progress: null,
output: externalData,
assetPayload: externalAssetPayloadBase,
},
});
}
if (externalStatus === "failed") {
const errorMessage = extractExternalTaskErrorMessage(
externalData,
externalPayload
);
await updatePersistedWorldAsset({
id: persistedWorldAsset?.id,
status: "failed",
progress: null,
error: errorMessage,
assetPayload: externalAssetPayloadBase,
});
logger.warn("external task failed status", {
elapsedMs: Date.now() - startedAt,
taskIdLength: normalizedTaskId.length,
providerId: config.providerId || null,
error: errorMessage,
});
return NextResponse.json({
success: true,
data: {
taskId: normalizedTaskId,
type: "world",
status: "failed",
progress: null,
errorMessage,
output: externalData,
assetPayload: externalAssetPayloadBase,
},
});
}
const extractedAssetPayload = extractExternalWorldAssetPayload(externalData);
const rawMeshUrl = extractedAssetPayload.meshUrl;
const rawThumbnailUrl = extractedAssetPayload.thumbnailUrl;
const rawPanoUrl = extractedAssetPayload.panoUrl;
const stableWorldUrl: string | null = null;
const stableModelUrl = await archiveRemoteAssetIfNeeded({
cacheKey: `worldlabs:${normalizedTaskId}:mesh`,
sourceUrl: rawMeshUrl,
filenamePrefix: `worldlabs-mesh-${normalizedTaskId}`,
fallbackExtension: "glb",
validateAs: "glb",
});
const stableThumbnailUrl = await archiveRemoteAssetIfNeeded({
cacheKey: `worldlabs:${normalizedTaskId}:thumb`,
sourceUrl: rawThumbnailUrl,
filenamePrefix: `worldlabs-thumb-${normalizedTaskId}`,
fallbackExtension: "jpg",
});
const stablePanoUrl = await archiveRemoteAssetIfNeeded({
cacheKey: `worldlabs:${normalizedTaskId}:pano`,
sourceUrl: rawPanoUrl,
filenamePrefix: `worldlabs-pano-${normalizedTaskId}`,
fallbackExtension: "jpg",
});
const stableSpzUrls = await archiveRemoteAssetMapIfNeeded({
cacheKeyPrefix: `worldlabs:${normalizedTaskId}:spz`,
sourceMap: extractedAssetPayload.spzUrls,
filenamePrefix: `worldlabs-splat-${normalizedTaskId}`,
fallbackExtension: "spz",
validateAs: "spz",
});
const assetPayload = {
...externalAssetPayloadBase,
worldId: extractedAssetPayload.worldId,
worldUrl: stableWorldUrl,
displayName: extractedAssetPayload.displayName,
model: resolveThreeDModelAliasId(
extractedAssetPayload.model,
modelAliasConfig.worldModels
),
caption: extractedAssetPayload.caption,
thumbnailUrl: stableThumbnailUrl || extractedAssetPayload.thumbnailUrl,
panoUrl: stablePanoUrl || extractedAssetPayload.panoUrl,
meshUrl: stableModelUrl,
spzUrls: stableSpzUrls,
semanticsMetadata: extractedAssetPayload.semanticsMetadata,
storageArchivedAt: new Date().toISOString(),
sourceWorldUrl: extractedAssetPayload.worldUrl || null,
sourceMeshUrl:
rawMeshUrl && stableModelUrl && rawMeshUrl !== stableModelUrl
? rawMeshUrl
: null,
sourceThumbnailUrl:
rawThumbnailUrl && stableThumbnailUrl && rawThumbnailUrl !== stableThumbnailUrl
? rawThumbnailUrl
: null,
sourcePanoUrl:
rawPanoUrl && stablePanoUrl && rawPanoUrl !== stablePanoUrl
? rawPanoUrl
: null,
sourceSpzUrls:
extractedAssetPayload.spzUrls &&
stableSpzUrls !== extractedAssetPayload.spzUrls
? extractedAssetPayload.spzUrls
: null,
};
await updatePersistedWorldAsset({
id: persistedWorldAsset?.id,
status: "failed",
progress: null,
error: errorMessage,
assetPayload: externalAssetPayloadBase,
status: "success",
progress: 100,
modelUrl: stableModelUrl,
previewImageUrl: stableThumbnailUrl || stablePanoUrl || null,
error: null,
assetPayload,
});
logger.warn("external task failed status", {
logger.info("external task success status", {
elapsedMs: Date.now() - startedAt,
taskIdLength: normalizedTaskId.length,
providerId: config.providerId || null,
error: errorMessage,
hasMesh: Boolean(stableModelUrl),
hasPano: Boolean(stablePanoUrl),
hasThumbnail: Boolean(stableThumbnailUrl),
hasSplat: Boolean(stableSpzUrls && Object.keys(stableSpzUrls).length > 0),
});
return NextResponse.json({
success: true,
data: {
taskId: normalizedTaskId,
type: "world",
status: "failed",
progress: null,
errorMessage,
status: "success",
progress: 100,
output: externalData,
assetPayload: externalAssetPayloadBase,
modelUrl: stableModelUrl,
previewImageUrl: stableThumbnailUrl || stablePanoUrl || null,
worldId: assetPayload.worldId,
worldUrl: stableWorldUrl,
panoUrl: stablePanoUrl,
caption: assetPayload.caption,
assetPayload,
},
});
} catch (error) {
const fallbackAssetPayload = {
...persistedAssetPayload,
providerId: config.providerId || null,
providerName: config.providerName || null,
marbleApi: true,
externalTaskApi: false,
};
shouldUseExternalTaskResultApi = false;
persistedAssetPayload = fallbackAssetPayload;
await updatePersistedWorldAsset({
id: persistedWorldAsset?.id,
status: "running",
assetPayload: fallbackAssetPayload,
}).catch(() => {});
logger.warn("external task result failed, fallback to marble status", {
elapsedMs: Date.now() - startedAt,
taskIdLength: normalizedTaskId.length,
providerId: config.providerId || null,
error: error instanceof Error ? error.message : String(error),
});
}
const extractedAssetPayload = extractExternalWorldAssetPayload(externalData);
const rawMeshUrl = extractedAssetPayload.meshUrl;
const rawThumbnailUrl = extractedAssetPayload.thumbnailUrl;
const rawPanoUrl = extractedAssetPayload.panoUrl;
const stableWorldUrl: string | null = null;
const stableModelUrl = await archiveRemoteAssetIfNeeded({
cacheKey: `worldlabs:${normalizedTaskId}:mesh`,
sourceUrl: rawMeshUrl,
filenamePrefix: `worldlabs-mesh-${normalizedTaskId}`,
fallbackExtension: "glb",
validateAs: "glb",
});
const stableThumbnailUrl = await archiveRemoteAssetIfNeeded({
cacheKey: `worldlabs:${normalizedTaskId}:thumb`,
sourceUrl: rawThumbnailUrl,
filenamePrefix: `worldlabs-thumb-${normalizedTaskId}`,
fallbackExtension: "jpg",
});
const stablePanoUrl = await archiveRemoteAssetIfNeeded({
cacheKey: `worldlabs:${normalizedTaskId}:pano`,
sourceUrl: rawPanoUrl,
filenamePrefix: `worldlabs-pano-${normalizedTaskId}`,
fallbackExtension: "jpg",
});
const stableSpzUrls = await archiveRemoteAssetMapIfNeeded({
cacheKeyPrefix: `worldlabs:${normalizedTaskId}:spz`,
sourceMap: extractedAssetPayload.spzUrls,
filenamePrefix: `worldlabs-splat-${normalizedTaskId}`,
fallbackExtension: "spz",
validateAs: "spz",
});
const assetPayload = {
...externalAssetPayloadBase,
worldId: extractedAssetPayload.worldId,
worldUrl: stableWorldUrl,
displayName: extractedAssetPayload.displayName,
model: resolveThreeDModelAliasId(
extractedAssetPayload.model,
modelAliasConfig.worldModels
),
caption: extractedAssetPayload.caption,
thumbnailUrl: stableThumbnailUrl || extractedAssetPayload.thumbnailUrl,
panoUrl: stablePanoUrl || extractedAssetPayload.panoUrl,
meshUrl: stableModelUrl,
spzUrls: stableSpzUrls,
semanticsMetadata: extractedAssetPayload.semanticsMetadata,
storageArchivedAt: new Date().toISOString(),
sourceWorldUrl: extractedAssetPayload.worldUrl || null,
sourceMeshUrl:
rawMeshUrl && stableModelUrl && rawMeshUrl !== stableModelUrl
? rawMeshUrl
: null,
sourceThumbnailUrl:
rawThumbnailUrl && stableThumbnailUrl && rawThumbnailUrl !== stableThumbnailUrl
? rawThumbnailUrl
: null,
sourcePanoUrl:
rawPanoUrl && stablePanoUrl && rawPanoUrl !== stablePanoUrl
? rawPanoUrl
: null,
sourceSpzUrls:
extractedAssetPayload.spzUrls && stableSpzUrls !== extractedAssetPayload.spzUrls
? extractedAssetPayload.spzUrls
: null,
};
await updatePersistedWorldAsset({
id: persistedWorldAsset?.id,
status: "success",
progress: 100,
modelUrl: stableModelUrl,
previewImageUrl: stableThumbnailUrl || stablePanoUrl || null,
error: null,
assetPayload,
});
logger.info("external task success status", {
elapsedMs: Date.now() - startedAt,
taskIdLength: normalizedTaskId.length,
providerId: config.providerId || null,
hasMesh: Boolean(stableModelUrl),
hasPano: Boolean(stablePanoUrl),
hasThumbnail: Boolean(stableThumbnailUrl),
hasSplat: Boolean(stableSpzUrls && Object.keys(stableSpzUrls).length > 0),
});
return NextResponse.json({
success: true,
data: {
taskId: normalizedTaskId,
type: "world",
status: "success",
progress: 100,
output: externalData,
modelUrl: stableModelUrl,
previewImageUrl: stableThumbnailUrl || stablePanoUrl || null,
worldId: assetPayload.worldId,
worldUrl: stableWorldUrl,
panoUrl: stablePanoUrl,
caption: assetPayload.caption,
assetPayload,
},
});
}
const operation = await worldLabsRequest<WorldLabsOperation>(config, {