feat: create chat message
This commit is contained in:
@@ -0,0 +1,29 @@
|
|||||||
|
using System.Text.Json;
|
||||||
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
|
using AnthropicClient.Models;
|
||||||
|
|
||||||
|
namespace AnthropicClient.Json;
|
||||||
|
|
||||||
|
class ContentConverter : JsonConverter<Content>
|
||||||
|
{
|
||||||
|
public override Content Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
|
||||||
|
{
|
||||||
|
using var jsonDocument = JsonDocument.ParseValue(ref reader);
|
||||||
|
var root = jsonDocument.RootElement;
|
||||||
|
var type = root.GetProperty("type").GetString();
|
||||||
|
return type switch
|
||||||
|
{
|
||||||
|
ContentType.Text => JsonSerializer.Deserialize<TextContent>(root.GetRawText(), options)!,
|
||||||
|
ContentType.Image => JsonSerializer.Deserialize<ImageContent>(root.GetRawText(), options)!,
|
||||||
|
ContentType.ToolUse => JsonSerializer.Deserialize<ToolUseContent>(root.GetRawText(), options)!,
|
||||||
|
ContentType.ToolResult => JsonSerializer.Deserialize<ToolResultContent>(root.GetRawText(), options)!,
|
||||||
|
_ => throw new JsonException($"Unknown content type: {type}")
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Write(Utf8JsonWriter writer, Content value, JsonSerializerOptions options)
|
||||||
|
{
|
||||||
|
JsonSerializer.Serialize(writer, value, value.GetType(), options);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
using System.Text.Json;
|
||||||
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
|
using AnthropicClient.Models;
|
||||||
|
|
||||||
|
namespace AnthropicClient.Json;
|
||||||
|
|
||||||
|
class ErrorConverter : JsonConverter<Error>
|
||||||
|
{
|
||||||
|
public override Error Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
|
||||||
|
{
|
||||||
|
using var jsonDocument = JsonDocument.ParseValue(ref reader);
|
||||||
|
var root = jsonDocument.RootElement;
|
||||||
|
var type = root.GetProperty("type").GetString();
|
||||||
|
|
||||||
|
return type switch
|
||||||
|
{
|
||||||
|
ErrorType.InvalidRequestError => JsonSerializer.Deserialize<InvalidRequestError>(root.GetRawText(), options)!,
|
||||||
|
ErrorType.AuthenticationError => JsonSerializer.Deserialize<AuthenticationError>(root.GetRawText(), options)!,
|
||||||
|
ErrorType.PermissionError => JsonSerializer.Deserialize<PermissionError>(root.GetRawText(), options)!,
|
||||||
|
ErrorType.NotFoundError => JsonSerializer.Deserialize<NotFoundError>(root.GetRawText(), options)!,
|
||||||
|
ErrorType.RateLimitError => JsonSerializer.Deserialize<RateLimitError>(root.GetRawText(), options)!,
|
||||||
|
ErrorType.ApiError => JsonSerializer.Deserialize<ApiError>(root.GetRawText(), options)!,
|
||||||
|
ErrorType.OverloadedError => JsonSerializer.Deserialize<OverloadedError>(root.GetRawText(), options)!,
|
||||||
|
_ => throw new JsonException($"Unknown error type: {type}")
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Write(Utf8JsonWriter writer, Error value, JsonSerializerOptions options)
|
||||||
|
{
|
||||||
|
JsonSerializer.Serialize(writer, value, value.GetType(), options);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
using System.Text.Json;
|
||||||
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
|
namespace AnthropicClient.Json;
|
||||||
|
|
||||||
|
static class JsonSerializationOptions
|
||||||
|
{
|
||||||
|
public static JsonSerializerOptions DefaultOptions => new()
|
||||||
|
{
|
||||||
|
PropertyNameCaseInsensitive = true,
|
||||||
|
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
|
||||||
|
Converters =
|
||||||
|
{
|
||||||
|
new ContentConverter(),
|
||||||
|
new ToolChoiceConverter(),
|
||||||
|
new ErrorConverter(),
|
||||||
|
},
|
||||||
|
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
using System.Text.Json;
|
||||||
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
|
using AnthropicClient.Models;
|
||||||
|
|
||||||
|
namespace AnthropicClient.Json;
|
||||||
|
|
||||||
|
class ToolChoiceConverter : JsonConverter<ToolChoice>
|
||||||
|
{
|
||||||
|
public override ToolChoice Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
|
||||||
|
{
|
||||||
|
using var jsonDocument = JsonDocument.ParseValue(ref reader);
|
||||||
|
var root = jsonDocument.RootElement;
|
||||||
|
var type = root.GetProperty("type").GetString();
|
||||||
|
return type switch
|
||||||
|
{
|
||||||
|
ToolChoiceType.Auto => JsonSerializer.Deserialize<AutoToolChoice>(root.GetRawText(), options)!,
|
||||||
|
ToolChoiceType.Any => JsonSerializer.Deserialize<AnyToolChoice>(root.GetRawText(), options)!,
|
||||||
|
ToolChoiceType.Tool => JsonSerializer.Deserialize<SpecificToolChoice>(root.GetRawText(), options)!,
|
||||||
|
_ => throw new JsonException($"Unknown tool choice type: {type}")
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Write(Utf8JsonWriter writer, ToolChoice value, JsonSerializerOptions options)
|
||||||
|
{
|
||||||
|
JsonSerializer.Serialize(writer, value, value.GetType(), options);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
|
namespace AnthropicClient.Models;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Represents an error response from the Anthropic API.
|
||||||
|
/// </summary>
|
||||||
|
public class AnthropicError
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The type of the error.
|
||||||
|
/// </summary>
|
||||||
|
public string Type { get; init; } = "error";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The error object.
|
||||||
|
/// </summary>
|
||||||
|
public Error? Error { get; init; } = null;
|
||||||
|
|
||||||
|
[JsonConstructor]
|
||||||
|
internal AnthropicError()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="AnthropicError"/> class.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="error">The error as an <see cref="Error"/> object.</param>
|
||||||
|
/// <returns>A new instance of the <see cref="AnthropicError"/> class.</returns>
|
||||||
|
public AnthropicError(Error error)
|
||||||
|
{
|
||||||
|
Error = error;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
namespace AnthropicClient.Models;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Provides constants for the Anthropic models.
|
||||||
|
/// </summary>
|
||||||
|
public static class AnthropicModels
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The Claude-3 Opus model.
|
||||||
|
/// </summary>
|
||||||
|
public const string Claude3Opus = "claude-3-opus-20240229";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The Claude-3 Sonnet model.
|
||||||
|
/// </summary>
|
||||||
|
public const string Claude3Sonnet = "claude-3-sonnet-20240229";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The Claude-3.5 Sonnet model.
|
||||||
|
/// </summary>
|
||||||
|
public const string Claude35Sonnet = "claude-3-5-sonnet-20240620";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The Claude-3 Haiku model.
|
||||||
|
/// </summary>
|
||||||
|
public const string Claude3Haiku = "claude-3-haiku-20240307";
|
||||||
|
|
||||||
|
internal static bool IsValidModel(string modelId) => modelId is Claude3Opus or Claude3Sonnet or Claude35Sonnet or Claude3Haiku;
|
||||||
|
}
|
||||||
@@ -0,0 +1,66 @@
|
|||||||
|
using AnthropicClient.Models;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Represents the result of an Anthropic API operation.
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="T">The type of the result value.</typeparam>
|
||||||
|
public class AnthropicResult<T>
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The value of the result.
|
||||||
|
/// </summary>
|
||||||
|
public T Value { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The error of the result.
|
||||||
|
/// </summary>
|
||||||
|
public AnthropicError Error { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Indicates whether the operation was successful.
|
||||||
|
/// </summary>
|
||||||
|
public bool IsSuccess { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The request ID of the operation.
|
||||||
|
/// </summary>
|
||||||
|
public string RequestId { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="AnthropicResult{T}"/> class.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="value">The value of the result.</param>
|
||||||
|
/// <param name="error">The error of the result.</param>
|
||||||
|
/// <param name="isSuccess">Indicates whether the operation was successful.</param>
|
||||||
|
/// <param name="requestId">The request ID of the operation.</param>
|
||||||
|
/// <returns>A new instance of the <see cref="AnthropicResult{T}"/> class.</returns>
|
||||||
|
protected AnthropicResult(T value, AnthropicError error, bool isSuccess, string requestId)
|
||||||
|
{
|
||||||
|
Value = value;
|
||||||
|
Error = error;
|
||||||
|
IsSuccess = isSuccess;
|
||||||
|
RequestId = requestId;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a successful result.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="value">The value of the result.</param>
|
||||||
|
/// <param name="requestId">The request ID of the operation.</param>
|
||||||
|
/// <returns>A new instance of the <see cref="AnthropicResult{T}"/> class.</returns>
|
||||||
|
public static AnthropicResult<T> Success(T value, string requestId)
|
||||||
|
{
|
||||||
|
return new AnthropicResult<T>(value, null!, true, requestId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a failed result.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="error">The error of the result.</param>
|
||||||
|
/// <param name="requestId">The request ID of the operation.</param>
|
||||||
|
/// <returns>A new instance of the <see cref="AnthropicResult{T}"/> class.</returns>
|
||||||
|
public static AnthropicResult<T> Failure(AnthropicError error, string requestId)
|
||||||
|
{
|
||||||
|
return new AnthropicResult<T>(default!, error, false, requestId);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
namespace AnthropicClient.Models;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Represents the any tool choice mode.
|
||||||
|
/// </summary>
|
||||||
|
public class AnyToolChoice : ToolChoice
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="AnyToolChoice"/> class.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>A new instance of the <see cref="AnyToolChoice"/> class.</returns>
|
||||||
|
public AnyToolChoice() : base(ToolChoiceType.Any) { }
|
||||||
|
}
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
|
namespace AnthropicClient.Models;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Represents an api_error response from the Anthropic API.
|
||||||
|
/// </summary>
|
||||||
|
public class ApiError : Error
|
||||||
|
{
|
||||||
|
[JsonConstructor]
|
||||||
|
internal ApiError() : base(ErrorType.ApiError)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="ApiError"/> class.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="message">The error message.</param>
|
||||||
|
/// <returns>A new instance of the <see cref="ApiError"/> class.</returns>
|
||||||
|
public ApiError(string message) : base(ErrorType.ApiError)
|
||||||
|
{
|
||||||
|
Message = message;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
|
namespace AnthropicClient.Models;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Represents an authentication_error response from the Anthropic API.
|
||||||
|
/// </summary>
|
||||||
|
public class AuthenticationError : Error
|
||||||
|
{
|
||||||
|
[JsonConstructor]
|
||||||
|
internal AuthenticationError() : base(ErrorType.AuthenticationError)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="AuthenticationError"/> class.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="message">The error message.</param>
|
||||||
|
/// <returns>A new instance of the <see cref="AuthenticationError"/> class.</returns>
|
||||||
|
public AuthenticationError(string message) : base(ErrorType.AuthenticationError)
|
||||||
|
{
|
||||||
|
Message = message;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
namespace AnthropicClient.Models;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Represents the auto tool choice mode.
|
||||||
|
/// </summary>
|
||||||
|
public class AutoToolChoice : ToolChoice
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="AutoToolChoice"/> class.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>A new instance of the <see cref="AutoToolChoice"/> class.</returns>
|
||||||
|
public AutoToolChoice() : base(ToolChoiceType.Auto) { }
|
||||||
|
}
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
|
using AnthropicClient.Utils;
|
||||||
|
|
||||||
|
namespace AnthropicClient.Models;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Represents a chat message.
|
||||||
|
/// </summary>
|
||||||
|
public class ChatMessage
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the role of the message.
|
||||||
|
/// </summary>
|
||||||
|
public string Role { get; init; } = string.Empty;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the contents of the message.
|
||||||
|
/// </summary>
|
||||||
|
public List<Content> Content { get; init; } = [];
|
||||||
|
|
||||||
|
[JsonConstructor]
|
||||||
|
internal ChatMessage()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="ChatMessage"/> class.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="role">The role of the message.</param>
|
||||||
|
/// <param name="content">The contents of the message.</param>
|
||||||
|
/// <exception cref="ArgumentException">Thrown when the role is invalid.</exception>
|
||||||
|
/// <exception cref="ArgumentNullException">Thrown when the role or content is null.</exception>
|
||||||
|
/// <returns>A new instance of the <see cref="ChatMessage"/> class.</returns>
|
||||||
|
public ChatMessage(string role, List<Content> content)
|
||||||
|
{
|
||||||
|
ArgumentValidator.ThrowIfNull(role, nameof(role));
|
||||||
|
ArgumentValidator.ThrowIfNull(content, nameof(content));
|
||||||
|
|
||||||
|
if (MessageRole.IsValidRole(role) is false)
|
||||||
|
{
|
||||||
|
throw new ArgumentException($"Invalid role: {role}");
|
||||||
|
}
|
||||||
|
|
||||||
|
Role = role;
|
||||||
|
Content = content;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,139 @@
|
|||||||
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
|
using AnthropicClient.Utils;
|
||||||
|
|
||||||
|
namespace AnthropicClient.Models;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Represents a chat message request.
|
||||||
|
/// </summary>
|
||||||
|
public class ChatMessageRequest : MessageRequest
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the model ID to use for the request.
|
||||||
|
/// </summary>
|
||||||
|
public string Model { get; init; } = string.Empty;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the system ID to use for the request.
|
||||||
|
/// </summary>
|
||||||
|
public string? System { get; init; } = null;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the messages to send to the model.
|
||||||
|
/// </summary>
|
||||||
|
public List<ChatMessage> Messages { get; init; } = [];
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the maximum number of tokens to generate.
|
||||||
|
/// </summary>
|
||||||
|
[JsonPropertyName("max_tokens")]
|
||||||
|
public int MaxTokens { get; init; } = 1024;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the metadata to include with the request.
|
||||||
|
/// </summary>
|
||||||
|
public Dictionary<string, object>? Metadata { get; init; } = null;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the prompt stop sequences.
|
||||||
|
/// </summary>
|
||||||
|
[JsonPropertyName("stop_sequences")]
|
||||||
|
public List<string> StopSequences { get; init; } = [];
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the temperature to use for the request.
|
||||||
|
/// </summary>
|
||||||
|
public decimal Temperature { get; init; } = 0.0m;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the top-K value to use for the request.
|
||||||
|
/// </summary>
|
||||||
|
public int? TopK { get; init; } = null;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the top-P value to use for the request.
|
||||||
|
/// </summary>
|
||||||
|
public decimal? TopP { get; init; } = null;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the tool choice mode to use for the request.
|
||||||
|
/// </summary>
|
||||||
|
[JsonPropertyName("tool_choice")]
|
||||||
|
public ToolChoice? ToolChoice { get; init; } = null;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the tools to use for the request.
|
||||||
|
/// </summary>
|
||||||
|
public List<Tool>? Tools { get; init; } = null;
|
||||||
|
|
||||||
|
[JsonConstructor]
|
||||||
|
internal ChatMessageRequest() : base(false) { }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="ChatMessageRequest"/> class.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="model">The model ID to use for the request.</param>
|
||||||
|
/// <param name="messages">The messages to send to the model.</param>
|
||||||
|
/// <param name="maxTokens">The maximum number of tokens to generate.</param>
|
||||||
|
/// <param name="system">The system ID to use for the request.</param>
|
||||||
|
/// <param name="metadata">The metadata to include with the request.</param>
|
||||||
|
/// <param name="temperature">The temperature to use for the request.</param>
|
||||||
|
/// <param name="topK">The top-K value to use for the request.</param>
|
||||||
|
/// <param name="topP">The top-P value to use for the request.</param>
|
||||||
|
/// <param name="toolChoice">The tool choice mode to use for the request.</param>
|
||||||
|
/// <param name="tools">The tools to use for the request.</param>
|
||||||
|
/// <exception cref="ArgumentException">Thrown when the model ID is invalid.</exception>
|
||||||
|
/// <exception cref="ArgumentNullException">Thrown when the model or messages is null.</exception>
|
||||||
|
/// <exception cref="ArgumentException">Thrown when the messages contain no messages.</exception>
|
||||||
|
/// <exception cref="ArgumentException">Thrown when the max tokens is less than one.</exception>
|
||||||
|
/// <exception cref="ArgumentException">Thrown when the temperature is less than zero or greater than one.</exception>
|
||||||
|
/// <returns>A new instance of the <see cref="ChatMessageRequest"/> class.</returns>
|
||||||
|
public ChatMessageRequest(
|
||||||
|
string model,
|
||||||
|
List<ChatMessage> messages,
|
||||||
|
int maxTokens = 1024,
|
||||||
|
string? system = null,
|
||||||
|
Dictionary<string, object>? metadata = null,
|
||||||
|
decimal temperature = 0.0m,
|
||||||
|
int? topK = null,
|
||||||
|
decimal? topP = null,
|
||||||
|
ToolChoice? toolChoice = null,
|
||||||
|
List<Tool>? tools = null
|
||||||
|
) : base(false)
|
||||||
|
{
|
||||||
|
ArgumentValidator.ThrowIfNull(model, nameof(model));
|
||||||
|
ArgumentValidator.ThrowIfNull(messages, nameof(messages));
|
||||||
|
|
||||||
|
if (AnthropicModels.IsValidModel(model) is false)
|
||||||
|
{
|
||||||
|
throw new ArgumentException($"Invalid model ID: {model}");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (messages.Count < 1)
|
||||||
|
{
|
||||||
|
throw new ArgumentException("Messages must contain at least one message");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (maxTokens < 1)
|
||||||
|
{
|
||||||
|
throw new ArgumentException($"Invalid max tokens: {maxTokens}");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (temperature < 0.0m || temperature > 1.0m)
|
||||||
|
{
|
||||||
|
throw new ArgumentException($"Invalid temperature: {temperature}");
|
||||||
|
}
|
||||||
|
|
||||||
|
Model = model;
|
||||||
|
Messages = messages;
|
||||||
|
MaxTokens = maxTokens;
|
||||||
|
System = system;
|
||||||
|
Metadata = metadata;
|
||||||
|
Temperature = temperature;
|
||||||
|
TopK = topK;
|
||||||
|
TopP = topP;
|
||||||
|
ToolChoice = toolChoice;
|
||||||
|
Tools = tools;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
|
namespace AnthropicClient.Models;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Represents a chat response.
|
||||||
|
/// </summary>
|
||||||
|
public class ChatResponse
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the ID of the chat response.
|
||||||
|
/// </summary>
|
||||||
|
public string Id { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the model used for the chat response.
|
||||||
|
/// </summary>
|
||||||
|
public string Model { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the role of the chat response.
|
||||||
|
/// </summary>
|
||||||
|
public string Role { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the stop reason of the chat response.
|
||||||
|
/// </summary>
|
||||||
|
[JsonPropertyName("stop_reason")]
|
||||||
|
public string StopReason { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the stop sequence of the chat response.
|
||||||
|
/// </summary>
|
||||||
|
[JsonPropertyName("stop_sequence")]
|
||||||
|
public string StopSequence { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the type of the chat response.
|
||||||
|
/// </summary>
|
||||||
|
public string Type { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the usage of the chat response.
|
||||||
|
/// </summary>
|
||||||
|
public ChatUsage Usage { get; set; } = new();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the contents of the chat response.
|
||||||
|
/// </summary>
|
||||||
|
public List<Content> Content { get; set; } = [];
|
||||||
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
|
namespace AnthropicClient.Models;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Represents the usage of a chat response.
|
||||||
|
/// </summary>
|
||||||
|
public class ChatUsage
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the number of input tokens used.
|
||||||
|
/// </summary>
|
||||||
|
[JsonPropertyName("input_tokens")]
|
||||||
|
public int InputTokens { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the number of output tokens used.
|
||||||
|
/// </summary>
|
||||||
|
[JsonPropertyName("output_tokens")]
|
||||||
|
public int OutputTokens { get; set; }
|
||||||
|
}
|
||||||
@@ -0,0 +1,83 @@
|
|||||||
|
using System.Net.Http.Headers;
|
||||||
|
using System.Text;
|
||||||
|
using System.Text.Json;
|
||||||
|
|
||||||
|
using AnthropicClient.Json;
|
||||||
|
using AnthropicClient.Utils;
|
||||||
|
|
||||||
|
namespace AnthropicClient.Models;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Represents a client for interacting with the Anthropic API.
|
||||||
|
/// </summary>
|
||||||
|
public interface IClient
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a chat message asynchronously.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="request">The chat message request to create.</param>
|
||||||
|
/// <returns>A task that represents the asynchronous operation. The task result contains the chat response as an <see cref="AnthropicResult{T}"/>.</returns>
|
||||||
|
Task<AnthropicResult<ChatResponse>> CreateChatMessageAsync(ChatMessageRequest request);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc cref="IClient"/>
|
||||||
|
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<string, string> _defaultHeaders = new()
|
||||||
|
{
|
||||||
|
{ "anthropic-version", "2023-06-01" },
|
||||||
|
};
|
||||||
|
private readonly HttpClient _httpClient;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="Client"/> class.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="apiKey">The API key to use for the client.</param>
|
||||||
|
/// <param name="httpClient">The HTTP client to use for the client.</param>
|
||||||
|
/// <exception cref="ArgumentNullException">Thrown when the API key or HTTP client is null.</exception>
|
||||||
|
/// <returns>A new instance of the <see cref="Client"/> class.</returns>
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public async Task<AnthropicResult<ChatResponse>> CreateChatMessageAsync(ChatMessageRequest request)
|
||||||
|
{
|
||||||
|
var requestContent = new StringContent(Serialize(request), Encoding.UTF8, JsonContentType);
|
||||||
|
var response = await _httpClient.PostAsync(MessagesEndpoint, requestContent);
|
||||||
|
var requestId = GetRequestId(response);
|
||||||
|
var responseContent = await response.Content.ReadAsStringAsync();
|
||||||
|
|
||||||
|
if (response.IsSuccessStatusCode is false)
|
||||||
|
{
|
||||||
|
var error = Deserialize<AnthropicError>(responseContent) ?? new AnthropicError();
|
||||||
|
return AnthropicResult<ChatResponse>.Failure(error, requestId);
|
||||||
|
}
|
||||||
|
|
||||||
|
var chatResponse = Deserialize<ChatResponse>(responseContent) ?? new ChatResponse();
|
||||||
|
return AnthropicResult<ChatResponse>.Success(chatResponse, requestId);
|
||||||
|
}
|
||||||
|
|
||||||
|
private string Serialize<T>(T obj) => JsonSerializer.Serialize(obj, JsonSerializationOptions.DefaultOptions);
|
||||||
|
private T? Deserialize<T>(string json) => JsonSerializer.Deserialize<T>(json, JsonSerializationOptions.DefaultOptions);
|
||||||
|
private string GetRequestId(HttpResponseMessage response) => response.Headers.GetValues(RequestIdHeader).FirstOrDefault() ?? string.Empty;
|
||||||
|
}
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
|
namespace AnthropicClient.Models;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Represents part of the content of a chat message.
|
||||||
|
/// </summary>
|
||||||
|
public abstract class Content
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the type of the content.
|
||||||
|
/// </summary>
|
||||||
|
public string Type { get; init; } = string.Empty;
|
||||||
|
|
||||||
|
[JsonConstructor]
|
||||||
|
internal Content()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="Content"/> class.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="type">The type of the content.</param>
|
||||||
|
/// <returns>A new instance of the <see cref="Content"/> class.</returns>
|
||||||
|
protected Content(string type)
|
||||||
|
{
|
||||||
|
Type = type;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
namespace AnthropicClient.Models;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Represents the content type.
|
||||||
|
/// </summary>
|
||||||
|
public static class ContentType
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Represents the text content type.
|
||||||
|
/// </summary>
|
||||||
|
public const string Text = "text";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Represents the image content type.
|
||||||
|
/// </summary>
|
||||||
|
public const string Image = "image";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Represents the tool use content type.
|
||||||
|
/// </summary>
|
||||||
|
public const string ToolUse = "tool_use";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Represents the tool result content type.
|
||||||
|
/// </summary>
|
||||||
|
public const string ToolResult = "tool_result";
|
||||||
|
}
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
namespace AnthropicClient.Models;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Represents an error.
|
||||||
|
/// </summary>
|
||||||
|
public abstract class Error
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the type of the error.
|
||||||
|
/// </summary>
|
||||||
|
public string Type { get; init; } = string.Empty;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the message of the error.
|
||||||
|
/// </summary>
|
||||||
|
public string Message { get; init; } = string.Empty;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="Error"/> class.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="type">The type of the error.</param>
|
||||||
|
/// <returns>A new instance of the <see cref="Error"/> class.</returns>
|
||||||
|
protected Error(string type)
|
||||||
|
{
|
||||||
|
Type = type;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
namespace AnthropicClient.Models;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Represents the error type.
|
||||||
|
/// </summary>
|
||||||
|
public class ErrorType
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Represents the invalid_request error type.
|
||||||
|
/// </summary>
|
||||||
|
public const string InvalidRequestError = "invalid_request_error";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Represents the authentication_error type.
|
||||||
|
/// </summary>
|
||||||
|
public const string AuthenticationError = "authentication_error";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Represents the permission_error type.
|
||||||
|
/// </summary>
|
||||||
|
public const string PermissionError = "permission_error";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Represents the not_found_error type.
|
||||||
|
/// </summary>
|
||||||
|
public const string NotFoundError = "not_found_error";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Represents the rate_limit_error type.
|
||||||
|
/// </summary>
|
||||||
|
public const string RateLimitError = "rate_limit_error";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Represents the api_error type.
|
||||||
|
/// </summary>
|
||||||
|
public const string ApiError = "api_error";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Represents the overloaded_error type.
|
||||||
|
/// </summary>
|
||||||
|
public const string OverloadedError = "overloaded_error";
|
||||||
|
}
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
|
using AnthropicClient.Utils;
|
||||||
|
|
||||||
|
namespace AnthropicClient.Models;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Represents image content that is part of a chat message.
|
||||||
|
/// </summary>
|
||||||
|
public class ImageContent : Content
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the source of the image.
|
||||||
|
/// </summary>
|
||||||
|
public ImageSource Source { get; init; } = new();
|
||||||
|
|
||||||
|
[JsonConstructor]
|
||||||
|
internal ImageContent()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="ImageContent"/> class.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="mediaType">The media type of the image.</param>
|
||||||
|
/// <param name="data">The data of the image.</param>
|
||||||
|
/// <exception cref="ArgumentNullException">Thrown when the media type or data is null.</exception>
|
||||||
|
/// <returns>A new instance of the <see cref="ImageContent"/> class.</returns>
|
||||||
|
public ImageContent(string mediaType, string data) : base(ContentType.Image)
|
||||||
|
{
|
||||||
|
ArgumentValidator.ThrowIfNull(mediaType, nameof(mediaType));
|
||||||
|
ArgumentValidator.ThrowIfNull(data, nameof(data));
|
||||||
|
|
||||||
|
Source = new(mediaType, data);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
|
using AnthropicClient.Utils;
|
||||||
|
|
||||||
|
namespace AnthropicClient.Models;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Represents an image source.
|
||||||
|
/// </summary>
|
||||||
|
public class ImageSource
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the media type of the image.
|
||||||
|
/// </summary>
|
||||||
|
[JsonPropertyName("media_type")]
|
||||||
|
public string MediaType { get; init; } = string.Empty;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the data of the image.
|
||||||
|
/// </summary>
|
||||||
|
public string Data { get; init; } = string.Empty;
|
||||||
|
|
||||||
|
[JsonConstructor]
|
||||||
|
internal ImageSource()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="ImageSource"/> class.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="mediaType">The media type of the image.</param>
|
||||||
|
/// <param name="data">The data of the image.</param>
|
||||||
|
/// <exception cref="ArgumentException">Thrown when the media type is invalid.</exception>
|
||||||
|
/// <exception cref="ArgumentNullException">Thrown when the media type or data is null.</exception>
|
||||||
|
/// <returns>A new instance of the <see cref="ImageSource"/> class.</returns>
|
||||||
|
public ImageSource(string mediaType, string data)
|
||||||
|
{
|
||||||
|
ArgumentValidator.ThrowIfNull(mediaType, nameof(mediaType));
|
||||||
|
|
||||||
|
if (ImageType.IsValidImageType(mediaType) is false)
|
||||||
|
{
|
||||||
|
throw new ArgumentException($"Invalid media type: {mediaType}");
|
||||||
|
}
|
||||||
|
|
||||||
|
ArgumentValidator.ThrowIfNull(data, nameof(data));
|
||||||
|
|
||||||
|
MediaType = mediaType;
|
||||||
|
Data = data;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
namespace AnthropicClient.Models;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Represents the image type.
|
||||||
|
/// </summary>
|
||||||
|
public static class ImageType
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Represents the JPEG image type.
|
||||||
|
/// </summary>
|
||||||
|
public const string Jpg = "image/jpeg";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Represents the PNG image type.
|
||||||
|
/// </summary>
|
||||||
|
public const string Png = "image/png";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Represents the GIF image type.
|
||||||
|
/// </summary>
|
||||||
|
public const string Gif = "image/gif";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Represents the WebP image type.
|
||||||
|
/// </summary>
|
||||||
|
public const string Webp = "image/webp";
|
||||||
|
|
||||||
|
internal static bool IsValidImageType(string imageType) => imageType is Jpg or Png or Gif or Webp;
|
||||||
|
}
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
|
using AnthropicClient.Utils;
|
||||||
|
|
||||||
|
namespace AnthropicClient.Models;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Represents an input property.
|
||||||
|
/// </summary>
|
||||||
|
public class InputProperty
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the type of the input property.
|
||||||
|
/// </summary>
|
||||||
|
public string Type { get; init; } = string.Empty;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the description of the input property.
|
||||||
|
/// </summary>
|
||||||
|
public string Description { get; init; } = string.Empty;
|
||||||
|
|
||||||
|
[JsonConstructor]
|
||||||
|
internal InputProperty()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="InputProperty"/> class.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="type">The type of the input property.</param>
|
||||||
|
/// <param name="description">The description of the input property.</param>
|
||||||
|
/// <exception cref="ArgumentNullException">Thrown when the type or description is null.</exception>
|
||||||
|
/// <returns>A new instance of the <see cref="InputProperty"/> class.</returns>
|
||||||
|
public InputProperty(string type, string description)
|
||||||
|
{
|
||||||
|
ArgumentValidator.ThrowIfNull(type, nameof(type));
|
||||||
|
ArgumentValidator.ThrowIfNull(description, nameof(description));
|
||||||
|
|
||||||
|
Type = type;
|
||||||
|
Description = description;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,53 @@
|
|||||||
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
|
using AnthropicClient.Utils;
|
||||||
|
|
||||||
|
namespace AnthropicClient.Models;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Represents an input schema.
|
||||||
|
/// </summary>
|
||||||
|
public class InputSchema
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the type of the input schema.
|
||||||
|
/// </summary>
|
||||||
|
public string Type { get; init; } = "object";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the properties of the input schema.
|
||||||
|
/// </summary>
|
||||||
|
public Dictionary<string, InputProperty> Properties { get; init; } = [];
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the required properties of the input schema.
|
||||||
|
/// </summary>
|
||||||
|
public List<string> Required { get; init; } = [];
|
||||||
|
|
||||||
|
[JsonConstructor]
|
||||||
|
internal InputSchema()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="InputSchema"/> class.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="properties">The properties of the input schema.</param>
|
||||||
|
/// <param name="required">The required properties of the input schema.</param>
|
||||||
|
/// <exception cref="ArgumentNullException">Thrown when the properties or required is null.</exception>
|
||||||
|
/// <exception cref="ArgumentException">Thrown when the required properties are not present in the properties dictionary.</exception>
|
||||||
|
/// <returns>A new instance of the <see cref="InputSchema"/> class.</returns>
|
||||||
|
public InputSchema(Dictionary<string, InputProperty> properties, List<string> required)
|
||||||
|
{
|
||||||
|
ArgumentValidator.ThrowIfNull(properties, nameof(properties));
|
||||||
|
ArgumentValidator.ThrowIfNull(required, nameof(required));
|
||||||
|
|
||||||
|
if (required.Any(r => properties.ContainsKey(r) is false))
|
||||||
|
{
|
||||||
|
throw new ArgumentException("Required properties must be present in the properties dictionary.");
|
||||||
|
}
|
||||||
|
|
||||||
|
Properties = properties;
|
||||||
|
Required = required;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
|
namespace AnthropicClient.Models;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Represents an invalid_request error.
|
||||||
|
/// </summary>
|
||||||
|
public class InvalidRequestError : Error
|
||||||
|
{
|
||||||
|
[JsonConstructor]
|
||||||
|
internal InvalidRequestError() : base(ErrorType.InvalidRequestError)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="InvalidRequestError"/> class.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="message">The message of the error.</param>
|
||||||
|
/// <returns>A new instance of the <see cref="InvalidRequestError"/> class.</returns>
|
||||||
|
public InvalidRequestError(string message) : base(ErrorType.InvalidRequestError)
|
||||||
|
{
|
||||||
|
Message = message;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
using System.ComponentModel;
|
||||||
|
|
||||||
|
namespace System.Runtime.CompilerServices
|
||||||
|
{
|
||||||
|
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||||
|
class IsExternalInit { }
|
||||||
|
}
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
namespace AnthropicClient.Models;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Represents a message request.
|
||||||
|
/// </summary>
|
||||||
|
public abstract class MessageRequest
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a value indicating whether the message should be streamed.
|
||||||
|
/// </summary>
|
||||||
|
public bool Stream { get; init; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="MessageRequest"/> class.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="stream">A value indicating whether the message should be streamed.</param>
|
||||||
|
/// <returns>A new instance of the <see cref="MessageRequest"/> class.</returns>
|
||||||
|
public MessageRequest(bool stream = false)
|
||||||
|
{
|
||||||
|
Stream = stream;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
namespace AnthropicClient.Models;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Represents the message role.
|
||||||
|
/// </summary>
|
||||||
|
public static class MessageRole
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Represents the user role.
|
||||||
|
/// </summary>
|
||||||
|
public const string User = "user";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Represents the assistant role.
|
||||||
|
/// </summary>
|
||||||
|
public const string Assistant = "assistant";
|
||||||
|
|
||||||
|
internal static bool IsValidRole(string role) => role is User or Assistant;
|
||||||
|
}
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
|
namespace AnthropicClient.Models;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Represents a not_found error.
|
||||||
|
/// </summary>
|
||||||
|
public class NotFoundError : Error
|
||||||
|
{
|
||||||
|
[JsonConstructor]
|
||||||
|
internal NotFoundError() : base(ErrorType.NotFoundError)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="NotFoundError"/> class.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="message">The message of the error.</param>
|
||||||
|
/// <returns>A new instance of the <see cref="NotFoundError"/> class.</returns>
|
||||||
|
public NotFoundError(string message) : base(ErrorType.NotFoundError)
|
||||||
|
{
|
||||||
|
Message = message;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
|
namespace AnthropicClient.Models;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Represents an overloaded error.
|
||||||
|
/// </summary>
|
||||||
|
public class OverloadedError : Error
|
||||||
|
{
|
||||||
|
[JsonConstructor]
|
||||||
|
internal OverloadedError() : base(ErrorType.OverloadedError)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="OverloadedError"/> class.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="message">The message of the error.</param>
|
||||||
|
/// <returns>A new instance of the <see cref="OverloadedError"/> class.</returns>
|
||||||
|
public OverloadedError(string message) : base(ErrorType.OverloadedError)
|
||||||
|
{
|
||||||
|
Message = message;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
|
namespace AnthropicClient.Models;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Represents a permission error.
|
||||||
|
/// </summary>
|
||||||
|
public class PermissionError : Error
|
||||||
|
{
|
||||||
|
[JsonConstructor]
|
||||||
|
internal PermissionError() : base(ErrorType.PermissionError)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="PermissionError"/> class.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="message">The message of the error.</param>
|
||||||
|
/// <returns>A new instance of the <see cref="PermissionError"/> class.</returns>
|
||||||
|
public PermissionError(string message) : base(ErrorType.PermissionError)
|
||||||
|
{
|
||||||
|
Message = message;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
|
namespace AnthropicClient.Models;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Represents a rate_limit error.
|
||||||
|
/// </summary>
|
||||||
|
public class RateLimitError : Error
|
||||||
|
{
|
||||||
|
[JsonConstructor]
|
||||||
|
internal RateLimitError() : base(ErrorType.RateLimitError)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="RateLimitError"/> class.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="message">The message of the error.</param>
|
||||||
|
/// <returns>A new instance of the <see cref="RateLimitError"/> class.</returns>
|
||||||
|
public RateLimitError(string message) : base(ErrorType.RateLimitError)
|
||||||
|
{
|
||||||
|
Message = message;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
|
using AnthropicClient.Utils;
|
||||||
|
|
||||||
|
namespace AnthropicClient.Models;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Represents the specific tool choice mode.
|
||||||
|
/// </summary>
|
||||||
|
public class SpecificToolChoice : ToolChoice
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the name of the tool.
|
||||||
|
/// </summary>
|
||||||
|
public string Name { get; init; } = string.Empty;
|
||||||
|
|
||||||
|
[JsonConstructor]
|
||||||
|
internal SpecificToolChoice() : base(ToolChoiceType.Tool)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="SpecificToolChoice"/> class.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="name">The name of the tool.</param>
|
||||||
|
/// <exception cref="ArgumentNullException">Thrown when the name is null.</exception>
|
||||||
|
/// <returns>A new instance of the <see cref="SpecificToolChoice"/> class.</returns>
|
||||||
|
public SpecificToolChoice(string name) : base(ToolChoiceType.Tool)
|
||||||
|
{
|
||||||
|
ArgumentValidator.ThrowIfNull(name, nameof(name));
|
||||||
|
|
||||||
|
Name = name;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
namespace AnthropicClient.Models;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Represents the stop reason type.
|
||||||
|
/// </summary>
|
||||||
|
public static class StopReasonType
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Represents the end_turn stop reason.
|
||||||
|
/// </summary>
|
||||||
|
public const string EndTurn = "end_turn";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Represents the max_tokens stop reason.
|
||||||
|
/// </summary>
|
||||||
|
public const string MaxTokens = "max_tokens";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Represents the stop_sequence stop reason.
|
||||||
|
/// </summary>
|
||||||
|
public const string StopSequence = "stop_sequence";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Represents the tool_use stop reason.
|
||||||
|
/// </summary>
|
||||||
|
public const string ToolUse = "tool_use";
|
||||||
|
}
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
|
using AnthropicClient.Utils;
|
||||||
|
|
||||||
|
namespace AnthropicClient.Models;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Represents text content that is part of a chat message.
|
||||||
|
/// </summary>
|
||||||
|
public class TextContent : Content
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the text of the content.
|
||||||
|
/// </summary>
|
||||||
|
public string Text { get; init; } = string.Empty;
|
||||||
|
|
||||||
|
[JsonConstructor]
|
||||||
|
internal TextContent() : base(ContentType.Text)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="TextContent"/> class.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="text">The text of the content.</param>
|
||||||
|
/// <exception cref="ArgumentNullException">Thrown when the text is null.</exception>
|
||||||
|
/// <returns>A new instance of the <see cref="TextContent"/> class.</returns>
|
||||||
|
public TextContent(string text) : base(ContentType.Text)
|
||||||
|
{
|
||||||
|
ArgumentValidator.ThrowIfNull(text, nameof(text));
|
||||||
|
|
||||||
|
Text = text;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
|
using AnthropicClient.Utils;
|
||||||
|
|
||||||
|
namespace AnthropicClient.Models;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Represents a tool that can be used in the chat.
|
||||||
|
/// </summary>
|
||||||
|
public class Tool
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the name of the tool.
|
||||||
|
/// </summary>
|
||||||
|
public string Name { get; init; } = string.Empty;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the description of the tool.
|
||||||
|
/// </summary>
|
||||||
|
public string Description { get; init; } = string.Empty;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the input schema of the tool.
|
||||||
|
/// </summary>
|
||||||
|
[JsonPropertyName("input_schema")]
|
||||||
|
public InputSchema InputSchema { get; init; } = new();
|
||||||
|
|
||||||
|
[JsonConstructor]
|
||||||
|
internal Tool()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="Tool"/> class.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="name">The name of the tool.</param>
|
||||||
|
/// <param name="description">The description of the tool.</param>
|
||||||
|
/// <param name="inputSchema">The input schema of the tool.</param>
|
||||||
|
/// <exception cref="ArgumentNullException">Thrown when the name, description, or input schema is null.</exception>
|
||||||
|
/// <returns>A new instance of the <see cref="Tool"/> class.</returns>
|
||||||
|
public Tool(string name, string description, InputSchema inputSchema)
|
||||||
|
{
|
||||||
|
ArgumentValidator.ThrowIfNull(name, nameof(name));
|
||||||
|
ArgumentValidator.ThrowIfNull(description, nameof(description));
|
||||||
|
ArgumentValidator.ThrowIfNull(inputSchema, nameof(inputSchema));
|
||||||
|
|
||||||
|
Name = name;
|
||||||
|
Description = description;
|
||||||
|
InputSchema = inputSchema;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
namespace AnthropicClient.Models;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Represents a tool choice mode.
|
||||||
|
/// </summary>
|
||||||
|
public abstract class ToolChoice
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the type of the tool choice.
|
||||||
|
/// </summary>
|
||||||
|
public string Type { get; init; } = string.Empty;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="ToolChoice"/> class.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="type">The type of the tool choice.</param>
|
||||||
|
/// <returns>A new instance of the <see cref="ToolChoice"/> class.</returns>
|
||||||
|
protected ToolChoice(string type)
|
||||||
|
{
|
||||||
|
Type = type;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
namespace AnthropicClient.Models;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Represents the tool choice type.
|
||||||
|
/// </summary>
|
||||||
|
public class ToolChoiceType
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Represents the auto tool choice type.
|
||||||
|
/// </summary>
|
||||||
|
public const string Auto = "auto";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Represents the any tool choice type.
|
||||||
|
/// </summary>
|
||||||
|
public const string Any = "any";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Represents the specific tool choice type.
|
||||||
|
/// </summary>
|
||||||
|
public const string Tool = "tool";
|
||||||
|
|
||||||
|
internal static bool IsValidType(string type) => type is Auto or Any or Tool;
|
||||||
|
}
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
|
using AnthropicClient.Utils;
|
||||||
|
|
||||||
|
namespace AnthropicClient.Models;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Represents tool result content that is part of a chat message.
|
||||||
|
/// </summary>
|
||||||
|
public class ToolResultContent : Content
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the tool use ID of the content.
|
||||||
|
/// </summary>
|
||||||
|
[JsonPropertyName("tool_use_id")]
|
||||||
|
public string ToolUseId { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the content of the tool result.
|
||||||
|
/// </summary>
|
||||||
|
public string Content { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
[JsonConstructor]
|
||||||
|
internal ToolResultContent() : base(ContentType.ToolResult) { }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="ToolResultContent"/> class.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="toolUseId">The tool use ID of the content.</param>
|
||||||
|
/// <param name="content">The content of the tool result.</param>
|
||||||
|
/// <exception cref="ArgumentNullException">Thrown when the tool use ID or content is null.</exception>
|
||||||
|
/// <returns>A new instance of the <see cref="ToolResultContent"/> class.</returns>
|
||||||
|
public ToolResultContent(string toolUseId, string content) : base(ContentType.ToolResult)
|
||||||
|
{
|
||||||
|
ArgumentValidator.ThrowIfNull(toolUseId, nameof(toolUseId));
|
||||||
|
ArgumentValidator.ThrowIfNull(content, nameof(content));
|
||||||
|
|
||||||
|
ToolUseId = toolUseId;
|
||||||
|
Content = content;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
namespace AnthropicClient.Models;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Represents tool use content that is part of a message.
|
||||||
|
/// </summary>
|
||||||
|
public class ToolUseContent : Content
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the ID of the tool use.
|
||||||
|
/// </summary>
|
||||||
|
public string Id { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the name of the tool.
|
||||||
|
/// </summary>
|
||||||
|
public string Name { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the input of the tool.
|
||||||
|
/// </summary>
|
||||||
|
public Dictionary<string, object?> Input { get; set; } = [];
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="ToolUseContent"/> class.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>A new instance of the <see cref="ToolUseContent"/> class.</returns>
|
||||||
|
public ToolUseContent() : base(ContentType.ToolUse)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
namespace AnthropicClient.Utils;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Provides methods to validate arguments.
|
||||||
|
/// </summary>
|
||||||
|
public static class ArgumentValidator
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Throws an <see cref="ArgumentNullException"/> if the value is null.
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="T">The type of the value.</typeparam>
|
||||||
|
/// <param name="value">The value to check.</param>
|
||||||
|
/// <param name="name">The name of the value.</param>
|
||||||
|
/// <exception cref="ArgumentNullException">Thrown when the value is null.</exception>
|
||||||
|
/// <returns>Nothing.</returns>
|
||||||
|
public static void ThrowIfNull<T>(T? value, string name)
|
||||||
|
{
|
||||||
|
if (value is null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
namespace AnthropicClient.Tests.EndToEnd;
|
||||||
|
|
||||||
|
public class ClientTests(
|
||||||
|
HttpClientFixture httpClientFixture,
|
||||||
|
ConfigurationFixture configFixture
|
||||||
|
) : EndToEndTest(httpClientFixture, configFixture)
|
||||||
|
{
|
||||||
|
[Fact]
|
||||||
|
public async Task CreateChatMessage_WhenCalled_ShouldReturnChatResponse()
|
||||||
|
{
|
||||||
|
var request = new ChatMessageRequest(
|
||||||
|
model: AnthropicModels.Claude3Haiku,
|
||||||
|
messages: [new(MessageRole.User, [new TextContent("Hello!")])]
|
||||||
|
);
|
||||||
|
|
||||||
|
var result = await _client.CreateChatMessageAsync(request);
|
||||||
|
|
||||||
|
result.IsSuccess.Should().BeTrue();
|
||||||
|
result.Value.Should().BeOfType<ChatResponse>();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
namespace AnthropicClient.Tests.EndToEnd;
|
||||||
|
|
||||||
|
public class ConfigurationFixture
|
||||||
|
{
|
||||||
|
public string AnthropicApiKey { get; }
|
||||||
|
|
||||||
|
public ConfigurationFixture()
|
||||||
|
{
|
||||||
|
var configuration = new ConfigurationBuilder()
|
||||||
|
.AddJsonFile("appsettings.Test.json")
|
||||||
|
.AddEnvironmentVariables()
|
||||||
|
.Build();
|
||||||
|
|
||||||
|
var anthropicApiKey = configuration["AnthropicApiKey"];
|
||||||
|
|
||||||
|
if (string.IsNullOrEmpty(anthropicApiKey))
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("AnthropicApiKey is required");
|
||||||
|
}
|
||||||
|
|
||||||
|
AnthropicApiKey = anthropicApiKey;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
namespace AnthropicClient.Tests.EndToEnd;
|
||||||
|
|
||||||
|
public class EndToEndTest(
|
||||||
|
HttpClientFixture httpClientFixture,
|
||||||
|
ConfigurationFixture configFixture
|
||||||
|
) : IClassFixture<HttpClientFixture>, IClassFixture<ConfigurationFixture>
|
||||||
|
{
|
||||||
|
protected readonly Client _client = new(configFixture.AnthropicApiKey, httpClientFixture.HttpClient);
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
namespace AnthropicClient.Tests.EndToEnd;
|
||||||
|
|
||||||
|
public class HttpClientFixture
|
||||||
|
{
|
||||||
|
public HttpClient HttpClient { get; }
|
||||||
|
|
||||||
|
public HttpClientFixture()
|
||||||
|
{
|
||||||
|
HttpClient = new HttpClient();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,168 @@
|
|||||||
|
namespace AnthropicClient.Tests.Unit.Models;
|
||||||
|
|
||||||
|
public class AnthropicErrorTests : SerializationTest
|
||||||
|
{
|
||||||
|
private readonly string _testApiErrorJson = @"{
|
||||||
|
""type"": ""error"",
|
||||||
|
""error"": {
|
||||||
|
""type"": ""api_error"",
|
||||||
|
""message"": ""message""
|
||||||
|
}
|
||||||
|
}";
|
||||||
|
|
||||||
|
private readonly string _testAuthenticationErrorJson = @"{
|
||||||
|
""type"": ""error"",
|
||||||
|
""error"": {
|
||||||
|
""type"": ""authentication_error"",
|
||||||
|
""message"": ""message""
|
||||||
|
}
|
||||||
|
}";
|
||||||
|
|
||||||
|
private readonly string _testRateLimitErrorJson = @"{
|
||||||
|
""type"": ""error"",
|
||||||
|
""error"": {
|
||||||
|
""type"": ""rate_limit_error"",
|
||||||
|
""message"": ""message""
|
||||||
|
}
|
||||||
|
}";
|
||||||
|
|
||||||
|
private readonly string _testPermissionErrorJson = @"{
|
||||||
|
""type"": ""error"",
|
||||||
|
""error"": {
|
||||||
|
""type"": ""permission_error"",
|
||||||
|
""message"": ""message""
|
||||||
|
}
|
||||||
|
}";
|
||||||
|
|
||||||
|
private readonly string _testNotFoundErrorJson = @"{
|
||||||
|
""type"": ""error"",
|
||||||
|
""error"": {
|
||||||
|
""type"": ""not_found_error"",
|
||||||
|
""message"": ""message""
|
||||||
|
}
|
||||||
|
}";
|
||||||
|
|
||||||
|
private readonly string _testOverloadedErrorJson = @"{
|
||||||
|
""type"": ""error"",
|
||||||
|
""error"": {
|
||||||
|
""type"": ""overloaded_error"",
|
||||||
|
""message"": ""message""
|
||||||
|
}
|
||||||
|
}";
|
||||||
|
|
||||||
|
private readonly string _testInvalidRequestErrorJson = @"{
|
||||||
|
""type"": ""error"",
|
||||||
|
""error"": {
|
||||||
|
""type"": ""invalid_request_error"",
|
||||||
|
""message"": ""message""
|
||||||
|
}
|
||||||
|
}";
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Constructor_WhenCalled_ItShouldInitializeProperties()
|
||||||
|
{
|
||||||
|
var apiError = new ApiError("message");
|
||||||
|
|
||||||
|
var error = new AnthropicError(apiError);
|
||||||
|
|
||||||
|
error.Type.Should().Be("error");
|
||||||
|
error.Error.Should().BeSameAs(apiError);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void JsonSerialization_WhenSerialized_ItShouldBeExpectedShape()
|
||||||
|
{
|
||||||
|
var apiError = new ApiError("message");
|
||||||
|
var error = new AnthropicError(apiError);
|
||||||
|
|
||||||
|
var json = Serialize(error);
|
||||||
|
|
||||||
|
JsonAssert.Equal(_testApiErrorJson, json);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void JsonDeserialization_WhenDeserialized_ItShouldBeExpectedModel()
|
||||||
|
{
|
||||||
|
var error = Deserialize<AnthropicError>(_testApiErrorJson);
|
||||||
|
|
||||||
|
error!.Type.Should().Be("error");
|
||||||
|
error.Error.Should().BeOfType<ApiError>();
|
||||||
|
error.Error!.Message.Should().Be("message");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void JsonDeserialization_WhenDeserialized_ItShouldBeExpectedModelForAuthenticationError()
|
||||||
|
{
|
||||||
|
var error = Deserialize<AnthropicError>(_testAuthenticationErrorJson);
|
||||||
|
|
||||||
|
error!.Type.Should().Be("error");
|
||||||
|
error.Error.Should().BeOfType<AuthenticationError>();
|
||||||
|
error.Error!.Message.Should().Be("message");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void JsonDeserialization_WhenDeserialized_ItShouldBeExpectedModelForRateLimitError()
|
||||||
|
{
|
||||||
|
var error = Deserialize<AnthropicError>(_testRateLimitErrorJson);
|
||||||
|
|
||||||
|
error!.Type.Should().Be("error");
|
||||||
|
error.Error.Should().BeOfType<RateLimitError>();
|
||||||
|
error.Error!.Message.Should().Be("message");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void JsonDeserialization_WhenDeserialized_ItShouldBeExpectedModelForPermissionError()
|
||||||
|
{
|
||||||
|
var error = Deserialize<AnthropicError>(_testPermissionErrorJson);
|
||||||
|
|
||||||
|
error!.Type.Should().Be("error");
|
||||||
|
error.Error.Should().BeOfType<PermissionError>();
|
||||||
|
error.Error!.Message.Should().Be("message");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void JsonDeserialization_WhenDeserialized_ItShouldBeExpectedModelForNotFoundError()
|
||||||
|
{
|
||||||
|
var error = Deserialize<AnthropicError>(_testNotFoundErrorJson);
|
||||||
|
|
||||||
|
error!.Type.Should().Be("error");
|
||||||
|
error.Error.Should().BeOfType<NotFoundError>();
|
||||||
|
error.Error!.Message.Should().Be("message");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void JsonDeserialization_WhenDeserialized_ItShouldBeExpectedModelForOverloadedError()
|
||||||
|
{
|
||||||
|
var error = Deserialize<AnthropicError>(_testOverloadedErrorJson);
|
||||||
|
|
||||||
|
error!.Type.Should().Be("error");
|
||||||
|
error.Error.Should().BeOfType<OverloadedError>();
|
||||||
|
error.Error!.Message.Should().Be("message");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void JsonDeserialization_WhenDeserialized_ItShouldBeExpectedModelForInvalidRequestError()
|
||||||
|
{
|
||||||
|
var error = Deserialize<AnthropicError>(_testInvalidRequestErrorJson);
|
||||||
|
|
||||||
|
error!.Type.Should().Be("error");
|
||||||
|
error.Error.Should().BeOfType<InvalidRequestError>();
|
||||||
|
error.Error!.Message.Should().Be("message");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void JsonDeserialization_WhenDeserialized_ItShouldBeExpectedModelForUnknownError()
|
||||||
|
{
|
||||||
|
var json = @"{
|
||||||
|
""type"": ""error"",
|
||||||
|
""error"": {
|
||||||
|
""type"": ""unknown_error"",
|
||||||
|
""message"": ""message""
|
||||||
|
}
|
||||||
|
}";
|
||||||
|
|
||||||
|
var action = () => Deserialize<AnthropicError>(json);
|
||||||
|
|
||||||
|
action.Should().Throw<JsonException>();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,67 @@
|
|||||||
|
namespace AnthropicClient.Tests.Unit.Models;
|
||||||
|
|
||||||
|
public class AnthropicModelsTests
|
||||||
|
{
|
||||||
|
[Fact]
|
||||||
|
public void Claude3Opus_WhenCalled_ItShouldReturnExpectedValue()
|
||||||
|
{
|
||||||
|
var expected = "claude-3-opus-20240229";
|
||||||
|
|
||||||
|
var actual = AnthropicModels.Claude3Opus;
|
||||||
|
|
||||||
|
actual.Should().Be(expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Claude3Sonnet_WhenCalled_ItShouldReturnExpectedValue()
|
||||||
|
{
|
||||||
|
var expected = "claude-3-sonnet-20240229";
|
||||||
|
|
||||||
|
var actual = AnthropicModels.Claude3Sonnet;
|
||||||
|
|
||||||
|
actual.Should().Be(expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Claude35Sonnet_WhenCalled_ItShouldReturnExpectedValue()
|
||||||
|
{
|
||||||
|
var expected = "claude-3-5-sonnet-20240620";
|
||||||
|
|
||||||
|
var actual = AnthropicModels.Claude35Sonnet;
|
||||||
|
|
||||||
|
actual.Should().Be(expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Claude3Haiku_WhenCalled_ItShouldReturnExpectedValue()
|
||||||
|
{
|
||||||
|
var expected = "claude-3-haiku-20240307";
|
||||||
|
|
||||||
|
var actual = AnthropicModels.Claude3Haiku;
|
||||||
|
|
||||||
|
actual.Should().Be(expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Claude35Sonnet_WhenCalled_ItShouldExpectedValue()
|
||||||
|
{
|
||||||
|
var expected = "claude-3-5-sonnet-20240620";
|
||||||
|
|
||||||
|
var actual = AnthropicModels.Claude35Sonnet;
|
||||||
|
|
||||||
|
actual.Should().Be(expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData("claude-3-opus-20240229", true)]
|
||||||
|
[InlineData("claude-3-sonnet-20240229", true)]
|
||||||
|
[InlineData("claude-3-5-sonnet-20240620", true)]
|
||||||
|
[InlineData("claude-3-haiku-20240307", true)]
|
||||||
|
[InlineData("invalid", false)]
|
||||||
|
public void IsValidModel_WhenCalled_ItShouldReturnExpectedValue(string modelId, bool expected)
|
||||||
|
{
|
||||||
|
var actual = AnthropicModels.IsValidModel(modelId);
|
||||||
|
|
||||||
|
actual.Should().Be(expected);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
namespace AnthropicClient.Tests.Unit.Models;
|
||||||
|
|
||||||
|
public class AnthropicResultTests
|
||||||
|
{
|
||||||
|
[Fact]
|
||||||
|
public void Success_WhenCalled_ItShouldReturnSuccessResult()
|
||||||
|
{
|
||||||
|
var value = "success";
|
||||||
|
var requestId = Guid.NewGuid().ToString();
|
||||||
|
|
||||||
|
var actual = AnthropicResult<string>.Success(value, requestId);
|
||||||
|
|
||||||
|
actual.IsSuccess.Should().BeTrue();
|
||||||
|
actual.Value.Should().Be(value);
|
||||||
|
actual.RequestId.Should().Be(requestId);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Failure_WhenCalled_ItShouldReturnFailureResult()
|
||||||
|
{
|
||||||
|
var error = new AnthropicError(new AuthenticationError("message"));
|
||||||
|
var requestId = Guid.NewGuid().ToString();
|
||||||
|
|
||||||
|
var actual = AnthropicResult<string>.Failure(error, requestId);
|
||||||
|
|
||||||
|
actual.IsSuccess.Should().BeFalse();
|
||||||
|
actual.Error.Should().Be(error);
|
||||||
|
actual.RequestId.Should().Be(requestId);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
namespace AnthropicClient.Tests.Unit.Models;
|
||||||
|
|
||||||
|
public class AnyToolChoiceTests : SerializationTest
|
||||||
|
{
|
||||||
|
private readonly string _testJson = @"{
|
||||||
|
""type"": ""any""
|
||||||
|
}";
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Constructor_WhenCalled_ItShouldSetTypeToAny()
|
||||||
|
{
|
||||||
|
var expected = "any";
|
||||||
|
|
||||||
|
var actual = new AnyToolChoice();
|
||||||
|
|
||||||
|
actual.Type.Should().Be(expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void JsonSerialization_WhenSerialized_ItShouldHaveExpectedShape()
|
||||||
|
{
|
||||||
|
var choice = new AnyToolChoice();
|
||||||
|
|
||||||
|
var actual = Serialize(choice);
|
||||||
|
|
||||||
|
JsonAssert.Equal(_testJson, actual);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void JsonDeserialization_WhenDeserialized_ItShouldHaveExpectedShape()
|
||||||
|
{
|
||||||
|
var expected = new AnyToolChoice();
|
||||||
|
|
||||||
|
var actual = Deserialize<AnyToolChoice>(_testJson);
|
||||||
|
|
||||||
|
actual.Should().BeEquivalentTo(expected);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
namespace AnthropicClient.Tests.Unit.Models;
|
||||||
|
|
||||||
|
public class AuthenticationErrorTests : SerializationTest
|
||||||
|
{
|
||||||
|
private readonly string _testJson = @"{
|
||||||
|
""message"": ""message"",
|
||||||
|
""type"": ""authentication_error""
|
||||||
|
}";
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Constructor_WhenCalled_ItShouldInitializeProperties()
|
||||||
|
{
|
||||||
|
var expectedMessage = "message";
|
||||||
|
|
||||||
|
var actual = new AuthenticationError(expectedMessage);
|
||||||
|
|
||||||
|
actual.Message.Should().Be(expectedMessage);
|
||||||
|
actual.Type.Should().Be("authentication_error");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void JsonSerialization_WhenSerialized_ItShouldHaveExpectedShape()
|
||||||
|
{
|
||||||
|
var error = new AuthenticationError("message");
|
||||||
|
|
||||||
|
var actual = Serialize(error);
|
||||||
|
|
||||||
|
JsonAssert.Equal(_testJson, actual);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void JsonDeserialization_WhenDeserialized_ItShouldHaveExpectedShape()
|
||||||
|
{
|
||||||
|
var expected = new AuthenticationError("message");
|
||||||
|
|
||||||
|
var actual = Deserialize<AuthenticationError>(_testJson);
|
||||||
|
|
||||||
|
actual.Should().BeEquivalentTo(expected);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
namespace AnthropicClient.Tests.Unit.Models;
|
||||||
|
|
||||||
|
public class AutoToolChoiceTests : SerializationTest
|
||||||
|
{
|
||||||
|
private readonly string _testJson = @"{
|
||||||
|
""type"": ""auto""
|
||||||
|
}";
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Constructor_WhenCalled_ItShouldSetTypeToAuto()
|
||||||
|
{
|
||||||
|
var expected = "auto";
|
||||||
|
|
||||||
|
var actual = new AutoToolChoice();
|
||||||
|
|
||||||
|
actual.Type.Should().Be(expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void JsonSerialization_WhenSerialized_ItShouldHaveExpectedShape()
|
||||||
|
{
|
||||||
|
var choice = new AutoToolChoice();
|
||||||
|
|
||||||
|
var actual = Serialize(choice);
|
||||||
|
|
||||||
|
JsonAssert.Equal(_testJson, actual);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void JsonDeserialization_WhenDeserialized_ItShouldHaveExpectedShape()
|
||||||
|
{
|
||||||
|
var expected = new AutoToolChoice();
|
||||||
|
|
||||||
|
var actual = Deserialize<AutoToolChoice>(_testJson);
|
||||||
|
|
||||||
|
actual.Should().BeEquivalentTo(expected);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,653 @@
|
|||||||
|
namespace AnthropicClient.Tests.Unit.Models;
|
||||||
|
|
||||||
|
public class ChatMessageRequestTests : SerializationTest
|
||||||
|
{
|
||||||
|
private readonly string _testJson = @"{
|
||||||
|
""model"": ""claude-3-sonnet-20240229"",
|
||||||
|
""system"": ""test-system"",
|
||||||
|
""messages"": [
|
||||||
|
{ ""role"": ""user"", ""content"": [{ ""text"": ""Hello!"", ""type"": ""text"" }] }
|
||||||
|
],
|
||||||
|
""max_tokens"": 512,
|
||||||
|
""metadata"": { ""test"":""test"" },
|
||||||
|
""stop_sequences"": [],
|
||||||
|
""temperature"": 0.5,
|
||||||
|
""topK"": 10,
|
||||||
|
""topP"": 0.5,
|
||||||
|
""tool_choice"": { ""type"":""auto"" },
|
||||||
|
""tools"": [
|
||||||
|
{
|
||||||
|
""name"": ""test-tool"",
|
||||||
|
""description"": ""test-description"",
|
||||||
|
""input_schema"": {
|
||||||
|
""type"": ""object"",
|
||||||
|
""properties"": {
|
||||||
|
""test-property"": {
|
||||||
|
""type"": ""string"",
|
||||||
|
""description"": ""test-description""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
""required"": [""test-property""]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
""stream"": false
|
||||||
|
}";
|
||||||
|
|
||||||
|
private readonly string _testJsonWithAnyToolChoice = @"{
|
||||||
|
""model"": ""claude-3-sonnet-20240229"",
|
||||||
|
""system"": ""test-system"",
|
||||||
|
""messages"":[
|
||||||
|
{ ""role"": ""user"", ""content"": [{ ""text"": ""Hello!"", ""type"":""text"" }] }
|
||||||
|
],
|
||||||
|
""max_tokens"": 512,
|
||||||
|
""metadata"": {""test"":""test""},
|
||||||
|
""stop_sequences"": [],
|
||||||
|
""temperature"": 0.5,
|
||||||
|
""topK"": 10,
|
||||||
|
""topP"": 0.5,
|
||||||
|
""tool_choice"": {""type"":""any""},
|
||||||
|
""tools"": [
|
||||||
|
{
|
||||||
|
""name"": ""test-tool"",
|
||||||
|
""description"": ""test-description"",
|
||||||
|
""input_schema"": {
|
||||||
|
""type"": ""object"",
|
||||||
|
""properties"": {
|
||||||
|
""test-property"": {
|
||||||
|
""type"": ""string"",
|
||||||
|
""description"": ""test-description""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
""required"": [""test-property""]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
""stream"":false
|
||||||
|
}";
|
||||||
|
|
||||||
|
private readonly string _testJsonWithSpecificToolChoice = @"{
|
||||||
|
""model"": ""claude-3-sonnet-20240229"",
|
||||||
|
""system"": ""test-system"",
|
||||||
|
""messages"": [
|
||||||
|
{ ""role"": ""user"", ""content"": [{ ""text"": ""Hello!"", ""type"": ""text"" }] }
|
||||||
|
],
|
||||||
|
""max_tokens"": 512,
|
||||||
|
""metadata"": { ""test"": ""test"" },
|
||||||
|
""stop_sequences"": [],
|
||||||
|
""temperature"": 0.5,
|
||||||
|
""topK"": 10,
|
||||||
|
""topP"": 0.5,
|
||||||
|
""tool_choice"": { ""type"": ""tool"", ""name"": ""test-tool"" },
|
||||||
|
""tools"": [
|
||||||
|
{
|
||||||
|
""name"": ""test-tool"",
|
||||||
|
""description"": ""test-description"",
|
||||||
|
""input_schema"": {
|
||||||
|
""type"": ""object"",
|
||||||
|
""properties"": {
|
||||||
|
""test-property"": {
|
||||||
|
""type"": ""string"",
|
||||||
|
""description"": ""test-description""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
""required"": [""test-property""]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
""stream"": false
|
||||||
|
}";
|
||||||
|
|
||||||
|
private readonly string _testJsonWithImageContent = @"{
|
||||||
|
""model"": ""claude-3-sonnet-20240229"",
|
||||||
|
""system"": ""test-system"",
|
||||||
|
""messages"":[
|
||||||
|
{
|
||||||
|
""role"": ""user"",
|
||||||
|
""content"": [
|
||||||
|
{
|
||||||
|
""type"": ""image"",
|
||||||
|
""source"": { ""media_type"": ""image/jpeg"", ""data"": ""data"" }
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
""max_tokens"": 512,
|
||||||
|
""metadata"": { ""test"": ""test"" },
|
||||||
|
""stop_sequences"": [],
|
||||||
|
""temperature"": 0.5,
|
||||||
|
""topK"": 10,
|
||||||
|
""topP"": 0.5,
|
||||||
|
""tool_choice"": { ""type"": ""auto"" },
|
||||||
|
""tools"":[
|
||||||
|
{
|
||||||
|
""name"": ""test-tool"",
|
||||||
|
""description"": ""test-description"",
|
||||||
|
""input_schema"": {
|
||||||
|
""type"": ""object"",
|
||||||
|
""properties"": {
|
||||||
|
""test-property"": {
|
||||||
|
""type"": ""string"",
|
||||||
|
""description"": ""test-description""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
""required"": [""test-property""]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
""stream"": false
|
||||||
|
}";
|
||||||
|
|
||||||
|
private readonly string _testJsonWithUnknownContent = @"{
|
||||||
|
""model"": ""claude-3-sonnet-20240229"",
|
||||||
|
""system"": ""test-system"",
|
||||||
|
""messages"": [{ ""role"": ""user"", ""content"": [{ ""type"": ""unknown"", ""text"": ""text"" }] }],
|
||||||
|
""max_tokens"": 512,
|
||||||
|
""metadata"": { ""test"": ""test"" },
|
||||||
|
""stop_sequences"": [],
|
||||||
|
""temperature"": 0.5,
|
||||||
|
""topK"": 10,
|
||||||
|
""topP"": 0.5,
|
||||||
|
""tool_choice"": { ""type"":""auto"" },
|
||||||
|
""tools"": [
|
||||||
|
{
|
||||||
|
""name"": ""test-tool"",
|
||||||
|
""description"": ""test-description"",
|
||||||
|
""input_schema"": {
|
||||||
|
""type"": ""object"",
|
||||||
|
""properties"": {
|
||||||
|
""test-property"": {
|
||||||
|
""type"": ""string"",
|
||||||
|
""description"": ""test-description""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
""required"": [""test-property""]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
""stream"": false
|
||||||
|
}";
|
||||||
|
|
||||||
|
private readonly string _testJsonWithToolUseContent = @"{
|
||||||
|
""model"": ""claude-3-sonnet-20240229"",
|
||||||
|
""system"": ""test-system"",
|
||||||
|
""messages"": [
|
||||||
|
{
|
||||||
|
""role"": ""assistant"",
|
||||||
|
""content"": [
|
||||||
|
{
|
||||||
|
""type"": ""tool_use"",
|
||||||
|
""name"": ""test-tool"",
|
||||||
|
""id"": ""test-tool-id"",
|
||||||
|
""input"": {
|
||||||
|
""test-property"": ""test-value""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
""max_tokens"": 512,
|
||||||
|
""metadata"": { ""test"": ""test"" },
|
||||||
|
""stop_sequences"": [],
|
||||||
|
""temperature"": 0.5,
|
||||||
|
""topK"": 10,
|
||||||
|
""topP"": 0.5,
|
||||||
|
""tool_choice"": { ""type"": ""auto"" },
|
||||||
|
""tools"": [
|
||||||
|
{
|
||||||
|
""name"": ""test-tool"",
|
||||||
|
""description"": ""test-description"",
|
||||||
|
""input_schema"": {
|
||||||
|
""type"": ""object"",
|
||||||
|
""properties"": {
|
||||||
|
""test-property"": {
|
||||||
|
""type"": ""string"",
|
||||||
|
""description"": ""test-description""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
""required"": [""test-property""]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
""stream"": false
|
||||||
|
}";
|
||||||
|
|
||||||
|
private readonly string _testJsonWithToolResultContent = @"{
|
||||||
|
""model"": ""claude-3-sonnet-20240229"",
|
||||||
|
""system"": ""test-system"",
|
||||||
|
""messages"": [
|
||||||
|
{
|
||||||
|
""role"": ""assistant"",
|
||||||
|
""content"": [
|
||||||
|
{
|
||||||
|
""type"": ""tool_result"",
|
||||||
|
""tool_use_id"": ""test-tool"",
|
||||||
|
""content"": ""test-value""
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
""max_tokens"": 512,
|
||||||
|
""metadata"": { ""test"": ""test"" },
|
||||||
|
""stop_sequences"": [],
|
||||||
|
""temperature"": 0.5,
|
||||||
|
""topK"": 10,
|
||||||
|
""topP"": 0.5,
|
||||||
|
""tool_choice"": { ""type"": ""auto"" },
|
||||||
|
""tools"": [
|
||||||
|
{
|
||||||
|
""name"": ""test-tool"",
|
||||||
|
""description"": ""test-description"",
|
||||||
|
""input_schema"": {
|
||||||
|
""type"": ""object"",
|
||||||
|
""properties"": {
|
||||||
|
""test-property"": {
|
||||||
|
""type"": ""string"",
|
||||||
|
""description"": ""test-description""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
""required"": [""test-property""]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
""stream"":false
|
||||||
|
}";
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Constructor_WhenCalled_ItShouldInitializeProperties()
|
||||||
|
{
|
||||||
|
var model = AnthropicModels.Claude3Sonnet;
|
||||||
|
var messages = new List<ChatMessage> { new() };
|
||||||
|
var maxTokens = 512;
|
||||||
|
var system = "test-system";
|
||||||
|
var metadata = new Dictionary<string, object> { ["test"] = "test" };
|
||||||
|
var temperature = 0.5m;
|
||||||
|
var topK = 10;
|
||||||
|
var topP = 0.5m;
|
||||||
|
var toolChoice = new AutoToolChoice();
|
||||||
|
var tools = new List<Tool> { new() };
|
||||||
|
|
||||||
|
var chatMessageRequest = new ChatMessageRequest(
|
||||||
|
model: model,
|
||||||
|
messages: messages,
|
||||||
|
maxTokens: maxTokens,
|
||||||
|
system: system,
|
||||||
|
metadata: metadata,
|
||||||
|
temperature: temperature,
|
||||||
|
topK: topK,
|
||||||
|
topP: topP,
|
||||||
|
toolChoice: toolChoice,
|
||||||
|
tools: tools
|
||||||
|
);
|
||||||
|
|
||||||
|
chatMessageRequest.Model.Should().Be(model);
|
||||||
|
chatMessageRequest.Messages.Should().BeSameAs(messages);
|
||||||
|
chatMessageRequest.MaxTokens.Should().Be(maxTokens);
|
||||||
|
chatMessageRequest.System.Should().Be(system);
|
||||||
|
chatMessageRequest.Metadata.Should().BeSameAs(metadata);
|
||||||
|
chatMessageRequest.Temperature.Should().Be(temperature);
|
||||||
|
chatMessageRequest.TopK.Should().Be(topK);
|
||||||
|
chatMessageRequest.TopP.Should().Be(topP);
|
||||||
|
chatMessageRequest.ToolChoice.Should().Be(toolChoice);
|
||||||
|
chatMessageRequest.Tools.Should().BeSameAs(tools);
|
||||||
|
chatMessageRequest.Stream.Should().BeFalse();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Constructor_WhenCalledAndModelIsNull_ItShouldThrowArgumentNullException()
|
||||||
|
{
|
||||||
|
var action = () => new ChatMessageRequest(
|
||||||
|
model: null!,
|
||||||
|
messages: [new()]
|
||||||
|
);
|
||||||
|
|
||||||
|
action.Should().Throw<ArgumentNullException>();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Constructor_WhenCalledAndMessagesIsNull_ItShouldThrowArgumentNullException()
|
||||||
|
{
|
||||||
|
var action = () => new ChatMessageRequest(
|
||||||
|
model: AnthropicModels.Claude3Sonnet,
|
||||||
|
messages: null!
|
||||||
|
);
|
||||||
|
|
||||||
|
action.Should().Throw<ArgumentNullException>();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Constructor_WhenCalledAndModelIsInvalid_ItShouldThrowArgumentException()
|
||||||
|
{
|
||||||
|
var action = () => new ChatMessageRequest(
|
||||||
|
model: "invalid-model",
|
||||||
|
messages: [new()]
|
||||||
|
);
|
||||||
|
|
||||||
|
action.Should().Throw<ArgumentException>();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Constructor_WhenCalledAndMessagesIsEmpty_ItShouldThrowArgumentException()
|
||||||
|
{
|
||||||
|
var action = () => new ChatMessageRequest(
|
||||||
|
model: AnthropicModels.Claude3Sonnet,
|
||||||
|
messages: []
|
||||||
|
);
|
||||||
|
|
||||||
|
action.Should().Throw<ArgumentException>();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Constructor_WhenCalledAndMaxTokensIsInvalid_ItShouldThrowArgumentException()
|
||||||
|
{
|
||||||
|
var action = () => new ChatMessageRequest(
|
||||||
|
model: AnthropicModels.Claude3Sonnet,
|
||||||
|
messages: [new()],
|
||||||
|
maxTokens: 0
|
||||||
|
);
|
||||||
|
|
||||||
|
action.Should().Throw<ArgumentException>();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData(-1)]
|
||||||
|
[InlineData(2)]
|
||||||
|
public void Constructor_WhenCalledAndTemperatureIsInvalid_ItShouldThrowArgumentException(decimal temperature)
|
||||||
|
{
|
||||||
|
var action = () => new ChatMessageRequest(
|
||||||
|
model: AnthropicModels.Claude3Sonnet,
|
||||||
|
messages: [new()],
|
||||||
|
temperature: temperature
|
||||||
|
);
|
||||||
|
|
||||||
|
action.Should().Throw<ArgumentException>();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void JsonSerialization_WhenSerialized_ItShouldHaveExpectedShape()
|
||||||
|
{
|
||||||
|
var messages = new List<ChatMessage>()
|
||||||
|
{
|
||||||
|
new()
|
||||||
|
{
|
||||||
|
Role = MessageRole.User,
|
||||||
|
Content = [new TextContent("Hello!")]
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var model = AnthropicModels.Claude3Sonnet;
|
||||||
|
var maxTokens = 512;
|
||||||
|
var system = "test-system";
|
||||||
|
var metadata = new Dictionary<string, object>
|
||||||
|
{
|
||||||
|
["test"] = "test"
|
||||||
|
};
|
||||||
|
var temperature = 0.5m;
|
||||||
|
var topK = 10;
|
||||||
|
var topP = 0.5m;
|
||||||
|
var toolChoice = new AutoToolChoice();
|
||||||
|
var tools = new List<Tool>
|
||||||
|
{
|
||||||
|
new()
|
||||||
|
{
|
||||||
|
Name = "test-tool",
|
||||||
|
Description = "test-description",
|
||||||
|
InputSchema = new InputSchema(
|
||||||
|
properties: new Dictionary<string, InputProperty>
|
||||||
|
{
|
||||||
|
["test-property"] = new InputProperty(
|
||||||
|
type: "string",
|
||||||
|
description: "test-description"
|
||||||
|
)
|
||||||
|
},
|
||||||
|
required: ["test-property"]
|
||||||
|
),
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var chatMessageRequest = new ChatMessageRequest(
|
||||||
|
model: model,
|
||||||
|
messages: messages,
|
||||||
|
maxTokens: maxTokens,
|
||||||
|
system: system,
|
||||||
|
metadata: metadata,
|
||||||
|
temperature: temperature,
|
||||||
|
topK: topK,
|
||||||
|
topP: topP,
|
||||||
|
toolChoice: toolChoice,
|
||||||
|
tools: tools
|
||||||
|
);
|
||||||
|
|
||||||
|
var actual = Serialize(chatMessageRequest);
|
||||||
|
|
||||||
|
JsonAssert.Equal(_testJson, actual);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void JsonDeserialization_WhenDeserialized_ItShouldHaveExpectedShape()
|
||||||
|
{
|
||||||
|
var chatMessageRequest = Deserialize<ChatMessageRequest>(_testJson);
|
||||||
|
|
||||||
|
chatMessageRequest!.Model.Should().Be(AnthropicModels.Claude3Sonnet);
|
||||||
|
chatMessageRequest.System.Should().Be("test-system");
|
||||||
|
chatMessageRequest.Messages.Should().HaveCount(1);
|
||||||
|
chatMessageRequest.MaxTokens.Should().Be(512);
|
||||||
|
chatMessageRequest.Metadata.Should().HaveCount(1);
|
||||||
|
|
||||||
|
var testValue = chatMessageRequest.Metadata!.GetValueOrDefault("test")!.ToString();
|
||||||
|
testValue.Should().Be("test");
|
||||||
|
|
||||||
|
chatMessageRequest.Temperature.Should().Be(0.5m);
|
||||||
|
chatMessageRequest.TopK.Should().Be(10);
|
||||||
|
chatMessageRequest.TopP.Should().Be(0.5m);
|
||||||
|
chatMessageRequest.ToolChoice.Should().BeOfType<AutoToolChoice>();
|
||||||
|
chatMessageRequest.ToolChoice!.Type.Should().Be("auto");
|
||||||
|
chatMessageRequest.Tools.Should().HaveCount(1);
|
||||||
|
chatMessageRequest.Tools![0].Name.Should().Be("test-tool");
|
||||||
|
chatMessageRequest.Tools[0].Description.Should().Be("test-description");
|
||||||
|
chatMessageRequest.Tools[0].InputSchema.Type.Should().Be("object");
|
||||||
|
chatMessageRequest.Tools[0].InputSchema.Properties.Should().HaveCount(1);
|
||||||
|
chatMessageRequest.Tools[0].InputSchema.Properties["test-property"].Type.Should().Be("string");
|
||||||
|
chatMessageRequest.Tools[0].InputSchema.Properties["test-property"].Description.Should().Be("test-description");
|
||||||
|
chatMessageRequest.Tools[0].InputSchema.Required.Should().HaveCount(1);
|
||||||
|
chatMessageRequest.Tools[0].InputSchema.Required[0].Should().Be("test-property");
|
||||||
|
chatMessageRequest.Stream.Should().BeFalse();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void JsonDeserialization_WhenDeserializedWithAnyToolChoice_ItShouldHaveExpectedShape()
|
||||||
|
{
|
||||||
|
var chatMessageRequest = Deserialize<ChatMessageRequest>(_testJsonWithAnyToolChoice);
|
||||||
|
|
||||||
|
chatMessageRequest!.Model.Should().Be(AnthropicModels.Claude3Sonnet);
|
||||||
|
chatMessageRequest.System.Should().Be("test-system");
|
||||||
|
chatMessageRequest.Messages.Should().HaveCount(1);
|
||||||
|
chatMessageRequest.MaxTokens.Should().Be(512);
|
||||||
|
chatMessageRequest.Metadata.Should().HaveCount(1);
|
||||||
|
|
||||||
|
var testValue = chatMessageRequest.Metadata!.GetValueOrDefault("test")!.ToString();
|
||||||
|
testValue.Should().Be("test");
|
||||||
|
|
||||||
|
chatMessageRequest.Temperature.Should().Be(0.5m);
|
||||||
|
chatMessageRequest.TopK.Should().Be(10);
|
||||||
|
chatMessageRequest.TopP.Should().Be(0.5m);
|
||||||
|
chatMessageRequest.ToolChoice.Should().BeOfType<AnyToolChoice>();
|
||||||
|
chatMessageRequest.ToolChoice!.Type.Should().Be("any");
|
||||||
|
chatMessageRequest.Tools.Should().HaveCount(1);
|
||||||
|
chatMessageRequest.Tools![0].Name.Should().Be("test-tool");
|
||||||
|
chatMessageRequest.Tools[0].Description.Should().Be("test-description");
|
||||||
|
chatMessageRequest.Tools[0].InputSchema.Type.Should().Be("object");
|
||||||
|
chatMessageRequest.Tools[0].InputSchema.Properties.Should().HaveCount(1);
|
||||||
|
chatMessageRequest.Tools[0].InputSchema.Properties["test-property"].Type.Should().Be("string");
|
||||||
|
chatMessageRequest.Tools[0].InputSchema.Properties["test-property"].Description.Should().Be("test-description");
|
||||||
|
chatMessageRequest.Tools[0].InputSchema.Required.Should().HaveCount(1);
|
||||||
|
chatMessageRequest.Tools[0].InputSchema.Required[0].Should().Be("test-property");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void JsonDeserialization_WhenDeserializedWithSpecificToolChoice_ItShouldHaveExpectedShape()
|
||||||
|
{
|
||||||
|
var chatMessageRequest = Deserialize<ChatMessageRequest>(_testJsonWithSpecificToolChoice);
|
||||||
|
|
||||||
|
chatMessageRequest!.Model.Should().Be(AnthropicModels.Claude3Sonnet);
|
||||||
|
chatMessageRequest.System.Should().Be("test-system");
|
||||||
|
chatMessageRequest.Messages.Should().HaveCount(1);
|
||||||
|
chatMessageRequest.MaxTokens.Should().Be(512);
|
||||||
|
chatMessageRequest.Metadata.Should().HaveCount(1);
|
||||||
|
|
||||||
|
var testValue = chatMessageRequest.Metadata!.GetValueOrDefault("test")!.ToString();
|
||||||
|
testValue.Should().Be("test");
|
||||||
|
|
||||||
|
chatMessageRequest.Temperature.Should().Be(0.5m);
|
||||||
|
chatMessageRequest.TopK.Should().Be(10);
|
||||||
|
chatMessageRequest.TopP.Should().Be(0.5m);
|
||||||
|
chatMessageRequest.ToolChoice.Should().BeOfType<SpecificToolChoice>();
|
||||||
|
|
||||||
|
var specificToolChoice = chatMessageRequest.ToolChoice as SpecificToolChoice;
|
||||||
|
specificToolChoice!.Type.Should().Be("tool");
|
||||||
|
specificToolChoice.Name.Should().Be("test-tool");
|
||||||
|
chatMessageRequest.Tools.Should().HaveCount(1);
|
||||||
|
chatMessageRequest.Tools![0].Name.Should().Be("test-tool");
|
||||||
|
chatMessageRequest.Tools[0].Description.Should().Be("test-description");
|
||||||
|
chatMessageRequest.Tools[0].InputSchema.Type.Should().Be("object");
|
||||||
|
chatMessageRequest.Tools[0].InputSchema.Properties.Should().HaveCount(1);
|
||||||
|
chatMessageRequest.Tools[0].InputSchema.Properties["test-property"].Type.Should().Be("string");
|
||||||
|
chatMessageRequest.Tools[0].InputSchema.Properties["test-property"].Description.Should().Be("test-description");
|
||||||
|
chatMessageRequest.Tools[0].InputSchema.Required.Should().HaveCount(1);
|
||||||
|
chatMessageRequest.Tools[0].InputSchema.Required[0].Should().Be("test-property");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void JsonDeserialization_WhenDeserializedWithUnknownToolChoice_ItShouldThrowJsonException()
|
||||||
|
{
|
||||||
|
var json = @"{""model"":""claude-3-sonnet-20240229"",""system"":""test-system"",""messages"":[{""role"":""user"",""content"":[{""text"":""Hello!"",""type"":""text""}]}],""max_tokens"":512,""metadata"":{""test"":""test""},""stop_sequences"":[],""temperature"":0.5,""topK"":10,""topP"":0.5,""tool_choice"":{""type"":""unknown""},""tools"":[{""name"":""test-tool"",""description"":""test-description"",""input_schema"":{""type"":""object"",""properties"":{""test-property"":{""type"":""string"",""description"":""test-description""}},""required"":[""test-property""]}}],""stream"":false}";
|
||||||
|
|
||||||
|
var action = () => Deserialize<ChatMessageRequest>(json);
|
||||||
|
|
||||||
|
action.Should().Throw<JsonException>();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void JsonDeserialization_WhenDeserializedWithImageContent_ItShouldHaveExpectedShape()
|
||||||
|
{
|
||||||
|
var chatMessageRequest = Deserialize<ChatMessageRequest>(_testJsonWithImageContent);
|
||||||
|
|
||||||
|
chatMessageRequest!.Model.Should().Be(AnthropicModels.Claude3Sonnet);
|
||||||
|
chatMessageRequest.System.Should().Be("test-system");
|
||||||
|
chatMessageRequest.Messages.Should().HaveCount(1);
|
||||||
|
chatMessageRequest.MaxTokens.Should().Be(512);
|
||||||
|
chatMessageRequest.Metadata.Should().HaveCount(1);
|
||||||
|
|
||||||
|
var testValue = chatMessageRequest.Metadata!.GetValueOrDefault("test")!.ToString();
|
||||||
|
testValue.Should().Be("test");
|
||||||
|
|
||||||
|
chatMessageRequest.Temperature.Should().Be(0.5m);
|
||||||
|
chatMessageRequest.TopK.Should().Be(10);
|
||||||
|
chatMessageRequest.TopP.Should().Be(0.5m);
|
||||||
|
chatMessageRequest.ToolChoice.Should().BeOfType<AutoToolChoice>();
|
||||||
|
chatMessageRequest.ToolChoice!.Type.Should().Be("auto");
|
||||||
|
chatMessageRequest.Tools.Should().HaveCount(1);
|
||||||
|
chatMessageRequest.Tools![0].Name.Should().Be("test-tool");
|
||||||
|
chatMessageRequest.Tools[0].Description.Should().Be("test-description");
|
||||||
|
chatMessageRequest.Tools[0].InputSchema.Type.Should().Be("object");
|
||||||
|
chatMessageRequest.Tools[0].InputSchema.Properties.Should().HaveCount(1);
|
||||||
|
chatMessageRequest.Tools[0].InputSchema.Properties["test-property"].Type.Should().Be("string");
|
||||||
|
chatMessageRequest.Tools[0].InputSchema.Properties["test-property"].Description.Should().Be("test-description");
|
||||||
|
chatMessageRequest.Tools[0].InputSchema.Required.Should().HaveCount(1);
|
||||||
|
chatMessageRequest.Tools[0].InputSchema.Required[0].Should().Be("test-property");
|
||||||
|
|
||||||
|
chatMessageRequest.Messages[0].Content.Should().HaveCount(1);
|
||||||
|
chatMessageRequest.Messages[0].Content[0].Should().BeOfType<ImageContent>();
|
||||||
|
|
||||||
|
var imageContent = chatMessageRequest.Messages[0].Content[0] as ImageContent;
|
||||||
|
imageContent!.Type.Should().Be("image");
|
||||||
|
imageContent.Source.MediaType.Should().Be("image/jpeg");
|
||||||
|
imageContent.Source.Data.Should().Be("data");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void JsonDeserialization_WhenDeserializedWithToolUseContent_ItShouldHaveExpectedShape()
|
||||||
|
{
|
||||||
|
var chatMessageRequest = Deserialize<ChatMessageRequest>(_testJsonWithToolUseContent);
|
||||||
|
|
||||||
|
chatMessageRequest!.Model.Should().Be(AnthropicModels.Claude3Sonnet);
|
||||||
|
chatMessageRequest.System.Should().Be("test-system");
|
||||||
|
chatMessageRequest.Messages.Should().HaveCount(1);
|
||||||
|
chatMessageRequest.MaxTokens.Should().Be(512);
|
||||||
|
chatMessageRequest.Metadata.Should().HaveCount(1);
|
||||||
|
|
||||||
|
var testValue = chatMessageRequest.Metadata!.GetValueOrDefault("test")!.ToString();
|
||||||
|
testValue.Should().Be("test");
|
||||||
|
|
||||||
|
chatMessageRequest.Temperature.Should().Be(0.5m);
|
||||||
|
chatMessageRequest.TopK.Should().Be(10);
|
||||||
|
chatMessageRequest.TopP.Should().Be(0.5m);
|
||||||
|
chatMessageRequest.ToolChoice.Should().BeOfType<AutoToolChoice>();
|
||||||
|
chatMessageRequest.ToolChoice!.Type.Should().Be("auto");
|
||||||
|
chatMessageRequest.Tools.Should().HaveCount(1);
|
||||||
|
chatMessageRequest.Tools![0].Name.Should().Be("test-tool");
|
||||||
|
chatMessageRequest.Tools[0].Description.Should().Be("test-description");
|
||||||
|
chatMessageRequest.Tools[0].InputSchema.Type.Should().Be("object");
|
||||||
|
chatMessageRequest.Tools[0].InputSchema.Properties.Should().HaveCount(1);
|
||||||
|
chatMessageRequest.Tools[0].InputSchema.Properties["test-property"].Type.Should().Be("string");
|
||||||
|
chatMessageRequest.Tools[0].InputSchema.Properties["test-property"].Description.Should().Be("test-description");
|
||||||
|
chatMessageRequest.Tools[0].InputSchema.Required.Should().HaveCount(1);
|
||||||
|
chatMessageRequest.Tools[0].InputSchema.Required[0].Should().Be("test-property");
|
||||||
|
|
||||||
|
chatMessageRequest.Messages[0].Content.Should().HaveCount(1);
|
||||||
|
chatMessageRequest.Messages[0].Content[0].Should().BeOfType<ToolUseContent>();
|
||||||
|
|
||||||
|
var toolUseContent = chatMessageRequest.Messages[0].Content[0] as ToolUseContent;
|
||||||
|
toolUseContent!.Type.Should().Be("tool_use");
|
||||||
|
toolUseContent.Name.Should().Be("test-tool");
|
||||||
|
toolUseContent.Id.Should().Be("test-tool-id");
|
||||||
|
toolUseContent.Input.Should().HaveCount(1);
|
||||||
|
toolUseContent.Input["test-property"]!.ToString().Should().Be("test-value");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void JsonDeserialization_WhenDeserializedWithToolResultContent_ItShouldHaveExpectedShape()
|
||||||
|
{
|
||||||
|
var chatMessageRequest = Deserialize<ChatMessageRequest>(_testJsonWithToolResultContent);
|
||||||
|
|
||||||
|
chatMessageRequest!.Model.Should().Be(AnthropicModels.Claude3Sonnet);
|
||||||
|
chatMessageRequest.System.Should().Be("test-system");
|
||||||
|
chatMessageRequest.Messages.Should().HaveCount(1);
|
||||||
|
chatMessageRequest.MaxTokens.Should().Be(512);
|
||||||
|
chatMessageRequest.Metadata.Should().HaveCount(1);
|
||||||
|
|
||||||
|
var testValue = chatMessageRequest.Metadata!.GetValueOrDefault("test")!.ToString();
|
||||||
|
testValue.Should().Be("test");
|
||||||
|
|
||||||
|
chatMessageRequest.Temperature.Should().Be(0.5m);
|
||||||
|
chatMessageRequest.TopK.Should().Be(10);
|
||||||
|
chatMessageRequest.TopP.Should().Be(0.5m);
|
||||||
|
chatMessageRequest.ToolChoice.Should().BeOfType<AutoToolChoice>();
|
||||||
|
chatMessageRequest.ToolChoice!.Type.Should().Be("auto");
|
||||||
|
chatMessageRequest.Tools.Should().HaveCount(1);
|
||||||
|
chatMessageRequest.Tools![0].Name.Should().Be("test-tool");
|
||||||
|
chatMessageRequest.Tools[0].Description.Should().Be("test-description");
|
||||||
|
chatMessageRequest.Tools[0].InputSchema.Type.Should().Be("object");
|
||||||
|
chatMessageRequest.Tools[0].InputSchema.Properties.Should().HaveCount(1);
|
||||||
|
chatMessageRequest.Tools[0].InputSchema.Properties["test-property"].Type.Should().Be("string");
|
||||||
|
chatMessageRequest.Tools[0].InputSchema.Properties["test-property"].Description.Should().Be("test-description");
|
||||||
|
chatMessageRequest.Tools[0].InputSchema.Required.Should().HaveCount(1);
|
||||||
|
chatMessageRequest.Tools[0].InputSchema.Required[0].Should().Be("test-property");
|
||||||
|
|
||||||
|
chatMessageRequest.Messages[0].Content.Should().HaveCount(1);
|
||||||
|
chatMessageRequest.Messages[0].Content[0].Should().BeOfType<ToolResultContent>();
|
||||||
|
|
||||||
|
var toolResultContent = chatMessageRequest.Messages[0].Content[0] as ToolResultContent;
|
||||||
|
toolResultContent!.Type.Should().Be("tool_result");
|
||||||
|
toolResultContent.ToolUseId.Should().Be("test-tool");
|
||||||
|
toolResultContent.Content.Should().Be("test-value");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void JsonDeserialization_WhenDeserializedWithUnknownContent_ItShouldThrowJsonException()
|
||||||
|
{
|
||||||
|
var action = () => Deserialize<ChatMessageRequest>(_testJsonWithUnknownContent);
|
||||||
|
|
||||||
|
action.Should().Throw<JsonException>();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,78 @@
|
|||||||
|
namespace AnthropicClient.Tests.Unit.Models;
|
||||||
|
|
||||||
|
public class ChatMessageTests : SerializationTest
|
||||||
|
{
|
||||||
|
private readonly string _testJson = @"{
|
||||||
|
""role"": ""assistant"",
|
||||||
|
""content"": [
|
||||||
|
{ ""text"": ""text"", ""type"": ""text"" }
|
||||||
|
]
|
||||||
|
}";
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Constructor_WhenCalled_ItShouldInitializeProperties()
|
||||||
|
{
|
||||||
|
var role = "assistant";
|
||||||
|
var content = new List<Content> { new TextContent("text") };
|
||||||
|
|
||||||
|
var chatMessage = new ChatMessage(role, content);
|
||||||
|
|
||||||
|
chatMessage.Role.Should().Be(role);
|
||||||
|
chatMessage.Content.Should().BeSameAs(content);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Constructor_WhenCalledAndRoleIsNull_ItShouldThrowArgumentNullException()
|
||||||
|
{
|
||||||
|
var content = new List<Content> { new TextContent("text") };
|
||||||
|
|
||||||
|
var action = () => new ChatMessage(null!, content);
|
||||||
|
|
||||||
|
action.Should().Throw<ArgumentNullException>();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Constructor_WhenCalledAndContentIsNull_ItShouldThrowArgumentNullException()
|
||||||
|
{
|
||||||
|
var role = "assistant";
|
||||||
|
|
||||||
|
var action = () => new ChatMessage(role, null!);
|
||||||
|
|
||||||
|
action.Should().Throw<ArgumentNullException>();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Constructor_WhenCalledAndRoleIsInvalid_ItShouldThrowArgumentException()
|
||||||
|
{
|
||||||
|
var role = "invalid";
|
||||||
|
var content = new List<Content> { new TextContent("text") };
|
||||||
|
|
||||||
|
var action = () => new ChatMessage(role, content);
|
||||||
|
|
||||||
|
action.Should().Throw<ArgumentException>();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void JsonSerialization_WhenSerialized_ItShouldReturnJsonString()
|
||||||
|
{
|
||||||
|
var role = "assistant";
|
||||||
|
var content = new List<Content> { new TextContent("text") };
|
||||||
|
var chatMessage = new ChatMessage(role, content);
|
||||||
|
|
||||||
|
var actual = Serialize(chatMessage);
|
||||||
|
|
||||||
|
JsonAssert.Equal(_testJson, actual);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void JsonDeserialization_WhenDeserialized_ItShouldReturnChatMessage()
|
||||||
|
{
|
||||||
|
var chatMessage = Deserialize<ChatMessage>(_testJson);
|
||||||
|
|
||||||
|
chatMessage.Should().NotBeNull();
|
||||||
|
chatMessage!.Role.Should().Be("assistant");
|
||||||
|
chatMessage.Content.Should().HaveCount(1);
|
||||||
|
chatMessage.Content[0].Should().BeOfType<TextContent>();
|
||||||
|
chatMessage.Content[0].As<TextContent>().Text.Should().Be("text");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,120 @@
|
|||||||
|
namespace AnthropicClient.Tests.Unit.Models;
|
||||||
|
|
||||||
|
public class ChatResponseTests : SerializationTest
|
||||||
|
{
|
||||||
|
[Fact]
|
||||||
|
public void Constructor_WhenCalled_ShouldInitializeProperties()
|
||||||
|
{
|
||||||
|
var expectedId = "id";
|
||||||
|
var expectedModel = "model";
|
||||||
|
var expectedRole = "role";
|
||||||
|
var expectedStopReason = "stop reason";
|
||||||
|
var expectedStopSequence = "stop sequence";
|
||||||
|
var expectedType = "type";
|
||||||
|
var expectedUsage = new ChatUsage
|
||||||
|
{
|
||||||
|
InputTokens = 1,
|
||||||
|
OutputTokens = 2
|
||||||
|
};
|
||||||
|
var expectedContent = new List<Content>
|
||||||
|
{
|
||||||
|
new TextContent("text content"),
|
||||||
|
};
|
||||||
|
|
||||||
|
var chatResponse = new ChatResponse
|
||||||
|
{
|
||||||
|
Id = expectedId,
|
||||||
|
Model = expectedModel,
|
||||||
|
Role = expectedRole,
|
||||||
|
StopReason = expectedStopReason,
|
||||||
|
StopSequence = expectedStopSequence,
|
||||||
|
Type = expectedType,
|
||||||
|
Usage = expectedUsage,
|
||||||
|
Content = expectedContent
|
||||||
|
};
|
||||||
|
|
||||||
|
chatResponse.Id.Should().Be(expectedId);
|
||||||
|
chatResponse.Model.Should().Be(expectedModel);
|
||||||
|
chatResponse.Role.Should().Be(expectedRole);
|
||||||
|
chatResponse.StopReason.Should().Be(expectedStopReason);
|
||||||
|
chatResponse.StopSequence.Should().Be(expectedStopSequence);
|
||||||
|
chatResponse.Type.Should().Be(expectedType);
|
||||||
|
chatResponse.Usage.Should().BeEquivalentTo(expectedUsage);
|
||||||
|
chatResponse.Content.Should().BeEquivalentTo(expectedContent);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void JsonSerialization_WhenCalled_ItShouldSerializeCorrectly()
|
||||||
|
{
|
||||||
|
var expectedJson = @"{
|
||||||
|
""id"": ""id"",
|
||||||
|
""model"": ""model"",
|
||||||
|
""role"": ""role"",
|
||||||
|
""stop_reason"": ""stop reason"",
|
||||||
|
""stop_sequence"": ""stop sequence"",
|
||||||
|
""type"": ""type"",
|
||||||
|
""usage"": { ""input_tokens"": 1, ""output_tokens"": 2 },
|
||||||
|
""content"": [
|
||||||
|
{ ""text"": ""text content"", ""type"": ""text"" }
|
||||||
|
]
|
||||||
|
}";
|
||||||
|
|
||||||
|
var chatResponse = new ChatResponse
|
||||||
|
{
|
||||||
|
Id = "id",
|
||||||
|
Model = "model",
|
||||||
|
Role = "role",
|
||||||
|
StopReason = "stop reason",
|
||||||
|
StopSequence = "stop sequence",
|
||||||
|
Type = "type",
|
||||||
|
Usage = new ChatUsage
|
||||||
|
{
|
||||||
|
InputTokens = 1,
|
||||||
|
OutputTokens = 2
|
||||||
|
},
|
||||||
|
Content =
|
||||||
|
[
|
||||||
|
new TextContent("text content"),
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
|
var actual = Serialize(chatResponse);
|
||||||
|
|
||||||
|
JsonAssert.Equal(expectedJson, actual);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void JsonDeserialization_WhenCalled_ItShouldDeserializeCorrectly()
|
||||||
|
{
|
||||||
|
var json = @"{
|
||||||
|
""id"": ""id"",
|
||||||
|
""model"": ""model"",
|
||||||
|
""role"": ""role"",
|
||||||
|
""stop_reason"": ""stop reason"",
|
||||||
|
""stop_sequence"": ""stop sequence"",
|
||||||
|
""type"": ""type"",
|
||||||
|
""usage"": { ""input_tokens"": 1, ""output_tokens"": 2 },
|
||||||
|
""content"": [
|
||||||
|
{ ""text"": ""text content"", ""type"": ""text"" }
|
||||||
|
]
|
||||||
|
}";
|
||||||
|
|
||||||
|
var chatResponse = Deserialize<ChatResponse>(json);
|
||||||
|
|
||||||
|
chatResponse!.Id.Should().Be("id");
|
||||||
|
chatResponse.Model.Should().Be("model");
|
||||||
|
chatResponse.Role.Should().Be("role");
|
||||||
|
chatResponse.StopReason.Should().Be("stop reason");
|
||||||
|
chatResponse.StopSequence.Should().Be("stop sequence");
|
||||||
|
chatResponse.Type.Should().Be("type");
|
||||||
|
chatResponse.Usage.Should().BeEquivalentTo(new ChatUsage
|
||||||
|
{
|
||||||
|
InputTokens = 1,
|
||||||
|
OutputTokens = 2
|
||||||
|
});
|
||||||
|
chatResponse.Content.Should().BeEquivalentTo(new List<Content>
|
||||||
|
{
|
||||||
|
new TextContent("text content"),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
namespace AnthropicClient.Tests.Unit.Models;
|
||||||
|
|
||||||
|
public class ChatUsageTests : SerializationTest
|
||||||
|
{
|
||||||
|
[Fact]
|
||||||
|
public void Constructor_WhenCalled_ShouldInitializeProperties()
|
||||||
|
{
|
||||||
|
var expectedInputTokens = 1;
|
||||||
|
var expectedOutputTokens = 2;
|
||||||
|
|
||||||
|
var chatUsage = new ChatUsage
|
||||||
|
{
|
||||||
|
InputTokens = expectedInputTokens,
|
||||||
|
OutputTokens = expectedOutputTokens
|
||||||
|
};
|
||||||
|
|
||||||
|
chatUsage.InputTokens.Should().Be(expectedInputTokens);
|
||||||
|
chatUsage.OutputTokens.Should().Be(expectedOutputTokens);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void JsonSerialization_WhenCalled_ItShouldSerializeCorrectly()
|
||||||
|
{
|
||||||
|
var expectedJson = @"{ ""input_tokens"": 1, ""output_tokens"": 2 }";
|
||||||
|
|
||||||
|
var chatUsage = new ChatUsage
|
||||||
|
{
|
||||||
|
InputTokens = 1,
|
||||||
|
OutputTokens = 2
|
||||||
|
};
|
||||||
|
|
||||||
|
var actual = Serialize(chatUsage);
|
||||||
|
|
||||||
|
JsonAssert.Equal(expectedJson, actual);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void JsonDeserialization_WhenCalled_ItShouldDeserializeCorrectly()
|
||||||
|
{
|
||||||
|
var json = @"{ ""input_tokens"": 1, ""output_tokens"": 2 }";
|
||||||
|
|
||||||
|
var chatUsage = Deserialize<ChatUsage>(json);
|
||||||
|
|
||||||
|
chatUsage!.InputTokens.Should().Be(1);
|
||||||
|
chatUsage.OutputTokens.Should().Be(2);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
namespace AnthropicClient.Tests.Unit.Models;
|
||||||
|
|
||||||
|
public class ContentTypeTests
|
||||||
|
{
|
||||||
|
[Fact]
|
||||||
|
public void Text_WhenCalled_ItShouldReturnText()
|
||||||
|
{
|
||||||
|
var expected = "text";
|
||||||
|
|
||||||
|
var actual = ContentType.Text;
|
||||||
|
|
||||||
|
actual.Should().Be(expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Image_WhenCalled_ItShouldReturnImage()
|
||||||
|
{
|
||||||
|
var expected = "image";
|
||||||
|
|
||||||
|
var actual = ContentType.Image;
|
||||||
|
|
||||||
|
actual.Should().Be(expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void ToolUse_WhenCalled_ItShouldReturnToolUse()
|
||||||
|
{
|
||||||
|
var expected = "tool_use";
|
||||||
|
|
||||||
|
var actual = ContentType.ToolUse;
|
||||||
|
|
||||||
|
actual.Should().Be(expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void ToolResult_WhenCalled_ItShouldReturnToolResult()
|
||||||
|
{
|
||||||
|
var expected = "tool_result";
|
||||||
|
|
||||||
|
var actual = ContentType.ToolResult;
|
||||||
|
|
||||||
|
actual.Should().Be(expected);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,74 @@
|
|||||||
|
namespace AnthropicClient.Tests.Unit.Models;
|
||||||
|
|
||||||
|
public class ImageContentTests : SerializationTest
|
||||||
|
{
|
||||||
|
private readonly string _testJson = @"{
|
||||||
|
""source"": {
|
||||||
|
""media_type"": ""image/png"",
|
||||||
|
""data"": ""data""
|
||||||
|
},
|
||||||
|
""type"": ""image""
|
||||||
|
}";
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Constructor_WhenCalled_ItShouldInitializeSource()
|
||||||
|
{
|
||||||
|
var expectedMediaType = "image/png";
|
||||||
|
var expectedData = "data";
|
||||||
|
|
||||||
|
var result = new ImageContent(expectedMediaType, expectedData);
|
||||||
|
|
||||||
|
result.Source.Should().BeEquivalentTo(new ImageSource(expectedMediaType, expectedData));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Constructor_WhenCalledAndMediaTypeIsNull_ItShouldThrowArgumentNullException()
|
||||||
|
{
|
||||||
|
var expectedData = "data";
|
||||||
|
|
||||||
|
var action = () => new ImageContent(null!, expectedData);
|
||||||
|
|
||||||
|
action.Should().Throw<ArgumentNullException>();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Constructor_WhenCalledAndDataIsNull_ItShouldThrowArgumentNullException()
|
||||||
|
{
|
||||||
|
var expectedMediaType = "image/png";
|
||||||
|
|
||||||
|
var action = () => new ImageContent(expectedMediaType, null!);
|
||||||
|
|
||||||
|
action.Should().Throw<ArgumentNullException>();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Constructor_WhenCalledWithInvalidMediaType_ItShouldThrowArgumentException()
|
||||||
|
{
|
||||||
|
var expectedMediaType = "invalid";
|
||||||
|
var expectedData = "data";
|
||||||
|
|
||||||
|
var action = () => new ImageContent(expectedMediaType, expectedData);
|
||||||
|
|
||||||
|
action.Should().Throw<ArgumentException>();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void JsonSerialization_WhenSerialized_ItShouldHaveExpectedShape()
|
||||||
|
{
|
||||||
|
var content = new ImageContent("image/png", "data");
|
||||||
|
|
||||||
|
var actual = Serialize(content);
|
||||||
|
|
||||||
|
JsonAssert.Equal(_testJson, actual);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void JsonDeserialization_WhenDeserialized_ItShouldHaveExpectedShape()
|
||||||
|
{
|
||||||
|
var expected = new ImageContent("image/png", "data");
|
||||||
|
|
||||||
|
var actual = Deserialize<ImageContent>(_testJson);
|
||||||
|
|
||||||
|
actual.Should().BeEquivalentTo(expected);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,57 @@
|
|||||||
|
namespace AnthropicClient.Tests.Unit.Models;
|
||||||
|
|
||||||
|
public class ImageTypeTests
|
||||||
|
{
|
||||||
|
[Fact]
|
||||||
|
public void Jpg_WhenCalled_ItShouldReturnJpg()
|
||||||
|
{
|
||||||
|
var expected = "image/jpeg";
|
||||||
|
|
||||||
|
var actual = ImageType.Jpg;
|
||||||
|
|
||||||
|
actual.Should().Be(expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Png_WhenCalled_ItShouldReturnPng()
|
||||||
|
{
|
||||||
|
var expected = "image/png";
|
||||||
|
|
||||||
|
var actual = ImageType.Png;
|
||||||
|
|
||||||
|
actual.Should().Be(expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Gif_WhenCalled_ItShouldReturnGif()
|
||||||
|
{
|
||||||
|
var expected = "image/gif";
|
||||||
|
|
||||||
|
var actual = ImageType.Gif;
|
||||||
|
|
||||||
|
actual.Should().Be(expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Webp_WhenCalled_ItShouldReturnWebp()
|
||||||
|
{
|
||||||
|
var expected = "image/webp";
|
||||||
|
|
||||||
|
var actual = ImageType.Webp;
|
||||||
|
|
||||||
|
actual.Should().Be(expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData("image/jpeg", true)]
|
||||||
|
[InlineData("image/png", true)]
|
||||||
|
[InlineData("image/gif", true)]
|
||||||
|
[InlineData("image/webp", true)]
|
||||||
|
[InlineData("invalid", false)]
|
||||||
|
public void IsValidImageType_WhenCalled_ItShouldReturnExpectedValue(string imageType, bool expected)
|
||||||
|
{
|
||||||
|
var actual = ImageType.IsValidImageType(imageType);
|
||||||
|
|
||||||
|
actual.Should().Be(expected);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,60 @@
|
|||||||
|
namespace AnthropicClient.Tests.Unit.Models;
|
||||||
|
|
||||||
|
public class InputPropertyTests : SerializationTest
|
||||||
|
{
|
||||||
|
private readonly string _testJson = @"{
|
||||||
|
""type"": ""type"",
|
||||||
|
""description"": ""description""
|
||||||
|
}";
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Constructor_WhenCalled_ShouldInitializeProperties()
|
||||||
|
{
|
||||||
|
var type = "type";
|
||||||
|
var description = "description";
|
||||||
|
|
||||||
|
var inputProperty = new InputProperty(type, description);
|
||||||
|
|
||||||
|
inputProperty.Type.Should().Be(type);
|
||||||
|
inputProperty.Description.Should().Be(description);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Constructor_WhenTypeIsNull_ShouldThrowArgumentNullException()
|
||||||
|
{
|
||||||
|
var description = "description";
|
||||||
|
|
||||||
|
var action = () => new InputProperty(null!, description);
|
||||||
|
|
||||||
|
action.Should().Throw<ArgumentNullException>().WithMessage("Value cannot be null. (Parameter 'type')");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Constructor_WhenDescriptionIsNull_ShouldThrowArgumentNullException()
|
||||||
|
{
|
||||||
|
var type = "type";
|
||||||
|
|
||||||
|
var action = () => new InputProperty(type, null!);
|
||||||
|
|
||||||
|
action.Should().Throw<ArgumentNullException>().WithMessage("Value cannot be null. (Parameter 'description')");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void JsonSerialization_WhenSerialized_ItShouldHaveExpectedShape()
|
||||||
|
{
|
||||||
|
var inputProperty = new InputProperty("type", "description");
|
||||||
|
|
||||||
|
var json = Serialize(inputProperty);
|
||||||
|
|
||||||
|
JsonAssert.Equal(_testJson, json);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void JsonDeserialization_WhenDeserialized_ItShouldHaveExpectedShape()
|
||||||
|
{
|
||||||
|
var inputProperty = Deserialize<InputProperty>(_testJson);
|
||||||
|
|
||||||
|
inputProperty!.Type.Should().Be("type");
|
||||||
|
inputProperty.Description.Should().Be("description");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,116 @@
|
|||||||
|
namespace AnthropicClient.Tests.Unit.Models;
|
||||||
|
|
||||||
|
public class InputSchemaTests : SerializationTest
|
||||||
|
{
|
||||||
|
private readonly string _testJson = @$"{{
|
||||||
|
""type"": ""object"",
|
||||||
|
""properties"": {{
|
||||||
|
""property1"": {{ ""type"": ""type1"", ""description"": ""description1"" }},
|
||||||
|
""property2"": {{ ""type"": ""type2"", ""description"": ""description2"" }}
|
||||||
|
}},
|
||||||
|
""required"": [""property1""]
|
||||||
|
}}";
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Constructor_WhenCalled_ItShouldInitializeProperties()
|
||||||
|
{
|
||||||
|
var inputProperties = new Dictionary<string, InputProperty>
|
||||||
|
{
|
||||||
|
{ "property1", new InputProperty("type1", "description1") },
|
||||||
|
{ "property2", new InputProperty("type2", "description2") }
|
||||||
|
};
|
||||||
|
|
||||||
|
var required = new List<string> { "property1" };
|
||||||
|
|
||||||
|
var inputSchema = new InputSchema(
|
||||||
|
properties: inputProperties,
|
||||||
|
required: required
|
||||||
|
);
|
||||||
|
|
||||||
|
inputSchema.Type.Should().Be("object");
|
||||||
|
inputSchema.Properties.Should().BeSameAs(inputProperties);
|
||||||
|
inputSchema.Required.Should().BeSameAs(required);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Constructor_WhenPropertiesIsNull_ItShouldThrowArgumentNullException()
|
||||||
|
{
|
||||||
|
var required = new List<string> { "property1" };
|
||||||
|
|
||||||
|
var action = () => new InputSchema(null!, required);
|
||||||
|
|
||||||
|
action.Should().Throw<ArgumentNullException>();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Constructor_WhenRequiredIsNull_ItShouldThrowArgumentNullException()
|
||||||
|
{
|
||||||
|
var inputProperties = new Dictionary<string, InputProperty>
|
||||||
|
{
|
||||||
|
{ "property1", new InputProperty("type1", "description1") },
|
||||||
|
{ "property2", new InputProperty("type2", "description2") }
|
||||||
|
};
|
||||||
|
|
||||||
|
var action = () => new InputSchema(inputProperties, null!);
|
||||||
|
|
||||||
|
action.Should().Throw<ArgumentNullException>();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Constructor_WhenRequiredPropertyIsNotInProperties_ItShouldThrowArgumentException()
|
||||||
|
{
|
||||||
|
var inputProperties = new Dictionary<string, InputProperty>
|
||||||
|
{
|
||||||
|
{ "property1", new InputProperty("type1", "description1") },
|
||||||
|
{ "property2", new InputProperty("type2", "description2") }
|
||||||
|
};
|
||||||
|
|
||||||
|
var required = new List<string> { "property3" };
|
||||||
|
|
||||||
|
var action = () => new InputSchema(inputProperties, required);
|
||||||
|
|
||||||
|
action.Should().Throw<ArgumentException>();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void JsonSerialization_WhenSerialized_ItShouldHaveExpectedShape()
|
||||||
|
{
|
||||||
|
var inputProperties = new Dictionary<string, InputProperty>
|
||||||
|
{
|
||||||
|
{ "property1", new InputProperty("type1", "description1") },
|
||||||
|
{ "property2", new InputProperty("type2", "description2") }
|
||||||
|
};
|
||||||
|
|
||||||
|
var required = new List<string> { "property1" };
|
||||||
|
|
||||||
|
var inputSchema = new InputSchema(
|
||||||
|
properties: inputProperties,
|
||||||
|
required: required
|
||||||
|
);
|
||||||
|
|
||||||
|
var actual = Serialize(inputSchema);
|
||||||
|
|
||||||
|
JsonAssert.Equal(_testJson, actual);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void JsonDeserialization_WhenDeserialized_ItShouldHaveExpectedShape()
|
||||||
|
{
|
||||||
|
var inputProperties = new Dictionary<string, InputProperty>
|
||||||
|
{
|
||||||
|
{ "property1", new InputProperty("type1", "description1") },
|
||||||
|
{ "property2", new InputProperty("type2", "description2") }
|
||||||
|
};
|
||||||
|
|
||||||
|
var required = new List<string> { "property1" };
|
||||||
|
|
||||||
|
var expected = new InputSchema(
|
||||||
|
properties: inputProperties,
|
||||||
|
required: required
|
||||||
|
);
|
||||||
|
|
||||||
|
var actual = Deserialize<InputSchema>(_testJson);
|
||||||
|
|
||||||
|
actual.Should().BeEquivalentTo(expected);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
namespace AnthropicClient.Tests.Unit.Models;
|
||||||
|
|
||||||
|
public class InvalidRequestErrorTests : SerializationTest
|
||||||
|
{
|
||||||
|
[Fact]
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Serialization_WhenSerialized_ItShouldHaveExpectedShape()
|
||||||
|
{
|
||||||
|
var message = "message";
|
||||||
|
|
||||||
|
var error = new InvalidRequestError(message);
|
||||||
|
|
||||||
|
var serialized = Serialize(error);
|
||||||
|
|
||||||
|
JsonAssert.Equal(
|
||||||
|
@"{
|
||||||
|
""type"": ""invalid_request_error"",
|
||||||
|
""message"": ""message""
|
||||||
|
}",
|
||||||
|
serialized
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Deserialization_WhenDeserialized_ItShouldHaveExpectedProperties()
|
||||||
|
{
|
||||||
|
var json = @"{
|
||||||
|
""type"": ""invalid_request_error"",
|
||||||
|
""message"": ""message""
|
||||||
|
}";
|
||||||
|
|
||||||
|
var error = Deserialize<InvalidRequestError>(json);
|
||||||
|
|
||||||
|
error!.Type.Should().Be("invalid_request_error");
|
||||||
|
error.Message.Should().Be("message");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
namespace AnthropicClient.Tests.Unit.Models;
|
||||||
|
|
||||||
|
public class MessageRoleTests
|
||||||
|
{
|
||||||
|
[Fact]
|
||||||
|
public void User_WhenCalled_ItShouldReturnUser()
|
||||||
|
{
|
||||||
|
var expected = "user";
|
||||||
|
|
||||||
|
var actual = MessageRole.User;
|
||||||
|
|
||||||
|
actual.Should().Be(expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Assistant_WhenCalled_ItShouldReturnAssistant()
|
||||||
|
{
|
||||||
|
var expected = "assistant";
|
||||||
|
|
||||||
|
var actual = MessageRole.Assistant;
|
||||||
|
|
||||||
|
actual.Should().Be(expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData("user", true)]
|
||||||
|
[InlineData("assistant", true)]
|
||||||
|
[InlineData("invalid", false)]
|
||||||
|
public void IsValidRole_WhenCalled_ItShouldReturnExpectedValue(string role, bool expected)
|
||||||
|
{
|
||||||
|
var actual = MessageRole.IsValidRole(role);
|
||||||
|
|
||||||
|
actual.Should().Be(expected);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
namespace AnthropicClient.Tests.Unit.Models;
|
||||||
|
|
||||||
|
public class NotFoundErrorTests : SerializationTest
|
||||||
|
{
|
||||||
|
private readonly string _testJson = @"{
|
||||||
|
""message"": ""message"",
|
||||||
|
""type"": ""not_found_error""
|
||||||
|
}";
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Constructor_WhenCalled_ItShouldInitializeProperties()
|
||||||
|
{
|
||||||
|
var expectedMessage = "message";
|
||||||
|
|
||||||
|
var actual = new NotFoundError(expectedMessage);
|
||||||
|
|
||||||
|
actual.Message.Should().Be(expectedMessage);
|
||||||
|
actual.Type.Should().Be("not_found_error");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void JsonSerialization_WhenSerialized_ItShouldHaveExpectedShape()
|
||||||
|
{
|
||||||
|
var error = new NotFoundError("message");
|
||||||
|
|
||||||
|
var actual = Serialize(error);
|
||||||
|
|
||||||
|
JsonAssert.Equal(_testJson, actual);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void JsonDeserialization_WhenDeserialized_ItShouldHaveExpectedShape()
|
||||||
|
{
|
||||||
|
var expected = new NotFoundError("message");
|
||||||
|
|
||||||
|
var actual = Deserialize<NotFoundError>(_testJson);
|
||||||
|
|
||||||
|
actual.Should().BeEquivalentTo(expected);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
namespace AnthropicClient.Tests.Unit.Models;
|
||||||
|
|
||||||
|
public class OverloadedErrorTests : SerializationTest
|
||||||
|
{
|
||||||
|
private readonly string _testJson = @"{
|
||||||
|
""message"": ""message"",
|
||||||
|
""type"": ""overloaded_error""
|
||||||
|
}";
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Constructor_WhenCalled_ItShouldInitializeProperties()
|
||||||
|
{
|
||||||
|
var expectedMessage = "message";
|
||||||
|
|
||||||
|
var actual = new OverloadedError(expectedMessage);
|
||||||
|
|
||||||
|
actual.Message.Should().Be(expectedMessage);
|
||||||
|
actual.Type.Should().Be("overloaded_error");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void JsonSerialization_WhenSerialized_ItShouldHaveExpectedShape()
|
||||||
|
{
|
||||||
|
var error = new OverloadedError("message");
|
||||||
|
|
||||||
|
var actual = Serialize(error);
|
||||||
|
|
||||||
|
JsonAssert.Equal(_testJson, actual);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void JsonDeserialization_WhenDeserialized_ItShouldHaveExpectedShape()
|
||||||
|
{
|
||||||
|
var expected = new OverloadedError("message");
|
||||||
|
|
||||||
|
var actual = Deserialize<OverloadedError>(_testJson);
|
||||||
|
|
||||||
|
actual.Should().BeEquivalentTo(expected);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
namespace AnthropicClient.Tests.Unit.Models;
|
||||||
|
|
||||||
|
public class PermissionErrorTests : SerializationTest
|
||||||
|
{
|
||||||
|
private readonly string _testJson = @"{
|
||||||
|
""message"": ""message"",
|
||||||
|
""type"": ""permission_error""
|
||||||
|
}";
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Constructor_WhenCalled_ItShouldInitializeProperties()
|
||||||
|
{
|
||||||
|
var expectedMessage = "message";
|
||||||
|
|
||||||
|
var actual = new PermissionError(expectedMessage);
|
||||||
|
|
||||||
|
actual.Message.Should().Be(expectedMessage);
|
||||||
|
actual.Type.Should().Be("permission_error");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void JsonSerialization_WhenSerialized_ItShouldHaveExpectedShape()
|
||||||
|
{
|
||||||
|
var error = new PermissionError("message");
|
||||||
|
|
||||||
|
var actual = Serialize(error);
|
||||||
|
|
||||||
|
JsonAssert.Equal(_testJson, actual);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void JsonDeserialization_WhenDeserialized_ItShouldHaveExpectedShape()
|
||||||
|
{
|
||||||
|
var expected = new PermissionError("message");
|
||||||
|
|
||||||
|
var actual = Deserialize<PermissionError>(_testJson);
|
||||||
|
|
||||||
|
actual.Should().BeEquivalentTo(expected);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
namespace AnthropicClient.Tests.Unit.Models;
|
||||||
|
|
||||||
|
public class RateLimitErrorTests : SerializationTest
|
||||||
|
{
|
||||||
|
private readonly string _testJson = @"{
|
||||||
|
""message"": ""message"",
|
||||||
|
""type"": ""rate_limit_error""
|
||||||
|
}";
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Constructor_WhenCalled_ItShouldInitializeProperties()
|
||||||
|
{
|
||||||
|
var expectedMessage = "message";
|
||||||
|
|
||||||
|
var actual = new RateLimitError(expectedMessage);
|
||||||
|
|
||||||
|
actual.Message.Should().Be(expectedMessage);
|
||||||
|
actual.Type.Should().Be("rate_limit_error");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void JsonSerialization_WhenSerialized_ItShouldHaveExpectedShape()
|
||||||
|
{
|
||||||
|
var error = new RateLimitError("message");
|
||||||
|
|
||||||
|
var actual = Serialize(error);
|
||||||
|
|
||||||
|
JsonAssert.Equal(_testJson, actual);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void JsonDeserialization_WhenDeserialized_ItShouldHaveExpectedShape()
|
||||||
|
{
|
||||||
|
var expected = new RateLimitError("message");
|
||||||
|
|
||||||
|
var actual = Deserialize<RateLimitError>(_testJson);
|
||||||
|
|
||||||
|
actual.Should().BeEquivalentTo(expected);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
namespace AnthropicClient.Tests.Unit.Models;
|
||||||
|
|
||||||
|
public class SpecificToolChoiceTests : SerializationTest
|
||||||
|
{
|
||||||
|
private readonly string _testToolName = "tool";
|
||||||
|
private string GetTestJson(string name) => @$"{{
|
||||||
|
""name"": ""{name}"",
|
||||||
|
""type"": ""tool""
|
||||||
|
}}";
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Constructor_WhenCalled_ItShouldSetTypeToTool()
|
||||||
|
{
|
||||||
|
var expectedType = "tool";
|
||||||
|
|
||||||
|
var actual = new SpecificToolChoice(_testToolName);
|
||||||
|
|
||||||
|
actual.Name.Should().Be(_testToolName);
|
||||||
|
actual.Type.Should().Be(expectedType);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Constructor_WhenCalledWithNull_ItShouldThrowArgumentNullException()
|
||||||
|
{
|
||||||
|
var action = () => new SpecificToolChoice(null!);
|
||||||
|
|
||||||
|
action.Should().Throw<ArgumentNullException>();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void JsonSerialization_WhenSerialized_ItShouldHaveExpectedShape()
|
||||||
|
{
|
||||||
|
var expected = GetTestJson(_testToolName);
|
||||||
|
|
||||||
|
var choice = new SpecificToolChoice(_testToolName);
|
||||||
|
|
||||||
|
var actual = Serialize(choice);
|
||||||
|
|
||||||
|
JsonAssert.Equal(expected, actual);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void JsonDeserialization_WhenDeserialized_ItShouldHaveExpectedShape()
|
||||||
|
{
|
||||||
|
var expected = new SpecificToolChoice(_testToolName);
|
||||||
|
|
||||||
|
var actual = Deserialize<SpecificToolChoice>(GetTestJson(_testToolName));
|
||||||
|
|
||||||
|
actual.Should().BeEquivalentTo(expected);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
namespace AnthropicClient.Tests.Unit.Models;
|
||||||
|
|
||||||
|
public class StopReasonTypeTests
|
||||||
|
{
|
||||||
|
[Fact]
|
||||||
|
public void EndTurn_WhenCalled_ItShouldReturnEndTurn()
|
||||||
|
{
|
||||||
|
var expected = "end_turn";
|
||||||
|
|
||||||
|
var actual = StopReasonType.EndTurn;
|
||||||
|
|
||||||
|
actual.Should().Be(expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void MaxTokens_WhenCalled_ItShouldReturnMaxTokens()
|
||||||
|
{
|
||||||
|
var expected = "max_tokens";
|
||||||
|
|
||||||
|
var actual = StopReasonType.MaxTokens;
|
||||||
|
|
||||||
|
actual.Should().Be(expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void StopSequence_WhenCalled_ItShouldReturnStopSequence()
|
||||||
|
{
|
||||||
|
var expected = "stop_sequence";
|
||||||
|
|
||||||
|
var actual = StopReasonType.StopSequence;
|
||||||
|
|
||||||
|
actual.Should().Be(expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void ToolUse_WhenCalled_ItShouldReturnToolUse()
|
||||||
|
{
|
||||||
|
var expected = "tool_use";
|
||||||
|
|
||||||
|
var actual = StopReasonType.ToolUse;
|
||||||
|
|
||||||
|
actual.Should().Be(expected);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
namespace AnthropicClient.Tests.Unit.Models;
|
||||||
|
|
||||||
|
public class TextContentTests : SerializationTest
|
||||||
|
{
|
||||||
|
private readonly string _testJson = @"{ ""text"": ""text"", ""type"": ""text"" }";
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Constructor_WhenCalled_ItShouldInitializeText()
|
||||||
|
{
|
||||||
|
var expectedText = "text";
|
||||||
|
|
||||||
|
var result = new TextContent(expectedText);
|
||||||
|
|
||||||
|
result.Text.Should().Be(expectedText);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Constructor_WhenCalledAndTextIsNull_ItShouldThrowArgumentNullException()
|
||||||
|
{
|
||||||
|
var action = () => new TextContent(null!);
|
||||||
|
|
||||||
|
action.Should().Throw<ArgumentNullException>();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void JsonSerialization_WhenSerialized_ItShouldHaveExpectedShape()
|
||||||
|
{
|
||||||
|
var content = new TextContent("text");
|
||||||
|
|
||||||
|
var actual = Serialize(content);
|
||||||
|
|
||||||
|
JsonAssert.Equal(_testJson, actual);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void JsonDeserialization_WhenDeserialized_ItShouldHaveExpectedShape()
|
||||||
|
{
|
||||||
|
var expected = new TextContent("text");
|
||||||
|
|
||||||
|
var actual = Deserialize<TextContent>(_testJson);
|
||||||
|
|
||||||
|
actual.Should().BeEquivalentTo(expected);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
namespace AnthropicClient.Tests.Unit.Models;
|
||||||
|
|
||||||
|
public class ToolChoiceTypeTests
|
||||||
|
{
|
||||||
|
[Fact]
|
||||||
|
public void Auto_WhenCalled_ItShouldReturnAuto()
|
||||||
|
{
|
||||||
|
var expected = "auto";
|
||||||
|
|
||||||
|
var actual = ToolChoiceType.Auto;
|
||||||
|
|
||||||
|
actual.Should().Be(expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Any_WhenCalled_ItShouldReturnAny()
|
||||||
|
{
|
||||||
|
var expected = "any";
|
||||||
|
|
||||||
|
var actual = ToolChoiceType.Any;
|
||||||
|
|
||||||
|
Assert.Equal(expected, actual);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Tool_WhenCalled_ItShouldReturnTool()
|
||||||
|
{
|
||||||
|
var expected = "tool";
|
||||||
|
|
||||||
|
var actual = ToolChoiceType.Tool;
|
||||||
|
|
||||||
|
Assert.Equal(expected, actual);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData("auto", true)]
|
||||||
|
[InlineData("any", true)]
|
||||||
|
[InlineData("tool", true)]
|
||||||
|
[InlineData("invalid", false)]
|
||||||
|
public void IsValidType_WhenCalledWithType_ItShouldReturnExpectedResult(string type, bool expected)
|
||||||
|
{
|
||||||
|
var actual = ToolChoiceType.IsValidType(type);
|
||||||
|
|
||||||
|
Assert.Equal(expected, actual);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,79 @@
|
|||||||
|
namespace AnthropicClient.Tests.Unit.Models;
|
||||||
|
|
||||||
|
public class ToolResultContentTests : SerializationTest
|
||||||
|
{
|
||||||
|
[Fact]
|
||||||
|
public void Constructor_WhenCalled_ItShouldInitializeProperties()
|
||||||
|
{
|
||||||
|
var toolUseId = Guid.NewGuid().ToString();
|
||||||
|
var content = "content";
|
||||||
|
|
||||||
|
var actual = new ToolResultContent(toolUseId, content);
|
||||||
|
|
||||||
|
actual.ToolUseId.Should().Be(toolUseId);
|
||||||
|
actual.Content.Should().Be(content);
|
||||||
|
actual.Type.Should().Be("tool_result");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Constructor_WhenCalledAndToolUseIdIsNull_ItShouldThrowArgumentNullException()
|
||||||
|
{
|
||||||
|
var content = "content";
|
||||||
|
|
||||||
|
var action = () => new ToolResultContent(null!, content);
|
||||||
|
|
||||||
|
action.Should().Throw<ArgumentNullException>();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Constructor_WhenCalledAndContentIsNull_ItShouldThrowArgumentNullException()
|
||||||
|
{
|
||||||
|
var toolUseId = Guid.NewGuid().ToString();
|
||||||
|
|
||||||
|
var action = () => new ToolResultContent(toolUseId, null!);
|
||||||
|
|
||||||
|
action.Should().Throw<ArgumentNullException>();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void JsonSerialization_WhenSerialized_ItShouldReturnJsonString()
|
||||||
|
{
|
||||||
|
var toolUseId = Guid.NewGuid().ToString();
|
||||||
|
var content = "content";
|
||||||
|
|
||||||
|
var expectedJson = @$"{{
|
||||||
|
""tool_use_id"": ""{toolUseId}"",
|
||||||
|
""content"": ""{content}"",
|
||||||
|
""type"": ""tool_result""
|
||||||
|
}}";
|
||||||
|
|
||||||
|
var toolResultContent = new ToolResultContent
|
||||||
|
{
|
||||||
|
ToolUseId = toolUseId,
|
||||||
|
Content = content
|
||||||
|
};
|
||||||
|
|
||||||
|
var actual = Serialize(toolResultContent);
|
||||||
|
|
||||||
|
JsonAssert.Equal(expectedJson, actual);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void JsonDeserialization_WhenDeserialized_ItShouldReturnToolResultContent()
|
||||||
|
{
|
||||||
|
var toolUseId = Guid.NewGuid().ToString();
|
||||||
|
var content = "content";
|
||||||
|
|
||||||
|
var json = @$"{{
|
||||||
|
""tool_use_id"": ""{toolUseId}"",
|
||||||
|
""content"": ""{content}"",
|
||||||
|
""type"": ""tool_result""
|
||||||
|
}}";
|
||||||
|
|
||||||
|
var actual = Deserialize<ToolResultContent>(json);
|
||||||
|
|
||||||
|
actual!.ToolUseId.Should().Be(toolUseId);
|
||||||
|
actual.Content.Should().Be(content);
|
||||||
|
actual.Type.Should().Be("tool_result");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,88 @@
|
|||||||
|
namespace AnthropicClient.Tests.Unit.Models;
|
||||||
|
|
||||||
|
public class ToolTests : SerializationTest
|
||||||
|
{
|
||||||
|
private readonly string _testJson = @"{
|
||||||
|
""name"": ""test-name"",
|
||||||
|
""description"": ""test-description"",
|
||||||
|
""input_schema"": { ""type"": ""object"", ""properties"": {}, ""required"": [] }
|
||||||
|
}";
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Constructor_WhenCalled_ItShouldInitializeProperties()
|
||||||
|
{
|
||||||
|
var name = "test-name";
|
||||||
|
var description = "test-description";
|
||||||
|
var inputSchema = new InputSchema();
|
||||||
|
|
||||||
|
var tool = new Tool(
|
||||||
|
name: name,
|
||||||
|
description: description,
|
||||||
|
inputSchema: inputSchema
|
||||||
|
);
|
||||||
|
|
||||||
|
tool.Name.Should().Be(name);
|
||||||
|
tool.Description.Should().Be(description);
|
||||||
|
tool.InputSchema.Should().Be(inputSchema);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Constructor_WhenCalledAndNameIsNull_ItShouldThrowArgumentNullException()
|
||||||
|
{
|
||||||
|
var action = () => new Tool(
|
||||||
|
name: null!,
|
||||||
|
description: "test-description",
|
||||||
|
inputSchema: new()
|
||||||
|
);
|
||||||
|
|
||||||
|
action.Should().Throw<ArgumentNullException>();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Constructor_WhenCalledAndDescriptionIsNull_ItShouldThrowArgumentNullException()
|
||||||
|
{
|
||||||
|
var action = () => new Tool(
|
||||||
|
name: "test-name",
|
||||||
|
description: null!,
|
||||||
|
inputSchema: new()
|
||||||
|
);
|
||||||
|
|
||||||
|
action.Should().Throw<ArgumentNullException>();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Constructor_WhenCalledAndInputSchemaIsNull_ItShouldThrowArgumentNullException()
|
||||||
|
{
|
||||||
|
var action = () => new Tool(
|
||||||
|
name: "test-name",
|
||||||
|
description: "test-description",
|
||||||
|
inputSchema: null!
|
||||||
|
);
|
||||||
|
|
||||||
|
action.Should().Throw<ArgumentNullException>();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void JsonSerialization_WhenSerialized_ItShouldHaveExpectedShape()
|
||||||
|
{
|
||||||
|
var tool = new Tool(
|
||||||
|
name: "test-name",
|
||||||
|
description: "test-description",
|
||||||
|
inputSchema: new()
|
||||||
|
);
|
||||||
|
|
||||||
|
var json = Serialize(tool);
|
||||||
|
|
||||||
|
JsonAssert.Equal(_testJson, json);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void JsonDeserialization_WhenDeserialized_ItShouldHaveExpectedShape()
|
||||||
|
{
|
||||||
|
var tool = Deserialize<Tool>(_testJson);
|
||||||
|
|
||||||
|
tool!.Name.Should().Be("test-name");
|
||||||
|
tool.Description.Should().Be("test-description");
|
||||||
|
tool.InputSchema.Should().BeEquivalentTo(new InputSchema());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,83 @@
|
|||||||
|
namespace AnthropicClient.Tests.Unit.Models;
|
||||||
|
|
||||||
|
public class ToolUseContentTests : SerializationTest
|
||||||
|
{
|
||||||
|
[Fact]
|
||||||
|
public void Constructor_WhenCalled_ItShouldInitializeProperties()
|
||||||
|
{
|
||||||
|
var id = Guid.NewGuid().ToString();
|
||||||
|
var name = "name";
|
||||||
|
var input = new Dictionary<string, object?> { { "name", "input" } };
|
||||||
|
|
||||||
|
var actual = new ToolUseContent()
|
||||||
|
{
|
||||||
|
Id = id,
|
||||||
|
Name = name,
|
||||||
|
Input = input
|
||||||
|
};
|
||||||
|
|
||||||
|
actual.Id.Should().Be(id);
|
||||||
|
actual.Name.Should().Be(name);
|
||||||
|
actual.Input.Should().BeEquivalentTo(input);
|
||||||
|
actual.Type.Should().Be("tool_use");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void JsonSerialization_WhenSerialized_ItShouldReturnJsonString()
|
||||||
|
{
|
||||||
|
var id = Guid.NewGuid().ToString();
|
||||||
|
var name = "name";
|
||||||
|
var input = new Dictionary<string, object?> { { "name", "input" } };
|
||||||
|
|
||||||
|
var expectedJson = @$"{{
|
||||||
|
""id"": ""{id}"",
|
||||||
|
""name"": ""{name}"",
|
||||||
|
""input"": {{ ""name"": ""input"" }},
|
||||||
|
""type"": ""tool_use""
|
||||||
|
}}";
|
||||||
|
|
||||||
|
var toolUseContent = new ToolUseContent()
|
||||||
|
{
|
||||||
|
Id = id,
|
||||||
|
Name = name,
|
||||||
|
Input = input
|
||||||
|
};
|
||||||
|
|
||||||
|
var actual = Serialize(toolUseContent);
|
||||||
|
|
||||||
|
JsonAssert.Equal(expectedJson, actual);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void JsonDeserialization_WhenDeserialized_ItShouldReturnToolUseContent()
|
||||||
|
{
|
||||||
|
var id = Guid.NewGuid().ToString();
|
||||||
|
var name = "name";
|
||||||
|
var input = new Dictionary<string, object?> { { "name", "input" } };
|
||||||
|
|
||||||
|
var json = @$"{{
|
||||||
|
""id"": ""{id}"",
|
||||||
|
""name"": ""{name}"",
|
||||||
|
""input"": {{ ""name"": ""input"" }},
|
||||||
|
""type"": ""tool_use""
|
||||||
|
}}";
|
||||||
|
|
||||||
|
var expected = new ToolUseContent()
|
||||||
|
{
|
||||||
|
Id = id,
|
||||||
|
Name = name,
|
||||||
|
Input = input
|
||||||
|
};
|
||||||
|
|
||||||
|
var actual = Deserialize<ToolUseContent>(json);
|
||||||
|
|
||||||
|
actual!.Id.Should().Be(expected.Id);
|
||||||
|
actual.Name.Should().Be(expected.Name);
|
||||||
|
|
||||||
|
var actualInput = actual.Input.GetValueOrDefault("name")!.ToString();
|
||||||
|
var expectedInput = expected.Input.GetValueOrDefault("name")!.ToString();
|
||||||
|
actualInput.Should().Be(expectedInput);
|
||||||
|
|
||||||
|
actual.Type.Should().Be(expected.Type);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
using AnthropicClient.Json;
|
||||||
|
|
||||||
|
namespace AnthropicClient.Tests.Unit;
|
||||||
|
|
||||||
|
public class SerializationTest
|
||||||
|
{
|
||||||
|
private readonly JsonSerializerOptions _jsonSerializerOptions = JsonSerializationOptions.DefaultOptions;
|
||||||
|
|
||||||
|
protected string Serialize<T>(T obj) => JsonSerializer.Serialize(obj, _jsonSerializerOptions);
|
||||||
|
|
||||||
|
protected T? Deserialize<T>(string json) => JsonSerializer.Deserialize<T>(json, _jsonSerializerOptions);
|
||||||
|
}
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
namespace AnthropicClient.Tests.Unit.Utils;
|
||||||
|
|
||||||
|
public class ArgumentValidatorTests
|
||||||
|
{
|
||||||
|
[Fact]
|
||||||
|
public void ThrowIfNull_WhenValueIsNull_ItShouldThrowArgumentNullException()
|
||||||
|
{
|
||||||
|
object? value = null;
|
||||||
|
|
||||||
|
var action = () => ArgumentValidator.ThrowIfNull(value, "value");
|
||||||
|
|
||||||
|
action.Should().Throw<ArgumentNullException>();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void ThrowIfNull_WhenValueIsNotNull_ItShouldNotThrow()
|
||||||
|
{
|
||||||
|
var value = new object();
|
||||||
|
|
||||||
|
var action = () => ArgumentValidator.ThrowIfNull(value, "value");
|
||||||
|
|
||||||
|
action.Should().NotThrow();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
global using FluentAssertions;
|
||||||
|
|
||||||
|
global using AnthropicClient.Models;
|
||||||
|
global using AnthropicClient.Utils;
|
||||||
|
|
||||||
|
global using System.Text.Json;
|
||||||
|
global using System.Text.Json.JsonDiffPatch.Xunit;
|
||||||
|
|
||||||
|
global using Microsoft.Extensions.Configuration;
|
||||||
Reference in New Issue
Block a user