219 lines
8.1 KiB
C#
Executable File
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;
|
|
} |