diff --git a/README.md b/README.md index 04a1744..246ebbb 100644 --- a/README.md +++ b/README.md @@ -201,6 +201,72 @@ if (response.IsFailure) Console.WriteLine("Model Id: {0}", response.Value.Id); ``` +### Files API + +The `AnthropicApiClient` provides support for the Anthropic Files API, which allows you to upload and manage files for use with the Anthropic API. + +#### Create a File + +You can create a file using the Files API in several ways: + +##### From a Byte Array + +```csharp +using AnthropicClient; +using AnthropicClient.Models; +using System.Text; + +// Create a file from a byte array +var content = "This is a sample text file for the Anthropic Files API."; +var fileBytes = Encoding.UTF8.GetBytes(content); + +var request = new CreateFileRequest(fileBytes, "sample.txt", "text/plain"); +var response = await client.CreateFileAsync(request); + +if (response.IsSuccess) +{ + Console.WriteLine("File created successfully from byte array!"); + Console.WriteLine("File ID: {0}", response.Value.Id); + Console.WriteLine("File Name: {0}", response.Value.FileName); + Console.WriteLine("File Type: {0}", response.Value.FileType); + Console.WriteLine("File Size: {0} bytes", response.Value.SizeBytes); +} +else +{ + Console.WriteLine("Failed to create file"); + Console.WriteLine("Error Type: {0}", response.Error.Error.Type); + Console.WriteLine("Error Message: {0}", response.Error.Error.Message); +} +``` + +##### From a Stream + +```csharp +using AnthropicClient; +using AnthropicClient.Models; +using System.Text; + +// Create a file from a stream +using var stream = new MemoryStream(Encoding.UTF8.GetBytes("Stream content")); +var request = new CreateFileRequest(stream, "stream-file.txt", "text/plain"); +var response = await client.CreateFileAsync(request); + +if (response.IsSuccess) +{ + Console.WriteLine("File created successfully from stream!"); + Console.WriteLine("File ID: {0}", response.Value.Id); +} +else +{ + Console.WriteLine("Failed to create file"); + Console.WriteLine("Error Type: {0}", response.Error.Error.Type); + Console.WriteLine("Error Message: {0}", response.Error.Error.Message); +} +``` + +> [!NOTE] +> The Files API has certain limitations on file size, supported file types, and usage quotas. Please refer to the [Anthropic API Documentation](https://docs.anthropic.com/en/docs/build-with-claude/files) for the most up-to-date information on these limitations. + ### Create a message The `AnthropicApiClient` exposes a method named `CreateMessageAsync` that can be used to create a message. The method requires a `MessageRequest` or a `StreamMessageRequest` instance as a parameter. The `MessageRequest` class is used to create a message whose response is not streamed and the `StreamMessageRequest` class is used to create a message whose response is streamed. The `MessageRequest` instance's properties can be set to configure how the message is created. diff --git a/src/AnthropicClient/AnthropicApiClient.cs b/src/AnthropicClient/AnthropicApiClient.cs index 2e78ac2..cfcb63c 100644 --- a/src/AnthropicClient/AnthropicApiClient.cs +++ b/src/AnthropicClient/AnthropicApiClient.cs @@ -18,6 +18,7 @@ public class AnthropicApiClient : IAnthropicApiClient private string CountTokensEndpoint => $"{MessagesEndpoint}/count_tokens"; private string MessageBatchesEndpoint => $"{MessagesEndpoint}/batches"; private const string ModelsEndpoint = "models"; + private const string FilesEndpoint = "files"; private const string JsonContentType = "application/json"; private const string EventPrefix = "event:"; private const string DataPrefix = "data:"; @@ -380,6 +381,13 @@ public class AnthropicApiClient : IAnthropicApiClient return await CreateResultAsync(response); } + /// + public async Task> CreateFileAsync(CreateFileRequest request, CancellationToken cancellationToken = default) + { + var response = await SendFileRequestAsync(FilesEndpoint, request, cancellationToken); + return await CreateResultAsync(response); + } + private async IAsyncEnumerable>> GetAllPagesAsync(string endpoint, int limit = 20, [EnumeratorCancellation] CancellationToken cancellationToken = default) { var pagingRequest = new PagingRequest(limit: limit); @@ -462,6 +470,17 @@ public class AnthropicApiClient : IAnthropicApiClient return await _httpClient.PostAsync(endpoint, requestContent, cancellationToken); } + private async Task SendFileRequestAsync(string endpoint, CreateFileRequest request, CancellationToken cancellationToken = default) + { + using var multipartContent = new MultipartFormDataContent(); + + using var fileContent = new ByteArrayContent(request.File); + fileContent.Headers.ContentType = new MediaTypeHeaderValue(request.FileType); + multipartContent.Add(fileContent, "file", request.FileName); + + return await _httpClient.PostAsync(endpoint, multipartContent, cancellationToken); + } + private string Serialize(T obj) => JsonSerializer.Serialize(obj, JsonSerializationOptions.DefaultOptions); private T? Deserialize(string json) => JsonSerializer.Deserialize(json, JsonSerializationOptions.DefaultOptions); } \ No newline at end of file diff --git a/src/AnthropicClient/IAnthropicApiClient.cs b/src/AnthropicClient/IAnthropicApiClient.cs index e4f63b9..0dee03f 100644 --- a/src/AnthropicClient/IAnthropicApiClient.cs +++ b/src/AnthropicClient/IAnthropicApiClient.cs @@ -111,4 +111,12 @@ public interface IAnthropicApiClient /// A token to cancel the asynchronous operation. /// A task that represents the asynchronous operation. The task result contains the response as an where T is . Task> GetModelAsync(string modelId, CancellationToken cancellationToken = default); + + /// + /// Creates a file asynchronously using the Files API. + /// + /// The file creation request. + /// A token to cancel the asynchronous operation. + /// A task that represents the asynchronous operation. The task result contains the response as an where T is . + Task> CreateFileAsync(CreateFileRequest request, CancellationToken cancellationToken = default); } \ No newline at end of file diff --git a/src/AnthropicClient/Models/AnthropicFile.cs b/src/AnthropicClient/Models/AnthropicFile.cs new file mode 100644 index 0000000..0cc2ca6 --- /dev/null +++ b/src/AnthropicClient/Models/AnthropicFile.cs @@ -0,0 +1,53 @@ +using System.Text.Json.Serialization; + +namespace AnthropicClient.Models; + +/// +/// Represents a file object from the Anthropic Files API. +/// +public class AnthropicFile +{ + /// + /// Unique object identifier. + /// The format and length of IDs may change over time. + /// + [JsonPropertyName("id")] + public string Id { get; init; } = string.Empty; + + /// + /// Object type. + /// For files, this is always "file". + /// + [JsonPropertyName("type")] + public string Type { get; init; } = "file"; + + /// + /// Original filename of the uploaded file. + /// + [JsonPropertyName("file_name")] + public string FileName { get; init; } = string.Empty; + + /// + /// RFC 3339 datetime string representing when the file was created. + /// + [JsonPropertyName("created_at")] + public string CreatedAt { get; init; } = string.Empty; + + /// + /// Size of the file in bytes. + /// + [JsonPropertyName("size_bytes")] + public long SizeBytes { get; init; } + + /// + /// MIME type of the file. + /// + [JsonPropertyName("file_type")] + public string FileType { get; init; } = string.Empty; + + /// + /// Whether the file can be downloaded. + /// + [JsonPropertyName("downloadable")] + public bool Downloadable { get; init; } +} diff --git a/src/AnthropicClient/Models/CreateFileRequest.cs b/src/AnthropicClient/Models/CreateFileRequest.cs new file mode 100644 index 0000000..33bae0c --- /dev/null +++ b/src/AnthropicClient/Models/CreateFileRequest.cs @@ -0,0 +1,66 @@ +using System.Text.Json.Serialization; + +using AnthropicClient.Utils; + +namespace AnthropicClient.Models; + +/// +/// Represents a request to create a file via the Anthropic Files API. +/// +public class CreateFileRequest +{ + /// + /// The file content as a byte array. + /// + public byte[] File { get; } + + /// + /// The original filename of the file being uploaded. + /// + public string FileName { get; } + + /// + /// The MIME type of the file. + /// + public string FileType { get; } + + /// + /// Initializes a new instance of the class. + /// + /// The file content as a byte array. + /// The original filename of the file being uploaded. + /// The MIME type of the file. + /// Thrown when , , or is null. + public CreateFileRequest(byte[] file, string fileName, string fileType) + { + ArgumentValidator.ThrowIfNull(file, nameof(file)); + ArgumentValidator.ThrowIfNullOrWhitespace(fileName, nameof(fileName)); + ArgumentValidator.ThrowIfNullOrWhitespace(fileType, nameof(fileType)); + + File = file; + FileName = fileName; + FileType = fileType; + } + + /// + /// Initializes a new instance of the class from a stream. + /// + /// The stream containing the file content. + /// The original filename of the file being uploaded. + /// The MIME type of the file. + /// Thrown when , , or is null. + public CreateFileRequest(Stream stream, string fileName, string fileType) + { + ArgumentValidator.ThrowIfNull(stream, nameof(stream)); + ArgumentValidator.ThrowIfNullOrWhitespace(fileName, nameof(fileName)); + ArgumentValidator.ThrowIfNullOrWhitespace(fileType, nameof(fileType)); + + using var memoryStream = new MemoryStream(); + stream.CopyToAsync(memoryStream); + var fileContent = memoryStream.ToArray(); + + File = fileContent; + FileName = fileName; + FileType = fileType; + } +} diff --git a/tests/AnthropicClient.Tests/Unit/Models/CreateFileRequestTests.cs b/tests/AnthropicClient.Tests/Unit/Models/CreateFileRequestTests.cs new file mode 100644 index 0000000..e69de29