export const SECRET_RESOLUTION_MAX_ATTEMPTS = 3; export const SECRET_RESOLUTION_MAX_RETRY_AFTER_MS = 2_000; // Per-attempt client deadline for the secret-resolution gateway call. This is an // internal control-plane fetch (resolve a named secret), not provider work, so it // must return quickly. Without a bound, a stalled connection hangs the tool call // "1 in flight" indefinitely; an abort here is caught as a transport failure and // retried/failed loudly via secretResolutionRetryDecision. export const SECRET_RESOLUTION_ATTEMPT_TIMEOUT_MS = 30_000; const SECRET_RESOLUTION_RETRY_JITTER_CAPS_MS = [100, 250] as const; export type SecretResolutionRetryDecision = { retry: boolean; retryDelayMs: number; retryAfterMs: number | null; retryAfterExceedsCap: boolean; }; export function isRetryableSecretResolutionStatus(status: number): boolean { return status === 429 || (status >= 500 && status <= 599); } function parsedRetryAfterMs( value: string | null, nowMs: number, ): number | null { const normalized = value?.trim(); if (!normalized) return null; if (/^\d+$/.test(normalized)) { const seconds = Number(normalized); const milliseconds = seconds * 1_000; return Number.isSafeInteger(milliseconds) ? milliseconds : null; } const deadlineMs = Date.parse(normalized); if (!Number.isFinite(deadlineMs) || deadlineMs <= nowMs) return null; return deadlineMs - nowMs; } function boundedRandom(value: number): number { if (!Number.isFinite(value)) return 0; return Math.min(Math.max(value, 0), 1); } export function secretResolutionRetryDecision(input: { attempt: number; retryAfter: string | null; nowMs?: number; random?: () => number; }): SecretResolutionRetryDecision { if (input.attempt >= SECRET_RESOLUTION_MAX_ATTEMPTS) { return { retry: false, retryDelayMs: 0, retryAfterMs: null, retryAfterExceedsCap: false, }; } const retryAfterMs = parsedRetryAfterMs( input.retryAfter, input.nowMs ?? Date.now(), ); if (retryAfterMs !== null) { const retryAfterExceedsCap = retryAfterMs > SECRET_RESOLUTION_MAX_RETRY_AFTER_MS; return { retry: !retryAfterExceedsCap, retryDelayMs: retryAfterExceedsCap ? 0 : retryAfterMs, retryAfterMs, retryAfterExceedsCap, }; } const jitterCapMs = SECRET_RESOLUTION_RETRY_JITTER_CAPS_MS[input.attempt - 1] ?? SECRET_RESOLUTION_RETRY_JITTER_CAPS_MS[1]; const retryDelayMs = Math.floor( boundedRandom((input.random ?? Math.random)()) * (jitterCapMs + 1), ); return { retry: true, retryDelayMs: Math.min(retryDelayMs, jitterCapMs), retryAfterMs: null, retryAfterExceedsCap: false, }; }