diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 2825527..8668753 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -13,10 +13,10 @@ jobs: with: fetch-depth: 0 token: ${{ secrets.ACTIONS_PAT }} - - name: Setup .NET 8 + - name: Setup .NET 9 uses: actions/setup-dotnet@v4 with: - dotnet-version: 8.x + dotnet-version: 9.x - name: Install versionize run: dotnet tool install --global Versionize - name: Setup git @@ -53,10 +53,10 @@ jobs: fetch-depth: 0 ref: ${{ github.ref }} token: ${{ secrets.ACTIONS_PAT }} - - name: Setup .NET 8 + - name: Setup .NET 9 uses: actions/setup-dotnet@v4 with: - dotnet-version: 8.x + dotnet-version: 9.x - name: Get project version uses: kzrnm/get-net-sdk-project-versions-action@v1 id: get-version @@ -88,10 +88,10 @@ jobs: fetch-depth: 0 ref: ${{ github.ref }} token: ${{ secrets.ACTIONS_PAT }} - - name: Setup .NET 8 + - name: Setup .NET 9 uses: actions/setup-dotnet@v4 with: - dotnet-version: 8.x + dotnet-version: 9.x - name: Install Docfx run: dotnet tool install --global docfx - name: Get project version diff --git a/.github/workflows/pull_request.yml b/.github/workflows/pull_request.yml index b6a83cd..f12943b 100644 --- a/.github/workflows/pull_request.yml +++ b/.github/workflows/pull_request.yml @@ -15,10 +15,10 @@ jobs: steps: - name: Checkout repository uses: actions/checkout@v4 - - name: Setup .NET 8 + - name: Setup .NET 9 uses: actions/setup-dotnet@v4 with: - dotnet-version: 8.x + dotnet-version: 9.x - name: Restore dependencies run: dotnet restore - name: Format code @@ -28,10 +28,10 @@ jobs: steps: - name: Checkout repository uses: actions/checkout@v4 - - name: Setup .NET 8 + - name: Setup .NET 9 uses: actions/setup-dotnet@v4 with: - dotnet-version: 8.x + dotnet-version: 9.x - name: Install report generator run: dotnet tool install --global dotnet-reportgenerator-globaltool --version 5.3.7 - name: Restore dependencies diff --git a/.vscode/settings.json b/.vscode/settings.json index b2f391d..11a990d 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -3,9 +3,18 @@ "dotnet.defaultSolution": "AnthropicClient.sln", "cSpell.words": [ "Browsable", + "buildtransitive", + "contentfiles", + "Docfx", + "globaltool", "haikus", + "Linq", + "msbuild", "nameof", + "reportgenerator", + "reporttypes", "Szalay", + "targetdir", "typeof" ], "dotnet.unitTests.runSettingsPath": "./tests/AnthropicClient.Tests/.runsettings" diff --git a/src/AnthropicClient/AnthropicApiClient.cs b/src/AnthropicClient/AnthropicApiClient.cs index 826b735..f05f3e6 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; @@ -52,9 +53,9 @@ public AnthropicApiClient(string apiKey, HttpClient httpClient) } /// - 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(); @@ -75,13 +76,14 @@ public async Task> CreateMessageAsync(MessageRe } /// - 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 errorContent = await response.Content.ReadAsStringAsync(); + var error = Deserialize(errorContent) ?? new AnthropicError(); yield return new AnthropicEvent(EventType.Error, new ErrorEventData(error.Error)); yield break; } @@ -239,57 +241,57 @@ msgResponse is not null } /// - public async Task> CreateMessageBatchAsync(MessageBatchRequest request) + public async Task> CreateMessageBatchAsync(MessageBatchRequest request, CancellationToken cancellationToken = default) { - var response = await SendRequestAsync(MessageBatchesEndpoint, request); + var response = await SendRequestAsync(MessageBatchesEndpoint, request, cancellationToken); return await CreateResultAsync(response); } /// - public async Task> GetMessageBatchAsync(string batchId) + public async Task> GetMessageBatchAsync(string batchId, CancellationToken cancellationToken = default) { - var response = await SendRequestAsync($"{MessageBatchesEndpoint}/{batchId}"); + var response = await SendRequestAsync($"{MessageBatchesEndpoint}/{batchId}", cancellationToken: cancellationToken); return await CreateResultAsync(response); } /// - 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); + var response = await SendRequestAsync(endpoint, cancellationToken: cancellationToken); return await CreateResultAsync>(response); } /// - 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); + var response = await SendRequestAsync(endpoint, HttpMethod.Post, cancellationToken); return await CreateResultAsync(response); } /// - 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); + var response = await SendRequestAsync(endpoint, HttpMethod.Delete, cancellationToken); return await CreateResultAsync(response); } /// - 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) @@ -319,39 +321,39 @@ async IAsyncEnumerable ReadResultsAsync() } /// - public async Task> CountMessageTokensAsync(CountMessageTokensRequest request) + public async Task> CountMessageTokensAsync(CountMessageTokensRequest request, CancellationToken cancellationToken = default) { - var response = await SendRequestAsync(CountTokensEndpoint, request); + var response = await SendRequestAsync(CountTokensEndpoint, request, cancellationToken); return await CreateResultAsync(response); } /// - 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); + var response = await SendRequestAsync(endpoint, cancellationToken: cancellationToken); return await CreateResultAsync>(response); } /// - 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); + var response = await SendRequestAsync(endpoint, cancellationToken: cancellationToken); return await CreateResultAsync(response); } - 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,7 +361,7 @@ private async IAsyncEnumerable>> GetAllPagesAsync(str 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(); @@ -420,17 +422,17 @@ private async IAsyncEnumerable>> GetAllPagesAsync(str 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/AnthropicClient.csproj b/src/AnthropicClient/AnthropicClient.csproj index 8f16d24..0a58e81 100644 --- a/src/AnthropicClient/AnthropicClient.csproj +++ b/src/AnthropicClient/AnthropicClient.csproj @@ -34,8 +34,8 @@ - - + + diff --git a/src/AnthropicClient/IAnthropicApiClient.cs b/src/AnthropicClient/IAnthropicApiClient.cs index 27c7e00..e4f63b9 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. + /// Lists models asynchronously, returning a single page of results. /// /// 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 + /// Lists all models asynchronously, returning every page of results. /// /// 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 diff --git a/tests/AnthropicClient.Tests/AnthropicClient.Tests.csproj b/tests/AnthropicClient.Tests/AnthropicClient.Tests.csproj index 0a5f3db..1962db3 100644 --- a/tests/AnthropicClient.Tests/AnthropicClient.Tests.csproj +++ b/tests/AnthropicClient.Tests/AnthropicClient.Tests.csproj @@ -1,7 +1,7 @@ - net8.0 + net9.0 enable enable false @@ -10,21 +10,21 @@ - - - - - + + + + + - - - + + + runtime; build; native; contentfiles; analyzers; buildtransitive all - + runtime; build; native; contentfiles; analyzers; buildtransitive all @@ -54,7 +54,7 @@ PreserveNewest - + PreserveNewest