diff --git a/src/AnthropicClient/AnthropicApiClient.cs b/src/AnthropicClient/AnthropicApiClient.cs index 195add8..1d33ad7 100644 --- a/src/AnthropicClient/AnthropicApiClient.cs +++ b/src/AnthropicClient/AnthropicApiClient.cs @@ -3,8 +3,8 @@ using System.Text; using System.Text.Json; using AnthropicClient.Json; -using AnthropicClient.Utils; using AnthropicClient.Models; +using AnthropicClient.Utils; namespace AnthropicClient; @@ -82,12 +82,12 @@ public class AnthropicApiClient : IAnthropicApiClient } var chatResponse = Deserialize(responseContent) ?? new ChatResponse(); - + if (request.Tools is not null && request.Tools.Count > 0) { chatResponse.ToolCall = GetToolCall(chatResponse, request.Tools); } - + return AnthropicResult.Success(chatResponse, anthropicHeaders); } @@ -108,13 +108,13 @@ public class AnthropicApiClient : IAnthropicApiClient 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) { @@ -173,7 +173,7 @@ public class AnthropicApiClient : IAnthropicApiClient // update chat response with message delta data if ( - currentEvent.Type is EventType.MessageDelta && + currentEvent.Type is EventType.MessageDelta && currentEvent.Data is MessageDeltaEventData msgDeltaData && chatResponse is not null ) diff --git a/src/AnthropicClient/Models/AnthropicHeaders.cs b/src/AnthropicClient/Models/AnthropicHeaders.cs index b2e08f2..6c75c56 100644 --- a/src/AnthropicClient/Models/AnthropicHeaders.cs +++ b/src/AnthropicClient/Models/AnthropicHeaders.cs @@ -70,48 +70,48 @@ public class AnthropicHeaders RequestId = headers.TryGetValues(RequestIdHeaderKey, out var requestIdValues) ? requestIdValues.FirstOrDefault() : string.Empty; - + RateLimitRequestsLimit = int.Parse( headers.TryGetValues(RateLimitRequestsLimitHeaderKey, out var requestLimitValues) ? requestLimitValues.FirstOrDefault() : "0" ); - + RateLimitRequestsRemaining = int.Parse( headers.TryGetValues(RateLimitRequestsRemainingHeaderKey, out var remainingRequestsValues) ? remainingRequestsValues.FirstOrDefault() : "0" ); - + RateLimitRequestsReset = DateTimeOffset.Parse( headers.TryGetValues(RateLimitRequestsResetHeaderKey, out var requestResetValues) ? requestResetValues.FirstOrDefault() : DateTimeOffset.MinValue.ToString() ); - - + + RateLimitTokensLimit = int.Parse( headers.TryGetValues(RateLimitTokensLimitHeaderKey, out var limitValues) ? limitValues.FirstOrDefault() : "0" ); - + RateLimitTokensRemaining = int.Parse( headers.TryGetValues(RateLimitTokensRemainingHeaderKey, out var remainingTokensValues) ? remainingTokensValues.FirstOrDefault() : "0" ); - + RateLimitTokensReset = DateTimeOffset.Parse( - headers.TryGetValues(RateLimitTokensResetHeaderKey, out var tokenResetValues) - ? tokenResetValues.FirstOrDefault() + headers.TryGetValues(RateLimitTokensResetHeaderKey, out var tokenResetValues) + ? tokenResetValues.FirstOrDefault() : DateTimeOffset.MinValue.ToString() ); - - + + RetryAfter = int.Parse( - headers.TryGetValues(RetryAfterHeaderKey, out var retryValues) - ? retryValues.FirstOrDefault() + headers.TryGetValues(RetryAfterHeaderKey, out var retryValues) + ? retryValues.FirstOrDefault() : "0" ); } diff --git a/src/AnthropicClient/Models/AnthropicResult.cs b/src/AnthropicClient/Models/AnthropicResult.cs index 19c27a9..ddadaeb 100644 --- a/src/AnthropicClient/Models/AnthropicResult.cs +++ b/src/AnthropicClient/Models/AnthropicResult.cs @@ -63,4 +63,4 @@ public class AnthropicResult { return new AnthropicResult(default!, error, false, headers); } -} +} \ No newline at end of file diff --git a/src/AnthropicClient/Models/FunctionParameterAttribute.cs b/src/AnthropicClient/Models/FunctionParameterAttribute.cs index 5fb4196..60eb4cd 100644 --- a/src/AnthropicClient/Models/FunctionParameterAttribute.cs +++ b/src/AnthropicClient/Models/FunctionParameterAttribute.cs @@ -7,7 +7,7 @@ namespace AnthropicClient.Models; /// [AttributeUsage(AttributeTargets.Parameter)] public sealed class FunctionParameterAttribute : Attribute -{ +{ /// /// The name of the parameter. /// diff --git a/src/AnthropicClient/Models/ImageType.cs b/src/AnthropicClient/Models/ImageType.cs index 3fd5415..0260b43 100644 --- a/src/AnthropicClient/Models/ImageType.cs +++ b/src/AnthropicClient/Models/ImageType.cs @@ -24,6 +24,6 @@ public static class ImageType /// Represents the WebP image type. /// public const string Webp = "image/webp"; - + internal static bool IsValidImageType(string imageType) => imageType is Jpg or Png or Gif or Webp; } \ No newline at end of file diff --git a/src/AnthropicClient/Models/MessageRequest.cs b/src/AnthropicClient/Models/MessageRequest.cs index 7beeeaa..b477581 100644 --- a/src/AnthropicClient/Models/MessageRequest.cs +++ b/src/AnthropicClient/Models/MessageRequest.cs @@ -9,7 +9,7 @@ namespace AnthropicClient.Models; /// public abstract class MessageRequest { - /// + /// /// Gets the model ID to use for the request. /// public string Model { get; init; } = string.Empty; @@ -66,7 +66,7 @@ public abstract class MessageRequest /// Gets the tools to use for the request. /// public List? Tools { get; init; } = null; - + /// /// Gets a value indicating whether the message should be streamed. /// diff --git a/src/AnthropicClient/Models/MessageRole.cs b/src/AnthropicClient/Models/MessageRole.cs index 45ad365..976ebc4 100644 --- a/src/AnthropicClient/Models/MessageRole.cs +++ b/src/AnthropicClient/Models/MessageRole.cs @@ -14,6 +14,6 @@ public static class MessageRole /// Represents the assistant role. /// public const string Assistant = "assistant"; - + internal static bool IsValidRole(string role) => role is User or Assistant; } \ No newline at end of file diff --git a/src/AnthropicClient/Models/Tool.cs b/src/AnthropicClient/Models/Tool.cs index bb70153..a459c51 100644 --- a/src/AnthropicClient/Models/Tool.cs +++ b/src/AnthropicClient/Models/Tool.cs @@ -39,13 +39,13 @@ public class Tool /// [JsonIgnore] public string DisplayName { get; } - + internal Tool(string name, string description, AnthropicFunction function) { ArgumentValidator.ThrowIfNullOrWhitespace(name, nameof(name)); ArgumentValidator.ThrowIfNullOrWhitespace(description, nameof(description)); ArgumentValidator.ThrowIfNull(function, nameof(function)); - + // Anthropic imposes a limit on tool names. Tool names must be... // - between 1 and 64 characters long // - contain only letters, numbers, underscores, and hyphens @@ -76,7 +76,7 @@ public class Tool ArgumentValidator.ThrowIfNull(type, nameof(type)); var method = type.GetMethod(methodName, BindingFlags.Public | BindingFlags.Static); - + if (method is null) { throw new ArgumentException($"Method '{methodName}' not found in type '{type.FullName}'.", nameof(methodName)); @@ -103,7 +103,7 @@ public class Tool ArgumentValidator.ThrowIfNull(instance, nameof(instance)); var method = instance.GetType().GetMethod(methodName, BindingFlags.Public | BindingFlags.Instance); - + if (method is null) { throw new ArgumentException($"Method '{methodName}' not found in type '{instance.GetType().FullName}'.", nameof(methodName)); @@ -165,7 +165,7 @@ public class Tool return new Tool(name, description, new AnthropicFunction(func.Method, func.Target)); } - + private static string SanitizeName(string name) { diff --git a/src/AnthropicClient/Models/ToolCall.cs b/src/AnthropicClient/Models/ToolCall.cs index fd9e409..c78a1ae 100644 --- a/src/AnthropicClient/Models/ToolCall.cs +++ b/src/AnthropicClient/Models/ToolCall.cs @@ -60,9 +60,9 @@ public class ToolCall if (isAwaitable) { var task = (Task)Tool.Function.Method.Invoke(Tool.Function.Instance, arguments); - + await task; - + const string resultPropertyName = "Result"; var resultProperty = task.GetType().GetProperty(resultPropertyName); result = resultProperty is not null ? (T)resultProperty.GetValue(task) : default; @@ -89,9 +89,9 @@ public class ToolCall { var parameter = parameters[i]; var attribute = parameter.GetCustomAttribute(); - var parameterName = attribute is not null - ? string.IsNullOrWhiteSpace(attribute.Name) - ? parameter.Name + var parameterName = attribute is not null + ? string.IsNullOrWhiteSpace(attribute.Name) + ? parameter.Name : attribute.Name : parameter.Name; diff --git a/src/AnthropicClient/Models/ToolChoiceType.cs b/src/AnthropicClient/Models/ToolChoiceType.cs index 79d7c86..7ebd46c 100644 --- a/src/AnthropicClient/Models/ToolChoiceType.cs +++ b/src/AnthropicClient/Models/ToolChoiceType.cs @@ -19,6 +19,6 @@ public class ToolChoiceType /// Represents the specific tool choice type. /// public const string Tool = "tool"; - + internal static bool IsValidType(string type) => type is Auto or Any or Tool; } \ No newline at end of file diff --git a/tests/AnthropicClient.Tests/EndToEnd/AnthropicApiClientTests.cs b/tests/AnthropicClient.Tests/EndToEnd/AnthropicApiClientTests.cs index 583ec2a..0a05083 100644 --- a/tests/AnthropicClient.Tests/EndToEnd/AnthropicApiClientTests.cs +++ b/tests/AnthropicClient.Tests/EndToEnd/AnthropicApiClientTests.cs @@ -26,7 +26,7 @@ public class ClientTests(ConfigurationFixture configFixture) : EndToEndTest(conf ); var response = _client.CreateChatMessageAsync(request); - + var events = new List(); await foreach (var e in response) @@ -46,7 +46,7 @@ public class ClientTests(ConfigurationFixture configFixture) : EndToEndTest(conf ); var response = _client.CreateChatMessageAsync(request); - + await foreach (var e in response) { if (e.Data is MessageCompleteEventData messageCompleteData) diff --git a/tests/AnthropicClient.Tests/Unit/Models/AnthropicResultTests.cs b/tests/AnthropicClient.Tests/Unit/Models/AnthropicResultTests.cs index fa02473..7d4264b 100644 --- a/tests/AnthropicClient.Tests/Unit/Models/AnthropicResultTests.cs +++ b/tests/AnthropicClient.Tests/Unit/Models/AnthropicResultTests.cs @@ -1,6 +1,6 @@ namespace AnthropicClient.Tests.Unit.Models; -public class AnthropicResultTests +public class AnthropicResultTests { [Fact] public void Success_WhenCalled_ItShouldReturnSuccessResult() diff --git a/tests/AnthropicClient.Tests/Unit/Models/ChatMessageRequestTests.cs b/tests/AnthropicClient.Tests/Unit/Models/ChatMessageRequestTests.cs index 236affd..6afea5c 100644 --- a/tests/AnthropicClient.Tests/Unit/Models/ChatMessageRequestTests.cs +++ b/tests/AnthropicClient.Tests/Unit/Models/ChatMessageRequestTests.cs @@ -35,7 +35,7 @@ public class ChatMessageRequestTests : SerializationTest ""tools"": [], ""stream"":false }"; - + private readonly string _testJsonWithSpecificToolChoice = @"{ ""model"": ""claude-3-sonnet-20240229"", ""system"": ""test-system"", @@ -52,7 +52,7 @@ public class ChatMessageRequestTests : SerializationTest ""tools"": [], ""stream"": false }"; - + private readonly string _testJsonWithImageContent = @"{ ""model"": ""claude-3-sonnet-20240229"", ""system"": ""test-system"", @@ -77,7 +77,7 @@ public class ChatMessageRequestTests : SerializationTest ""tools"":[], ""stream"": false }"; - + private readonly string _testJsonWithUnknownContent = @"{ ""model"": ""claude-3-sonnet-20240229"", ""system"": ""test-system"", @@ -92,7 +92,7 @@ public class ChatMessageRequestTests : SerializationTest ""tools"": [], ""stream"": false }"; - + private readonly string _testJsonWithToolUseContent = @"{ ""model"": ""claude-3-sonnet-20240229"", ""system"": ""test-system"", @@ -121,7 +121,7 @@ public class ChatMessageRequestTests : SerializationTest ""tools"": [], ""stream"": false }"; - + private readonly string _testJsonWithToolResultContent = @"{ ""model"": ""claude-3-sonnet-20240229"", ""system"": ""test-system"", @@ -369,7 +369,7 @@ public class ChatMessageRequestTests : SerializationTest specificToolChoice!.Type.Should().Be("tool"); specificToolChoice.Name.Should().Be("test-tool"); chatMessageRequest.Tools.Should().HaveCount(0); -} + } [Fact] public void JsonDeserialization_WhenDeserializedWithUnknownToolChoice_ItShouldThrowJsonException() diff --git a/tests/AnthropicClient.Tests/Unit/Models/ContentDeltaTypeTests.cs b/tests/AnthropicClient.Tests/Unit/Models/ContentDeltaTypeTests.cs index 9bddaa2..32a0023 100644 --- a/tests/AnthropicClient.Tests/Unit/Models/ContentDeltaTypeTests.cs +++ b/tests/AnthropicClient.Tests/Unit/Models/ContentDeltaTypeTests.cs @@ -6,9 +6,9 @@ public class ContentDeltaTypeTests public void TextDelta_WhenCalled_ItShouldReturnExpectedValue() { var expected = "text_delta"; - + var actual = ContentDeltaType.TextDelta; - + actual.Should().Be(expected); } @@ -16,9 +16,9 @@ public class ContentDeltaTypeTests public void JsonDelta_WhenCalled_ItShouldReturnExpectedValue() { var expected = "input_json_delta"; - + var actual = ContentDeltaType.JsonDelta; - + actual.Should().Be(expected); } } \ No newline at end of file diff --git a/tests/AnthropicClient.Tests/Unit/Models/ContentStartEventDataTests.cs b/tests/AnthropicClient.Tests/Unit/Models/ContentStartEventDataTests.cs index 25bf30e..4eba619 100644 --- a/tests/AnthropicClient.Tests/Unit/Models/ContentStartEventDataTests.cs +++ b/tests/AnthropicClient.Tests/Unit/Models/ContentStartEventDataTests.cs @@ -67,11 +67,11 @@ public class ContentStartEventDataTests : SerializationTest Name = "get_weather", Input = [] }; - + var contentStartEventData = new ContentStartEventData(index, toolUseContent); var actual = Serialize(contentStartEventData); JsonAssert.Equal(_testJsonWithToolUseBlock, actual); } -} +} \ No newline at end of file diff --git a/tests/AnthropicClient.Tests/Unit/Models/InvalidRequestErrorTests.cs b/tests/AnthropicClient.Tests/Unit/Models/InvalidRequestErrorTests.cs index 9f61427..9a80f77 100644 --- a/tests/AnthropicClient.Tests/Unit/Models/InvalidRequestErrorTests.cs +++ b/tests/AnthropicClient.Tests/Unit/Models/InvalidRequestErrorTests.cs @@ -6,9 +6,9 @@ public class InvalidRequestErrorTests : SerializationTest public void Constructor_WhenCalled_ItShouldInitializeProperties() { var message = "message"; - + var error = new InvalidRequestError(message); - + error.Type.Should().Be("invalid_request_error"); error.Message.Should().Be(message); } @@ -17,11 +17,11 @@ public class InvalidRequestErrorTests : SerializationTest public void Serialization_WhenSerialized_ItShouldHaveExpectedShape() { var message = "message"; - + var error = new InvalidRequestError(message); - + var serialized = Serialize(error); - + JsonAssert.Equal( @"{ ""type"": ""invalid_request_error"", @@ -38,9 +38,9 @@ public class InvalidRequestErrorTests : SerializationTest ""type"": ""invalid_request_error"", ""message"": ""message"" }"; - + var error = Deserialize(json); - + error!.Type.Should().Be("invalid_request_error"); error.Message.Should().Be("message"); } diff --git a/tests/AnthropicClient.Tests/Unit/Models/TextDeltaTests.cs b/tests/AnthropicClient.Tests/Unit/Models/TextDeltaTests.cs index 9f68a64..5196f37 100644 --- a/tests/AnthropicClient.Tests/Unit/Models/TextDeltaTests.cs +++ b/tests/AnthropicClient.Tests/Unit/Models/TextDeltaTests.cs @@ -11,9 +11,9 @@ public class TextDeltaTests : SerializationTest public void Constructor_WhenCalled_ItShouldInitializeProperties() { var text = "Hello World!"; - + var textDelta = new TextDelta(text); - + textDelta.Type.Should().Be("text_delta"); textDelta.Text.Should().Be(text); } @@ -24,7 +24,7 @@ public class TextDeltaTests : SerializationTest var textDelta = new TextDelta("Hello World!"); var actual = Serialize(textDelta); - + JsonAssert.Equal(_testJson, actual); } diff --git a/tests/AnthropicClient.Tests/Unit/Models/ToolCallTests.cs b/tests/AnthropicClient.Tests/Unit/Models/ToolCallTests.cs index 3d6e10a..a347cbe 100644 --- a/tests/AnthropicClient.Tests/Unit/Models/ToolCallTests.cs +++ b/tests/AnthropicClient.Tests/Unit/Models/ToolCallTests.cs @@ -135,7 +135,7 @@ public class ToolCallTests : SerializationTest [Fact] public async Task InvokeAsync_WhenCalledAndToolHasCustomParameterName_ItShouldReturnSuccessResult() { - var func = ([FunctionParameter("Person's Age", "Age")]int i) => i.ToString(); + var func = ([FunctionParameter("Person's Age", "Age")] int i) => i.ToString(); var anthropicFunction = new AnthropicFunction(func.Method, func.Target); var tool = new Tool("tool", "description", anthropicFunction); @@ -212,7 +212,7 @@ public class ToolCallTests : SerializationTest }"; var input = Deserialize>(personJson); - + var toolCall = new ToolCall(tool, new ToolUseContent { Input = input! }); var result = await toolCall.InvokeAsync(); diff --git a/tests/AnthropicClient.Tests/Unit/Models/ToolTests.cs b/tests/AnthropicClient.Tests/Unit/Models/ToolTests.cs index cb07982..b84a11e 100644 --- a/tests/AnthropicClient.Tests/Unit/Models/ToolTests.cs +++ b/tests/AnthropicClient.Tests/Unit/Models/ToolTests.cs @@ -12,7 +12,7 @@ public class ToolTests : SerializationTest public void Constructor_WhenGivenInvalidNameValue_ItShouldThrowArgumentException(string name) { var method = () => true; - var function = new AnthropicFunction(method.Method); + var function = new AnthropicFunction(method.Method); var action = () => new Tool(name, "description", function); action.Should().Throw(); @@ -25,7 +25,7 @@ public class ToolTests : SerializationTest public void Constructor_WhenGivenInvalidDescriptionValue_ItShouldThrowArgumentException(string description) { var method = () => true; - var function = new AnthropicFunction(method.Method); + var function = new AnthropicFunction(method.Method); var action = () => new Tool("name", description, function); action.Should().Throw(); @@ -43,7 +43,7 @@ public class ToolTests : SerializationTest public void Constructor_WhenCalled_ItShouldInitializeProperties() { var method = () => true; - var function = new AnthropicFunction(method.Method); + var function = new AnthropicFunction(method.Method); var tool = new Tool("test name", "description", function); var expectedSchema = new JsonObject() @@ -56,7 +56,7 @@ public class ToolTests : SerializationTest tool.Description.Should().Be("description"); tool.Function.Should().Be(function); tool.InputSchema.Should().BeEquivalentTo( - expectedSchema, + expectedSchema, t => t.IgnoringCyclicReferences() ); } @@ -65,7 +65,7 @@ public class ToolTests : SerializationTest public void Constructor_WhenCalledAndGivenNameIsMoreThanMaxLength_ItShouldTruncateName() { var method = () => true; - var function = new AnthropicFunction(method.Method); + var function = new AnthropicFunction(method.Method); var tool = new Tool(new string('a', 65), "description", function); tool.Name.Should().HaveLength(64); @@ -115,7 +115,7 @@ public class ToolTests : SerializationTest tool.Function.Method.Name.Should().Be(nameof(TestClass.TestStaticMethod)); tool.Function.Instance.Should().BeNull(); tool.InputSchema.Should().BeEquivalentTo( - expectedSchema, + expectedSchema, t => t.IgnoringCyclicReferences() ); } @@ -173,7 +173,7 @@ public class ToolTests : SerializationTest tool.Function.Method.Name.Should().Be(nameof(TestClass.TestInstanceMethod)); tool.Function.Instance.Should().Be(instance); tool.InputSchema.Should().BeEquivalentTo( - expectedSchema, + expectedSchema, t => t.IgnoringCyclicReferences() ); } @@ -202,7 +202,7 @@ public class ToolTests : SerializationTest tool.Description.Should().Be("description"); tool.Function.Method.Should().BeSameAs(func.Method); tool.InputSchema.Should().BeEquivalentTo( - expectedSchema, + expectedSchema, t => t.IgnoringCyclicReferences() ); } @@ -242,7 +242,7 @@ public class ToolTests : SerializationTest tool.Description.Should().Be("description"); tool.Function.Method.Should().BeSameAs(func.Method); tool.InputSchema.Should().BeEquivalentTo( - expectedSchema, + expectedSchema, t => t.IgnoringCyclicReferences() ); } diff --git a/tests/AnthropicClient.Tests/Unit/Utils/JsonSchemaGeneratorTests.cs b/tests/AnthropicClient.Tests/Unit/Utils/JsonSchemaGeneratorTests.cs index e4d839d..18b5074 100644 --- a/tests/AnthropicClient.Tests/Unit/Utils/JsonSchemaGeneratorTests.cs +++ b/tests/AnthropicClient.Tests/Unit/Utils/JsonSchemaGeneratorTests.cs @@ -13,7 +13,7 @@ public class JsonSchemaGeneratorTests }; var testMethod = () => true; var function = new AnthropicFunction(testMethod.Method); - + var schema = JsonSchemaGenerator.GenerateInputSchema(function); JsonAssert.Equal(expectedSchema, schema); @@ -38,7 +38,7 @@ public class JsonSchemaGeneratorTests var testMethod = (int age = 0) => age; var function = new AnthropicFunction(testMethod.Method); - + var schema = JsonSchemaGenerator.GenerateInputSchema(function); JsonAssert.Equal(expectedSchema, schema); @@ -66,7 +66,7 @@ public class JsonSchemaGeneratorTests var testMethod = (string name) => name; var function = new AnthropicFunction(testMethod.Method); - + var schema = JsonSchemaGenerator.GenerateInputSchema(function); JsonAssert.Equal(expectedSchema, schema); diff --git a/tests/AnthropicClient.Tests/Usings.cs b/tests/AnthropicClient.Tests/Usings.cs index cc90782..4b78fc5 100644 --- a/tests/AnthropicClient.Tests/Usings.cs +++ b/tests/AnthropicClient.Tests/Usings.cs @@ -1,9 +1,9 @@ -global using FluentAssertions; +global using System.Text.Json; +global using System.Text.Json.JsonDiffPatch.Xunit; global using AnthropicClient.Models; global using AnthropicClient.Utils; -global using System.Text.Json; -global using System.Text.Json.JsonDiffPatch.Xunit; +global using FluentAssertions; global using Microsoft.Extensions.Configuration; \ No newline at end of file