diff --git a/Anthropic.SDK.Tests/SkillsTests.cs b/Anthropic.SDK.Tests/SkillsTests.cs index 95d4f16..1e47b88 100644 --- a/Anthropic.SDK.Tests/SkillsTests.cs +++ b/Anthropic.SDK.Tests/SkillsTests.cs @@ -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 { @@ -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(); + 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 + { + 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 + { + new Function("code_execution", "code_execution_20250825", + new Dictionary { { "name", "code_execution" } }) + } + }; + var outputs = new List(); + 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() { diff --git a/Anthropic.SDK.Tests/Tools.cs b/Anthropic.SDK.Tests/Tools.cs index 1c82dfe..00aa9f4 100644 --- a/Anthropic.SDK.Tests/Tools.cs +++ b/Anthropic.SDK.Tests/Tools.cs @@ -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); @@ -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")); diff --git a/Anthropic.SDK/Extensions/SingleOrArrayConverter.cs b/Anthropic.SDK/Extensions/SingleOrArrayConverter.cs new file mode 100644 index 0000000..cf3d495 --- /dev/null +++ b/Anthropic.SDK/Extensions/SingleOrArrayConverter.cs @@ -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 : JsonConverter> + { + public override List Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + var result = new List(); + if (reader.TokenType == JsonTokenType.StartArray) + { + while (reader.Read()) + { + if (reader.TokenType == JsonTokenType.EndArray) break; + var item = JsonSerializer.Deserialize(ref reader, options); + result.Add(item); + } + return result; + } + else + { + var item = JsonSerializer.Deserialize(ref reader, options); + result.Add(item); + return result; + } + } + + public override void Write(Utf8JsonWriter writer, List value, JsonSerializerOptions options) + { + writer.WriteStartArray(); + foreach (var item in value) + JsonSerializer.Serialize(writer, item, options); + writer.WriteEndArray(); + } + } +} diff --git a/Anthropic.SDK/Messaging/Container.cs b/Anthropic.SDK/Messaging/Container.cs index 51714df..b8d8af8 100644 --- a/Anthropic.SDK/Messaging/Container.cs +++ b/Anthropic.SDK/Messaging/Container.cs @@ -1,3 +1,4 @@ +using System; using System.Collections.Generic; using System.Text.Json.Serialization; @@ -60,5 +61,11 @@ public class ContainerResponse /// [JsonPropertyName("id")] public string Id { get; set; } + + /// + /// The expiration date for the container + /// + [JsonPropertyName("expires_at")] + public DateTime? ExpiresAt { get; set; } } } diff --git a/Anthropic.SDK/Messaging/Message.cs b/Anthropic.SDK/Messaging/Message.cs index 1deb432..8a1584b 100644 --- a/Anthropic.SDK/Messaging/Message.cs +++ b/Anthropic.SDK/Messaging/Message.cs @@ -226,6 +226,8 @@ public Message(List asyncResponses) } + + var webToolResultFound = false; WebSearchToolResultContent webToolUseContent = null; var webSearchPartialJson = string.Empty; @@ -245,7 +247,36 @@ public Message(List 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) diff --git a/Anthropic.SDK/Messaging/MessageResponse.cs b/Anthropic.SDK/Messaging/MessageResponse.cs index bf01d9b..caf2115 100644 --- a/Anthropic.SDK/Messaging/MessageResponse.cs +++ b/Anthropic.SDK/Messaging/MessageResponse.cs @@ -9,6 +9,7 @@ namespace Anthropic.SDK.Messaging public class MessageResponse { [JsonPropertyName("content")] + [JsonConverter(typeof(SingleOrArrayConverter))] public List Content { get; set; } [JsonPropertyName("id")] @@ -146,12 +147,15 @@ public class ContentBlock [JsonPropertyName("tool_use_id")] public string? ToolUseId { get; set; } [JsonPropertyName("content")] + [JsonConverter(typeof(SingleOrArrayConverter))] public List Content { get; set; } [JsonPropertyName("is_error")] public bool? IsError { get; set; } } + + public class Usage { [JsonPropertyName("input_tokens")]