Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions Anthropic.SDK.Tests/SkillsTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -335,5 +335,13 @@ c is BashCodeExecutionToolResultContent bashCodeExecutionToolResultContent &&
bashCodeExecutionToolResultContent.Content is BashCodeExecutionOutputContent bashCodeExecutionOutputContent &&
!string.IsNullOrEmpty(bashCodeExecutionOutputContent.FileId)) != null);
}

[TestMethod]
public async Task GetSkills()
{
var client = new AnthropicClient();
var skills = await client.Skills.ListSkillsAsync();
Assert.IsNotNull(skills);
}
}
}
2 changes: 1 addition & 1 deletion Anthropic.SDK/AnthropicClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ public class AnthropicClient : IDisposable
/// <summary>
/// Version of the Anthropic Beta API
/// </summary>
public string AnthropicBetaVersion { get; set; } = "prompt-caching-2024-07-31,message-batches-2024-09-24,computer-use-2024-10-22,pdfs-2024-09-25,output-128k-2025-02-19,mcp-client-2025-04-04,code-execution-2025-08-25,skills-2025-10-02,files-api-2025-04-14";
public string AnthropicBetaVersion { get; set; } = "prompt-caching-2024-07-31,message-batches-2024-09-24,computer-use-2024-10-22,pdfs-2024-09-25,output-128k-2025-02-19,mcp-client-2025-04-04,code-execution-2025-08-25,files-api-2025-04-14";

/// <summary>
/// The API authentication information to use for API calls
Expand Down
20 changes: 10 additions & 10 deletions Anthropic.SDK/BaseEndpoint.cs
Original file line number Diff line number Diff line change
Expand Up @@ -112,9 +112,9 @@ private static void TryParseHeaderValue<T>(HttpResponseMessage message, string h
/// Makes an HTTP request and deserializes the response to the specified type without custom converters.
/// </summary>
protected async Task<T> HttpRequestSimple<T>(string url = null, HttpMethod verb = null,
object postData = null, CancellationToken ctx = default)
object postData = null, Dictionary<string, string> additionalHeaders = null, CancellationToken ctx = default)
{
var response = await HttpRequestRaw(url, verb, postData, false, ctx).ConfigureAwait(false);
var response = await HttpRequestRaw(url, verb, postData, false, additionalHeaders, ctx).ConfigureAwait(false);

// Optimization: Deserialize directly from HTTP response stream
// Avoids intermediate string allocation and UTF8 encoding conversion
Expand All @@ -128,14 +128,14 @@ protected async Task<T> HttpRequestSimple<T>(string url = null, HttpMethod verb
return res;
}

/// <summary>
/// Makes a raw HTTP request and returns the response.
/// </summary>
protected async Task<HttpResponseMessage> HttpRequestRaw(string url = null, HttpMethod verb = null,
object postData = null, bool streaming = false, CancellationToken ctx = default)
{
return await HttpRequestRaw(url, verb, postData, streaming, null, ctx).ConfigureAwait(false);
}
///// <summary>
///// Makes a raw HTTP request and returns the response.
///// </summary>
//protected async Task<HttpResponseMessage> HttpRequestRaw(string url = null, HttpMethod verb = null,
// object postData = null, bool streaming = false, CancellationToken ctx = default)
//{
// return await HttpRequestRaw(url, verb, postData, streaming, null, ctx).ConfigureAwait(false);
//}

/// <summary>
/// Makes a raw HTTP request and returns the response with additional headers.
Expand Down
8 changes: 4 additions & 4 deletions Anthropic.SDK/Batches/BatchesEndpoint.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ internal BatchesEndpoint(AnthropicClient client) : base(client) { }
/// <param name="ctx"></param>
public async Task<BatchResponse> CreateBatchAsync(List<BatchRequest> batches, CancellationToken ctx = default)
{
var response = await HttpRequestSimple<BatchResponse>(Url, HttpMethod.Post, new { requests = batches }, ctx).ConfigureAwait(false);
var response = await HttpRequestSimple<BatchResponse>(Url, HttpMethod.Post, new { requests = batches }, null, ctx).ConfigureAwait(false);

return response;
}
Expand All @@ -36,7 +36,7 @@ public async Task<BatchResponse> CreateBatchAsync(List<BatchRequest> batches, Ca
/// <param name="ctx"></param>
public async Task<BatchResponse> CancelBatchAsync(string batchId, CancellationToken ctx = default)
{
var response = await HttpRequestSimple<BatchResponse>(Url + $"/{batchId}/cancel", HttpMethod.Post, null, ctx).ConfigureAwait(false);
var response = await HttpRequestSimple<BatchResponse>(Url + $"/{batchId}/cancel", HttpMethod.Post, null, null, ctx).ConfigureAwait(false);

return response;
}
Expand All @@ -48,7 +48,7 @@ public async Task<BatchResponse> CancelBatchAsync(string batchId, CancellationTo
/// <param name="ctx"></param>
public async Task<BatchResponse> RetrieveBatchStatusAsync(string batchId, CancellationToken ctx = default)
{
var response = await HttpRequestSimple<BatchResponse>(Url + $"/{batchId}", HttpMethod.Get, null, ctx).ConfigureAwait(false);
var response = await HttpRequestSimple<BatchResponse>(Url + $"/{batchId}", HttpMethod.Get, null, null, ctx).ConfigureAwait(false);

return response;
}
Expand Down Expand Up @@ -104,7 +104,7 @@ public async Task<BatchList> ListBatchesAsync(string beforeId = null, string aft
url += $"&after_id={afterId}";
}

var response = await HttpRequestSimple<BatchList>(url, HttpMethod.Get, null, ctx).ConfigureAwait(false);
var response = await HttpRequestSimple<BatchList>(url, HttpMethod.Get, null, null, ctx).ConfigureAwait(false);

return response;
}
Expand Down
4 changes: 2 additions & 2 deletions Anthropic.SDK/EndpointBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ protected async IAsyncEnumerable<BatchLine> HttpStreamingRequestBatches(string u
HttpMethod verb = null,
object postData = null, [EnumeratorCancellation] CancellationToken ctx = default)
{
var response = await HttpRequestRaw(url, verb, postData, streaming: true, ctx).ConfigureAwait(false);
var response = await HttpRequestRaw(url, verb, postData, streaming: true, null, ctx).ConfigureAwait(false);
#if NET6_0_OR_GREATER
await using var stream = await response.Content.ReadAsStreamAsync(ctx).ConfigureAwait(false);
#else
Expand Down Expand Up @@ -139,7 +139,7 @@ protected async IAsyncEnumerable<string> HttpStreamingRequestBatchesJsonl(string
HttpMethod verb = null,
object postData = null, [EnumeratorCancellation] CancellationToken ctx = default)
{
var response = await HttpRequestRaw(url, verb, postData, streaming: true, ctx).ConfigureAwait(false);
var response = await HttpRequestRaw(url, verb, postData, streaming: true, null, ctx).ConfigureAwait(false);
#if NET6_0_OR_GREATER
await using var stream = await response.Content.ReadAsStreamAsync(ctx).ConfigureAwait(false);
#else
Expand Down
14 changes: 7 additions & 7 deletions Anthropic.SDK/Files/FilesEndpoint.cs
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ public async Task<FileListResponse> ListFilesAsync(
queryParams.Add($"after_id={afterId}");

var queryString = "?" + string.Join("&", queryParams);
return await HttpRequestSimple<FileListResponse>($"{Endpoint}{queryString}", HttpMethod.Get, null, cancellationToken);
return await HttpRequestSimple<FileListResponse>($"{Url}{queryString}", HttpMethod.Get, null, null, cancellationToken);
}

/// <summary>
Expand All @@ -67,7 +67,7 @@ public async Task<FileMetadata> GetFileMetadataAsync(
throw new ArgumentNullException(nameof(fileId), "File ID cannot be null or empty.");
}

return await HttpRequestSimple<FileMetadata>($"{Endpoint}/{fileId}", HttpMethod.Get, null, cancellationToken);
return await HttpRequestSimple<FileMetadata>($"{Url}/{fileId}", HttpMethod.Get, null, null, cancellationToken);
}

/// <summary>
Expand All @@ -86,7 +86,7 @@ public async Task<FileDeleteResponse> DeleteFileAsync(
throw new ArgumentNullException(nameof(fileId), "File ID cannot be null or empty.");
}

return await HttpRequestSimple<FileDeleteResponse>($"{Endpoint}/{fileId}", HttpMethod.Delete, null, cancellationToken);
return await HttpRequestSimple<FileDeleteResponse>($"{Url}/{fileId}", HttpMethod.Delete, null, null, cancellationToken);
}

/// <summary>
Expand Down Expand Up @@ -157,7 +157,7 @@ public async Task<FileMetadata> UploadFileBytesAsync(byte[] fileBytes, string fi
fileContent.Headers.ContentType = new MediaTypeHeaderValue(mimeType);
content.Add(fileContent, "file", fileName);

var result = await HttpRequestSimple<FileMetadata>(Url, HttpMethod.Post, content, ctx).ConfigureAwait(false);
var result = await HttpRequestSimple<FileMetadata>(Url, HttpMethod.Post, content, null, ctx).ConfigureAwait(false);

return result;
}
Expand Down Expand Up @@ -194,7 +194,7 @@ public async Task<FileMetadata> UploadFileStreamAsync(Stream fileStream, string
streamContent.Headers.ContentType = new MediaTypeHeaderValue(mimeType);
content.Add(streamContent, "file", fileName);

var result = await HttpRequestSimple<FileMetadata>(Url, HttpMethod.Post, content, ctx).ConfigureAwait(false);
var result = await HttpRequestSimple<FileMetadata>(Url, HttpMethod.Post, content, null, ctx).ConfigureAwait(false);

return result;
}
Expand All @@ -215,7 +215,7 @@ public async Task<byte[]> DownloadFileAsync(string fileId, string outputPath = n
}

var url = $"{Url}/{fileId}/content";
var response = await HttpRequestRaw(url, HttpMethod.Get, null, streaming: false, ctx).ConfigureAwait(false);
var response = await HttpRequestRaw(url, HttpMethod.Get, null, streaming: false, null, ctx).ConfigureAwait(false);

#if NET6_0_OR_GREATER
var content = await response.Content.ReadAsByteArrayAsync(ctx).ConfigureAwait(false);
Expand Down Expand Up @@ -263,7 +263,7 @@ public async Task DownloadFileToStreamAsync(string fileId, Stream outputStream,
}

var url = $"{Url}/{fileId}/content";
var response = await HttpRequestRaw(url, HttpMethod.Get, null, streaming: true, ctx).ConfigureAwait(false);
var response = await HttpRequestRaw(url, HttpMethod.Get, null, streaming: true, null, ctx).ConfigureAwait(false);

#if NET6_0_OR_GREATER
await using var stream = await response.Content.ReadAsStreamAsync(ctx).ConfigureAwait(false);
Expand Down
88 changes: 47 additions & 41 deletions Anthropic.SDK/Messaging/MessagesEndpoint.cs
Original file line number Diff line number Diff line change
Expand Up @@ -31,26 +31,7 @@ public async Task<MessageResponse> GetClaudeMessageAsync(MessageParameters param
parameters.Stream = false;

// Check if interleaved thinking is needed and add the header
Dictionary<string, string> additionalHeaders = null;
if (parameters.Thinking?.UseInterleavedThinking == true)
{
// Add the interleaved thinking beta header to the existing beta features
var existingBeta = Client.AnthropicBetaVersion;
var interleavedBeta = "interleaved-thinking-2025-05-14";

// Combine with existing beta features if they don't already include interleaved thinking
if (!existingBeta.Contains(interleavedBeta))
{
var combinedBeta = string.IsNullOrWhiteSpace(existingBeta)
? interleavedBeta
: $"{existingBeta},{interleavedBeta}";

additionalHeaders = new Dictionary<string, string>
{
["anthropic-beta"] = combinedBeta
};
}
}
var additionalHeaders = SetAdditionalHeaders(parameters);

var response = await HttpRequestMessages<MessageResponse>(Url, HttpMethod.Post, parameters, additionalHeaders, ctx).ConfigureAwait(false);

Expand Down Expand Up @@ -118,27 +99,7 @@ public async IAsyncEnumerable<MessageResponse> StreamClaudeMessageAsync(MessageP

parameters.Stream = true;

// Check if interleaved thinking is needed and add the header
Dictionary<string, string> additionalHeaders = null;
if (parameters.Thinking?.UseInterleavedThinking == true)
{
// Add the interleaved thinking beta header to the existing beta features
var existingBeta = Client.AnthropicBetaVersion;
var interleavedBeta = "interleaved-thinking-2025-05-14";

// Combine with existing beta features if they don't already include interleaved thinking
if (!existingBeta.Contains(interleavedBeta))
{
var combinedBeta = string.IsNullOrWhiteSpace(existingBeta)
? interleavedBeta
: $"{existingBeta},{interleavedBeta}";

additionalHeaders = new Dictionary<string, string>
{
["anthropic-beta"] = combinedBeta
};
}
}
var additionalHeaders = SetAdditionalHeaders(parameters);

var toolCalls = new List<Function>();
var arguments = string.Empty;
Expand Down Expand Up @@ -178,6 +139,51 @@ public async IAsyncEnumerable<MessageResponse> StreamClaudeMessageAsync(MessageP
yield return result;
}
}

private Dictionary<string, string> SetAdditionalHeaders(MessageParameters parameters)
{
// Check if interleaved thinking is needed and add the header
Dictionary<string, string> additionalHeaders = null;
if (parameters.Thinking?.UseInterleavedThinking == true)
{
// Add the interleaved thinking beta header to the existing beta features
var existingBeta = Client.AnthropicBetaVersion;
var interleavedBeta = "interleaved-thinking-2025-05-14";
// Combine with existing beta features if they don't already include interleaved thinking
if (!existingBeta.Contains(interleavedBeta))
{
var combinedBeta = string.IsNullOrWhiteSpace(existingBeta)
? interleavedBeta
: $"{existingBeta},{interleavedBeta}";

additionalHeaders = new Dictionary<string, string>
{
["anthropic-beta"] = combinedBeta
};
}
}
if (parameters.Container != null)
{
if (additionalHeaders == null)
{
additionalHeaders = new Dictionary<string, string>();
}
var existingBeta = Client.AnthropicBetaVersion;
var skillsBeta = "skills-2025-10-02";
// Combine with existing beta features if they don't already include skills
if (!existingBeta.Contains(skillsBeta))
{
var combinedBeta = string.IsNullOrWhiteSpace(existingBeta)
? skillsBeta
: $"{existingBeta},{skillsBeta}";
additionalHeaders["anthropic-beta"] = combinedBeta;
}
}

return additionalHeaders;
}


/// <summary>
/// Makes a call to count the number of tokens in a request.
/// </summary>
Expand Down
4 changes: 2 additions & 2 deletions Anthropic.SDK/Models/ModelsEndpoint.cs
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ public async Task<ModelList> ListModelsAsync(string beforeId = null, string afte
url += $"&after_id={afterId}";
}

var response = await HttpRequestSimple<ModelList>(url, HttpMethod.Get, null, ctx).ConfigureAwait(false);
var response = await HttpRequestSimple<ModelList>(url, HttpMethod.Get, null, null, ctx).ConfigureAwait(false);

return response;
}
Expand All @@ -52,7 +52,7 @@ public async Task<ModelList> ListModelsAsync(string beforeId = null, string afte
/// <param name="ctx"></param>
public async Task<ModelResponse> GetModelAsync(string modelId, CancellationToken ctx = default)
{
var response = await HttpRequestSimple<ModelResponse>(Url + $"/{modelId}", HttpMethod.Get, null, ctx).ConfigureAwait(false);
var response = await HttpRequestSimple<ModelResponse>(Url + $"/{modelId}", HttpMethod.Get, null, null, ctx).ConfigureAwait(false);

return response;
}
Expand Down
Loading
Loading