MarketAlly.AIPlugin.Extensions/MarketAlly.AIPlugin.ClaudeCode/Interfaces/IRateLimitAwareHttpClient.cs

219 lines
8.1 KiB
C#
Executable File

using System.Text;
namespace MarketAlly.AIPlugin.ClaudeCode;
/// <summary>
/// HTTP client interface with built-in rate limiting awareness
/// </summary>
public interface IRateLimitAwareHttpClient
{
/// <summary>
/// Sends a GET request with rate limit handling
/// </summary>
/// <param name="endpoint">API endpoint</param>
/// <param name="cancellationToken">Cancellation token</param>
/// <returns>HTTP response with rate limit info</returns>
Task<RateLimitAwareResponse> GetAsync(string endpoint, CancellationToken cancellationToken = default);
/// <summary>
/// Sends a POST request with rate limit handling
/// </summary>
/// <param name="endpoint">API endpoint</param>
/// <param name="content">Request content</param>
/// <param name="cancellationToken">Cancellation token</param>
/// <returns>HTTP response with rate limit info</returns>
Task<RateLimitAwareResponse> PostAsync(string endpoint, HttpContent? content = null, CancellationToken cancellationToken = default);
/// <summary>
/// Sends a POST request with JSON content
/// </summary>
/// <param name="endpoint">API endpoint</param>
/// <param name="data">Data to serialize as JSON</param>
/// <param name="cancellationToken">Cancellation token</param>
/// <returns>HTTP response with rate limit info</returns>
Task<RateLimitAwareResponse> PostAsJsonAsync<T>(string endpoint, T data, CancellationToken cancellationToken = default);
/// <summary>
/// Sends a PUT request with rate limit handling
/// </summary>
/// <param name="endpoint">API endpoint</param>
/// <param name="content">Request content</param>
/// <param name="cancellationToken">Cancellation token</param>
/// <returns>HTTP response with rate limit info</returns>
Task<RateLimitAwareResponse> PutAsync(string endpoint, HttpContent? content = null, CancellationToken cancellationToken = default);
/// <summary>
/// Sends a DELETE request with rate limit handling
/// </summary>
/// <param name="endpoint">API endpoint</param>
/// <param name="cancellationToken">Cancellation token</param>
/// <returns>HTTP response with rate limit info</returns>
Task<RateLimitAwareResponse> DeleteAsync(string endpoint, CancellationToken cancellationToken = default);
/// <summary>
/// Gets current rate limit status
/// </summary>
/// <param name="cancellationToken">Cancellation token</param>
/// <returns>Current rate limit information</returns>
Task<RateLimitInfo> GetRateLimitStatusAsync(CancellationToken cancellationToken = default);
/// <summary>
/// Checks if a request can be made without hitting rate limits
/// </summary>
/// <param name="endpoint">Optional endpoint to check</param>
/// <returns>True if request can be made</returns>
Task<bool> CanMakeRequestAsync(string? endpoint = null);
/// <summary>
/// Waits for rate limit reset if necessary
/// </summary>
/// <param name="maxWaitTime">Maximum time to wait</param>
/// <param name="cancellationToken">Cancellation token</param>
/// <returns>True if reset occurred, false if timeout</returns>
Task<bool> WaitForRateLimitResetAsync(TimeSpan? maxWaitTime = null, CancellationToken cancellationToken = default);
/// <summary>
/// Configures the HTTP client with authentication and base settings
/// </summary>
/// <param name="baseUrl">Base URL for the API</param>
/// <param name="apiKey">API key for authentication</param>
/// <param name="tenantId">Optional tenant ID</param>
void Configure(string baseUrl, string apiKey, string? tenantId = null);
}
/// <summary>
/// HTTP response with rate limit information
/// </summary>
public class RateLimitAwareResponse
{
public bool IsSuccessStatusCode { get; set; }
public int StatusCode { get; set; }
public string? Content { get; set; }
public RateLimitInfo? RateLimitInfo { get; set; }
public Dictionary<string, string> Headers { get; set; } = new();
public string? ErrorMessage { get; set; }
public bool IsRateLimited { get; set; }
public TimeSpan? RetryAfter { get; set; }
/// <summary>
/// Deserializes the response content as JSON
/// </summary>
/// <typeparam name="T">Type to deserialize to</typeparam>
/// <returns>Deserialized object</returns>
public T? DeserializeJson<T>()
{
if (string.IsNullOrEmpty(Content))
return default;
try
{
return System.Text.Json.JsonSerializer.Deserialize<T>(Content, new System.Text.Json.JsonSerializerOptions
{
PropertyNameCaseInsensitive = true
});
}
catch
{
return default;
}
}
/// <summary>
/// Ensures the response was successful, throwing an exception if not
/// </summary>
/// <returns>This response for chaining</returns>
/// <exception cref="HttpRequestException">Thrown if the request was not successful</exception>
public RateLimitAwareResponse EnsureSuccessStatusCode()
{
if (!IsSuccessStatusCode)
{
var message = !string.IsNullOrEmpty(ErrorMessage)
? ErrorMessage
: $"HTTP request failed with status code {StatusCode}";
if (IsRateLimited)
{
throw new RateLimitExceededException(message, RateLimitInfo, RetryAfter);
}
throw new HttpRequestException(message);
}
return this;
}
/// <summary>
/// Creates a successful response
/// </summary>
/// <param name="content">Response content</param>
/// <param name="rateLimitInfo">Rate limit information</param>
/// <returns>Successful response</returns>
public static RateLimitAwareResponse Success(string content, RateLimitInfo? rateLimitInfo = null)
{
return new RateLimitAwareResponse
{
IsSuccessStatusCode = true,
StatusCode = 200,
Content = content,
RateLimitInfo = rateLimitInfo
};
}
/// <summary>
/// Creates an error response
/// </summary>
/// <param name="statusCode">HTTP status code</param>
/// <param name="errorMessage">Error message</param>
/// <param name="rateLimitInfo">Rate limit information</param>
/// <returns>Error response</returns>
public static RateLimitAwareResponse Error(int statusCode, string errorMessage, RateLimitInfo? rateLimitInfo = null)
{
return new RateLimitAwareResponse
{
IsSuccessStatusCode = false,
StatusCode = statusCode,
ErrorMessage = errorMessage,
RateLimitInfo = rateLimitInfo,
IsRateLimited = statusCode == 429
};
}
}
/// <summary>
/// Exception thrown when rate limits are exceeded
/// </summary>
public class RateLimitExceededException : Exception
{
public RateLimitInfo? RateLimitInfo { get; }
public TimeSpan? RetryAfter { get; }
public RateLimitExceededException(string message, RateLimitInfo? rateLimitInfo = null, TimeSpan? retryAfter = null)
: base(message)
{
RateLimitInfo = rateLimitInfo;
RetryAfter = retryAfter;
}
public RateLimitExceededException(string message, Exception innerException, RateLimitInfo? rateLimitInfo = null, TimeSpan? retryAfter = null)
: base(message, innerException)
{
RateLimitInfo = rateLimitInfo;
RetryAfter = retryAfter;
}
}
/// <summary>
/// HTTP client configuration options
/// </summary>
public class HttpClientOptions
{
public string BaseUrl { get; set; } = string.Empty;
public string ApiKey { get; set; } = string.Empty;
public string? TenantId { get; set; }
public TimeSpan Timeout { get; set; } = TimeSpan.FromMinutes(5);
public int MaxRetries { get; set; } = 3;
public TimeSpan BaseRetryDelay { get; set; } = TimeSpan.FromSeconds(1);
public double BackoffMultiplier { get; set; } = 2.0;
public Dictionary<string, string> DefaultHeaders { get; set; } = new();
public bool EnableDetailedLogging { get; set; } = false;
}