diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
new file mode 100644
index 0000000..723ed2d
--- /dev/null
+++ b/CONTRIBUTING.md
@@ -0,0 +1,90 @@
+# Contributing to Anthropic.Net
+
+Thank you for your interest in contributing to Anthropic.Net! We welcome contributions from the community to help improve this SDK.
+
+## Development Environment
+
+To get started, ensure you have the following installed:
+
+- **.NET SDK**: Version 8.0 or later (see `global.json`).
+- **IDE**: Visual Studio 2022, VS Code, or JetBrains Rider.
+- **PowerShell**: Required for running build scripts.
+
+## Getting Started
+
+1. **Fork** the repository on GitHub.
+2. **Clone** your fork locally:
+
+ ```bash
+ git clone https://github.com/YOUR-USERNAME/anthropic.net.git
+ cd anthropic.net
+ ```
+
+3. **Restore** dependencies and tools:
+
+ ```bash
+ dotnet tool restore
+ dotnet restore
+ ```
+
+## Building and Testing
+
+The project uses [Cake](https://cakebuild.net/) for build automation. You can run the build script using the dotnet tool:
+
+```bash
+dotnet cake --target=Build
+```
+
+To run tests:
+
+```bash
+dotnet cake --target=Test
+```
+
+To run the full CI pipeline (Build, Test, Pack):
+
+```bash
+dotnet cake --target=Default
+```
+
+## Code Style
+
+We enforce code style using `.editorconfig` and StyleCop analyzers.
+
+- **Naming**: PascalCase for public members, camelCase for private fields/parameters.
+- **Formatting**: Standard C# conventions (K&R braces, 4-space indentation).
+- **Headers**: File headers are not required.
+- **Warnings**: Treat warnings as errors is enabled in CI. Ensure your code builds without warnings.
+
+Run `dotnet format` to automatically fix style issues.
+
+## Pull Request Process
+
+1. Create a new branch for your feature or fix:
+
+ ```bash
+ git checkout -b feature/my-awesome-feature
+ ```
+
+2. Commit your changes with clear, descriptive messages.
+3. Push your branch to your fork.
+4. Open a Pull Request (PR) against the `main` branch of the `tinonetic/anthropic.net` repository.
+5. Ensure all CI checks pass.
+
+## Release Process (Maintainers Only)
+
+Releases are automated via GitHub Actions and MinVer.
+
+1. **Versioning**: Determine the next version number (SemVer).
+2. **Tagging**: Create and push a git tag for the new version (e.g., `v2.0.0`).
+
+ ```bash
+ git tag v2.0.0
+ git push origin v2.0.0
+ ```
+
+3. **Publishing**: Go to GitHub Releases, draft a new release for the tag, and click "Publish". This triggers the NuGet publication workflow.
+
+## License
+
+By contributing, you agree that your contributions will be licensed under the project's [MIT License](LICENSE.md).
diff --git a/Source/Anthropic.Net/IAnthropicApiClient.cs b/Source/Anthropic.Net/IAnthropicApiClient.cs
index d1b74bf..faa3276 100644
--- a/Source/Anthropic.Net/IAnthropicApiClient.cs
+++ b/Source/Anthropic.Net/IAnthropicApiClient.cs
@@ -7,7 +7,7 @@ namespace Anthropic.Net;
///
/// The Anthropic API client interface.
///
-internal interface IAnthropicApiClient
+public interface IAnthropicApiClient
{
///
/// Sends a prompt to the Anthropic API for completion.
diff --git a/Tests/Anthropic.Net.Tests/ApiClientTests.cs b/Tests/Anthropic.Net.Tests/ApiClientTests.cs
index 14db254..7fccf49 100644
--- a/Tests/Anthropic.Net.Tests/ApiClientTests.cs
+++ b/Tests/Anthropic.Net.Tests/ApiClientTests.cs
@@ -3,6 +3,8 @@ namespace Anthropic.Net.Test;
using System.Net;
using Anthropic.Net.Constants;
using Anthropic.Net.Models.Messages;
+using Anthropic.Net.Models.Messages.Streaming;
+using Anthropic.Net.Models.Messages.Streaming.StreamingEvents;
using NSubstitute;
using Shouldly;
using Xunit;
@@ -82,4 +84,53 @@ public async Task TestMessageAsync_ValidRequestAsync()
response.Content[0].Type.ShouldBe("text");
((TextContentBlock)response.Content[0]).Text.ShouldBe("Hello, world!");
}
+ [Fact]
+ public async Task TestStreamMessageAsync_ValidRequestAsync()
+ {
+ // Arrange
+ var mockSseResponse = """
+ event: message_start
+ data: {"type": "message_start", "message": {"id": "msg_1", "type": "message", "role": "assistant", "content": [], "model": "claude-3-sonnet-20240229", "stop_reason": null, "stop_sequence": null, "usage": {"input_tokens": 25, "output_tokens": 1}}}
+
+ event: content_block_start
+ data: {"type": "content_block_start", "index": 0, "content_block": {"type": "text", "text": ""}}
+
+ event: content_block_delta
+ data: {"type": "content_block_delta", "index": 0, "delta": {"type": "text_delta", "text": "Hello"}}
+
+ event: content_block_stop
+ data: {"type": "content_block_stop", "index": 0}
+
+ event: message_delta
+ data: {"type": "message_delta", "delta": {"stop_reason": "end_turn", "stop_sequence": null}, "usage": {"output_tokens": 15}}
+
+ event: message_stop
+ data: {"type": "message_stop"}
+ """;
+
+ var messageHandler = new MockHttpMessageHandler(mockSseResponse, HttpStatusCode.OK);
+ var httpClientFactory = Substitute.For();
+ _ = httpClientFactory.CreateClient().Returns(
+ new HttpClient(messageHandler)
+ {
+ BaseAddress = _baseAddress,
+ });
+
+ var sut = new AnthropicApiClient("test-api-key", httpClientFactory);
+
+ // Act
+ var messages = new List { Message.FromUser("Hello") };
+ var events = new List();
+ await foreach (var ev in sut.StreamMessageAsync(new MessageRequest(AnthropicModels.Claude3Sonnet, messages)))
+ {
+ events.Add(ev);
+ }
+
+ // Assert
+ events.ShouldNotBeEmpty();
+ events.Count.ShouldBe(6);
+ events[0].ShouldBeOfType();
+ events[2].ShouldBeOfType();
+ ((ContentBlockDeltaEvent)events[2]).Delta.Text.ShouldBe("Hello");
+ }
}