diff --git a/Anthropic.SDK.Tests/TextEditorCodeExecutionTests.cs b/Anthropic.SDK.Tests/TextEditorCodeExecutionTests.cs new file mode 100644 index 0000000..a6f27f7 --- /dev/null +++ b/Anthropic.SDK.Tests/TextEditorCodeExecutionTests.cs @@ -0,0 +1,173 @@ +using System.Text.Json; +using Anthropic.SDK.Extensions; +using Anthropic.SDK.Messaging; + +namespace Anthropic.SDK.Tests; + +[TestClass] +public class TextEditorCodeExecutionTests +{ + [TestMethod] + public void TestTextEditorToolUseDeserialization() + { + var json = @"{ + ""type"": ""server_tool_use"", + ""id"": ""srvtoolu_01E6F7G8H9I0J1K2L3M4N5O6"", + ""name"": ""text_editor_code_execution"", + ""input"": { + ""command"": ""str_replace"", + ""path"": ""config.json"", + ""old_str"": ""\""debug\"": true"", + ""new_str"": ""\""debug\"": false"" + } +}"; + + var options = new JsonSerializerOptions + { + Converters = { ContentConverter.Instance } + }; + + var content = JsonSerializer.Deserialize(json, options); + + Assert.IsNotNull(content); + Assert.AreEqual(ContentType.server_tool_use, content.Type); + Assert.AreEqual("srvtoolu_01E6F7G8H9I0J1K2L3M4N5O6", content.Id); + Assert.AreEqual("text_editor_code_execution", content.Name); + Assert.IsNotNull(content.Input); + Assert.AreEqual("str_replace", content.Input.Command); + Assert.AreEqual("config.json", content.Input.Path); + Assert.AreEqual("\"debug\": true", content.Input.OldStr); + Assert.AreEqual("\"debug\": false", content.Input.NewStr); + } + + [TestMethod] + public void TestTextEditorToolResultWithDiffDeserialization() + { + var json = @"{ + ""type"": ""text_editor_code_execution_tool_result"", + ""tool_use_id"": ""srvtoolu_01E6F7G8H9I0J1K2L3M4N5O6"", + ""content"": { + ""type"": ""text_editor_code_execution_result"", + ""oldStart"": 3, + ""oldLines"": 1, + ""newStart"": 3, + ""newLines"": 1, + ""lines"": [""- \""debug\"": true"", ""+ \""debug\"": false""] + } +}"; + + var options = new JsonSerializerOptions + { + Converters = { ContentConverter.Instance } + }; + + var content = JsonSerializer.Deserialize(json, options); + + Assert.IsNotNull(content); + Assert.AreEqual(ContentType.text_editor_code_execution_tool_result, content.Type); + Assert.AreEqual("srvtoolu_01E6F7G8H9I0J1K2L3M4N5O6", content.ToolUseId); + Assert.IsNotNull(content.Content); + + var result = content.Content as TextEditorCodeExecutionResultContent; + Assert.IsNotNull(result); + Assert.AreEqual(ContentType.text_editor_code_execution_result, result.Type); + Assert.AreEqual(3, result.OldStart); + Assert.AreEqual(1, result.OldLines); + Assert.AreEqual(3, result.NewStart); + Assert.AreEqual(1, result.NewLines); + Assert.IsNotNull(result.Lines); + Assert.AreEqual(2, result.Lines.Count); + Assert.AreEqual("- \"debug\": true", result.Lines[0]); + Assert.AreEqual("+ \"debug\": false", result.Lines[1]); + } + + [TestMethod] + public void TestTextEditorCreateToolResultDeserialization() + { + var json = @"{ + ""type"": ""text_editor_code_execution_tool_result"", + ""tool_use_id"": ""srvtoolu_01D5E6F7G8H9I0J1K2L3M4N5"", + ""content"": { + ""type"": ""text_editor_code_execution_result"", + ""is_file_update"": false + } +}"; + + var options = new JsonSerializerOptions + { + Converters = { ContentConverter.Instance } + }; + + var content = JsonSerializer.Deserialize(json, options); + + Assert.IsNotNull(content); + Assert.AreEqual(ContentType.text_editor_code_execution_tool_result, content.Type); + Assert.AreEqual("srvtoolu_01D5E6F7G8H9I0J1K2L3M4N5", content.ToolUseId); + Assert.IsNotNull(content.Content); + + var result = content.Content as TextEditorCodeExecutionResultContent; + Assert.IsNotNull(result); + Assert.AreEqual(ContentType.text_editor_code_execution_result, result.Type); + Assert.AreEqual(false, result.IsFileUpdate); + } + + [TestMethod] + public void TestTextEditorToolResultErrorDeserialization() + { + var json = @"{ + ""type"": ""text_editor_code_execution_tool_result"", + ""tool_use_id"": ""srvtoolu_01VfmxgZ46TiHbmXgy928hQR"", + ""content"": { + ""type"": ""text_editor_code_execution_tool_result_error"", + ""error_code"": ""unavailable"" + } +}"; + + var options = new JsonSerializerOptions + { + Converters = { ContentConverter.Instance } + }; + + var content = JsonSerializer.Deserialize(json, options); + + Assert.IsNotNull(content); + Assert.AreEqual(ContentType.text_editor_code_execution_tool_result, content.Type); + Assert.AreEqual("srvtoolu_01VfmxgZ46TiHbmXgy928hQR", content.ToolUseId); + Assert.IsNotNull(content.Content); + + var error = content.Content as TextEditorCodeExecutionToolResultErrorContent; + Assert.IsNotNull(error); + Assert.AreEqual(ContentType.text_editor_code_execution_tool_result_error, error.Type); + Assert.AreEqual("unavailable", error.ErrorCode); + } + + [TestMethod] + public void TestPauseTurnStopReason() + { + var json = @"{ + ""id"": ""msg_01XFDUDYJgAACzvnptvVoYEL"", + ""type"": ""message"", + ""role"": ""assistant"", + ""content"": [{""type"": ""text"", ""text"": ""Processing...""}], + ""model"": ""claude-3-5-sonnet-20241022"", + ""stop_reason"": ""pause_turn"", + ""stop_sequence"": null, + ""usage"": { + ""input_tokens"": 10, + ""output_tokens"": 20 + } +}"; + + var options = new JsonSerializerOptions + { + Converters = { ContentConverter.Instance } + }; + + var response = JsonSerializer.Deserialize(json, options); + + Assert.IsNotNull(response); + Assert.AreEqual("pause_turn", response.StopReason); + Assert.AreEqual("msg_01XFDUDYJgAACzvnptvVoYEL", response.Id); + Assert.AreEqual("assistant", response.Role.ToString().ToLower()); + } +} diff --git a/Anthropic.SDK/Extensions/ContentConverter.cs b/Anthropic.SDK/Extensions/ContentConverter.cs index 2814c13..a28829f 100644 --- a/Anthropic.SDK/Extensions/ContentConverter.cs +++ b/Anthropic.SDK/Extensions/ContentConverter.cs @@ -56,6 +56,12 @@ public override ContentBase Read(ref Utf8JsonReader reader, Type typeToConvert, return JsonSerializer.Deserialize(root.GetRawText(), options); case "bash_code_execution_tool_result_error": return JsonSerializer.Deserialize(root.GetRawText(), options); + case "text_editor_code_execution_tool_result": + return JsonSerializer.Deserialize(root.GetRawText(), options); + case "text_editor_code_execution_result": + return JsonSerializer.Deserialize(root.GetRawText(), options); + case "text_editor_code_execution_tool_result_error": + return JsonSerializer.Deserialize(root.GetRawText(), options); // Add cases for other types as necessary default: throw new JsonException($"Unknown type {type}"); diff --git a/Anthropic.SDK/Messaging/Content.cs b/Anthropic.SDK/Messaging/Content.cs index 798bcd1..726d3be 100644 --- a/Anthropic.SDK/Messaging/Content.cs +++ b/Anthropic.SDK/Messaging/Content.cs @@ -50,8 +50,41 @@ public class ServerToolUseContent : ContentBase public class ServerToolInput { + /// + /// Query for web_search tool + /// [JsonPropertyName("query")] public string Query { get; set; } + + /// + /// Command for text_editor_code_execution or bash_code_execution tools + /// + [JsonPropertyName("command")] + public string Command { get; set; } + + /// + /// File path for text_editor_code_execution tool + /// + [JsonPropertyName("path")] + public string Path { get; set; } + + /// + /// Old string to replace (for str_replace command) + /// + [JsonPropertyName("old_str")] + public string OldStr { get; set; } + + /// + /// New string to replace with (for str_replace command) + /// + [JsonPropertyName("new_str")] + public string NewStr { get; set; } + + /// + /// File text content (for create command) + /// + [JsonPropertyName("file_text")] + public string FileText { get; set; } } @@ -589,4 +622,124 @@ public class BashCodeExecutionToolResultErrorContent : ContentBase [JsonPropertyName("error_code")] public string ErrorCode { get; set; } } + + /// + /// Text Editor Code Execution Tool Result Content + /// + public class TextEditorCodeExecutionToolResultContent : ContentBase + { + /// + /// Type of Content (text_editor_code_execution_tool_result, pre-set) + /// + [JsonPropertyName("type")] + public override ContentType Type => ContentType.text_editor_code_execution_tool_result; + + /// + /// Tool Use Id + /// + [JsonPropertyName("tool_use_id")] + public string ToolUseId { get; set; } + + /// + /// Content - can be either TextEditorCodeExecutionResultContent or TextEditorCodeExecutionToolResultErrorContent + /// + [JsonPropertyName("content")] + public ContentBase Content { get; set; } + } + + /// + /// Text Editor Code Execution Result Content + /// + public class TextEditorCodeExecutionResultContent : ContentBase + { + /// + /// Type of Content (text_editor_code_execution_result, pre-set) + /// + [JsonPropertyName("type")] + public override ContentType Type => ContentType.text_editor_code_execution_result; + + /// + /// Whether file already existed (for create operations) + /// + [JsonPropertyName("is_file_update")] + public bool? IsFileUpdate { get; set; } + + /// + /// File type (for view operations) + /// + [JsonPropertyName("file_type")] + public string FileType { get; set; } + + /// + /// Content of the file (for view operations) + /// + [JsonPropertyName("content")] + public string Content { get; set; } + + /// + /// Number of lines (for view operations) + /// + [JsonPropertyName("numLines")] + public int? NumLines { get; set; } + + /// + /// Start line number (for view operations) + /// + [JsonPropertyName("startLine")] + public int? StartLine { get; set; } + + /// + /// Total lines in file (for view operations) + /// + [JsonPropertyName("totalLines")] + public int? TotalLines { get; set; } + + /// + /// Old start line (for edit operations) + /// + [JsonPropertyName("oldStart")] + public int? OldStart { get; set; } + + /// + /// Old number of lines (for edit operations) + /// + [JsonPropertyName("oldLines")] + public int? OldLines { get; set; } + + /// + /// New start line (for edit operations) + /// + [JsonPropertyName("newStart")] + public int? NewStart { get; set; } + + /// + /// New number of lines (for edit operations) + /// + [JsonPropertyName("newLines")] + public int? NewLines { get; set; } + + /// + /// Diff lines (for edit operations) + /// + [JsonPropertyName("lines")] + public List Lines { get; set; } + } + + /// + /// Text Editor Code Execution Tool Result Error Content + /// + public class TextEditorCodeExecutionToolResultErrorContent : ContentBase + { + /// + /// Type of Content (text_editor_code_execution_tool_result_error, pre-set) + /// + [JsonPropertyName("type")] + public override ContentType Type => ContentType.text_editor_code_execution_tool_result_error; + + /// + /// Error code describing the failure + /// + [JsonPropertyName("error_code")] + public string ErrorCode { get; set; } + } } diff --git a/Anthropic.SDK/Messaging/ContentType.cs b/Anthropic.SDK/Messaging/ContentType.cs index bec9981..a81148d 100644 --- a/Anthropic.SDK/Messaging/ContentType.cs +++ b/Anthropic.SDK/Messaging/ContentType.cs @@ -45,6 +45,12 @@ public enum ContentType bash_code_execution_output, - bash_code_execution_tool_result_error + bash_code_execution_tool_result_error, + + text_editor_code_execution_tool_result, + + text_editor_code_execution_result, + + text_editor_code_execution_tool_result_error } }