diff --git a/src/AnthropicClient/AnthropicApiClient.cs b/src/AnthropicClient/AnthropicApiClient.cs
index 8c0b3fb..34244c3 100644
--- a/src/AnthropicClient/AnthropicApiClient.cs
+++ b/src/AnthropicClient/AnthropicApiClient.cs
@@ -55,6 +55,13 @@ public interface IAnthropicApiClient
/// An asynchronous enumerable that yields the response as an where T is where T is .
IAsyncEnumerable>> ListAllMessageBatchesAsync(int limit = 20);
+ ///
+ /// Cancels a message batch asynchronously.
+ ///
+ /// The ID of the message batch to cancel.
+ /// A task that represents the asynchronous operation. The task result contains the response as an where T is .
+ Task> CancelMessageBatchAsync(string batchId);
+
///
/// Gets the results of a message batch asynchronously.
///
@@ -384,6 +391,24 @@ public class AnthropicApiClient : IAnthropicApiClient
}
}
+ ///
+ public async Task> CancelMessageBatchAsync(string batchId)
+ {
+ var endpoint = $"{MessageBatchesEndpoint}/{batchId}/cancel";
+ var response = await SendRequestAsync(endpoint, HttpMethod.Post);
+ var anthropicHeaders = new AnthropicHeaders(response.Headers);
+ var responseContent = await response.Content.ReadAsStringAsync();
+
+ if (response.IsSuccessStatusCode is false)
+ {
+ var error = Deserialize(responseContent) ?? new AnthropicError();
+ return AnthropicResult.Failure(error, anthropicHeaders);
+ }
+
+ var msgBatchResponse = Deserialize(responseContent) ?? new MessageBatchResponse();
+ return AnthropicResult.Success(msgBatchResponse, anthropicHeaders);
+ }
+
///
public async Task>> GetMessageBatchResultsAsync(string batchId)
{
@@ -533,9 +558,10 @@ public class AnthropicApiClient : IAnthropicApiClient
return new ToolCall(tool, toolUse);
}
- private async Task SendRequestAsync(string endpoint)
+ private async Task SendRequestAsync(string endpoint, HttpMethod? method = null)
{
- return await _httpClient.GetAsync(endpoint);
+ var request = new HttpRequestMessage(method ?? HttpMethod.Get, endpoint);
+ return await _httpClient.SendAsync(request);
}
private async Task SendRequestAsync(string endpoint, T request)
diff --git a/tests/AnthropicClient.Tests/Integration/AnthropicApiClientTests.cs b/tests/AnthropicClient.Tests/Integration/AnthropicApiClientTests.cs
index 2b458c2..8b312c2 100644
--- a/tests/AnthropicClient.Tests/Integration/AnthropicApiClientTests.cs
+++ b/tests/AnthropicClient.Tests/Integration/AnthropicApiClientTests.cs
@@ -1282,26 +1282,26 @@ public class AnthropicApiClientTests : IntegrationTest
var result = await Client.GetMessageBatchAsync(batchId);
result.IsSuccess.Should().BeTrue();
- result.Value.Should().BeOfType();
- result.Value.Id.Should().Be("msgbatch_013Zva2CMHLNnXjNJJKqJ2EF");
- result.Value.Type.Should().Be("message_batch");
- result.Value.ProcessingStatus.Should().Be("in_progress");
- result.Value.RequestCounts.Should().BeEquivalentTo(new MessageBatchRequestCounts
+ result.Value.Should().BeEquivalentTo(new MessageBatchResponse()
{
- Processing = 100,
- Succeeded = 50,
- Errored = 30,
- Canceled = 10,
- Expired = 10
+ Id = "msgbatch_013Zva2CMHLNnXjNJJKqJ2EF",
+ Type = "message_batch",
+ ProcessingStatus = "in_progress",
+ RequestCounts = new MessageBatchRequestCounts
+ {
+ Processing = 100,
+ Succeeded = 50,
+ Errored = 30,
+ Canceled = 10,
+ Expired = 10
+ },
+ EndedAt = DateTimeOffset.Parse("2024-08-20T18:37:24.100435Z"),
+ CreatedAt = DateTimeOffset.Parse("2024-08-20T18:37:24.100435Z"),
+ ExpiresAt = DateTimeOffset.Parse("2024-08-20T18:37:24.100435Z"),
+ ArchivedAt = DateTimeOffset.Parse("2024-08-20T18:37:24.100435Z"),
+ CancelInitiatedAt = DateTimeOffset.Parse("2024-08-20T18:37:24.100435Z"),
+ ResultsUrl = "https://api.anthropic.com/v1/messages/batches/msgbatch_013Zva2CMHLNnXjNJJKqJ2EF/results"
});
-
- result.Value.EndedAt.Should().Be(DateTimeOffset.Parse("2024-08-20T18:37:24.100435Z"));
- result.Value.CreatedAt.Should().Be(DateTimeOffset.Parse("2024-08-20T18:37:24.100435Z"));
- result.Value.ExpiresAt.Should().Be(DateTimeOffset.Parse("2024-08-20T18:37:24.100435Z"));
- result.Value.ArchivedAt.Should().Be(DateTimeOffset.Parse("2024-08-20T18:37:24.100435Z"));
- result.Value.CancelInitiatedAt.Should().Be(DateTimeOffset.Parse("2024-08-20T18:37:24.100435Z"));
- result.Value.ResultsUrl.Should()
- .Be("https://api.anthropic.com/v1/messages/batches/msgbatch_013Zva2CMHLNnXjNJJKqJ2EF/results");
}
[Fact]
@@ -1823,4 +1823,85 @@ public class AnthropicApiClientTests : IntegrationTest
}
});
}
+
+ [Fact]
+ public async Task CancelMessageBatchAsync_WhenCalled_ItShouldReturnBatch()
+ {
+ var batchId = "msgbatch_013Zva2CMHLNnXjNJJKqJ2EF";
+
+ _mockHttpMessageHandler
+ .WhenCancelMessageBatchRequest(batchId)
+ .Respond(
+ HttpStatusCode.OK,
+ "application/json",
+ @"{
+ ""id"": ""msgbatch_013Zva2CMHLNnXjNJJKqJ2EF"",
+ ""type"": ""message_batch"",
+ ""processing_status"": ""in_progress"",
+ ""request_counts"": {
+ ""processing"": 100,
+ ""succeeded"": 50,
+ ""errored"": 30,
+ ""canceled"": 10,
+ ""expired"": 10
+ },
+ ""ended_at"": ""2024-08-20T18:37:24.100435Z"",
+ ""created_at"": ""2024-08-20T18:37:24.100435Z"",
+ ""expires_at"": ""2024-08-20T18:37:24.100435Z"",
+ ""archived_at"": ""2024-08-20T18:37:24.100435Z"",
+ ""cancel_initiated_at"": ""2024-08-20T18:37:24.100435Z"",
+ ""results_url"": ""https://api.anthropic.com/v1/messages/batches/msgbatch_013Zva2CMHLNnXjNJJKqJ2EF/results""
+ }"
+ );
+
+ var result = await Client.CancelMessageBatchAsync(batchId);
+
+ result.IsSuccess.Should().BeTrue();
+ result.Value.Should().BeEquivalentTo(new MessageBatchResponse()
+ {
+ Id = "msgbatch_013Zva2CMHLNnXjNJJKqJ2EF",
+ Type = "message_batch",
+ ProcessingStatus = "in_progress",
+ RequestCounts = new MessageBatchRequestCounts
+ {
+ Processing = 100,
+ Succeeded = 50,
+ Errored = 30,
+ Canceled = 10,
+ Expired = 10
+ },
+ EndedAt = DateTimeOffset.Parse("2024-08-20T18:37:24.100435Z"),
+ CreatedAt = DateTimeOffset.Parse("2024-08-20T18:37:24.100435Z"),
+ ExpiresAt = DateTimeOffset.Parse("2024-08-20T18:37:24.100435Z"),
+ ArchivedAt = DateTimeOffset.Parse("2024-08-20T18:37:24.100435Z"),
+ CancelInitiatedAt = DateTimeOffset.Parse("2024-08-20T18:37:24.100435Z"),
+ ResultsUrl = "https://api.anthropic.com/v1/messages/batches/msgbatch_013Zva2CMHLNnXjNJJKqJ2EF/results"
+ });
+ }
+
+ [Fact]
+ public async Task CancelMessageBatchAsync_WhenCalledAndFails_ItShouldReturnError()
+ {
+ var batchId = "msgbatch_013Zva2CMHLNnXjNJJKqJ2EF";
+
+ _mockHttpMessageHandler
+ .WhenCancelMessageBatchRequest(batchId)
+ .Respond(
+ HttpStatusCode.BadRequest,
+ "application/json",
+ @"{
+ ""type"": ""error"",
+ ""error"": {
+ ""type"": ""invalid_request_error"",
+ ""message"": ""batch: batch not found""
+ }
+ }"
+ );
+
+ var result = await Client.CancelMessageBatchAsync(batchId);
+
+ result.IsSuccess.Should().BeFalse();
+ result.Error.Should().BeOfType();
+ result.Error.Error.Should().BeOfType();
+ }
}
\ No newline at end of file
diff --git a/tests/AnthropicClient.Tests/Integration/IntegrationTest.cs b/tests/AnthropicClient.Tests/Integration/IntegrationTest.cs
index 465319d..e521d0c 100644
--- a/tests/AnthropicClient.Tests/Integration/IntegrationTest.cs
+++ b/tests/AnthropicClient.Tests/Integration/IntegrationTest.cs
@@ -91,4 +91,10 @@ public static class MockHttpMessageHandlerExtensions
return mockHttpMessageHandler
.SetupBaseRequest(HttpMethod.Get, MessageBatchesEndpoint);
}
+
+ public static MockedRequest WhenCancelMessageBatchRequest(this MockHttpMessageHandler mockHttpMessageHandler, string batchId)
+ {
+ return mockHttpMessageHandler
+ .SetupBaseRequest(HttpMethod.Post, $"{MessageBatchesEndpoint}/{batchId}/cancel");
+ }
}
\ No newline at end of file