diff --git a/src/AnthropicClient/AnthropicApiClient.cs b/src/AnthropicClient/AnthropicApiClient.cs index 826b735..5ea4e02 100644 --- a/src/AnthropicClient/AnthropicApiClient.cs +++ b/src/AnthropicClient/AnthropicApiClient.cs @@ -1,4 +1,5 @@ using System.Net.Http.Headers; +using System.Runtime.CompilerServices; using System.Text; using System.Text.Json; @@ -50,13 +51,12 @@ public class AnthropicApiClient : IAnthropicApiClient _httpClient.DefaultRequestHeaders.Add(pair.Key, pair.Value); } } - /// - public async Task> CreateMessageAsync(MessageRequest request) + public async Task> CreateMessageAsync(MessageRequest request, CancellationToken cancellationToken = default) { - var response = await SendRequestAsync(MessagesEndpoint, request); + var response = await SendRequestAsync(MessagesEndpoint, request, cancellationToken); var anthropicHeaders = new AnthropicHeaders(response.Headers); - var responseContent = await response.Content.ReadAsStringAsync(); + var responseContent = await response.Content.ReadAsStringAsync(cancellationToken); if (response.IsSuccessStatusCode is false) { @@ -75,13 +75,13 @@ public class AnthropicApiClient : IAnthropicApiClient } /// - public async IAsyncEnumerable CreateMessageAsync(StreamMessageRequest request) + public async IAsyncEnumerable CreateMessageAsync(StreamMessageRequest request, [EnumeratorCancellation] CancellationToken cancellationToken = default) { - var response = await SendRequestAsync(MessagesEndpoint, request); + var response = await SendRequestAsync(MessagesEndpoint, request, cancellationToken); if (response.IsSuccessStatusCode is false) { - var error = Deserialize(await response.Content.ReadAsStringAsync()) ?? new AnthropicError(); + var error = Deserialize(await response.Content.ReadAsStringAsync(cancellationToken)) ?? new AnthropicError(); yield return new AnthropicEvent(EventType.Error, new ErrorEventData(error.Error)); yield break; } @@ -237,73 +237,66 @@ public class AnthropicApiClient : IAnthropicApiClient } } while (true); } - /// - public async Task> CreateMessageBatchAsync(MessageBatchRequest request) + public async Task> CreateMessageBatchAsync(MessageBatchRequest request, CancellationToken cancellationToken = default) { - var response = await SendRequestAsync(MessageBatchesEndpoint, request); - return await CreateResultAsync(response); + var response = await SendRequestAsync(MessageBatchesEndpoint, request, cancellationToken); + return await CreateResultAsync(response, cancellationToken); } - /// - public async Task> GetMessageBatchAsync(string batchId) + public async Task> GetMessageBatchAsync(string batchId, CancellationToken cancellationToken = default) { - var response = await SendRequestAsync($"{MessageBatchesEndpoint}/{batchId}"); - return await CreateResultAsync(response); + var response = await SendRequestAsync($"{MessageBatchesEndpoint}/{batchId}", cancellationToken: cancellationToken); + return await CreateResultAsync(response, cancellationToken); } - /// - public async Task>> ListMessageBatchesAsync(PagingRequest? request = null) + public async Task>> ListMessageBatchesAsync(PagingRequest? request = null, CancellationToken cancellationToken = default) { var pagingRequest = request ?? new PagingRequest(); var endpoint = $"{MessageBatchesEndpoint}?{pagingRequest.ToQueryParameters()}"; - var response = await SendRequestAsync(endpoint); - return await CreateResultAsync>(response); + var response = await SendRequestAsync(endpoint, cancellationToken: cancellationToken); + return await CreateResultAsync>(response, cancellationToken); } - /// - public async IAsyncEnumerable>> ListAllMessageBatchesAsync(int limit = 20) + public async IAsyncEnumerable>> ListAllMessageBatchesAsync(int limit = 20, [EnumeratorCancellation] CancellationToken cancellationToken = default) { - await foreach (var result in GetAllPagesAsync(MessageBatchesEndpoint, limit)) + await foreach (var result in GetAllPagesAsync(MessageBatchesEndpoint, limit, cancellationToken)) { yield return result; } } - /// - public async Task> CancelMessageBatchAsync(string batchId) + public async Task> CancelMessageBatchAsync(string batchId, CancellationToken cancellationToken = default) { var endpoint = $"{MessageBatchesEndpoint}/{batchId}/cancel"; - var response = await SendRequestAsync(endpoint, HttpMethod.Post); - return await CreateResultAsync(response); + var response = await SendRequestAsync(endpoint, HttpMethod.Post, cancellationToken); + return await CreateResultAsync(response, cancellationToken); } - /// - public async Task> DeleteMessageBatchAsync(string batchId) + public async Task> DeleteMessageBatchAsync(string batchId, CancellationToken cancellationToken = default) { var endpoint = $"{MessageBatchesEndpoint}/{batchId}"; - var response = await SendRequestAsync(endpoint, HttpMethod.Delete); - return await CreateResultAsync(response); + var response = await SendRequestAsync(endpoint, HttpMethod.Delete, cancellationToken); + return await CreateResultAsync(response, cancellationToken); } - /// - public async Task>> GetMessageBatchResultsAsync(string batchId) + public async Task>> GetMessageBatchResultsAsync(string batchId, CancellationToken cancellationToken = default) { - var response = await SendRequestAsync($"{MessageBatchesEndpoint}/{batchId}/results"); + var response = await SendRequestAsync($"{MessageBatchesEndpoint}/{batchId}/results", cancellationToken: cancellationToken); var anthropicHeaders = new AnthropicHeaders(response.Headers); if (response.IsSuccessStatusCode is false) { - var content = await response.Content.ReadAsStringAsync(); + var content = await response.Content.ReadAsStringAsync(cancellationToken); var error = Deserialize(content) ?? new AnthropicError(); return AnthropicResult>.Failure(error, anthropicHeaders); } return AnthropicResult>.Success(ReadResultsAsync(), anthropicHeaders); - async IAsyncEnumerable ReadResultsAsync() + async IAsyncEnumerable ReadResultsAsync([EnumeratorCancellation] CancellationToken ct = default) { - using var responseContent = await response.Content.ReadAsStreamAsync(); + using var responseContent = await response.Content.ReadAsStreamAsync(ct); using var streamReader = new StreamReader(responseContent); var line = await streamReader.ReadLineAsync(); @@ -317,41 +310,36 @@ public class AnthropicApiClient : IAnthropicApiClient } } } - /// - public async Task> CountMessageTokensAsync(CountMessageTokensRequest request) + public async Task> CountMessageTokensAsync(CountMessageTokensRequest request, CancellationToken cancellationToken = default) { - var response = await SendRequestAsync(CountTokensEndpoint, request); - return await CreateResultAsync(response); + var response = await SendRequestAsync(CountTokensEndpoint, request, cancellationToken); + return await CreateResultAsync(response, cancellationToken); } - /// - public async Task>> ListModelsAsync(PagingRequest? request = null) + public async Task>> ListModelsAsync(PagingRequest? request = null, CancellationToken cancellationToken = default) { var pagingRequest = request ?? new PagingRequest(); var endpoint = $"{ModelsEndpoint}?{pagingRequest.ToQueryParameters()}"; - var response = await SendRequestAsync(endpoint); - return await CreateResultAsync>(response); + var response = await SendRequestAsync(endpoint, cancellationToken: cancellationToken); + return await CreateResultAsync>(response, cancellationToken); } - /// - public async IAsyncEnumerable>> ListAllModelsAsync(int limit = 20) + public async IAsyncEnumerable>> ListAllModelsAsync(int limit = 20, [EnumeratorCancellation] CancellationToken cancellationToken = default) { - await foreach (var result in GetAllPagesAsync(ModelsEndpoint, limit)) + await foreach (var result in GetAllPagesAsync(ModelsEndpoint, limit, cancellationToken)) { yield return result; } } - /// - public async Task> GetModelAsync(string modelId) + public async Task> GetModelAsync(string modelId, CancellationToken cancellationToken = default) { var endpoint = $"{ModelsEndpoint}/{modelId}"; - var response = await SendRequestAsync(endpoint); - return await CreateResultAsync(response); + var response = await SendRequestAsync(endpoint, cancellationToken: cancellationToken); + return await CreateResultAsync(response, cancellationToken); } - - private async IAsyncEnumerable>> GetAllPagesAsync(string endpoint, int limit = 20) + private async IAsyncEnumerable>> GetAllPagesAsync(string endpoint, int limit = 20, [EnumeratorCancellation] CancellationToken cancellationToken = default) { var pagingRequest = new PagingRequest(limit: limit); string Endpoint() => $"{endpoint}?{pagingRequest.ToQueryParameters()}"; @@ -359,9 +347,9 @@ public class AnthropicApiClient : IAnthropicApiClient do { - var response = await SendRequestAsync(Endpoint()); + var response = await SendRequestAsync(Endpoint(), cancellationToken: cancellationToken); var anthropicHeaders = new AnthropicHeaders(response.Headers); - var responseContent = await response.Content.ReadAsStringAsync(); + var responseContent = await response.Content.ReadAsStringAsync(cancellationToken); if (response.IsSuccessStatusCode is false) { @@ -404,11 +392,10 @@ public class AnthropicApiClient : IAnthropicApiClient return new ToolCall(tool, toolUse); } - - private async Task> CreateResultAsync(HttpResponseMessage response) where T : new() + private async Task> CreateResultAsync(HttpResponseMessage response, CancellationToken cancellationToken = default) where T : new() { var anthropicHeaders = new AnthropicHeaders(response.Headers); - var responseContent = await response.Content.ReadAsStringAsync(); + var responseContent = await response.Content.ReadAsStringAsync(cancellationToken); if (response.IsSuccessStatusCode is false) { @@ -419,18 +406,17 @@ public class AnthropicApiClient : IAnthropicApiClient var model = Deserialize(responseContent) ?? new T(); return AnthropicResult.Success(model, anthropicHeaders); } - - private async Task SendRequestAsync(string endpoint, HttpMethod? method = null) + private async Task SendRequestAsync(string endpoint, HttpMethod? method = null, CancellationToken cancellationToken = default) { var request = new HttpRequestMessage(method ?? HttpMethod.Get, endpoint); - return await _httpClient.SendAsync(request); + return await _httpClient.SendAsync(request, cancellationToken); } - private async Task SendRequestAsync(string endpoint, T request) + private async Task SendRequestAsync(string endpoint, T request, CancellationToken cancellationToken = default) { var requestJson = Serialize(request); var requestContent = new StringContent(requestJson, Encoding.UTF8, JsonContentType); - return await _httpClient.PostAsync(endpoint, requestContent); + return await _httpClient.PostAsync(endpoint, requestContent, cancellationToken); } private string Serialize(T obj) => JsonSerializer.Serialize(obj, JsonSerializationOptions.DefaultOptions); diff --git a/src/AnthropicClient/IAnthropicApiClient.cs b/src/AnthropicClient/IAnthropicApiClient.cs index 27c7e00..7b33bbe 100644 --- a/src/AnthropicClient/IAnthropicApiClient.cs +++ b/src/AnthropicClient/IAnthropicApiClient.cs @@ -11,91 +11,104 @@ public interface IAnthropicApiClient /// Creates a message asynchronously. /// /// The message request to create. + /// A token to cancel the asynchronous operation. /// A task that represents the asynchronous operation. The task result contains the response as an . - Task> CreateMessageAsync(MessageRequest request); + Task> CreateMessageAsync(MessageRequest request, CancellationToken cancellationToken = default); /// /// Creates a message asynchronously and streams the response. /// /// The message request to create. + /// A token to cancel the asynchronous operation. /// An asynchronous enumerable that yields the response event by event. - IAsyncEnumerable CreateMessageAsync(StreamMessageRequest request); + IAsyncEnumerable CreateMessageAsync(StreamMessageRequest request, CancellationToken cancellationToken = default); /// /// Creates a batch of messages asynchronously. /// /// The message batch request to create. + /// A token to cancel the asynchronous operation. /// A task that represents the asynchronous operation. The task result contains the response as an where T is . - Task> CreateMessageBatchAsync(MessageBatchRequest request); + Task> CreateMessageBatchAsync(MessageBatchRequest request, CancellationToken cancellationToken = default); /// /// Gets a message batch asynchronously. /// /// The ID of the message batch to get. + /// A token to cancel the asynchronous operation. /// A task that represents the asynchronous operation. The task result contains the response as an where T is . - Task> GetMessageBatchAsync(string batchId); + Task> GetMessageBatchAsync(string batchId, CancellationToken cancellationToken = default); /// /// Lists the message batches asynchronously. /// /// The paging request to use for listing the message batches. + /// A token to cancel the asynchronous operation. /// A task that represents the asynchronous operation. The task result contains the response as an where T is where T is . - Task>> ListMessageBatchesAsync(PagingRequest? request = null); + Task>> ListMessageBatchesAsync(PagingRequest? request = null, CancellationToken cancellationToken = default); /// /// Lists all message batches asynchronously. /// /// The maximum number of message batches to return in each page. + /// A token to cancel the asynchronous operation. /// An asynchronous enumerable that yields the response as an where T is where T is . - IAsyncEnumerable>> ListAllMessageBatchesAsync(int limit = 20); + IAsyncEnumerable>> ListAllMessageBatchesAsync(int limit = 20, CancellationToken cancellationToken = default); /// /// Cancels a message batch asynchronously. /// /// The ID of the message batch to cancel. + /// A token to cancel the asynchronous operation. /// A task that represents the asynchronous operation. The task result contains the response as an where T is . - Task> CancelMessageBatchAsync(string batchId); + Task> CancelMessageBatchAsync(string batchId, CancellationToken cancellationToken = default); /// /// Deletes a message batch asynchronously. /// /// The ID of the message batch to delete. + /// A token to cancel the asynchronous operation. /// A task that represents the asynchronous operation. The task result contains the response as an where T is . - Task> DeleteMessageBatchAsync(string batchId); + Task> DeleteMessageBatchAsync(string batchId, CancellationToken cancellationToken = default); /// /// Gets the results of a message batch asynchronously. /// /// The ID of the message batch to get the results for. + /// A token to cancel the asynchronous operation. /// A task that represents the asynchronous operation. The task result contains the response as an where T is where T is . - Task>> GetMessageBatchResultsAsync(string batchId); + Task>> GetMessageBatchResultsAsync(string batchId, CancellationToken cancellationToken = default); /// /// Counts the tokens in a message asynchronously. /// /// The count message tokens request. + /// A token to cancel the asynchronous operation. /// A task that represents the asynchronous operation. The task result contains the response as an where T is . - Task> CountMessageTokensAsync(CountMessageTokensRequest request); + Task> CountMessageTokensAsync(CountMessageTokensRequest request, CancellationToken cancellationToken = default); /// /// Lists the models asynchronously. /// /// The paging request to use for listing the models. + /// A token to cancel the asynchronous operation. /// A task that represents the asynchronous operation. The task result contains the response as an where T is where T is . - Task>> ListModelsAsync(PagingRequest? request = null); + Task>> ListModelsAsync(PagingRequest? request = null, CancellationToken cancellationToken = default); /// /// Lists the models asynchronously /// /// The maximum number of models to return in each page. + /// A token to cancel the asynchronous operation. /// An asynchronous enumerable that yields the response as an where T is where T is . /// - IAsyncEnumerable>> ListAllModelsAsync(int limit = 20); + IAsyncEnumerable>> ListAllModelsAsync(int limit = 20, CancellationToken cancellationToken = default); /// /// Gets a model by its ID asynchronously. /// /// The ID of the model to get. + /// A token to cancel the asynchronous operation. /// A task that represents the asynchronous operation. The task result contains the response as an where T is . - Task> GetModelAsync(string modelId); + Task> GetModelAsync(string modelId, CancellationToken cancellationToken = default); } \ No newline at end of file