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