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
61 changes: 58 additions & 3 deletions Anthropic.SDK.Tests/SkillsTests.cs
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
using System.Collections.Generic;
using System.Linq;
using System.Text.Json;
using Anthropic.SDK.Common;
using Anthropic.SDK.Constants;
using Anthropic.SDK.Messaging;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text.Json;

namespace Anthropic.SDK.Tests
{
Expand Down Expand Up @@ -336,6 +337,60 @@ bashCodeExecutionToolResultContent.Content is BashCodeExecutionOutputContent bas
!string.IsNullOrEmpty(bashCodeExecutionOutputContent.FileId)) != null);
}

[TestMethod]
public async Task TestSkillUseMessageStreaming()
{
var client = new AnthropicClient();
var messages = new List<Message>();
messages.Add(new Message(RoleType.User, "Please create a one-page PowerPoint presentation with the title 'Testing Skills' and a bullet point list with the items 'Skill 1', 'Skill 2', and 'Skill 3'."));

var container = new Container
{
Skills = new List<Skill>
{
new Skill
{
Type = "anthropic",
SkillId = "pptx", // Built-in PowerPoint skill
Version = "latest"
}
}
};

var parameters = new MessageParameters()
{
Messages = messages,
MaxTokens = 4096,
Model = AnthropicModels.Claude4Sonnet,
Stream = true,
Temperature = 1.0m,
Container = container,
Tools = new List<Common.Tool>
{
new Function("code_execution", "code_execution_20250825",
new Dictionary<string, object> { { "name", "code_execution" } })
}
};
var outputs = new List<MessageResponse>();
await foreach (var res in client.Messages.StreamClaudeMessageAsync(parameters))
{
if (res.Delta != null)
{
Debug.Write(res.Delta.Text);
}

outputs.Add(res);
}

var message = new Message(outputs);

Assert.IsNotNull(message.ToString());
Assert.IsNotNull(message.Content.LastOrDefault(c =>
c is BashCodeExecutionToolResultContent bashCodeExecutionToolResultContent &&
bashCodeExecutionToolResultContent.Content is BashCodeExecutionOutputContent bashCodeExecutionOutputContent &&
!string.IsNullOrEmpty(bashCodeExecutionOutputContent.FileId)) != null);
}

[TestMethod]
public async Task GetSkills()
{
Expand Down
8 changes: 7 additions & 1 deletion Anthropic.SDK.Tests/Tools.cs
Original file line number Diff line number Diff line change
Expand Up @@ -509,7 +509,11 @@ public async Task TestFuncListTool()
Model = AnthropicModels.Claude4Sonnet,
Stream = false,
Temperature = 1.0m,
Tools = tools
Tools = tools,
ToolChoice = new ToolChoice()
{
Type = ToolChoiceType.Any
}
};
var res = await client.Messages.GetClaudeMessageAsync(parameters);

Expand All @@ -522,6 +526,8 @@ public async Task TestFuncListTool()
messages.Add(new Message(toolCall, response));
}

parameters.ToolChoice = null;

var finalResult = await client.Messages.GetClaudeMessageAsync(parameters);

Assert.IsTrue(finalResult.Message.ToString().Contains("sum") || finalResult.Message.ToString().Contains("total"));
Expand Down
41 changes: 41 additions & 0 deletions Anthropic.SDK/Extensions/SingleOrArrayConverter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
using System;
using System.Collections.Generic;
using System.Text;
using System.Text.Json;
using System.Text.Json.Serialization;

namespace Anthropic.SDK.Extensions
{
// C#
public class SingleOrArrayConverter<T> : JsonConverter<List<T>>
{
public override List<T> Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
var result = new List<T>();
if (reader.TokenType == JsonTokenType.StartArray)
{
while (reader.Read())
{
if (reader.TokenType == JsonTokenType.EndArray) break;
var item = JsonSerializer.Deserialize<T>(ref reader, options);
result.Add(item);
}
return result;
}
else
{
var item = JsonSerializer.Deserialize<T>(ref reader, options);
result.Add(item);
return result;
}
}

public override void Write(Utf8JsonWriter writer, List<T> value, JsonSerializerOptions options)
{
writer.WriteStartArray();
foreach (var item in value)
JsonSerializer.Serialize(writer, item, options);
writer.WriteEndArray();
}
}
}
7 changes: 7 additions & 0 deletions Anthropic.SDK/Messaging/Container.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
using System;
using System.Collections.Generic;
using System.Text.Json.Serialization;

Expand Down Expand Up @@ -60,5 +61,11 @@ public class ContainerResponse
/// </summary>
[JsonPropertyName("id")]
public string Id { get; set; }

/// <summary>
/// The expiration date for the container
/// </summary>
[JsonPropertyName("expires_at")]
public DateTime? ExpiresAt { get; set; }
}
}
31 changes: 31 additions & 0 deletions Anthropic.SDK/Messaging/Message.cs
Original file line number Diff line number Diff line change
Expand Up @@ -226,6 +226,8 @@ public Message(List<MessageResponse> asyncResponses)

}



var webToolResultFound = false;
WebSearchToolResultContent webToolUseContent = null;
var webSearchPartialJson = string.Empty;
Expand All @@ -245,7 +247,36 @@ public Message(List<MessageResponse> asyncResponses)

}


BashCodeExecutionToolResultContent bashCodeExecutionToolResultContent = null;
foreach (var result in asyncResponses)
{
if (result.ContentBlock != null && result.ContentBlock.Type == "bash_code_execution_tool_result")
{

bashCodeExecutionToolResultContent = new BashCodeExecutionToolResultContent()
{
ToolUseId = result.ContentBlock.ToolUseId,
Content = result.ContentBlock.Content.FirstOrDefault()
};
Content.Add(bashCodeExecutionToolResultContent);
}
}

TextEditorCodeExecutionToolResultContent textEditorCodeExecutionToolResultContent = null;
foreach (var result in asyncResponses)
{
if (result.ContentBlock != null && result.ContentBlock.Type == "text_editor_code_execution_tool_result")
{

textEditorCodeExecutionToolResultContent = new TextEditorCodeExecutionToolResultContent()
{
ToolUseId = result.ContentBlock.ToolUseId,
Content = result.ContentBlock.Content.FirstOrDefault()
};
Content.Add(textEditorCodeExecutionToolResultContent);
}
}


foreach (var result in asyncResponses)
Expand Down
4 changes: 4 additions & 0 deletions Anthropic.SDK/Messaging/MessageResponse.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
public class MessageResponse
{
[JsonPropertyName("content")]
[JsonConverter(typeof(SingleOrArrayConverter<ContentBase>))]
public List<ContentBase> Content { get; set; }

[JsonPropertyName("id")]
Expand Down Expand Up @@ -37,7 +38,7 @@
public Delta Delta { get; set; }

[JsonPropertyName("content_block")]
public ContentBlock? ContentBlock { get; set; }

Check warning on line 41 in Anthropic.SDK/Messaging/MessageResponse.cs

View workflow job for this annotation

GitHub Actions / build

The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.

[JsonPropertyName("message")]
public StreamMessage StreamStartMessage { get; set; }
Expand Down Expand Up @@ -119,7 +120,7 @@
public string Name { get; set; }

[JsonPropertyName("partial_json")]
public string? PartialJson { get; set; }

Check warning on line 123 in Anthropic.SDK/Messaging/MessageResponse.cs

View workflow job for this annotation

GitHub Actions / build

The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.
[JsonPropertyName("citation")]
public CitationResult Citation { get; set; }
}
Expand All @@ -130,28 +131,31 @@
public string Type { get; set; }

[JsonPropertyName("id")]
public string? Id { get; set; }

Check warning on line 134 in Anthropic.SDK/Messaging/MessageResponse.cs

View workflow job for this annotation

GitHub Actions / build

The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.

[JsonPropertyName("text")]
public string? Text { get; set; }

Check warning on line 137 in Anthropic.SDK/Messaging/MessageResponse.cs

View workflow job for this annotation

GitHub Actions / build

The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.

[JsonPropertyName("name")]
public string? Name { get; set; }

Check warning on line 140 in Anthropic.SDK/Messaging/MessageResponse.cs

View workflow job for this annotation

GitHub Actions / build

The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.

[JsonPropertyName("server_name")]
public string? ServerName { get; set; }

Check warning on line 143 in Anthropic.SDK/Messaging/MessageResponse.cs

View workflow job for this annotation

GitHub Actions / build

The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.

[JsonPropertyName("data")]
public string? Data { get; set; }

Check warning on line 146 in Anthropic.SDK/Messaging/MessageResponse.cs

View workflow job for this annotation

GitHub Actions / build

The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.
[JsonPropertyName("tool_use_id")]
public string? ToolUseId { get; set; }

Check warning on line 148 in Anthropic.SDK/Messaging/MessageResponse.cs

View workflow job for this annotation

GitHub Actions / build

The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.
[JsonPropertyName("content")]
[JsonConverter(typeof(SingleOrArrayConverter<ContentBase>))]
public List<ContentBase> Content { get; set; }

[JsonPropertyName("is_error")]
public bool? IsError { get; set; }
}



public class Usage
{
[JsonPropertyName("input_tokens")]
Expand Down
Loading