Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 6 additions & 6 deletions .github/workflows/publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
8 changes: 4 additions & 4 deletions .github/workflows/pull_request.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down
9 changes: 9 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
68 changes: 35 additions & 33 deletions src/AnthropicClient/AnthropicApiClient.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System.Net.Http.Headers;
using System.Runtime.CompilerServices;
using System.Text;
using System.Text.Json;

Expand Down Expand Up @@ -52,9 +53,9 @@ public AnthropicApiClient(string apiKey, HttpClient httpClient)
}

/// <inheritdoc/>
public async Task<AnthropicResult<MessageResponse>> CreateMessageAsync(MessageRequest request)
public async Task<AnthropicResult<MessageResponse>> 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();

Expand All @@ -75,13 +76,14 @@ public async Task<AnthropicResult<MessageResponse>> CreateMessageAsync(MessageRe
}

/// <inheritdoc/>
public async IAsyncEnumerable<AnthropicEvent> CreateMessageAsync(StreamMessageRequest request)
public async IAsyncEnumerable<AnthropicEvent> 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<AnthropicError>(await response.Content.ReadAsStringAsync()) ?? new AnthropicError();
var errorContent = await response.Content.ReadAsStringAsync();
var error = Deserialize<AnthropicError>(errorContent) ?? new AnthropicError();
yield return new AnthropicEvent(EventType.Error, new ErrorEventData(error.Error));
yield break;
}
Expand Down Expand Up @@ -239,57 +241,57 @@ msgResponse is not null
}

/// <inheritdoc/>
public async Task<AnthropicResult<MessageBatchResponse>> CreateMessageBatchAsync(MessageBatchRequest request)
public async Task<AnthropicResult<MessageBatchResponse>> CreateMessageBatchAsync(MessageBatchRequest request, CancellationToken cancellationToken = default)
{
var response = await SendRequestAsync(MessageBatchesEndpoint, request);
var response = await SendRequestAsync(MessageBatchesEndpoint, request, cancellationToken);
return await CreateResultAsync<MessageBatchResponse>(response);
}

/// <inheritdoc/>
public async Task<AnthropicResult<MessageBatchResponse>> GetMessageBatchAsync(string batchId)
public async Task<AnthropicResult<MessageBatchResponse>> GetMessageBatchAsync(string batchId, CancellationToken cancellationToken = default)
{
var response = await SendRequestAsync($"{MessageBatchesEndpoint}/{batchId}");
var response = await SendRequestAsync($"{MessageBatchesEndpoint}/{batchId}", cancellationToken: cancellationToken);
return await CreateResultAsync<MessageBatchResponse>(response);
}

/// <inheritdoc/>
public async Task<AnthropicResult<Page<MessageBatchResponse>>> ListMessageBatchesAsync(PagingRequest? request = null)
public async Task<AnthropicResult<Page<MessageBatchResponse>>> 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<Page<MessageBatchResponse>>(response);
}

/// <inheritdoc/>
public async IAsyncEnumerable<AnthropicResult<Page<MessageBatchResponse>>> ListAllMessageBatchesAsync(int limit = 20)
public async IAsyncEnumerable<AnthropicResult<Page<MessageBatchResponse>>> ListAllMessageBatchesAsync(int limit = 20, [EnumeratorCancellation] CancellationToken cancellationToken = default)
{
await foreach (var result in GetAllPagesAsync<MessageBatchResponse>(MessageBatchesEndpoint, limit))
await foreach (var result in GetAllPagesAsync<MessageBatchResponse>(MessageBatchesEndpoint, limit, cancellationToken))
{
yield return result;
}
}

/// <inheritdoc/>
public async Task<AnthropicResult<MessageBatchResponse>> CancelMessageBatchAsync(string batchId)
public async Task<AnthropicResult<MessageBatchResponse>> 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<MessageBatchResponse>(response);
}

/// <inheritdoc/>
public async Task<AnthropicResult<MessageBatchDeleteResponse>> DeleteMessageBatchAsync(string batchId)
public async Task<AnthropicResult<MessageBatchDeleteResponse>> 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<MessageBatchDeleteResponse>(response);
}

/// <inheritdoc/>
public async Task<AnthropicResult<IAsyncEnumerable<MessageBatchResultItem>>> GetMessageBatchResultsAsync(string batchId)
public async Task<AnthropicResult<IAsyncEnumerable<MessageBatchResultItem>>> 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)
Expand Down Expand Up @@ -319,47 +321,47 @@ async IAsyncEnumerable<MessageBatchResultItem> ReadResultsAsync()
}

/// <inheritdoc/>
public async Task<AnthropicResult<TokenCountResponse>> CountMessageTokensAsync(CountMessageTokensRequest request)
public async Task<AnthropicResult<TokenCountResponse>> CountMessageTokensAsync(CountMessageTokensRequest request, CancellationToken cancellationToken = default)
{
var response = await SendRequestAsync(CountTokensEndpoint, request);
var response = await SendRequestAsync(CountTokensEndpoint, request, cancellationToken);
return await CreateResultAsync<TokenCountResponse>(response);
}

/// <inheritdoc/>
public async Task<AnthropicResult<Page<AnthropicModel>>> ListModelsAsync(PagingRequest? request = null)
public async Task<AnthropicResult<Page<AnthropicModel>>> 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<Page<AnthropicModel>>(response);
}

/// <inheritdoc/>
public async IAsyncEnumerable<AnthropicResult<Page<AnthropicModel>>> ListAllModelsAsync(int limit = 20)
public async IAsyncEnumerable<AnthropicResult<Page<AnthropicModel>>> ListAllModelsAsync(int limit = 20, [EnumeratorCancellation] CancellationToken cancellationToken = default)
{
await foreach (var result in GetAllPagesAsync<AnthropicModel>(ModelsEndpoint, limit))
await foreach (var result in GetAllPagesAsync<AnthropicModel>(ModelsEndpoint, limit, cancellationToken))
{
yield return result;
}
}

/// <inheritdoc/>
public async Task<AnthropicResult<AnthropicModel>> GetModelAsync(string modelId)
public async Task<AnthropicResult<AnthropicModel>> 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<AnthropicModel>(response);
}

private async IAsyncEnumerable<AnthropicResult<Page<T>>> GetAllPagesAsync<T>(string endpoint, int limit = 20)
private async IAsyncEnumerable<AnthropicResult<Page<T>>> GetAllPagesAsync<T>(string endpoint, int limit = 20, [EnumeratorCancellation] CancellationToken cancellationToken = default)
{
var pagingRequest = new PagingRequest(limit: limit);
string Endpoint() => $"{endpoint}?{pagingRequest.ToQueryParameters()}";
bool hasMore;

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();

Expand Down Expand Up @@ -420,17 +422,17 @@ private async IAsyncEnumerable<AnthropicResult<Page<T>>> GetAllPagesAsync<T>(str
return AnthropicResult<T>.Success(model, anthropicHeaders);
}

private async Task<HttpResponseMessage> SendRequestAsync(string endpoint, HttpMethod? method = null)
private async Task<HttpResponseMessage> 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<HttpResponseMessage> SendRequestAsync<T>(string endpoint, T request)
private async Task<HttpResponseMessage> SendRequestAsync<T>(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>(T obj) => JsonSerializer.Serialize(obj, JsonSerializationOptions.DefaultOptions);
Expand Down
4 changes: 2 additions & 2 deletions src/AnthropicClient/AnthropicClient.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,8 @@
</ItemGroup>

<ItemGroup>
<PackageReference Include="Microsoft.Bcl.AsyncInterfaces" Version="8.0.0" />
<PackageReference Include="System.Text.Json" Version="8.0.5" />
<PackageReference Include="Microsoft.Bcl.AsyncInterfaces" Version="9.0.5" />
<PackageReference Include="System.Text.Json" Version="9.0.5" />
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="8.0.0" PrivateAssets="All" />
</ItemGroup>

Expand Down
Loading