Rate limit detection and handling utilities.
Detection
Rate limits are detected from:
Codex.Errorstructs with:rate_limitkind- HTTP 429 status codes
- Error messages containing "rate_limit"
- Retry-After headers in responses
Handling
When rate limited, the SDK:
- Extracts retry-after hint if available
- Backs off for the specified duration (or default)
- Emits telemetry event
- Retries the request
Configuration
config :codex_sdk,
rate_limit_default_delay_ms: 60_000,
rate_limit_max_delay_ms: 300_000,
rate_limit_multiplier: 2.0Example
Codex.RateLimit.with_rate_limit_handling(fn ->
make_api_call()
end, max_attempts: 3)
Summary
Functions
Calculates delay for rate limit backoff.
Detects rate limit error from response or error.
Handles rate limit by waiting and emitting telemetry.
Parses Retry-After header from response.
Wraps function with rate limit handling.
Types
@type rate_limit_info() :: %{ optional(:retry_after_ms) => non_neg_integer() | nil, optional(:message) => String.t(), optional(:details) => map(), optional(:source) => atom(), optional(:body) => map() }
Functions
@spec calculate_delay(rate_limit_info(), pos_integer()) :: non_neg_integer()
Calculates delay for rate limit backoff.
If the rate limit info contains an explicit retry_after_ms, that value
is used. Otherwise, exponential backoff is applied based on the attempt number.
Examples
iex> Codex.RateLimit.calculate_delay(%{retry_after_ms: 45_000}, 1)
45_000
iex> delay1 = Codex.RateLimit.calculate_delay(%{}, 1)
iex> delay2 = Codex.RateLimit.calculate_delay(%{}, 2)
iex> delay2 > delay1
true
@spec detect(term()) :: {:rate_limited, rate_limit_info()} | :ok
Detects rate limit error from response or error.
Returns {:rate_limited, info} if a rate limit is detected,
or :ok if not rate limited.
Examples
iex> error = Codex.Error.rate_limit("Rate limited", retry_after_ms: 30_000)
iex> {:rate_limited, info} = Codex.RateLimit.detect({:error, error})
iex> info.retry_after_ms
30_000
iex> Codex.RateLimit.detect({:ok, :success})
:ok
@spec handle( rate_limit_info(), keyword() ) :: :ok
Handles rate limit by waiting and emitting telemetry.
Sleeps for the calculated delay and emits a [:codex, :rate_limit, :rate_limited]
telemetry event.
Options
:attempt- Current attempt number (default: 1)
Examples
info = %{retry_after_ms: 1000}
Codex.RateLimit.handle(info, attempt: 1)
# Sleeps for 1000ms and emits telemetry
@spec parse_retry_after(map()) :: non_neg_integer() | nil
Parses Retry-After header from response.
Handles both numeric seconds and HTTP-date formats.
Examples
iex> Codex.RateLimit.parse_retry_after(%{headers: %{"retry-after" => "60"}})
60_000
iex> Codex.RateLimit.parse_retry_after(%{headers: %{"Retry-After" => "120"}})
120_000
iex> Codex.RateLimit.parse_retry_after(%{})
nil
Wraps function with rate limit handling.
Automatically detects rate limit responses and retries with appropriate backoff. Uses exponential backoff by default, or respects explicit retry-after hints from the API.
Options
:max_attempts- Maximum number of attempts (default: 3)
Examples
result = Codex.RateLimit.with_rate_limit_handling(fn ->
make_api_call()
end, max_attempts: 3)