-module(anthropic@retry). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "src/anthropic/retry.gleam"). -export([default_retry_config/0, no_retry_config/0, aggressive_retry_config/0, with_max_retries/2, with_base_delay_ms/2, with_max_delay_ms/2, with_jitter_factor/2, with_backoff_multiplier/2, should_retry_error/1, get_retry_after_from_error/1, retries_enabled/1, calculate_delay/2, calculate_delay_with_retry_after/3, retry_with_backoff/2, retry/2, max_total_delay/1]). -export_type([retry_config/0, retry_result/1]). -if(?OTP_RELEASE >= 27). -define(MODULEDOC(Str), -moduledoc(Str)). -define(DOC(Str), -doc(Str)). -else. -define(MODULEDOC(Str), -compile([])). -define(DOC(Str), -compile([])). -endif. ?MODULEDOC( " Retry logic with exponential backoff for transient failures\n" "\n" " This module provides automatic retry functionality for API requests\n" " that encounter transient errors like rate limits, server overload, or timeouts.\n" "\n" " ## Features\n" "\n" " - Exponential backoff with configurable base delay\n" " - Jitter to prevent thundering herd\n" " - Configurable max retries\n" " - Respects retry-after headers when available\n" "\n" " ## Example\n" "\n" " ```gleam\n" " let retry_config = default_retry_config()\n" " |> with_max_retries(5)\n" " |> with_base_delay_ms(500)\n" "\n" " let result = retry_with_backoff(retry_config, fn() {\n" " api.create_message(client, request)\n" " })\n" " ```\n" ). -type retry_config() :: {retry_config, integer(), integer(), integer(), float(), float()}. -type retry_result(GRH) :: {retry_result, {ok, GRH} | {error, anthropic@types@error:anthropic_error()}, integer(), integer()}. -file("src/anthropic/retry.gleam", 74). ?DOC( " Create a default retry configuration\n" "\n" " Defaults:\n" " - max_retries: 3\n" " - base_delay_ms: 1000 (1 second)\n" " - max_delay_ms: 60000 (60 seconds)\n" " - jitter_factor: 0.25 (25% randomness)\n" " - backoff_multiplier: 2.0\n" ). -spec default_retry_config() -> retry_config(). default_retry_config() -> {retry_config, 3, 1000, 60000, 0.25, 2.0}. -file("src/anthropic/retry.gleam", 85). ?DOC(" Create a retry configuration with no retries\n"). -spec no_retry_config() -> retry_config(). no_retry_config() -> {retry_config, 0, 0, 0, +0.0, 1.0}. -file("src/anthropic/retry.gleam", 96). ?DOC(" Create an aggressive retry configuration for high availability\n"). -spec aggressive_retry_config() -> retry_config(). aggressive_retry_config() -> {retry_config, 5, 500, 30000, 0.5, 2.0}. -file("src/anthropic/retry.gleam", 111). ?DOC(" Set the maximum number of retries\n"). -spec with_max_retries(retry_config(), integer()) -> retry_config(). with_max_retries(Config, Max_retries) -> {retry_config, gleam@int:max(0, Max_retries), erlang:element(3, Config), erlang:element(4, Config), erlang:element(5, Config), erlang:element(6, Config)}. -file("src/anthropic/retry.gleam", 116). ?DOC(" Set the base delay in milliseconds\n"). -spec with_base_delay_ms(retry_config(), integer()) -> retry_config(). with_base_delay_ms(Config, Base_delay_ms) -> {retry_config, erlang:element(2, Config), gleam@int:max(0, Base_delay_ms), erlang:element(4, Config), erlang:element(5, Config), erlang:element(6, Config)}. -file("src/anthropic/retry.gleam", 124). ?DOC(" Set the maximum delay in milliseconds\n"). -spec with_max_delay_ms(retry_config(), integer()) -> retry_config(). with_max_delay_ms(Config, Max_delay_ms) -> {retry_config, erlang:element(2, Config), erlang:element(3, Config), gleam@int:max(0, Max_delay_ms), erlang:element(5, Config), erlang:element(6, Config)}. -file("src/anthropic/retry.gleam", 129). ?DOC(" Set the jitter factor (0.0 to 1.0)\n"). -spec with_jitter_factor(retry_config(), float()) -> retry_config(). with_jitter_factor(Config, Jitter_factor) -> Clamped = gleam@float:min(1.0, gleam@float:max(+0.0, Jitter_factor)), {retry_config, erlang:element(2, Config), erlang:element(3, Config), erlang:element(4, Config), Clamped, erlang:element(6, Config)}. -file("src/anthropic/retry.gleam", 138). ?DOC(" Set the backoff multiplier\n"). -spec with_backoff_multiplier(retry_config(), float()) -> retry_config(). with_backoff_multiplier(Config, Multiplier) -> {retry_config, erlang:element(2, Config), erlang:element(3, Config), erlang:element(4, Config), erlang:element(5, Config), gleam@float:max(1.0, Multiplier)}. -file("src/anthropic/retry.gleam", 256). ?DOC(" Check if an error should be retried based on custom logic\n"). -spec should_retry_error(anthropic@types@error:anthropic_error()) -> boolean(). should_retry_error(Error) -> anthropic@types@error:is_retryable(Error). -file("src/anthropic/retry.gleam", 261). ?DOC(" Get suggested retry delay from an error (if available)\n"). -spec get_retry_after_from_error(anthropic@types@error:anthropic_error()) -> gleam@option:option(integer()). get_retry_after_from_error(_) -> none. -file("src/anthropic/retry.gleam", 272). ?DOC(" Check if retry configuration allows retries\n"). -spec retries_enabled(retry_config()) -> boolean(). retries_enabled(Config) -> erlang:element(2, Config) > 0. -file("src/anthropic/retry.gleam", 316). -spec pow_loop(float(), integer(), float()) -> float(). pow_loop(Base, N, Acc) -> case N =< 0 of true -> Acc; false -> pow_loop(Base, N - 1, Acc * Base) end. -file("src/anthropic/retry.gleam", 309). ?DOC(" Simple power function for exponential backoff\n"). -spec pow(float(), float()) -> float(). pow(Base, Exponent) -> case Exponent =< +0.0 of true -> 1.0; false -> pow_loop(Base, erlang:trunc(Exponent), 1.0) end. -file("src/anthropic/retry.gleam", 150). ?DOC(" Calculate delay for a given attempt number (0-indexed)\n"). -spec calculate_delay(retry_config(), integer()) -> integer(). calculate_delay(Config, Attempt) -> Base = erlang:float(erlang:element(3, Config)), Exponential = Base * pow(erlang:element(6, Config), erlang:float(Attempt)), Capped = gleam@float:min( Exponential, erlang:float(erlang:element(4, Config)) ), Jitter_range = Capped * erlang:element(5, Config), Jitter = ((rand:uniform() * Jitter_range) * 2.0) - Jitter_range, With_jitter = Capped + Jitter, erlang:trunc(gleam@float:max(+0.0, With_jitter)). -file("src/anthropic/retry.gleam", 169). ?DOC(" Calculate delay with optional retry-after header value\n"). -spec calculate_delay_with_retry_after( retry_config(), integer(), gleam@option:option(integer()) ) -> integer(). calculate_delay_with_retry_after(Config, Attempt, Retry_after_ms) -> Calculated = calculate_delay(Config, Attempt), case Retry_after_ms of {some, Retry_after} -> gleam@int:max(Calculated, Retry_after); none -> Calculated end. -file("src/anthropic/retry.gleam", 198). ?DOC(" Internal retry loop\n"). -spec retry_loop( retry_config(), fun(() -> {ok, GRN} | {error, anthropic@types@error:anthropic_error()}), integer(), integer() ) -> retry_result(GRN). retry_loop(Config, Operation, Attempt, Total_delay) -> Result = Operation(), case Result of {ok, _} -> {retry_result, Result, Attempt + 1, Total_delay}; {error, Err} -> Should_retry = ((Attempt < erlang:element(2, Config)) andalso anthropic@types@error:is_retryable( Err )) andalso (erlang:element(2, Config) > 0), case Should_retry of true -> Delay = calculate_delay(Config, Attempt), timer:sleep(Delay), retry_loop( Config, Operation, Attempt + 1, Total_delay + Delay ); false -> {retry_result, Result, Attempt + 1, Total_delay} end end. -file("src/anthropic/retry.gleam", 190). ?DOC( " Execute a function with retry logic\n" "\n" " Returns the first successful result, or the last error after all retries\n" " are exhausted.\n" ). -spec retry_with_backoff( retry_config(), fun(() -> {ok, GRJ} | {error, anthropic@types@error:anthropic_error()}) ) -> retry_result(GRJ). retry_with_backoff(Config, Operation) -> retry_loop(Config, Operation, 0, 0). -file("src/anthropic/retry.gleam", 243). ?DOC(" Execute with retry and return just the result\n"). -spec retry( retry_config(), fun(() -> {ok, GRR} | {error, anthropic@types@error:anthropic_error()}) ) -> {ok, GRR} | {error, anthropic@types@error:anthropic_error()}. retry(Config, Operation) -> {retry_result, Result, _, _} = retry_with_backoff(Config, Operation), Result. -file("src/anthropic/retry.gleam", 282). -spec calculate_max_total_delay(retry_config(), integer(), integer()) -> integer(). calculate_max_total_delay(Config, Attempt, Total) -> case Attempt >= erlang:element(2, Config) of true -> Total; false -> Delay = calculate_delay(Config, Attempt), calculate_max_total_delay(Config, Attempt + 1, Total + Delay) end. -file("src/anthropic/retry.gleam", 277). ?DOC(" Get the maximum possible delay for a configuration\n"). -spec max_total_delay(retry_config()) -> integer(). max_total_delay(Config) -> calculate_max_total_delay(Config, 0, 0).