@juspay/neurolink
Version:
The pipe layer of an AI nervous system: one interface connecting provider neurons to your application, across three inference types — generate, stream and decide. `decide` returns typed, calibrated judgements from a non-generative model (~400ms, ~$0.00002
92 lines (91 loc) • 4.73 kB
TypeScript
/**
* Provider-level retry utility for AI SDK calls (NL11)
*
* The Vercel AI SDK's `generateText()` and `streamText()` have built-in retry
* logic (`_retryWithExponentialBackoff()` with default `maxRetries: 2`) that
* retries on HTTP 429/500/503. These retries are completely invisible to OTel
* because they happen inside the AI SDK.
*
* This module provides an instrumented retry wrapper that:
* 1. Disables the AI SDK's internal retries (via `maxRetries: 0`)
* 2. Implements our own retry loop with full OTel span events
* 3. Records retry attempts, delays, status codes, and total attempt count
*
* @module utils/providerRetry
*/
import { type Span } from "@opentelemetry/api";
/** Maximum number of retry attempts after the initial call (total = 1 + MAX_PROVIDER_RETRIES). */
export declare const MAX_PROVIDER_RETRIES = 2;
/** Base delay in ms for exponential backoff between retries. */
export declare const BASE_RETRY_DELAY_MS = 1000;
/** Minimum delay in ms when a retryable response provides no retry timing. */
export declare const NO_HINT_FLOOR_MS = 10000;
/**
* Maximum server-requested retry delay honored by provider retries.
*
* A hint above this cap is not clamped-and-slept: the server declared an
* unavailability window we are not willing to wait out, so the error is
* surfaced immediately instead — fallback orchestration (providerFallback /
* modelChain) can route to another provider, and callers without fallback
* get a prompt rate-limit error rather than a silent multi-minute stall.
*/
export declare const MAX_RETRY_AFTER_MS = 60000;
/**
* Check whether an error thrown by the AI SDK is retryable.
*
* Uses `APICallError.isInstance()` for proper type-safe detection (the class
* uses a branded symbol marker, so `instanceof` doesn't work across package
* boundaries). Falls back to duck-typing for non-APICallError cases.
*/
/**
* Duck-typed HTTP status extraction for errors of unknown provenance.
* NeuroLink's hand-rolled clients stamp `.statusCode`; official SDK errors
* (e.g. @anthropic-ai/sdk APIError) expose `.status`. Shared with
* baseProvider's handleProviderError metadata preservation.
*/
export declare function duckTypedStatusCode(error: unknown): number | undefined;
/**
* Duck-typed Retry-After extraction for errors of unknown provenance.
* Reads a pre-parsed `.retryAfterMs` (stamped by metadata-preserving
* wrappers like handleProviderError), then `.headers` (Headers instance or
* record — e.g. @anthropic-ai/sdk APIError), then `.responseHeaders` (the
* hand-rolled OpenAI-compatible client's record). Shared with baseProvider.
*/
export declare function extractRetryAfterMsFromError(error: unknown): number | undefined;
/**
* OpenAI's chat transport keeps the type inside the JSON response body,
* rather than stamping it onto the raw error.
*/
export declare function readOpenAIBodyErrorType(responseBody: unknown): string | undefined;
/**
* An OpenAI-wire `insufficient_quota` error: the account is out of credit or
* has hit a spend cap.
*
* Exported and shared with the OpenAI error table so the retry predicate and
* the message a caller sees cannot disagree about what a permanent billing
* state looks like. The type is NOT a field on the error — OpenAI puts it
* inside the JSON response body, which the SDK keeps as a string — so any
* caller that needs it has to parse, and none should do so on its own.
*
* Deliberately keyed on the error TYPE, never on the word "quota". OpenAI and
* xAI send 429 + `type: "insufficient_quota"` for a permanent billing state,
* but other providers use "quota" for ordinary throttling — Google's 429 reads
* "Quota exceeded for quota metric ...", which IS retryable and must stay that
* way. Matching free text here would make Gemini throttling non-retryable and
* quietly remove a layer of resilience.
*/
export declare function isOpenAIQuotaExhaustedError(error: unknown): boolean;
export declare function isRetryableProviderError(error: unknown): boolean;
/**
* Extract the HTTP status code from an AI SDK error, if available.
*/
export declare function getErrorStatusCode(error: unknown): number | undefined;
/**
* Execute a provider call with instrumented retry logic.
*
* @param operation - The async operation to execute (should already use `maxRetries: 0`)
* @param span - The OTel span to annotate with retry events and attributes
* @param label - A human-readable label for log messages (e.g. "generateText", "streamText")
* @returns The result of the operation
*/
export declare function withProviderRetry<T>(operation: () => Promise<T>, span: Span | undefined, label: string, sleep?: (delayMs: number) => Promise<void>): Promise<T>;