
























@@ -311,6 +311,39 @@ async function resolveMinimaxVlmFallbackRuntime(params: {
311311};
312312}
313313314+function resolveImageDescriptionTimeoutMs(timeoutMs: number | undefined, startedAtMs: number) {
315+if (typeof timeoutMs !== "number" || !Number.isFinite(timeoutMs) || timeoutMs <= 0) {
316+return undefined;
317+}
318+return Math.max(1, Math.floor(timeoutMs - (Date.now() - startedAtMs)));
319+}
320+321+async function withImageDescriptionTimeout<T>(params: {
322+task: Promise<T>;
323+timeoutMs: number | undefined;
324+controller: AbortController;
325+}): Promise<T> {
326+if (params.timeoutMs === undefined) {
327+return await params.task;
328+}
329+let timeout: NodeJS.Timeout | undefined;
330+try {
331+return await Promise.race([
332+params.task,
333+new Promise<never>((_, reject) => {
334+timeout = setTimeout(() => {
335+params.controller.abort();
336+reject(new Error(`image description timed out after ${params.timeoutMs}ms`));
337+}, params.timeoutMs);
338+}),
339+]);
340+} finally {
341+if (timeout) {
342+clearTimeout(timeout);
343+}
344+}
345+}
346+314347async function describeImagesWithModelInternal(
315348params: ImagesDescriptionRequest,
316349options: { onPayload?: ProviderStreamOptions["onPayload"] } = {},
@@ -358,50 +391,45 @@ async function describeImagesWithModelInternal(
358391const context = buildImageContext(prompt, params.images, {
359392promptInUserContent: shouldPlaceImagePromptInUserContent(model),
360393});
394+const startedAtMs = Date.now();
361395const controller = new AbortController();
362-const timeout =
363-typeof params.timeoutMs === "number" &&
364-Number.isFinite(params.timeoutMs) &&
365-params.timeoutMs > 0
366- ? setTimeout(() => controller.abort(), params.timeoutMs)
367- : undefined;
368396369397const maxTokens = resolveImageToolMaxTokens(model.maxTokens, params.maxTokens ?? 512);
370398const completeImage = async (onPayload?: ProviderStreamOptions["onPayload"]) => {
371399const payloadHandler = composeImageDescriptionPayloadHandlers(onPayload, options.onPayload);
372-return await complete(model, context, {
373- apiKey,
374- maxTokens,
375-signal: controller.signal,
376- ...(payloadHandler ? { onPayload: payloadHandler } : {}),
400+return await withImageDescriptionTimeout({
401+ controller,
402+timeoutMs: resolveImageDescriptionTimeoutMs(params.timeoutMs, startedAtMs),
403+task: complete(model, context, {
404+ apiKey,
405+ maxTokens,
406+signal: controller.signal,
407+ ...(payloadHandler ? { onPayload: payloadHandler } : {}),
408+}),
377409});
378410};
379411412+const message = await completeImage();
380413try {
381-const message = await completeImage();
382-try {
383-const text = coerceImageAssistantText({
384- message,
385-provider: model.provider,
386-model: model.id,
387-});
388-return { text, model: model.id };
389-} catch (err) {
390-if (!isImageModelNoTextError(err) || !hasImageReasoningOnlyResponse(message)) {
391-throw err;
392-}
393-}
394-395-const retryMessage = await completeImage(disableReasoningForImageRetryPayload);
396414const text = coerceImageAssistantText({
397-message: retryMessage,
415+ message,
398416provider: model.provider,
399417model: model.id,
400418});
401419return { text, model: model.id };
402-} finally {
403-clearTimeout(timeout);
420+} catch (err) {
421+if (!isImageModelNoTextError(err) || !hasImageReasoningOnlyResponse(message)) {
422+throw err;
423+}
404424}
425+426+const retryMessage = await completeImage(disableReasoningForImageRetryPayload);
427+const text = coerceImageAssistantText({
428+message: retryMessage,
429+provider: model.provider,
430+model: model.id,
431+});
432+return { text, model: model.id };
405433}
406434407435export async function describeImagesWithModel(
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。