# `Codex.Retry`
[🔗](https://github.com/nshkrdotcom/codex_sdk/blob/v0.18.1/lib/codex/retry.ex#L1)

Retry logic with configurable backoff strategies.

## Strategies
  * `:exponential` - Exponential backoff (default)
  * `:linear` - Linear backoff
  * `:constant` - Fixed delay
  * `fun/1` - Custom backoff function

## Options
  * `:max_attempts` - Maximum retry attempts (default: 4)
  * `:base_delay_ms` - Base delay for backoff (default: 200)
  * `:max_delay_ms` - Maximum delay cap (default: 10_000)
  * `:jitter` - Add random jitter (default: true)
  * `:retry_if` - Predicate to determine if error is retryable
  * `:on_retry` - Callback invoked on each retry

## Example

    Codex.Retry.with_retry(fn ->
      make_api_call()
    end, max_attempts: 3, strategy: :exponential)

# `opts`

```elixir
@type opts() :: [
  max_attempts: pos_integer(),
  base_delay_ms: non_neg_integer(),
  max_delay_ms: non_neg_integer(),
  jitter: boolean(),
  strategy: strategy(),
  retry_if: (term() -&gt; boolean()),
  on_retry: (attempt :: pos_integer(), error :: term() -&gt; :ok)
]
```

# `strategy`

```elixir
@type strategy() ::
  :exponential
  | :linear
  | :constant
  | (attempt :: pos_integer() -&gt; non_neg_integer())
```

# `calculate_delay`

```elixir
@spec calculate_delay(pos_integer(), opts()) :: non_neg_integer()
```

Calculates delay for given attempt using configured strategy.

## Examples

    iex> opts = [base_delay_ms: 100, max_delay_ms: 10_000, strategy: :exponential, jitter: false]
    iex> Codex.Retry.calculate_delay(1, opts)
    100
    iex> Codex.Retry.calculate_delay(2, opts)
    200
    iex> Codex.Retry.calculate_delay(3, opts)
    400

# `default_opts`

```elixir
@spec default_opts() :: opts()
```

Returns the default options for retry operations.

Useful for inspecting or modifying default configuration.

## Examples

    iex> Codex.Retry.default_opts()[:max_attempts]
    4

# `retryable?`

```elixir
@spec retryable?(term()) :: boolean()
```

Default predicate for retryable errors.

Retries on:
- Timeout errors
- Connection errors
- 5xx HTTP errors
- Rate limit errors (429)
- `Codex.Error` with `:rate_limit` kind
- Stream errors
- `Codex.TransportError` with `retryable?: true`

Does NOT retry on:
- Authentication errors
- Invalid request errors
- Context window exceeded
- Unknown error types

## Examples

    iex> Codex.Retry.retryable?(:timeout)
    true
    iex> Codex.Retry.retryable?({:http_error, 503})
    true
    iex> Codex.Retry.retryable?({:http_error, 429})
    true
    iex> Codex.Retry.retryable?({:http_error, 401})
    false
    iex> Codex.Retry.retryable?(:auth_failed)
    false

# `with_retry`

```elixir
@spec with_retry((-&gt; {:ok, term()} | {:error, term()}), opts()) ::
  {:ok, term()} | {:error, term()}
```

Executes function with retry logic.

Returns `{:ok, result}` on success or `{:error, reason}` after all attempts exhausted.

## Options

  * `:max_attempts` - Maximum number of attempts (default: 4)
  * `:base_delay_ms` - Base delay in milliseconds (default: 200)
  * `:max_delay_ms` - Maximum delay cap in milliseconds (default: 10_000)
  * `:jitter` - Add random jitter to delays (default: true)
  * `:strategy` - Backoff strategy (default: `:exponential`)
  * `:retry_if` - Predicate function to determine if error is retryable
  * `:on_retry` - Callback invoked before each retry with attempt number and error

## Examples

    # Basic usage with defaults
    Codex.Retry.with_retry(fn -> make_api_call() end)

    # Custom configuration
    Codex.Retry.with_retry(
      fn -> risky_operation() end,
      max_attempts: 5,
      base_delay_ms: 100,
      strategy: :linear,
      on_retry: fn attempt, error ->
        Logger.warning("Retry #{attempt}: #{inspect(error)}")
      end
    )

# `with_stream_retry`

```elixir
@spec with_stream_retry((-&gt; Enumerable.t()), opts()) :: Enumerable.t()
```

Wraps an async stream with retry logic.

For streaming operations, retries the entire stream from the beginning
when a retryable error occurs.

## Options

Same as `with_retry/2`.

## Examples

    stream = Codex.Retry.with_stream_retry(fn ->
      make_streaming_request()
    end, max_attempts: 3)

    Enum.each(stream, &process_item/1)

---

*Consult [api-reference.md](api-reference.md) for complete listing*
