UNPKG

@openai/agents-core

Version:

The OpenAI Agents SDK is a lightweight yet powerful framework for building multi-agent workflows.

1,087 lines • 39.7 kB
import { ModelTimeoutError, UserError } from "../errors.mjs"; import { RequestUsage, Usage } from "../usage.mjs"; import { attachModelFailureUsage, consumeModelFailureUsage, createModelFailureUsageScope, } from "./usageTracking.mjs"; const DEFAULT_INITIAL_DELAY_MS = 250; const DEFAULT_MAX_DELAY_MS = 2_000; const DEFAULT_BACKOFF_MULTIPLIER = 2; const DEFAULT_BACKOFF_JITTER = true; const RETRY_AFTER_MS_HEADER = 'retry-after-ms'; const RETRY_AFTER_HEADER = 'retry-after'; const MAX_MODEL_TIMEOUT_MS = 2_147_483_647; // Marks internal veto and approval decisions without widening the public API. const hardVetoSymbol = Symbol('hardRetryVeto'); const delegableReplayVetoSymbol = Symbol('delegableReplayVeto'); const replaySafeApprovalSymbol = Symbol('replaySafeApproval'); const providerRetryAuthoritySymbol = Symbol('providerRetryAuthority'); function addFailedRetryAttemptsToUsage(usage, failedUsage) { if (failedUsage.length === 0) { return usage; } const inferredEndpoint = usage.requestUsageEntries?.[0]?.endpoint; const combinedUsage = new Usage(); for (const failedAttemptUsage of failedUsage) { combinedUsage.add(failedAttemptUsage ?? new Usage({ requests: 1, requestUsageEntries: [ new RequestUsage({ endpoint: inferredEndpoint, }), ], })); } combinedUsage.add(usage.requestUsageEntries?.length ? usage : new Usage({ requests: usage.requests, inputTokens: usage.inputTokens, outputTokens: usage.outputTokens, totalTokens: usage.totalTokens, inputTokensDetails: usage.inputTokensDetails, outputTokensDetails: usage.outputTokensDetails, requestUsageEntries: [ new RequestUsage({ inputTokens: usage.inputTokens, outputTokens: usage.outputTokens, totalTokens: usage.totalTokens, inputTokensDetails: usage.inputTokensDetails[0], outputTokensDetails: usage.outputTokensDetails[0], endpoint: inferredEndpoint, }), ], })); return combinedUsage; } function combineKnownFailureUsage(failedUsage) { let combinedUsage; for (const usage of failedUsage) { if (!usage) { continue; } combinedUsage ??= new Usage(); combinedUsage.add(usage); } return combinedUsage; } function createFailedAttemptUsageTracker(onFailureUsage) { const failedUsage = []; let pendingUsage; const report = (usage) => { pendingUsage ??= new Usage(); pendingUsage.add(usage); // Runner accounting must not depend on a later response being admitted. onFailureUsage?.(usage); }; const finishAttempt = () => { failedUsage.push(pendingUsage); pendingUsage = undefined; }; const takeKnownUsage = () => { if (pendingUsage) { finishAttempt(); } const knownUsage = combineKnownFailureUsage(failedUsage); failedUsage.length = 0; return knownUsage; }; return { report, hasAttempts: () => failedUsage.length > 0 || pendingUsage !== undefined, record(error) { const usage = consumeModelFailureUsage(error); if (usage) { report(usage); } finishAttempt(); }, addTo(usage) { if (pendingUsage) { finishAttempt(); } const combinedUsage = addFailedRetryAttemptsToUsage(usage, onFailureUsage ? failedUsage.filter((attemptUsage) => attemptUsage === undefined) : failedUsage); failedUsage.length = 0; return combinedUsage; }, finishFailure(error) { const replacementUsage = consumeModelFailureUsage(error); if (replacementUsage) { report(replacementUsage); } const knownUsage = takeKnownUsage(); if (knownUsage && !onFailureUsage) { attachModelFailureUsage(error, knownUsage); } }, close() { failedUsage.length = 0; pendingUsage = undefined; }, }; } function withRunnerManagedRetry(request) { return Object.assign({}, request, { _internal: { ...request._internal, runnerManagedRetry: true, }, }); } function shouldDisableProviderManagedRetry(request, attempt) { if (typeof request.modelSettings.retry === 'undefined') { return false; } return attempt > 1; } function isRecord(value) { return typeof value === 'object' && value !== null; } export function validateModelTimeoutMs(modelSettings) { const timeoutMs = modelSettings.timeoutMs; if (timeoutMs === undefined) { return undefined; } if (!Number.isFinite(timeoutMs) || timeoutMs <= 0 || timeoutMs > MAX_MODEL_TIMEOUT_MS) { throw new UserError(`modelSettings.timeoutMs must be a positive finite number less than or equal to ${MAX_MODEL_TIMEOUT_MS} when provided.`); } return timeoutMs; } function createModelTimeoutError(timeoutMs, cause) { return new ModelTimeoutError({ timeoutMs, cause }); } function createModelAttemptScope(request, timeoutMs) { if (timeoutMs === undefined) { return { request, cleanup: () => { }, normalizeError: (error) => error, timeoutError: () => undefined, timedOut: () => false, }; } const controller = new AbortController(); const parentSignal = request.signal; let didTimeOut = false; let timeoutError; let timeout; const clearAttemptTimeout = () => { if (timeout !== undefined) { clearTimeout(timeout); timeout = undefined; } }; const detachParentAbort = () => { parentSignal?.removeEventListener('abort', onParentAbort); }; const onParentAbort = () => { clearAttemptTimeout(); detachParentAbort(); controller.abort(parentSignal?.reason); }; if (parentSignal?.aborted) { onParentAbort(); } else { parentSignal?.addEventListener('abort', onParentAbort, { once: true }); timeout = setTimeout(() => { didTimeOut = true; timeoutError = createModelTimeoutError(timeoutMs); timeout = undefined; detachParentAbort(); controller.abort(timeoutError); }, timeoutMs); } const cleanup = () => { clearAttemptTimeout(); detachParentAbort(); }; return { request: { ...request, signal: controller.signal, }, cleanup, normalizeError: (error) => { if (parentSignal?.aborted) { try { throwAbortError(parentSignal); } catch (parentError) { return parentError; } } if (!didTimeOut) { return error; } if (error === timeoutError) { return error; } return createModelTimeoutError(timeoutMs, error); }, timeoutError: () => timeoutError, timedOut: () => didTimeOut, }; } function isStatefulRequest(request) { return Boolean(request.previousResponseId || request.conversationId); } function asError(value) { return value instanceof Error ? value : undefined; } function getNestedError(value) { if (!isRecord(value)) { return undefined; } const cause = value.cause; return cause instanceof Error ? cause : undefined; } function getErrorMessage(error) { return asError(error)?.message ?? ''; } function getErrorName(error) { return asError(error)?.name; } function getErrorCode(error) { if (isRecord(error)) { if (typeof error.code === 'string') { return error.code; } if (typeof error.errorCode === 'string') { return error.errorCode; } } const cause = getNestedError(error); return cause ? getErrorCode(cause) : undefined; } function getStatusCode(error) { if (isRecord(error)) { if (typeof error.statusCode === 'number') { return error.statusCode; } if (typeof error.status === 'number') { return error.status; } } const cause = getNestedError(error); return cause ? getStatusCode(cause) : undefined; } function isAbortLikeError(error) { if (error instanceof Error && error.name === 'AbortError') { return true; } const DomExceptionCtor = typeof DOMException !== 'undefined' ? DOMException : undefined; if (DomExceptionCtor && error instanceof DomExceptionCtor && error.name === 'AbortError') { return true; } const cause = getNestedError(error); return cause ? isAbortLikeError(cause) : false; } function isNetworkLikeError(error) { const name = getErrorName(error); if (name === 'APIConnectionError' || name === 'APIConnectionTimeoutError' || name === 'FetchError') { return true; } const code = getErrorCode(error); if (code === 'ECONNABORTED' || code === 'ECONNREFUSED' || code === 'ECONNRESET' || code === 'connection_closed_before_opening' || code === 'connection_closed_before_terminal_response_event' || code === 'EHOSTUNREACH' || code === 'ENETUNREACH' || code === 'ENOTFOUND' || code === 'EAI_AGAIN' || code === 'EPIPE' || code === 'socket_not_open' || code === 'ETIMEDOUT' || code === 'UND_ERR_CONNECT_TIMEOUT') { return true; } const message = getErrorMessage(error).toLowerCase(); if (message.includes('fetch failed') || message.includes('network error') || message.includes('connection error') || message === 'terminated' || message.includes('socket hang up')) { return true; } const cause = getNestedError(error); return cause ? isNetworkLikeError(cause) : false; } function extractHeaders(value) { if (!value) { return undefined; } if (typeof Headers !== 'undefined' && value instanceof Headers) { return value; } if (isRecord(value)) { if (typeof Headers !== 'undefined' && value.headers instanceof Headers) { return value.headers; } if (typeof Headers !== 'undefined' && value.responseHeaders instanceof Headers) { return value.responseHeaders; } if (value.responseHeaders && isRecord(value.responseHeaders)) { return Object.fromEntries(Object.entries(value.responseHeaders).flatMap(([key, headerValue]) => typeof headerValue === 'string' ? [[key, headerValue]] : [])); } if (value.response && isRecord(value.response) && value.response.headers instanceof Headers) { return value.response.headers; } } const cause = getNestedError(value); return cause ? extractHeaders(cause) : undefined; } function getHeaderValue(headers, key) { if (!headers) { return undefined; } if (typeof Headers !== 'undefined' && headers instanceof Headers) { return headers.get(key) ?? undefined; } const normalizedKey = key.toLowerCase(); for (const [headerName, headerValue] of Object.entries(headers)) { if (headerName.toLowerCase() === normalizedKey) { return headerValue; } } return undefined; } function parseRetryAfterDateOrSeconds(value) { const seconds = Number(value); if (Number.isFinite(seconds) && seconds >= 0) { return Math.round(seconds * 1_000); } const parsedDate = Date.parse(value); if (!Number.isNaN(parsedDate)) { const delayMs = parsedDate - Date.now(); return delayMs > 0 ? delayMs : undefined; } return undefined; } function getRetryAfterMs(headers) { const retryAfterMs = getHeaderValue(headers, RETRY_AFTER_MS_HEADER); if (retryAfterMs !== undefined) { const parsedMs = Number(retryAfterMs); if (Number.isFinite(parsedMs) && parsedMs >= 0) { return parsedMs; } } const retryAfter = getHeaderValue(headers, RETRY_AFTER_HEADER); if (!retryAfter) { return undefined; } return parseRetryAfterDateOrSeconds(retryAfter); } function normalizeRetryError(error, signal, providerAdvice) { const isModelTimeout = error instanceof ModelTimeoutError; const headers = extractHeaders(error); const normalized = { statusCode: getStatusCode(error), retryAfterMs: getRetryAfterMs(headers), errorCode: getErrorCode(error), isNetworkError: isNetworkLikeError(error), isAbort: !isModelTimeout && (Boolean(signal?.aborted) || isAbortLikeError(error)), }; if (providerAdvice?.retryAfterMs !== undefined) { normalized.retryAfterMs = providerAdvice.retryAfterMs; } const providerNormalized = providerAdvice?.normalized; return { ...normalized, ...(providerNormalized ?? {}), // Provider normalization may add abort evidence but cannot clear evidence // inferred from the signal or raw exception. isAbort: !isModelTimeout && (normalized.isAbort || providerNormalized?.isAbort === true), }; } function createProviderRetryAuthority(providerAdvice) { const replaySafety = providerAdvice?.replaySafety; return Object.freeze({ suggested: providerAdvice?.suggested, replaySafety: replaySafety === 'safe' || replaySafety === 'unsafe' ? replaySafety : 'unknown', responseStarted: providerAdvice?.responseStarted, }); } function withTimeoutReplayAuthority(providerAdvice, timedOut) { if (!timedOut || providerAdvice?.replaySafety === 'safe') { return providerAdvice; } return { ...providerAdvice, replaySafety: 'unsafe', }; } function requestMayHaveBeenAccepted(authority) { return (authority.replaySafety === 'unsafe' || authority.responseStarted === true); } function withProviderRetryAuthority(context, authority = createProviderRetryAuthority(context.providerAdvice)) { const existing = context[providerRetryAuthoritySymbol]; if (existing) { return context; } const internalContext = { ...context }; Object.defineProperties(internalContext, { replaySafety: { value: authority.replaySafety, enumerable: true, }, responseStarted: { value: authority.responseStarted, enumerable: true, }, statefulRequest: { value: context.statefulRequest ?? Boolean(context.previousResponseId || context.conversationId), enumerable: true, }, [providerRetryAuthoritySymbol]: { value: authority, }, }); return internalContext; } function getProviderRetryAuthority(context) { return (context[providerRetryAuthoritySymbol] ?? createProviderRetryAuthority(context.providerAdvice)); } function resolveRetryDecision(decision) { if (typeof decision === 'boolean') { return { retry: decision }; } return decision; } function markInternalDecision(decision, symbol) { const marked = { ...decision }; Object.defineProperty(marked, symbol, { value: true, enumerable: false, configurable: true, }); return marked; } function withHardVeto(decision) { return markInternalDecision(decision, hardVetoSymbol); } function withDelegableReplayVeto(decision) { const marked = withHardVeto(decision); Object.defineProperty(marked, delegableReplayVetoSymbol, { value: true, enumerable: false, configurable: true, }); return marked; } function withReplaySafeApproval(decision) { return markInternalDecision(decision, replaySafeApprovalSymbol); } function isHardVeto(decision) { return (typeof decision === 'object' && decision !== null && hardVetoSymbol in decision && decision[hardVetoSymbol] === true); } function isDelegableReplayVeto(decision) { return (isHardVeto(decision) && delegableReplayVetoSymbol in decision && decision[delegableReplayVetoSymbol] === true); } function isReplaySafeApproval(decision) { return (typeof decision === 'object' && decision !== null && replaySafeApprovalSymbol in decision && decision[replaySafeApprovalSymbol] === true); } function withUnsafeReplayApproval(decision, approveUnsafeReplay) { if (!approveUnsafeReplay || decision.approveUnsafeReplay) { return decision; } const approved = { ...decision, approveUnsafeReplay: true, }; return isReplaySafeApproval(decision) ? withReplaySafeApproval(approved) : approved; } function resolveDelegableReplayVeto(veto, approving) { if (!approving.retry || !approving.approveUnsafeReplay) { return veto; } const resolved = { retry: true, delayMs: approving.delayMs, reason: approving.reason ?? veto.reason, approveUnsafeReplay: true, }; return isReplaySafeApproval(approving) ? withReplaySafeApproval(resolved) : resolved; } function getDefaultDelayMs(attempt, backoff) { const initialDelayMs = backoff?.initialDelayMs ?? DEFAULT_INITIAL_DELAY_MS; const maxDelayMs = backoff?.maxDelayMs ?? DEFAULT_MAX_DELAY_MS; const multiplier = backoff?.multiplier ?? DEFAULT_BACKOFF_MULTIPLIER; const jitter = backoff?.jitter ?? DEFAULT_BACKOFF_JITTER; const exponent = Math.max(0, attempt - 1); const cappedDelayMs = Math.min(initialDelayMs * multiplier ** exponent, maxDelayMs); if (!jitter) { return cappedDelayMs; } return Math.round(cappedDelayMs * (0.875 + Math.random() * 0.25)); } function throwAbortError(signal) { const reason = signal?.reason; if (reason instanceof Error) { throw reason; } const error = new Error('The operation was aborted.'); error.name = 'AbortError'; throw error; } function throwIfAborted(signal) { if (signal?.aborted) { throwAbortError(signal); } } function throwIfTimedModelCallParentAborted(signal, timeoutMs) { if (timeoutMs !== undefined) { throwIfAborted(signal); } } async function awaitWithTimedModelCallParentAbort(promise, signal, timeoutMs) { if (timeoutMs === undefined || !signal) { return await promise; } if (signal.aborted) { throwAbortError(signal); } return await new Promise((resolve, reject) => { const onAbort = () => { cleanup(); try { throwAbortError(signal); } catch (error) { reject(error); } }; const cleanup = () => { signal.removeEventListener('abort', onAbort); }; signal.addEventListener('abort', onAbort, { once: true }); promise.then((value) => { cleanup(); resolve(value); }, (error) => { cleanup(); reject(error); }); }); } async function waitForRetryDelay(signal, delayMs) { if (delayMs <= 0) { if (signal?.aborted) { throwAbortError(signal); } return; } await new Promise((resolve, reject) => { const timeout = setTimeout(() => { cleanup(); resolve(); }, delayMs); const onAbort = () => { cleanup(); try { throwAbortError(signal); } catch (error) { reject(error); } }; const cleanup = () => { clearTimeout(timeout); signal?.removeEventListener('abort', onAbort); }; if (signal?.aborted) { onAbort(); return; } signal?.addEventListener('abort', onAbort, { once: true }); }); } async function getRetryAdvice(model, args) { const getModelRetryAdvice = model.getRetryAdvice; if (typeof getModelRetryAdvice !== 'function') { return undefined; } return await getModelRetryAdvice.call(model, args); } async function evaluateRetry({ error, attempt, maxRetries, retryPolicy, retryBackoff, signal, stream, request, emittedVisibleEvent, emittedRawModelEvent, providerAdvice, allowUnsafeStreamReplayApproval, }) { if (attempt > maxRetries) { return { retry: false }; } const normalized = normalizeRetryError(error, signal, providerAdvice); const authority = createProviderRetryAuthority(providerAdvice); const context = withProviderRetryAuthority({ error, attempt, maxRetries, stream, providerAdvice, normalized, previousResponseId: request.previousResponseId, conversationId: request.conversationId, }, authority); // Aborts and emitted stream events are absolute vetoes. Provider-unsafe // streaming failures also remain blocked before application policy runs. if (normalized.isAbort || emittedVisibleEvent || emittedRawModelEvent || (stream && authority.replaySafety === 'unsafe' && !allowUnsafeStreamReplayApproval)) { return { retry: false, reason: providerAdvice?.reason, }; } if (!retryPolicy) { return { retry: false }; } const decision = resolveRetryDecision(await retryPolicy(context)); if (!decision.retry) { return decision; } const statefulRequest = context.statefulRequest === true; const providerMarksReplaySafe = authority.replaySafety === 'safe'; const providerMarksReplayUnsafe = authority.replaySafety === 'unsafe'; if (statefulRequest && !(isReplaySafeApproval(decision) || providerMarksReplaySafe || (decision.approveUnsafeReplay && providerMarksReplayUnsafe))) { return { retry: false, reason: decision.reason ?? providerAdvice?.reason, }; } if (providerMarksReplayUnsafe && !(isReplaySafeApproval(decision) || decision.approveUnsafeReplay)) { return { retry: false, reason: decision.reason ?? providerAdvice?.reason, }; } return { retry: true, delayMs: decision.delayMs ?? normalized.retryAfterMs ?? getDefaultDelayMs(attempt, retryBackoff), reason: decision.reason ?? providerAdvice?.reason, approveUnsafeReplay: decision.approveUnsafeReplay, }; } export const retryPolicies = { never() { return () => false; }, providerSuggested() { return (inputContext) => { const context = withProviderRetryAuthority(inputContext); const authority = getProviderRetryAuthority(context); const { providerAdvice, normalized } = context; if (authority.suggested === false) { const veto = { retry: false, reason: providerAdvice?.reason, }; return authority.replaySafety === 'unsafe' ? withDelegableReplayVeto(veto) : withHardVeto(veto); } if (authority.suggested !== true) { return false; } const decision = { retry: true, delayMs: providerAdvice?.retryAfterMs ?? normalized.retryAfterMs, reason: providerAdvice?.reason, }; return authority.replaySafety === 'safe' ? withReplaySafeApproval(decision) : decision; }; }, networkError() { return ({ normalized }) => normalized.isNetworkError; }, httpStatus(statuses) { const allowed = new Set(statuses); return ({ normalized }) => normalized.statusCode !== undefined && allowed.has(normalized.statusCode); }, retryAfter() { return ({ normalized }) => { if (normalized.retryAfterMs === undefined) { return false; } return { retry: true, delayMs: normalized.retryAfterMs, }; }; }, any(...policies) { return async (inputContext) => { const context = withProviderRetryAuthority(inputContext); let firstRetryDecision; let lastObjectDecision; let delegableReplayVeto; for (const policy of policies) { const rawDecision = await policy(context); const decision = resolveRetryDecision(rawDecision); if (isHardVeto(decision)) { if (isDelegableReplayVeto(decision)) { delegableReplayVeto ??= decision; continue; } return decision; } if (decision.retry) { const approveUnsafeReplay = Boolean(firstRetryDecision?.approveUnsafeReplay || decision.approveUnsafeReplay); if (firstRetryDecision === undefined || (isReplaySafeApproval(decision) && !isReplaySafeApproval(firstRetryDecision))) { firstRetryDecision = decision; } firstRetryDecision = withUnsafeReplayApproval(firstRetryDecision, approveUnsafeReplay); continue; } if (typeof rawDecision !== 'boolean') { lastObjectDecision = decision; } } if (delegableReplayVeto) { return firstRetryDecision ? resolveDelegableReplayVeto(delegableReplayVeto, firstRetryDecision) : delegableReplayVeto; } if (firstRetryDecision) { return firstRetryDecision; } return lastObjectDecision ?? false; }; }, all(...policies) { return async (inputContext) => { const context = withProviderRetryAuthority(inputContext); if (policies.length === 0) { return false; } let merged = { retry: true }; let delegableReplayVeto; for (const policy of policies) { const decision = resolveRetryDecision(await policy(context)); if (isHardVeto(decision)) { if (isDelegableReplayVeto(decision)) { delegableReplayVeto ??= decision; continue; } return decision; } if (!decision.retry) { return false; } if (decision.delayMs !== undefined) { merged.delayMs = decision.delayMs; } if (decision.reason !== undefined) { merged.reason = decision.reason; } if (decision.approveUnsafeReplay) { merged.approveUnsafeReplay = true; } if (isReplaySafeApproval(decision)) { merged = withReplaySafeApproval(merged); } } if (delegableReplayVeto) { return resolveDelegableReplayVeto(delegableReplayVeto, merged); } return merged; }; }, }; export async function getResponseWithRetry(model, request, handlers = {}) { const maxRetries = request.modelSettings.retry?.maxRetries ?? 0; const retryPolicy = request.modelSettings.retry?.policy; const retryBackoff = request.modelSettings.retry?.backoff; const timeoutMs = validateModelTimeoutMs(request.modelSettings); const failedAttemptUsage = createFailedAttemptUsageTracker(handlers.onModelFailureUsage); let attempt = 1; try { while (true) { const requestForAttempt = shouldDisableProviderManagedRetry(request, attempt) ? withRunnerManagedRetry(request) : request; const attemptScope = createModelAttemptScope(requestForAttempt, timeoutMs); const usageScope = createModelFailureUsageScope(attemptScope.request, failedAttemptUsage.report); try { const response = await model.getResponse(usageScope.request); throwIfTimedModelCallParentAborted(request.signal, timeoutMs); if (attemptScope.timedOut()) { throw (attemptScope.timeoutError() ?? createModelTimeoutError(timeoutMs)); } if (!failedAttemptUsage.hasAttempts()) { return response; } return { ...response, usage: failedAttemptUsage.addTo(response.usage), }; } catch (caughtError) { failedAttemptUsage.record(caughtError); attemptScope.cleanup(); const error = attemptScope.normalizeError(caughtError); let providerAdvice; try { providerAdvice = await awaitWithTimedModelCallParentAbort(getRetryAdvice(model, { request, error: caughtError, stream: false, attempt, }), request.signal, timeoutMs); } catch (retryAdviceError) { if (attemptScope.timedOut() && isStatefulRequest(request)) { handlers.onPossiblyAcceptedRequestFailure?.(); } throwIfTimedModelCallParentAborted(request.signal, timeoutMs); throw retryAdviceError; } providerAdvice = withTimeoutReplayAuthority(providerAdvice, attemptScope.timedOut()); const authority = createProviderRetryAuthority(providerAdvice); const markPossiblyAcceptedFailure = () => { if (requestMayHaveBeenAccepted(authority) || (attemptScope.timedOut() && isStatefulRequest(request) && authority.replaySafety !== 'safe')) { handlers.onPossiblyAcceptedRequestFailure?.(); } }; const throwIfParentAbortedAfterTimedFailure = () => { if (timeoutMs !== undefined && request.signal?.aborted) { markPossiblyAcceptedFailure(); throwAbortError(request.signal); } }; throwIfParentAbortedAfterTimedFailure(); let decision; try { decision = await awaitWithTimedModelCallParentAbort(evaluateRetry({ error, attempt, maxRetries, retryPolicy, retryBackoff, signal: request.signal, stream: false, request, emittedVisibleEvent: false, emittedRawModelEvent: false, providerAdvice, }), request.signal, timeoutMs); } catch (retryError) { markPossiblyAcceptedFailure(); throwIfTimedModelCallParentAborted(request.signal, timeoutMs); throw retryError; } throwIfParentAbortedAfterTimedFailure(); if (!decision.retry) { markPossiblyAcceptedFailure(); throw error; } try { await waitForRetryDelay(request.signal, decision.delayMs ?? 0); } catch (retryDelayError) { markPossiblyAcceptedFailure(); throw retryDelayError; } attempt += 1; } finally { usageScope.close(); attemptScope.cleanup(); } } } catch (error) { failedAttemptUsage.finishFailure(error); throw error; } finally { failedAttemptUsage.close(); } } export async function* getStreamedResponseWithRetry(model, request, handlers = {}) { const maxRetries = request.modelSettings.retry?.maxRetries ?? 0; const retryPolicy = request.modelSettings.retry?.policy; const retryBackoff = request.modelSettings.retry?.backoff; const timeoutMs = validateModelTimeoutMs(request.modelSettings); const failedAttemptUsage = createFailedAttemptUsageTracker(handlers.onModelFailureUsage); let attempt = 1; try { while (true) { let emittedVisibleEvent = false; let emittedRawModelEvent = false; const requestForAttempt = shouldDisableProviderManagedRetry(request, attempt) ? withRunnerManagedRetry(request) : request; const attemptScope = createModelAttemptScope(requestForAttempt, timeoutMs); const usageScope = createModelFailureUsageScope(attemptScope.request, failedAttemptUsage.report); try { for await (const event of model.getStreamedResponse(usageScope.request)) { throwIfTimedModelCallParentAborted(request.signal, timeoutMs); if (attemptScope.timedOut()) { throw (attemptScope.timeoutError() ?? createModelTimeoutError(timeoutMs)); } if (event.type === 'model') { emittedRawModelEvent = true; } emittedVisibleEvent = true; if (event.type === 'response_done') { attemptScope.cleanup(); } if (event.type === 'response_done' && failedAttemptUsage.hasAttempts()) { yield { ...event, response: { ...event.response, usage: failedAttemptUsage.addTo(new Usage(event.response.usage)), }, }; continue; } yield event; } throwIfTimedModelCallParentAborted(request.signal, timeoutMs); if (attemptScope.timedOut()) { throw (attemptScope.timeoutError() ?? createModelTimeoutError(timeoutMs)); } return; } catch (caughtError) { failedAttemptUsage.record(caughtError); attemptScope.cleanup(); const error = attemptScope.normalizeError(caughtError); const markTimedOutFailure = () => { if (attemptScope.timedOut()) { handlers.onModelTimeout?.(); } }; let providerAdvice; try { providerAdvice = await awaitWithTimedModelCallParentAbort(getRetryAdvice(model, { request, error: caughtError, stream: true, attempt, }), request.signal, timeoutMs); } catch (retryAdviceError) { markTimedOutFailure(); if (attemptScope.timedOut() && isStatefulRequest(request)) { handlers.onPossiblyAcceptedRequestFailure?.(); } throwIfTimedModelCallParentAborted(request.signal, timeoutMs); throw retryAdviceError; } providerAdvice = withTimeoutReplayAuthority(providerAdvice, attemptScope.timedOut()); const authority = createProviderRetryAuthority(providerAdvice); const markPossiblyAcceptedFailure = () => { if (requestMayHaveBeenAccepted(authority) || (attemptScope.timedOut() && isStatefulRequest(request) && authority.replaySafety !== 'safe')) { handlers.onPossiblyAcceptedRequestFailure?.(); } }; const throwIfParentAbortedAfterTimedFailure = () => { if (timeoutMs !== undefined && request.signal?.aborted) { markPossiblyAcceptedFailure(); throwAbortError(request.signal); } }; throwIfParentAbortedAfterTimedFailure(); let decision; try { decision = await awaitWithTimedModelCallParentAbort(evaluateRetry({ error, attempt, maxRetries, retryPolicy, retryBackoff, signal: request.signal, stream: true, request, emittedVisibleEvent, emittedRawModelEvent, providerAdvice, allowUnsafeStreamReplayApproval: attemptScope.timedOut(), }), request.signal, timeoutMs); } catch (retryError) { markTimedOutFailure(); markPossiblyAcceptedFailure(); throwIfTimedModelCallParentAborted(request.signal, timeoutMs); throw retryError; } throwIfParentAbortedAfterTimedFailure(); if (!decision.retry) { markTimedOutFailure(); markPossiblyAcceptedFailure(); throw error; } try { await waitForRetryDelay(request.signal, decision.delayMs ?? 0); } catch (retryDelayError) { markTimedOutFailure(); markPossiblyAcceptedFailure(); throw retryDelayError; } attempt += 1; } finally { usageScope.close(); attemptScope.cleanup(); } } } catch (error) { failedAttemptUsage.finishFailure(error); throw error; } finally { failedAttemptUsage.close(); } } //# sourceMappingURL=modelRetry.mjs.map