diff --git a/src/AnthropicClient/AnthropicApiClient.cs b/src/AnthropicClient/AnthropicApiClient.cs
new file mode 100644
index 0000000..9006d24
--- /dev/null
+++ b/src/AnthropicClient/AnthropicApiClient.cs
@@ -0,0 +1,233 @@
+using System.Net.Http.Headers;
+using System.Text;
+using System.Text.Json;
+
+using AnthropicClient.Json;
+using AnthropicClient.Utils;
+using AnthropicClient.Models;
+
+namespace AnthropicClient;
+
+///
+/// Represents a client for interacting with the Anthropic API.
+///
+public interface IAnthropicApiClient
+{
+ ///
+ /// Creates a chat message asynchronously.
+ ///
+ /// The chat message request to create.
+ /// A task that represents the asynchronous operation. The task result contains the chat response as an .
+ Task> CreateChatMessageAsync(ChatMessageRequest request);
+
+ ///
+ /// Creates a chat message asynchronously and streams the response.
+ ///
+ /// The chat message request to create.
+ /// An asynchronous enumerable that yields the chat response line by line.
+ IAsyncEnumerable CreateChatMessageAsync(StreamChatMessageRequest request);
+}
+
+///
+public class AnthropicApiClient : IAnthropicApiClient
+{
+ private const string BaseUrl = "https://api.anthropic.com/v1/";
+ private const string ApiKeyHeader = "x-api-key";
+ private const string MessagesEndpoint = "messages";
+ private const string JsonContentType = "application/json";
+ private const string RequestIdHeader = "request-id";
+ private const string EventPrefix = "event:";
+ private const string DataPrefix = "data:";
+ private readonly Dictionary _defaultHeaders = new()
+ {
+ { "anthropic-version", "2023-06-01" },
+ };
+ private readonly HttpClient _httpClient;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The API key to use for the client.
+ /// The HTTP client to use for the client.
+ /// Thrown when the API key or HTTP client is null.
+ /// A new instance of the class.
+ public AnthropicApiClient(string apiKey, HttpClient httpClient)
+ {
+ ArgumentValidator.ThrowIfNull(apiKey, nameof(apiKey));
+ ArgumentValidator.ThrowIfNull(httpClient, nameof(httpClient));
+
+ _httpClient = httpClient;
+ _httpClient.BaseAddress = new Uri(BaseUrl);
+ _httpClient.DefaultRequestHeaders.Add(ApiKeyHeader, apiKey);
+ _httpClient.DefaultRequestHeaders
+ .Accept
+ .Add(new MediaTypeWithQualityHeaderValue(JsonContentType));
+
+ foreach (var pair in _defaultHeaders)
+ {
+ _httpClient.DefaultRequestHeaders.Add(pair.Key, pair.Value);
+ }
+ }
+
+ ///
+ public async Task> CreateChatMessageAsync(ChatMessageRequest request)
+ {
+ var response = await SendRequestAsync(request);
+ 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 chatResponse = Deserialize(responseContent) ?? new ChatResponse();
+ return AnthropicResult.Success(chatResponse, anthropicHeaders);
+ }
+
+ ///
+ public async IAsyncEnumerable CreateChatMessageAsync(StreamChatMessageRequest request)
+ {
+ var response = await SendRequestAsync(request);
+ var anthropicHeaders = new AnthropicHeaders(response.Headers);
+
+ using var responseContent = await response.Content.ReadAsStreamAsync();
+ using var streamReader = new StreamReader(responseContent);
+
+ ChatResponse? chatResponse = null;
+ Content? content = null;
+ var toolInputJsonStringBuilder = new StringBuilder();
+ var currentEvent = new AnthropicEvent();
+
+ do
+ {
+ var line = await streamReader.ReadLineAsync();
+
+ // I know...this is not pretty, but here is why...
+ // as events are being yielded I want to also
+ // build up the complete chat response
+ // so I can yield it as a special event to make tool
+ // calling easier to handle
+
+ // initialize chat response on message start
+ if (currentEvent.Type is EventType.MessageStart && currentEvent.Data is MessageStartEventData msgStartData)
+ {
+ chatResponse = msgStartData.Message;
+ }
+
+ // initialize content block on content block start
+ if (currentEvent.Type is EventType.ContentBlockStart && currentEvent.Data is ContentStartEventData contentStartData)
+ {
+ content = contentStartData.ContentBlock;
+ }
+
+ // update content block with deltas based on
+ // current content type and delta type
+ if (currentEvent.Type is EventType.ContentBlockDelta && currentEvent.Data is ContentDeltaEventData contentDeltaData)
+ {
+ if (content is TextContent textContent && contentDeltaData.Delta is TextDelta textDelta)
+ {
+ var newText = textContent.Text + textDelta.Text;
+ content = new TextContent(newText);
+ }
+
+ if (content is ToolUseContent toolUseContent && contentDeltaData.Delta is JsonDelta jsonDelta)
+ {
+ toolInputJsonStringBuilder.Append(jsonDelta.PartialJson);
+ }
+ }
+
+ // finalize content block on content block stop
+ // and add it to the chat response
+ if (currentEvent.Type is EventType.ContentBlockStop)
+ {
+ if (content is not null && chatResponse is not null)
+ {
+ if (content is TextContent textContent)
+ {
+ chatResponse.Content.Add(textContent);
+ }
+
+ if (content is ToolUseContent toolUseContent)
+ {
+ var input = Deserialize>(toolInputJsonStringBuilder.ToString());
+ var newToolUseContent = new ToolUseContent()
+ {
+ Id = toolUseContent.Id,
+ Name = toolUseContent.Name,
+ Input = input!,
+ };
+
+ chatResponse.Content.Add(newToolUseContent);
+ }
+
+ content = null;
+ }
+ }
+
+ // update chat response with message delta data
+ if (
+ currentEvent.Type is EventType.MessageDelta &&
+ currentEvent.Data is MessageDeltaEventData msgDeltaData &&
+ chatResponse is not null
+ )
+ {
+ chatResponse = new ChatResponse()
+ {
+ Id = chatResponse.Id,
+ Model = chatResponse.Model,
+ Role = chatResponse.Role,
+ StopReason = msgDeltaData.Delta.StopReason,
+ StopSequence = msgDeltaData.Delta.StopSequence,
+ Type = chatResponse.Type,
+ Usage = msgDeltaData.Usage,
+ Content = chatResponse.Content,
+ };
+ }
+
+ // yield chat response on message stop
+ if (currentEvent.Type is EventType.MessageStop && chatResponse is not null)
+ {
+ var eventData = new MessageCompleteEventData(chatResponse, anthropicHeaders);
+ yield return new AnthropicEvent(EventType.MessageComplete, eventData);
+ chatResponse = null;
+ }
+
+ if (line is null)
+ {
+ break;
+ }
+
+ if (line == string.Empty)
+ {
+ yield return currentEvent;
+ currentEvent = new AnthropicEvent();
+ }
+
+ if (line.StartsWith(EventPrefix))
+ {
+ var eventType = line.Substring(EventPrefix.Length).Trim();
+ currentEvent = new AnthropicEvent(eventType, currentEvent.Data);
+ continue;
+ }
+
+ if (line.StartsWith(DataPrefix))
+ {
+ var eventData = line.Substring(DataPrefix.Length).Trim();
+ var eventDataJson = Deserialize(eventData);
+ currentEvent = new AnthropicEvent(currentEvent.Type, eventDataJson!);
+ continue;
+ }
+ } while (true);
+ }
+
+ private async Task SendRequestAsync(MessageRequest request)
+ {
+ var requestContent = new StringContent(Serialize(request), Encoding.UTF8, JsonContentType);
+ return await _httpClient.PostAsync(MessagesEndpoint, requestContent);
+ }
+
+ private string Serialize(T obj) => JsonSerializer.Serialize(obj, JsonSerializationOptions.DefaultOptions);
+ private T? Deserialize(string json) => JsonSerializer.Deserialize(json, JsonSerializationOptions.DefaultOptions);
+}
\ No newline at end of file
diff --git a/src/AnthropicClient/Client.cs b/src/AnthropicClient/Client.cs
deleted file mode 100644
index a3cf0b8..0000000
--- a/src/AnthropicClient/Client.cs
+++ /dev/null
@@ -1,154 +0,0 @@
-using System.Net.Http.Headers;
-using System.Text;
-using System.Text.Json;
-
-using AnthropicClient.Json;
-using AnthropicClient.Utils;
-using AnthropicClient.Models;
-
-namespace AnthropicClient;
-
-///
-/// Represents a client for interacting with the Anthropic API.
-///
-public interface IClient
-{
- ///
- /// Creates a chat message asynchronously.
- ///
- /// The chat message request to create.
- /// A task that represents the asynchronous operation. The task result contains the chat response as an .
- Task> CreateChatMessageAsync(ChatMessageRequest request);
-
- ///
- /// Creates a chat message asynchronously and streams the response.
- ///
- /// The chat message request to create.
- /// An asynchronous enumerable that yields the chat response line by line.
- IAsyncEnumerable CreateChatMessageAsync(StreamChatMessageRequest request);
-}
-
-///
-public class Client : IClient
-{
- private const string BaseUrl = "https://api.anthropic.com/v1/";
- private const string ApiKeyHeader = "x-api-key";
- private const string MessagesEndpoint = "messages";
- private const string JsonContentType = "application/json";
- private const string RequestIdHeader = "request-id";
- private readonly Dictionary _defaultHeaders = new()
- {
- { "anthropic-version", "2023-06-01" },
- };
- private readonly HttpClient _httpClient;
-
- ///
- /// Initializes a new instance of the class.
- ///
- /// The API key to use for the client.
- /// The HTTP client to use for the client.
- /// Thrown when the API key or HTTP client is null.
- /// A new instance of the class.
- public Client(string apiKey, HttpClient httpClient)
- {
- ArgumentValidator.ThrowIfNull(apiKey, nameof(apiKey));
- ArgumentValidator.ThrowIfNull(httpClient, nameof(httpClient));
-
- _httpClient = httpClient;
- _httpClient.BaseAddress = new Uri(BaseUrl);
- _httpClient.DefaultRequestHeaders.Add(ApiKeyHeader, apiKey);
- _httpClient.DefaultRequestHeaders
- .Accept
- .Add(new MediaTypeWithQualityHeaderValue(JsonContentType));
-
- foreach (var pair in _defaultHeaders)
- {
- _httpClient.DefaultRequestHeaders.Add(pair.Key, pair.Value);
- }
- }
-
- ///
- public async Task> CreateChatMessageAsync(ChatMessageRequest request)
- {
- var response = await SendRequestAsync(request);
- var requestId = GetRequestId(response);
- var responseContent = await response.Content.ReadAsStringAsync();
-
- if (response.IsSuccessStatusCode is false)
- {
- var error = Deserialize(responseContent) ?? new AnthropicError();
- return AnthropicResult.Failure(error, requestId);
- }
-
- var chatResponse = Deserialize(responseContent) ?? new ChatResponse();
- return AnthropicResult.Success(chatResponse, requestId);
- }
-
- ///
- public async IAsyncEnumerable CreateChatMessageAsync(StreamChatMessageRequest request)
- {
- var response = await SendRequestAsync(request);
- var requestId = GetRequestId(response);
-
- using var responseContent = await response.Content.ReadAsStreamAsync();
- using var streamReader = new StreamReader(responseContent);
-
- var currentEvent = new AnthropicEvent();
-
- // TODO: I'd like to emit custom events unique to this client
- // - "content_block_complete"
- // - provides the complete content block that was streamed
- // - useful for streaming in text, but then handling tool calls with their entire input
-
- // will need to keep track of current content so that we can build it up as relevant
- // deltas are streamed in
-
- // will want to capture the content block start event
- // then continue to build this up until we get to the content block stop event
- // at that point we should have the complete block and we should be able to yield
- // return the complete block and reset the current content block
-
- // event: content_block_start
- // data: {"type":"content_block_start","index":1,"content_block":{"type":"tool_use","id":"toolu_01T1x1fJ34qAmk2tNTrN7Up6","name":"get_weather","input":{}}}
-
-
- do
- {
- var line = await streamReader.ReadLineAsync();
-
- if (line is null)
- {
- break;
- }
-
- if (line.StartsWith("event:"))
- {
- var eventType = line.Substring("event:".Length).Trim();
- currentEvent = currentEvent with { Type = eventType };
- }
-
- if (line.StartsWith("data:"))
- {
- var eventData = line.Substring("data:".Length).Trim();
- var eventDataJson = JsonSerializer.Deserialize(eventData, JsonSerializationOptions.DefaultOptions);
- currentEvent = currentEvent with { Data = eventDataJson! };
- }
-
- if (line == string.Empty)
- {
- yield return currentEvent;
- currentEvent = new AnthropicEvent();
- }
- } while (true);
- }
-
- private async Task SendRequestAsync(MessageRequest request)
- {
- var requestContent = new StringContent(Serialize(request), Encoding.UTF8, JsonContentType);
- return await _httpClient.PostAsync(MessagesEndpoint, requestContent);
- }
-
- private string Serialize(T obj) => JsonSerializer.Serialize(obj, JsonSerializationOptions.DefaultOptions);
- private T? Deserialize(string json) => JsonSerializer.Deserialize(json, JsonSerializationOptions.DefaultOptions);
- private string GetRequestId(HttpResponseMessage response) => response.Headers.GetValues(RequestIdHeader).FirstOrDefault() ?? string.Empty;
-}
\ No newline at end of file
diff --git a/src/AnthropicClient/Models/AnthropicEvent.cs b/src/AnthropicClient/Models/AnthropicEvent.cs
index 91bab77..2205fd3 100644
--- a/src/AnthropicClient/Models/AnthropicEvent.cs
+++ b/src/AnthropicClient/Models/AnthropicEvent.cs
@@ -2,7 +2,7 @@ using System.Text.Json.Serialization;
namespace AnthropicClient.Models;
-public record AnthropicEvent
+public class AnthropicEvent
{
public string Type { get; init; } = string.Empty;
public EventData Data { get; init; } = default!;
diff --git a/src/AnthropicClient/Models/AnthropicHeaders.cs b/src/AnthropicClient/Models/AnthropicHeaders.cs
new file mode 100644
index 0000000..0eaac8f
--- /dev/null
+++ b/src/AnthropicClient/Models/AnthropicHeaders.cs
@@ -0,0 +1,79 @@
+using System.Net.Http.Headers;
+
+namespace AnthropicClient.Models;
+
+///
+/// Represents headers included in Anthropic API responses.
+///
+public class AnthropicHeaders
+{
+ private const string RequestIdHeaderKey = "request-id";
+ private const string RateLimitRequestsLimitHeaderKey = "anthropic-ratelimit-requests-limit";
+ private const string RateLimitRequestsRemainingHeaderKey = "anthropic-ratelimit-requests-remaining";
+ private const string RateLimitRequestsResetHeaderKey = "anthropic-ratelimit-requests-reset";
+ private const string RateLimitTokensLimitHeaderKey = "anthropic-ratelimit-tokens-limit";
+ private const string RateLimitTokensRemainingHeaderKey = "anthropic-ratelimit-tokens-remaining";
+ private const string RateLimitTokensResetHeaderKey = "anthropic-ratelimit-tokens-reset";
+ private const string RetryAfterHeaderKey = "retry-after";
+
+ ///
+ /// Gets the request ID.
+ ///
+ public string RequestId { get; init; } = string.Empty;
+
+ ///
+ /// Gets the rate limit requests limit.
+ ///
+ public int RateLimitRequestsLimit { get; init; }
+
+ ///
+ /// Gets the rate limit requests remaining.
+ ///
+ public int RateLimitRequestsRemaining { get; init; }
+
+ ///
+ /// Gets the time of the rate limit requests reset.
+ ///
+ public DateTimeOffset RateLimitRequestsReset { get; init; }
+
+ ///
+ /// Gets the rate limit tokens limit.
+ ///
+ public int RateLimitTokensLimit { get; init; }
+
+ ///
+ /// Gets the rate limit tokens remaining.
+ ///
+ public int RateLimitTokensRemaining { get; init; }
+
+ ///
+ /// Gets the time of the rate limit tokens reset.
+ ///
+ public DateTimeOffset RateLimitTokensReset { get; init; }
+
+ ///
+ /// Gets the retry after value.
+ ///
+ public int RetryAfter { get; init; }
+
+ internal AnthropicHeaders()
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The HTTP response headers.
+ /// A new instance of the class.
+ public AnthropicHeaders(HttpResponseHeaders headers)
+ {
+ RequestId = headers.GetValues(RequestIdHeaderKey).FirstOrDefault() ?? string.Empty;
+ RateLimitRequestsLimit = int.Parse(headers.GetValues(RateLimitRequestsLimitHeaderKey).FirstOrDefault() ?? "0");
+ RateLimitRequestsRemaining = int.Parse(headers.GetValues(RateLimitRequestsRemainingHeaderKey).FirstOrDefault() ?? "0");
+ RateLimitRequestsReset = DateTimeOffset.Parse(headers.GetValues(RateLimitRequestsResetHeaderKey).FirstOrDefault() ?? DateTimeOffset.MinValue.ToString());
+ RateLimitTokensLimit = int.Parse(headers.GetValues(RateLimitTokensLimitHeaderKey).FirstOrDefault() ?? "0");
+ RateLimitTokensRemaining = int.Parse(headers.GetValues(RateLimitTokensRemainingHeaderKey).FirstOrDefault() ?? "0");
+ RateLimitTokensReset = DateTimeOffset.Parse(headers.GetValues(RateLimitTokensResetHeaderKey).FirstOrDefault() ?? DateTimeOffset.MinValue.ToString());
+ RetryAfter = int.Parse(headers.GetValues(RetryAfterHeaderKey).FirstOrDefault() ?? "0");
+ }
+}
\ No newline at end of file
diff --git a/src/AnthropicClient/Models/AnthropicResult.cs b/src/AnthropicClient/Models/AnthropicResult.cs
index 1fdc4e1..19c27a9 100644
--- a/src/AnthropicClient/Models/AnthropicResult.cs
+++ b/src/AnthropicClient/Models/AnthropicResult.cs
@@ -24,7 +24,7 @@ public class AnthropicResult
///
/// The request ID of the operation.
///
- public string RequestId { get; }
+ public AnthropicHeaders Headers { get; }
///
/// Initializes a new instance of the class.
@@ -32,35 +32,35 @@ public class AnthropicResult
/// The value of the result.
/// The error of the result.
/// Indicates whether the operation was successful.
- /// The request ID of the operation.
+ /// The Anthropic headers for the result.
/// A new instance of the class.
- protected AnthropicResult(T value, AnthropicError error, bool isSuccess, string requestId)
+ protected AnthropicResult(T value, AnthropicError error, bool isSuccess, AnthropicHeaders headers)
{
Value = value;
Error = error;
IsSuccess = isSuccess;
- RequestId = requestId;
+ Headers = headers;
}
///
/// Creates a successful result.
///
/// The value of the result.
- /// The request ID of the operation.
+ /// The Anthropic headers for the result.
/// A new instance of the class.
- public static AnthropicResult Success(T value, string requestId)
+ public static AnthropicResult Success(T value, AnthropicHeaders headers)
{
- return new AnthropicResult(value, null!, true, requestId);
+ return new AnthropicResult(value, null!, true, headers);
}
///
/// Creates a failed result.
///
/// The error of the result.
- /// The request ID of the operation.
+ /// The Anthropic headers for the result.
/// A new instance of the class.
- public static AnthropicResult Failure(AnthropicError error, string requestId)
+ public static AnthropicResult Failure(AnthropicError error, AnthropicHeaders headers)
{
- return new AnthropicResult(default!, error, false, requestId);
+ return new AnthropicResult(default!, error, false, headers);
}
}
diff --git a/src/AnthropicClient/Models/ContentStartEventData.cs b/src/AnthropicClient/Models/ContentStartEventData.cs
index 1edfa2e..ed2c9cf 100644
--- a/src/AnthropicClient/Models/ContentStartEventData.cs
+++ b/src/AnthropicClient/Models/ContentStartEventData.cs
@@ -7,7 +7,7 @@ public class ContentStartEventData : EventData
public int Index { get; init; }
[JsonPropertyName("content_block")]
- public Content ContentBlock { get; init; }
+ public Content ContentBlock { get; init; } = default!;
[JsonConstructor]
internal ContentStartEventData() : base(EventType.ContentBlockStart)
diff --git a/src/AnthropicClient/Models/EventType.cs b/src/AnthropicClient/Models/EventType.cs
index a652168..48c652f 100644
--- a/src/AnthropicClient/Models/EventType.cs
+++ b/src/AnthropicClient/Models/EventType.cs
@@ -7,6 +7,7 @@ public static class EventType
public const string MessageStart = "message_start";
public const string MessageDelta = "message_delta";
public const string MessageStop = "message_stop";
+ public const string MessageComplete = "message_complete";
public const string ContentBlockStart = "content_block_start";
public const string ContentBlockDelta = "content_block_delta";
public const string ContentBlockStop = "content_block_stop";
diff --git a/src/AnthropicClient/Models/MessageCompleteEventData.cs b/src/AnthropicClient/Models/MessageCompleteEventData.cs
new file mode 100644
index 0000000..85218ad
--- /dev/null
+++ b/src/AnthropicClient/Models/MessageCompleteEventData.cs
@@ -0,0 +1,15 @@
+using System.Text.Json.Serialization;
+
+namespace AnthropicClient.Models;
+
+public class MessageCompleteEventData : EventData
+{
+ public AnthropicHeaders Headers { get; init; } = new();
+ public ChatResponse Message { get; init; } = new();
+
+ public MessageCompleteEventData(ChatResponse message, AnthropicHeaders headers) : base(EventType.MessageComplete)
+ {
+ Message = message;
+ Headers = headers;
+ }
+}
\ No newline at end of file
diff --git a/src/AnthropicClient/Models/MessageDeltaEventData.cs b/src/AnthropicClient/Models/MessageDeltaEventData.cs
index 240e307..b150d2e 100644
--- a/src/AnthropicClient/Models/MessageDeltaEventData.cs
+++ b/src/AnthropicClient/Models/MessageDeltaEventData.cs
@@ -4,8 +4,8 @@ namespace AnthropicClient.Models;
public class MessageDeltaEventData : EventData
{
- public MessageDelta Delta { get; init; }
- public ChatUsage Usage { get; init; }
+ public MessageDelta Delta { get; init; } = new();
+ public ChatUsage Usage { get; init; } = new();
[JsonConstructor]
internal MessageDeltaEventData() : base(EventType.MessageDelta)
diff --git a/src/AnthropicClient/Models/MessageStartEventData.cs b/src/AnthropicClient/Models/MessageStartEventData.cs
index 16b4451..ccf5c18 100644
--- a/src/AnthropicClient/Models/MessageStartEventData.cs
+++ b/src/AnthropicClient/Models/MessageStartEventData.cs
@@ -4,14 +4,14 @@ namespace AnthropicClient.Models;
public class MessageStartEventData : EventData
{
- public ChatMessage Message { get; init; } = new();
+ public ChatResponse Message { get; init; } = new();
[JsonConstructor]
internal MessageStartEventData() : base(EventType.MessageStart)
{
}
- public MessageStartEventData(ChatMessage message) : base(EventType.MessageStart)
+ public MessageStartEventData(ChatResponse message) : base(EventType.MessageStart)
{
Message = message;
}
diff --git a/tests/AnthropicClient.Tests/EndToEnd/ClientTests.cs b/tests/AnthropicClient.Tests/EndToEnd/AnthropicApiClientTests.cs
similarity index 60%
rename from tests/AnthropicClient.Tests/EndToEnd/ClientTests.cs
rename to tests/AnthropicClient.Tests/EndToEnd/AnthropicApiClientTests.cs
index aee03d6..fe5560c 100644
--- a/tests/AnthropicClient.Tests/EndToEnd/ClientTests.cs
+++ b/tests/AnthropicClient.Tests/EndToEnd/AnthropicApiClientTests.cs
@@ -6,7 +6,7 @@ public class ClientTests(
) : EndToEndTest(httpClientFixture, configFixture)
{
[Fact]
- public async Task CreateChatMessage_WhenCalled_ShouldReturnChatResponse()
+ public async Task CreateChatMessage_WhenCalled_ItShouldReturnChatResponse()
{
var request = new ChatMessageRequest(
model: AnthropicModels.Claude3Haiku,
@@ -20,7 +20,7 @@ public class ClientTests(
}
[Fact]
- public async Task CreateChatMessage_WhenCalledWithStreamRequest_IteratesOverChatResponse()
+ public async Task CreateChatMessage_WhenCalledWithStreamRequest_ItShouldReturnEvents()
{
var request = new StreamChatMessageRequest(
model: AnthropicModels.Claude3Haiku,
@@ -38,4 +38,24 @@ public class ClientTests(
events.Should().NotBeEmpty();
}
+
+ [Fact]
+ public async Task CreateChatMessage_WhenCalledWithStreamRequest_ItShouldYieldAMessageCompleteEvent()
+ {
+ var request = new StreamChatMessageRequest(
+ model: AnthropicModels.Claude3Haiku,
+ messages: [new(MessageRole.User, [new TextContent("Hello!")])]
+ );
+
+ var response = _client.CreateChatMessageAsync(request);
+
+ await foreach (var e in response)
+ {
+ if (e.Data is MessageCompleteEventData messageCompleteData)
+ {
+ messageCompleteData.Message.Should().NotBeNull();
+ break;
+ }
+ }
+ }
}
\ No newline at end of file
diff --git a/tests/AnthropicClient.Tests/EndToEnd/EndToEndTest.cs b/tests/AnthropicClient.Tests/EndToEnd/EndToEndTest.cs
index c808412..b4cc22d 100644
--- a/tests/AnthropicClient.Tests/EndToEnd/EndToEndTest.cs
+++ b/tests/AnthropicClient.Tests/EndToEnd/EndToEndTest.cs
@@ -5,5 +5,5 @@ public class EndToEndTest(
ConfigurationFixture configFixture
) : IClassFixture, IClassFixture
{
- protected readonly Client _client = new(configFixture.AnthropicApiKey, httpClientFixture.HttpClient);
+ protected readonly AnthropicApiClient _client = new(configFixture.AnthropicApiKey, httpClientFixture.HttpClient);
}
\ No newline at end of file