diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 4dd60f452..ff6616c2d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,12 +1,14 @@ name: CI on: push: - branches-ignore: - - 'generated' - - 'codegen/**' - - 'integrated/**' - - 'stl-preview-head/**' - - 'stl-preview-base/**' + branches: + - '**' + - '!integrated/**' + - '!stl-preview-head/**' + - '!stl-preview-base/**' + - '!generated' + - '!codegen/**' + - 'codegen/stl/**' pull_request: branches-ignore: - 'stl-preview-head/**' @@ -17,7 +19,7 @@ jobs: timeout-minutes: 10 name: lint runs-on: ${{ github.repository == 'stainless-sdks/anthropic-csharp' && 'depot-ubuntu-24.04' || 'ubuntu-latest' }} - if: github.event_name == 'push' || github.event.pull_request.head.repo.fork + if: (github.event_name == 'push' || github.event.pull_request.head.repo.fork) steps: - uses: actions/checkout@v6 @@ -36,7 +38,7 @@ jobs: timeout-minutes: 10 name: build runs-on: ${{ github.repository == 'stainless-sdks/anthropic-csharp' && 'depot-ubuntu-24.04' || 'ubuntu-latest' }} - if: github.event_name == 'push' || github.event.pull_request.head.repo.fork + if: (github.event_name == 'push' || github.event.pull_request.head.repo.fork) steps: - uses: actions/checkout@v6 diff --git a/.gitignore b/.gitignore index cb0f7844e..1ce7f8483 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ .prism.log +.stdy.log bin/ obj/ .vs/ diff --git a/.release-please-manifest.json b/.release-please-manifest.json index 79386130e..9781711eb 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,6 +1,6 @@ { - "src/Anthropic": "12.9.0", + "src/Anthropic": "12.10.0", "src/Anthropic.Foundry": "0.5.0", - "src/Anthropic.Bedrock": "0.1.0", + "src/Anthropic.Bedrock": "0.1.1", "src/Anthropic.Vertex": "0.1.0" } diff --git a/.stats.yml b/.stats.yml index 8691776c3..6dd5a3a0d 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 33 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/anthropic%2Fanthropic-3f1132b9be2d8218a7255103c40a0fbfc2b6d65db76f160db4477f9221493561.yml -openapi_spec_hash: 58021ab18daccd5c45a930ffd7d6ab4d -config_hash: 6debadaa8ff30c5b5ee61176ef42a5fc +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/anthropic%2Fanthropic-3088a13fc94214c1b39c66c09fe0af997aed84b412f2fda478af89a9c016f3e6.yml +openapi_spec_hash: ebeeaa9a9bf7603f0bbcce30389e27ca +config_hash: 6f5727994013c43f452e6ac0b4d5e92a diff --git a/scripts/mock b/scripts/mock index b7f3e9319..fcdc20068 100755 --- a/scripts/mock +++ b/scripts/mock @@ -22,9 +22,9 @@ echo "==> Starting mock server with URL ${URL}" # Run prism mock on the given spec if [ "$1" == "--daemon" ]; then # Pre-install the package so the download doesn't eat into the startup timeout - npm exec --package=@stdy/cli@0.19.3 -- steady --version + npm exec --package=@stdy/cli@0.20.1 -- steady --version - npm exec --package=@stdy/cli@0.19.3 -- steady --host 127.0.0.1 -p 4010 --validator-query-array-format=comma --validator-query-object-format=brackets "$URL" &> .stdy.log & + npm exec --package=@stdy/cli@0.20.1 -- steady --host 127.0.0.1 -p 4010 --validator-query-array-format=comma --validator-form-array-format=comma --validator-query-object-format=brackets --validator-form-object-format=brackets "$URL" &> .stdy.log & # Wait for server to come online via health endpoint (max 30s) echo -n "Waiting for server" @@ -53,5 +53,5 @@ if [ "$1" == "--daemon" ]; then echo else - npm exec --package=@stdy/cli@0.19.3 -- steady --host 127.0.0.1 -p 4010 --validator-query-array-format=comma --validator-query-object-format=brackets "$URL" + npm exec --package=@stdy/cli@0.20.1 -- steady --host 127.0.0.1 -p 4010 --validator-query-array-format=comma --validator-form-array-format=comma --validator-query-object-format=brackets --validator-form-object-format=brackets "$URL" fi diff --git a/scripts/test b/scripts/test index 101123b67..4ae0e03a4 100755 --- a/scripts/test +++ b/scripts/test @@ -47,7 +47,7 @@ elif ! prism_is_running ; then echo -e "To run the server, pass in the path or url of your OpenAPI" echo -e "spec to the prism command:" echo - echo -e " \$ ${YELLOW}npm exec --package=@stdy/cli@0.19.3 -- steady path/to/your.openapi.yml --host 127.0.0.1 -p 4010 --validator-query-array-format=comma --validator-query-object-format=brackets${NC}" + echo -e " \$ ${YELLOW}npm exec --package=@stdy/cli@0.20.1 -- steady path/to/your.openapi.yml --host 127.0.0.1 -p 4010 --validator-query-array-format=comma --validator-form-array-format=comma --validator-query-object-format=brackets --validator-form-object-format=brackets${NC}" echo exit 1 diff --git a/src/Anthropic.Bedrock/Anthropic.Bedrock.csproj b/src/Anthropic.Bedrock/Anthropic.Bedrock.csproj index c237158d8..a3d1005c6 100644 --- a/src/Anthropic.Bedrock/Anthropic.Bedrock.csproj +++ b/src/Anthropic.Bedrock/Anthropic.Bedrock.csproj @@ -7,9 +7,13 @@ 13.0 Anthropic.Bedrock - 0.1.0 + 0.1.1 + + + + diff --git a/src/Anthropic.Bedrock/CHANGELOG.md b/src/Anthropic.Bedrock/CHANGELOG.md index 10095107a..35cc054f3 100644 --- a/src/Anthropic.Bedrock/CHANGELOG.md +++ b/src/Anthropic.Bedrock/CHANGELOG.md @@ -1,5 +1,13 @@ # Changelog +## 0.1.1 (2026-03-31) + +Full Changelog: [Bedrock-v0.1.0...Bedrock-v0.1.1](https://github.com/anthropics/anthropic-sdk-csharp/compare/Bedrock-v0.1.0...Bedrock-v0.1.1) + +### Bug Fixes + +* handle oversized SSE events in Bedrock SseEventContentWrapper ([#147](https://github.com/anthropics/anthropic-sdk-csharp/issues/147)) ([dcbf8cc](https://github.com/anthropics/anthropic-sdk-csharp/commit/dcbf8ccb4dc4f1d67fe85e3ff1248517bea6af23)) + ## 0.1.0 (2026-03-16) Full Changelog: [Bedrock-v0.0.1...Bedrock-v0.1.0](https://github.com/anthropics/anthropic-sdk-csharp/compare/Bedrock-v0.0.1...Bedrock-v0.1.0) diff --git a/src/Anthropic.Bedrock/SseEventContentWrapper.cs b/src/Anthropic.Bedrock/SseEventContentWrapper.cs index 0ee5df4c7..31a644a96 100644 --- a/src/Anthropic.Bedrock/SseEventContentWrapper.cs +++ b/src/Anthropic.Bedrock/SseEventContentWrapper.cs @@ -45,6 +45,7 @@ protected override bool TryComputeLength(out long length) private class SseLazyEventStream : Stream { private readonly Stream _sourceStream; + private Memory _remainder = Memory.Empty; public SseLazyEventStream(Stream source) { @@ -69,6 +70,15 @@ public override async ValueTask ReadAsync( CancellationToken cancellationToken = default ) { + // Return buffered remainder from a previous oversized event first + if (_remainder.Length > 0) + { + var toCopy = Math.Min(_remainder.Length, buffer.Length); + _remainder[..toCopy].CopyTo(buffer); + _remainder = _remainder[toCopy..]; + return toCopy; + } + var (data, success) = await AwsEventStreamHelpers .ReadStreamMessage(_sourceStream, cancellationToken) .ConfigureAwait(false); @@ -78,8 +88,13 @@ public override async ValueTask ReadAsync( } var encodedData = Encoding.UTF8.GetBytes(data!); - encodedData.CopyTo(buffer); - return encodedData.Length; + var bytesToCopy = Math.Min(encodedData.Length, buffer.Length); + encodedData.AsMemory(0, bytesToCopy).CopyTo(buffer); + if (bytesToCopy < encodedData.Length) + { + _remainder = encodedData.AsMemory(bytesToCopy); + } + return bytesToCopy; } #else public override async Task ReadAsync( @@ -89,6 +104,15 @@ public override async Task ReadAsync( CancellationToken cancellationToken ) { + // Return buffered remainder from a previous oversized event first + if (_remainder.Length > 0) + { + var toCopy = Math.Min(_remainder.Length, count); + _remainder[..toCopy].CopyTo(buffer.AsMemory(offset)); + _remainder = _remainder[toCopy..]; + return toCopy; + } + var (data, success) = await AwsEventStreamHelpers .ReadStreamMessage(_sourceStream, cancellationToken) .ConfigureAwait(false); @@ -98,8 +122,13 @@ CancellationToken cancellationToken } var encodedData = Encoding.UTF8.GetBytes(data!); - encodedData.CopyTo(buffer, offset); - return encodedData.Length; + var bytesToCopy = Math.Min(encodedData.Length, count); + Array.Copy(encodedData, 0, buffer, offset, bytesToCopy); + if (bytesToCopy < encodedData.Length) + { + _remainder = encodedData.AsMemory(bytesToCopy); + } + return bytesToCopy; } #endif diff --git a/src/Anthropic.Tests/AnthropicClientBetaExtensionsTests.cs b/src/Anthropic.Tests/AnthropicClientBetaExtensionsTests.cs index 7226e84cc..ec29756bd 100644 --- a/src/Anthropic.Tests/AnthropicClientBetaExtensionsTests.cs +++ b/src/Anthropic.Tests/AnthropicClientBetaExtensionsTests.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.IO; using System.Linq; using System.Text.Json; using System.Threading.Tasks; @@ -450,7 +451,7 @@ public async Task GetResponseAsync_McpToolUseBlock_CreatesCorrectContent() ); Assert.NotNull(mcpToolCall); Assert.Equal("mcp_call_123", mcpToolCall.CallId); - Assert.Equal("search", mcpToolCall.ToolName); + Assert.Equal("search", mcpToolCall.Name); Assert.Equal("my-mcp-server", mcpToolCall.ServerName); Assert.NotNull(mcpToolCall.Arguments); Assert.True(mcpToolCall.Arguments.ContainsKey("query")); @@ -511,305 +512,13 @@ public async Task GetResponseAsync_McpToolResultBlock_WithTextContent() ); Assert.NotNull(mcpResult); Assert.Equal("mcp_call_456", mcpResult.CallId); - Assert.NotNull(mcpResult.Output); - Assert.Single(mcpResult.Output); - Assert.Equal("Result from MCP tool", ((TextContent)mcpResult.Output[0]).Text); + Assert.NotNull(mcpResult.Outputs); + Assert.Single(mcpResult.Outputs); + Assert.Equal("Result from MCP tool", ((TextContent)mcpResult.Outputs[0]).Text); Assert.NotNull(mcpResult.RawRepresentation); Assert.IsType(mcpResult.RawRepresentation); } - [Fact] - public async Task GetResponseAsync_WithSimpleResponseFormat_ReturnsStructuredJSON() - { - VerbatimHttpHandler handler = new( - expectedRequest: """ - { - "max_tokens": 1024, - "model": "claude-sonnet-4-5-20250929", - "messages": [{ - "role": "user", - "content": [{ - "type": "text", - "text": "Tell me about Albert Einstein. Respond with his name and age at death." - }] - }], - "output_config": { - "format": { - "type": "json_schema", - "schema": { - "type": "object", - "properties": { - "name": { "type": "string" }, - "age": { "type": "integer" } - }, - "required": ["name", "age"], - "additionalProperties": false - } - } - } - } - """, - actualResponse: """ - { - "id": "msg_format_01", - "type": "message", - "role": "assistant", - "model": "claude-sonnet-4-5-20250929", - "content": [{ - "type": "text", - "text": "{\"name\":\"Albert Einstein\",\"age\":76}" - }], - "stop_reason": "end_turn", - "usage": { - "input_tokens": 25, - "output_tokens": 15 - } - } - """ - ); - - IChatClient chatClient = CreateChatClient(handler, "claude-sonnet-4-5-20250929"); - - ChatOptions options = new() - { - ResponseFormat = ChatResponseFormat.ForJsonSchema( - JsonElement.Parse( - """ - { - "type": "object", - "properties": { - "name": { "type": "string" }, - "age": { "type": "integer" } - }, - "required": ["name", "age"] - } - """ - ), - "person_info" - ), - }; - - ChatResponse response = await chatClient.GetResponseAsync( - "Tell me about Albert Einstein. Respond with his name and age at death.", - options, - TestContext.Current.CancellationToken - ); - - Assert.NotNull(response); - TextContent textContent = Assert.IsType(response.Messages[0].Contents[0]); - Assert.Contains("Einstein", textContent.Text); - Assert.Contains("76", textContent.Text); - } - - [Fact] - public async Task GetResponseAsync_WithNestedObjectSchema_ReturnsStructuredJSON() - { - VerbatimHttpHandler handler = new( - expectedRequest: """ - { - "max_tokens": 1024, - "model": "claude-sonnet-4-5-20250929", - "messages": [{ - "role": "user", - "content": [{ - "type": "text", - "text": "Tell me about the book '1984' by George Orwell." - }] - }], - "output_config": { - "format": { - "type": "json_schema", - "schema": { - "type": "object", - "properties": { - "title": { "type": "string" }, - "author": { - "type": "object", - "properties": { - "name": { "type": "string" }, - "birth_year": { "type": "integer" } - }, - "required": ["name", "birth_year"], - "additionalProperties": false - }, - "published_year": { - "type": "integer" - } - }, - "required": ["title", "author", "published_year"], - "additionalProperties": false - } - } - } - } - """, - actualResponse: """ - { - "id": "msg_format_02", - "type": "message", - "role": "assistant", - "model": "claude-sonnet-4-5-20250929", - "content": [{ - "type": "text", - "text": "{\"title\":\"1984\",\"author\":{\"name\":\"George Orwell\",\"birth_year\":1903},\"published_year\":1949}" - }], - "stop_reason": "end_turn", - "usage": { - "input_tokens": 30, - "output_tokens": 25 - } - } - """ - ); - - IChatClient chatClient = CreateChatClient(handler, "claude-sonnet-4-5-20250929"); - - ChatOptions options = new() - { - ResponseFormat = ChatResponseFormat.ForJsonSchema( - JsonElement.Parse( - """ - { - "type": "object", - "properties": { - "title": { "type": "string" }, - "author": { - "type": "object", - "properties": { - "name": { "type": "string" }, - "birth_year": { "type": "integer" } - }, - "required": ["name", "birth_year"] - }, - "published_year": { "type": "integer" } - }, - "required": ["title", "author", "published_year"] - } - """ - ), - "book_info" - ), - }; - - ChatResponse response = await chatClient.GetResponseAsync( - "Tell me about the book '1984' by George Orwell.", - options, - TestContext.Current.CancellationToken - ); - - Assert.NotNull(response); - TextContent textContent = Assert.IsType(response.Messages[0].Contents[0]); - Assert.Contains("1984", textContent.Text); - Assert.Contains("Orwell", textContent.Text); - Assert.Contains("1903", textContent.Text); - Assert.Contains("1949", textContent.Text); - } - - [Fact] - public async Task GetResponseAsync_WithArraySchema_ReturnsStructuredJSON() - { - VerbatimHttpHandler handler = new( - expectedRequest: """ - { - "max_tokens": 1024, - "model": "claude-sonnet-4-5-20250929", - "messages": [{ - "role": "user", - "content": [{ - "type": "text", - "text": "List 3 common fruits: apple, orange, and banana." - }] - }], - "output_config": { - "format": { - "type": "json_schema", - "schema": { - "type": "object", - "properties": { - "fruits": { - "type": "array", - "items": { - "type": "object", - "properties": { - "name": { "type": "string" }, - "color": { "type": "string" }, - "is_citrus": { "type": "boolean" } - }, - "required": ["name", "color", "is_citrus"], - "additionalProperties": false - } - } - }, - "required": ["fruits"], - "additionalProperties": false - } - } - } - } - """, - actualResponse: """ - { - "id": "msg_format_03", - "type": "message", - "role": "assistant", - "model": "claude-sonnet-4-5-20250929", - "content": [{ - "type": "text", - "text": "{\"fruits\":[{\"name\":\"apple\",\"color\":\"red\",\"is_citrus\":false},{\"name\":\"orange\",\"color\":\"orange\",\"is_citrus\":true},{\"name\":\"banana\",\"color\":\"yellow\",\"is_citrus\":false}]}" - }], - "stop_reason": "end_turn", - "usage": { - "input_tokens": 35, - "output_tokens": 40 - } - } - """ - ); - - IChatClient chatClient = CreateChatClient(handler, "claude-sonnet-4-5-20250929"); - - ChatOptions options = new() - { - ResponseFormat = ChatResponseFormat.ForJsonSchema( - JsonElement.Parse( - """ - { - "type": "object", - "properties": { - "fruits": { - "type": "array", - "items": { - "type": "object", - "properties": { - "name": { "type": "string" }, - "color": { "type": "string" }, - "is_citrus": { "type": "boolean" } - }, - "required": ["name", "color", "is_citrus"] - } - } - }, - "required": ["fruits"] - } - """ - ), - "fruit_list" - ), - }; - - ChatResponse response = await chatClient.GetResponseAsync( - "List 3 common fruits: apple, orange, and banana.", - options, - TestContext.Current.CancellationToken - ); - - Assert.NotNull(response); - TextContent textContent = Assert.IsType(response.Messages[0].Contents[0]); - Assert.Contains("apple", textContent.Text); - Assert.Contains("orange", textContent.Text); - Assert.Contains("banana", textContent.Text); - } - [Fact] public async Task GetResponseAsync_WithMultipleBetaToolUnionsAsAITools_FlowsThroughToRequest() { @@ -954,71 +663,13 @@ public async Task GetResponseAsync_McpToolResultBlock_WithError() ); Assert.NotNull(mcpResult); Assert.Equal("mcp_call_error_1", mcpResult.CallId); - Assert.NotNull(mcpResult.Output); - Assert.Single(mcpResult.Output); + Assert.NotNull(mcpResult.Outputs); + Assert.Single(mcpResult.Outputs); - ErrorContent errorContent = Assert.IsType(mcpResult.Output[0]); + ErrorContent errorContent = Assert.IsType(mcpResult.Outputs[0]); Assert.Equal("Connection timeout", errorContent.Message); } - [Fact] - public async Task GetResponseAsync_CodeExecutionToolResult_WithError() - { - VerbatimHttpHandler handler = new( - expectedRequest: """ - { - "max_tokens": 1024, - "model": "claude-haiku-4-5", - "messages": [{ - "role": "user", - "content": [{ - "type": "text", - "text": "Test code execution error" - }] - }] - } - """, - actualResponse: """ - { - "id": "msg_code_error_01", - "type": "message", - "role": "assistant", - "model": "claude-haiku-4-5", - "content": [{ - "type": "code_execution_tool_result", - "tool_use_id": "code_exec_error_1", - "content": { - "type": "code_execution_tool_result_error", - "error_code": "execution_time_exceeded" - } - }], - "stop_reason": "end_turn", - "usage": { - "input_tokens": 10, - "output_tokens": 5 - } - } - """ - ); - - IChatClient chatClient = CreateChatClient(handler, "claude-haiku-4-5"); - ChatResponse response = await chatClient.GetResponseAsync( - "Test code execution error", - new(), - TestContext.Current.CancellationToken - ); - - CodeInterpreterToolResultContent codeResult = - Assert.IsType(response.Messages[0].Contents[0]); - Assert.NotNull(codeResult); - Assert.Equal("code_exec_error_1", codeResult.CallId); - Assert.NotNull(codeResult.Outputs); - Assert.Single(codeResult.Outputs); - - ErrorContent errorContent = Assert.IsType(codeResult.Outputs[0]); - Assert.Equal("ExecutionTimeExceeded", errorContent.ErrorCode); - } - [Fact] public async Task GetResponseAsync_WithFunctionResultContent_HostedFileContent() { @@ -1111,7 +762,32 @@ public async Task GetResponseAsync_WithFunctionResultContent_HostedFileContent() } [Fact] - public async Task GetResponseAsync_WithAIFunctionTool_AdditionalProperties_FlowsThrough() + public void AsAITool_GetService_ReturnsToolUnion() + { + BetaToolUnion toolUnion = new BetaWebSearchTool20250305() + { + AllowedDomains = ["example.com"], + }; + AITool aiTool = toolUnion.AsAITool(); + Assert.Same(toolUnion, aiTool.GetService()); + + Assert.Null(aiTool.GetService("key")); + Assert.Null(aiTool.GetService()); + + Assert.Contains(nameof(BetaWebSearchTool20250305), aiTool.Name); + } + + [Fact] + public void AsAITool_GetService_ThrowsOnNullServiceType() + { + AITool aiTool = ( + (BetaToolUnion)new BetaWebSearchTool20250305() { AllowedDomains = ["example.com"] } + ).AsAITool(); + Assert.Throws(() => aiTool.GetService(null!, null)); + } + + [Fact] + public async Task GetResponseAsync_WithHostedFileContent() { VerbatimHttpHandler handler = new( expectedRequest: """ @@ -1121,224 +797,29 @@ public async Task GetResponseAsync_WithAIFunctionTool_AdditionalProperties_Flows "messages": [{ "role": "user", "content": [{ - "type": "text", - "text": "Use enhanced tool" - }] - }], - "tools": [{ - "name": "enhanced_tool", - "description": "A tool with additional properties", - "input_schema": { - "type": "object", - "properties": { - "query": { - "type": "string" - } - }, - "required": ["query"] - }, - "defer_loading": true, - "strict": true, - "input_examples": [ - { - "query": "example query" + "type": "document", + "source": { + "type": "file", + "file_id": "file_abc123" } - ], - "allowed_callers": [ - "direct" - ] + }] }] } """, actualResponse: """ { - "id": "msg_enhanced_tool_01", + "id": "msg_hosted_file_01", "type": "message", "role": "assistant", "model": "claude-haiku-4-5", "content": [{ "type": "text", - "text": "Tool is ready" + "text": "I read the hosted file." }], "stop_reason": "end_turn", "usage": { - "input_tokens": 40, - "output_tokens": 10 - } - } - """ - ); - - IChatClient chatClient = CreateChatClient(handler, "claude-haiku-4-5"); - - var enhancedFunction = AIFunctionFactory.Create( - (string query) => "result", - new AIFunctionFactoryOptions - { - Name = "enhanced_tool", - Description = "A tool with additional properties", - AdditionalProperties = new Dictionary - { - [nameof(BetaTool.DeferLoading)] = true, - [nameof(BetaTool.Strict)] = true, - [nameof(BetaTool.InputExamples)] = new List> - { - new() { ["query"] = JsonSerializer.SerializeToElement("example query") }, - }, - [nameof(BetaTool.AllowedCallers)] = new List< - ApiEnum - > - { - new(JsonSerializer.SerializeToElement("direct")), - }, - }, - } - ); - - ChatOptions options = new() { Tools = [enhancedFunction] }; - - ChatResponse response = await chatClient.GetResponseAsync( - "Use enhanced tool", - options, - TestContext.Current.CancellationToken - ); - Assert.NotNull(response); - } - - [Fact] - public async Task GetResponseAsync_WithAIFunctionTool_PartialAdditionalProperties() - { - VerbatimHttpHandler handler = new( - expectedRequest: """ - { - "max_tokens": 1024, - "model": "claude-haiku-4-5", - "messages": [{ - "role": "user", - "content": [{ - "type": "text", - "text": "Use strict tool" - }] - }], - "tools": [{ - "name": "strict_tool", - "description": "A tool with only strict property", - "input_schema": { - "type": "object", - "properties": { - "value": { - "type": "integer" - } - }, - "required": ["value"] - }, - "strict": true - }] - } - """, - actualResponse: """ - { - "id": "msg_strict_tool_01", - "type": "message", - "role": "assistant", - "model": "claude-haiku-4-5", - "content": [{ - "type": "text", - "text": "Strict mode enabled" - }], - "stop_reason": "end_turn", - "usage": { - "input_tokens": 35, - "output_tokens": 8 - } - } - """ - ); - - IChatClient chatClient = CreateChatClient(handler, "claude-haiku-4-5"); - - var strictFunction = AIFunctionFactory.Create( - (int value) => value * 2, - new AIFunctionFactoryOptions - { - Name = "strict_tool", - Description = "A tool with only strict property", - AdditionalProperties = new Dictionary - { - [nameof(BetaTool.Strict)] = true, - }, - } - ); - - ChatOptions options = new() { Tools = [strictFunction] }; - - ChatResponse response = await chatClient.GetResponseAsync( - "Use strict tool", - options, - TestContext.Current.CancellationToken - ); - Assert.NotNull(response); - } - - [Fact] - public void AsAITool_GetService_ReturnsToolUnion() - { - BetaToolUnion toolUnion = new BetaWebSearchTool20250305() - { - AllowedDomains = ["example.com"], - }; - AITool aiTool = toolUnion.AsAITool(); - Assert.Same(toolUnion, aiTool.GetService()); - - Assert.Null(aiTool.GetService("key")); - Assert.Null(aiTool.GetService()); - - Assert.Contains(nameof(BetaWebSearchTool20250305), aiTool.Name); - } - - [Fact] - public void AsAITool_GetService_ThrowsOnNullServiceType() - { - AITool aiTool = ( - (BetaToolUnion)new BetaWebSearchTool20250305() { AllowedDomains = ["example.com"] } - ).AsAITool(); - Assert.Throws(() => aiTool.GetService(null!, null)); - } - - [Fact] - public async Task GetResponseAsync_WithHostedFileContent() - { - VerbatimHttpHandler handler = new( - expectedRequest: """ - { - "max_tokens": 1024, - "model": "claude-haiku-4-5", - "messages": [{ - "role": "user", - "content": [{ - "type": "document", - "source": { - "type": "file", - "file_id": "file_abc123" - } - }] - }] - } - """, - actualResponse: """ - { - "id": "msg_hosted_file_01", - "type": "message", - "role": "assistant", - "model": "claude-haiku-4-5", - "content": [{ - "type": "text", - "text": "I read the hosted file." - }], - "stop_reason": "end_turn", - "usage": { - "input_tokens": 20, - "output_tokens": 6 + "input_tokens": 20, + "output_tokens": 6 } } """ @@ -1357,40 +838,45 @@ [new ChatMessage(ChatRole.User, [hostedFile])], } [Fact] - public async Task GetResponseAsync_WithHostedCodeInterpreterTool() + public async Task GetResponseAsync_WithRawRepresentationFactory() { VerbatimHttpHandler handler = new( expectedRequest: """ { - "max_tokens": 1024, + "max_tokens": 2048, "model": "claude-haiku-4-5", - "messages": [{ - "role": "user", - "content": [{ - "type": "text", - "text": "Execute code" - }] - }], - "tools": [{ - "type": "code_execution_20250825", - "name": "code_execution" - }] + "messages": [ + { + "role": "user", + "content": [{ + "type": "text", + "text": "Preconfigured message" + }] + }, + { + "role": "user", + "content": [{ + "type": "text", + "text": "New message" + }] + } + ] } """, actualResponse: """ { - "id": "msg_code_exec_01", + "id": "msg_factory_01", "type": "message", "role": "assistant", "model": "claude-haiku-4-5", "content": [{ "type": "text", - "text": "I can execute code." + "text": "Response" }], "stop_reason": "end_turn", "usage": { - "input_tokens": 15, - "output_tokens": 6 + "input_tokens": 20, + "output_tokens": 5 } } """ @@ -1398,10 +884,27 @@ public async Task GetResponseAsync_WithHostedCodeInterpreterTool() IChatClient chatClient = CreateChatClient(handler, "claude-haiku-4-5"); - ChatOptions options = new() { Tools = [new HostedCodeInterpreterTool()] }; + ChatOptions options = new() + { + RawRepresentationFactory = _ => new MessageCreateParams() + { + MaxTokens = 2048, + Model = "claude-haiku-4-5", + Messages = + [ + new BetaMessageParam() + { + Role = Role.User, + Content = new BetaMessageParamContent( + [new BetaTextBlockParam() { Text = "Preconfigured message" }] + ), + }, + ], + }, + }; ChatResponse response = await chatClient.GetResponseAsync( - "Execute code", + "New message", options, TestContext.Current.CancellationToken ); @@ -1409,7 +912,7 @@ public async Task GetResponseAsync_WithHostedCodeInterpreterTool() } [Fact] - public async Task GetResponseAsync_WithRawRepresentationFactory() + public async Task GetResponseAsync_WithNonEmptyMessageParams_EmptyMessages() { VerbatimHttpHandler handler = new( expectedRequest: """ @@ -1423,20 +926,13 @@ public async Task GetResponseAsync_WithRawRepresentationFactory() "type": "text", "text": "Preconfigured message" }] - }, - { - "role": "user", - "content": [{ - "type": "text", - "text": "New message" - }] } ] } """, actualResponse: """ { - "id": "msg_factory_01", + "id": "msg_factory_02", "type": "message", "role": "assistant", "model": "claude-haiku-4-5", @@ -1446,7 +942,7 @@ public async Task GetResponseAsync_WithRawRepresentationFactory() }], "stop_reason": "end_turn", "usage": { - "input_tokens": 20, + "input_tokens": 10, "output_tokens": 5 } } @@ -1475,7 +971,7 @@ [new BetaTextBlockParam() { Text = "Preconfigured message" }] }; ChatResponse response = await chatClient.GetResponseAsync( - "New message", + [], options, TestContext.Current.CancellationToken ); @@ -1748,15 +1244,16 @@ public async Task GetResponseAsync_McpToolResultWithTextList() ); Assert.NotNull(mcpResult); Assert.Equal("mcp_call_789", mcpResult.CallId); - Assert.NotNull(mcpResult.Output); - Assert.Equal(2, mcpResult.Output.Count); - Assert.Equal("First result", ((TextContent)mcpResult.Output[0]).Text); - Assert.Equal("Second result", ((TextContent)mcpResult.Output[1]).Text); + Assert.NotNull(mcpResult.Outputs); + Assert.Equal(2, mcpResult.Outputs.Count); + Assert.Equal("First result", ((TextContent)mcpResult.Outputs[0]).Text); + Assert.Equal("Second result", ((TextContent)mcpResult.Outputs[1]).Text); } [Fact] - public async Task GetResponseAsync_CodeExecutionResult_WithStdout() + public async Task GetResponseAsync_WithHostedTools_AddsBetaHeaders() { + IEnumerable? capturedBetaHeaders = null; VerbatimHttpHandler handler = new( expectedRequest: """ { @@ -1766,58 +1263,69 @@ public async Task GetResponseAsync_CodeExecutionResult_WithStdout() "role": "user", "content": [{ "type": "text", - "text": "Run code" + "text": "Use both tools" }] + }], + "tools": [{ + "type": "code_execution_20250825", + "name": "code_execution" + }], + "mcp_servers": [{ + "name": "mcp", + "type": "url", + "url": "https://mcp.example.com/server" }] } """, actualResponse: """ { - "id": "msg_code_stdout_01", + "id": "msg_both_beta_01", "type": "message", "role": "assistant", "model": "claude-haiku-4-5", "content": [{ - "type": "code_execution_tool_result", - "tool_use_id": "code_exec_1", - "content": { - "type": "code_execution_result", - "stdout": "Hello World\n42\n", - "stderr": "", - "return_code": 0, - "content": [] - } + "type": "text", + "text": "I have access to both tools." }], "stop_reason": "end_turn", "usage": { - "input_tokens": 10, - "output_tokens": 5 + "input_tokens": 25, + "output_tokens": 10 } } """ - ); + ) + { + OnRequestHeaders = headers => + headers.TryGetValues("anthropic-beta", out capturedBetaHeaders), + }; IChatClient chatClient = CreateChatClient(handler, "claude-haiku-4-5"); + + ChatOptions options = new() + { + Tools = + [ + new HostedCodeInterpreterTool(), + new HostedMcpServerTool("my-mcp-server", new Uri("https://mcp.example.com/server")), + ], + }; + ChatResponse response = await chatClient.GetResponseAsync( - "Run code", - new(), + "Use both tools", + options, TestContext.Current.CancellationToken ); - - CodeInterpreterToolResultContent codeResult = - Assert.IsType(response.Messages[0].Contents[0]); - Assert.NotNull(codeResult); - Assert.Equal("code_exec_1", codeResult.CallId); - Assert.NotNull(codeResult.Outputs); - Assert.Single(codeResult.Outputs); - - TextContent textOutput = Assert.IsType(codeResult.Outputs[0]); - Assert.Equal("Hello World\n42\n", textOutput.Text); + Assert.NotNull(response); + Assert.NotNull(capturedBetaHeaders); + Assert.Contains("code-execution-2025-08-25", capturedBetaHeaders); + Assert.Contains("mcp-client-2025-11-20", capturedBetaHeaders); } [Fact] - public async Task GetResponseAsync_CodeExecutionResult_WithStderrAndNonZeroReturnCode() + public async Task GetResponseAsync_WithHostedToolsAndExistingBetas_PreservesAndDeduplicatesBetas() { + IEnumerable? capturedBetaHeaders = null; VerbatimHttpHandler handler = new( expectedRequest: """ { @@ -1827,27 +1335,29 @@ public async Task GetResponseAsync_CodeExecutionResult_WithStderrAndNonZeroRetur "role": "user", "content": [{ "type": "text", - "text": "Run failing code" + "text": "Test" }] + }], + "tools": [{ + "type": "code_execution_20250825", + "name": "code_execution" + }], + "mcp_servers": [{ + "name": "mcp", + "type": "url", + "url": "https://mcp.example.com/server" }] } """, actualResponse: """ { - "id": "msg_code_stderr_01", + "id": "msg_preserve_beta_01", "type": "message", "role": "assistant", "model": "claude-haiku-4-5", "content": [{ - "type": "code_execution_tool_result", - "tool_use_id": "code_exec_2", - "content": { - "type": "code_execution_result", - "stdout": "", - "stderr": "Division by zero error", - "return_code": 1, - "content": [] - } + "type": "text", + "text": "Response" }], "stop_reason": "end_turn", "usage": { @@ -1856,28 +1366,47 @@ public async Task GetResponseAsync_CodeExecutionResult_WithStderrAndNonZeroRetur } } """ - ); + ) + { + OnRequestHeaders = headers => + headers.TryGetValues("anthropic-beta", out capturedBetaHeaders), + }; IChatClient chatClient = CreateChatClient(handler, "claude-haiku-4-5"); + + ChatOptions options = new() + { + RawRepresentationFactory = _ => new MessageCreateParams() + { + MaxTokens = 1024, + Model = "claude-haiku-4-5", + Messages = [], + Betas = ["custom-beta-feature", "code-execution-2025-08-25"], + }, + Tools = + [ + new HostedCodeInterpreterTool(), + new HostedMcpServerTool("my-mcp-server", new Uri("https://mcp.example.com/server")), + ], + }; + ChatResponse response = await chatClient.GetResponseAsync( - "Run failing code", - new(), + "Test", + options, TestContext.Current.CancellationToken ); - - CodeInterpreterToolResultContent codeResult = - Assert.IsType(response.Messages[0].Contents[0]); - Assert.NotNull(codeResult.Outputs); - Assert.Single(codeResult.Outputs); - - ErrorContent errorOutput = Assert.IsType(codeResult.Outputs[0]); - Assert.Equal("Division by zero error", errorOutput.Message); - Assert.Equal("1", errorOutput.ErrorCode); + Assert.NotNull(response); + Assert.NotNull(capturedBetaHeaders); + Assert.Equal(3, capturedBetaHeaders.Count()); + Assert.Contains("custom-beta-feature", capturedBetaHeaders); + Assert.Contains("code-execution-2025-08-25", capturedBetaHeaders); + Assert.Contains("mcp-client-2025-11-20", capturedBetaHeaders); } [Fact] - public async Task GetResponseAsync_CodeExecutionResult_WithFileOutputs() + public async Task GetResponseAsync_IncludesMeaiUserAgentHeader() { + string[]? capturedUserAgentValues = null; VerbatimHttpHandler handler = new( expectedRequest: """ { @@ -1887,33 +1416,20 @@ public async Task GetResponseAsync_CodeExecutionResult_WithFileOutputs() "role": "user", "content": [{ "type": "text", - "text": "Create file" + "text": "Test" }] }] } """, actualResponse: """ { - "id": "msg_code_files_01", + "id": "msg_meai_header_01", "type": "message", "role": "assistant", "model": "claude-haiku-4-5", "content": [{ - "type": "code_execution_tool_result", - "tool_use_id": "code_exec_3", - "content": { - "type": "code_execution_result", - "stdout": "File created", - "stderr": "", - "return_code": 0, - "content": [{ - "type": "code_execution_output", - "file_id": "file_output_123" - }, { - "type": "code_execution_output", - "file_id": "file_output_456" - }] - } + "type": "text", + "text": "Response" }], "stop_reason": "end_turn", "usage": { @@ -1922,33 +1438,37 @@ public async Task GetResponseAsync_CodeExecutionResult_WithFileOutputs() } } """ - ); + ) + { + OnRequestHeaders = headers => + { + // Verify there's exactly one User-Agent header entry + Assert.Single(headers, h => h.Key == "User-Agent"); + if (headers.TryGetValues("User-Agent", out var values)) + { + capturedUserAgentValues = [.. values]; + } + }, + }; IChatClient chatClient = CreateChatClient(handler, "claude-haiku-4-5"); + ChatResponse response = await chatClient.GetResponseAsync( - "Create file", + "Test", new(), TestContext.Current.CancellationToken ); - CodeInterpreterToolResultContent codeResult = - Assert.IsType(response.Messages[0].Contents[0]); - Assert.NotNull(codeResult.Outputs); - Assert.Equal(3, codeResult.Outputs.Count); - - TextContent textOutput = Assert.IsType(codeResult.Outputs[0]); - Assert.Equal("File created", textOutput.Text); - - HostedFileContent fileOutput1 = Assert.IsType(codeResult.Outputs[1]); - Assert.Equal("file_output_123", fileOutput1.FileId); - - HostedFileContent fileOutput2 = Assert.IsType(codeResult.Outputs[2]); - Assert.Equal("file_output_456", fileOutput2.FileId); + Assert.NotNull(response); + Assert.NotNull(capturedUserAgentValues); + Assert.Contains(capturedUserAgentValues, v => v.Contains("MEAI")); + Assert.Contains(capturedUserAgentValues, v => v.Contains("AnthropicClient")); } [Fact] - public async Task GetResponseAsync_BashCodeExecutionResult_WithStdout() + public async Task GetStreamingResponseAsync_IncludesMeaiUserAgentHeader() { + string[]? capturedUserAgentValues = null; VerbatimHttpHandler handler = new( expectedRequest: """ { @@ -1958,58 +1478,67 @@ public async Task GetResponseAsync_BashCodeExecutionResult_WithStdout() "role": "user", "content": [{ "type": "text", - "text": "Run bash" + "text": "Test streaming" }] - }] + }], + "stream": true } """, actualResponse: """ + event: message_start + data: {"type":"message_start","message":{"id":"msg_stream_meai_01","type":"message","role":"assistant","model":"claude-haiku-4-5","content":[],"stop_reason":null,"stop_sequence":null,"usage":{"input_tokens":10,"output_tokens":0}}} + + event: content_block_start + data: {"type":"content_block_start","index":0,"content_block":{"type":"text","text":""}} + + event: content_block_delta + data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":"Response"}} + + event: content_block_stop + data: {"type":"content_block_stop","index":0} + + event: message_delta + data: {"type":"message_delta","delta":{"stop_reason":"end_turn","stop_sequence":null},"usage":{"output_tokens":5}} + + event: message_stop + data: {"type":"message_stop"} + + """ + ) + { + OnRequestHeaders = headers => { - "id": "msg_bash_stdout_01", - "type": "message", - "role": "assistant", - "model": "claude-haiku-4-5", - "content": [{ - "type": "bash_code_execution_tool_result", - "tool_use_id": "bash_exec_1", - "content": { - "type": "bash_code_execution_result", - "stdout": "Hello from bash\n5\n", - "stderr": "", - "return_code": 0, - "content": [] - } - }], - "stop_reason": "end_turn", - "usage": { - "input_tokens": 10, - "output_tokens": 5 + // Verify there's exactly one User-Agent header entry + Assert.Single(headers, h => h.Key == "User-Agent"); + if (headers.TryGetValues("User-Agent", out var values)) + { + capturedUserAgentValues = [.. values]; } - } - """ - ); + }, + }; IChatClient chatClient = CreateChatClient(handler, "claude-haiku-4-5"); - ChatResponse response = await chatClient.GetResponseAsync( - "Run bash", - new(), - TestContext.Current.CancellationToken - ); - CodeInterpreterToolResultContent codeResult = - Assert.IsType(response.Messages[0].Contents[0]); - Assert.NotNull(codeResult); - Assert.Equal("bash_exec_1", codeResult.CallId); - Assert.NotNull(codeResult.Outputs); - Assert.Single(codeResult.Outputs); + await foreach ( + var update in chatClient.GetStreamingResponseAsync( + "Test streaming", + new(), + TestContext.Current.CancellationToken + ) + ) + { + // Consume the stream + } - TextContent textOutput = Assert.IsType(codeResult.Outputs[0]); - Assert.Equal("Hello from bash\n5\n", textOutput.Text); + Assert.NotNull(capturedUserAgentValues); + Assert.Contains(capturedUserAgentValues, v => v.Contains("MEAI")); + Assert.Contains(capturedUserAgentValues, v => v.Contains("AnthropicClient")); } [Fact] - public async Task GetResponseAsync_BashCodeExecutionResult_WithStderrAndNonZeroReturnCode() + public async Task GetResponseAsync_MeaiUserAgentHeader_HasCorrectFormat() { + string[]? capturedUserAgentValues = null; VerbatimHttpHandler handler = new( expectedRequest: """ { @@ -2019,27 +1548,20 @@ public async Task GetResponseAsync_BashCodeExecutionResult_WithStderrAndNonZeroR "role": "user", "content": [{ "type": "text", - "text": "Run failing bash" + "text": "Test" }] }] } """, actualResponse: """ { - "id": "msg_bash_stderr_01", + "id": "msg_meai_format_01", "type": "message", "role": "assistant", "model": "claude-haiku-4-5", "content": [{ - "type": "bash_code_execution_tool_result", - "tool_use_id": "bash_exec_2", - "content": { - "type": "bash_code_execution_result", - "stdout": "", - "stderr": "bash: command not found: nonexistent", - "return_code": 127, - "content": [] - } + "type": "text", + "text": "Response" }], "stop_reason": "end_turn", "usage": { @@ -2048,28 +1570,44 @@ public async Task GetResponseAsync_BashCodeExecutionResult_WithStderrAndNonZeroR } } """ - ); + ) + { + OnRequestHeaders = headers => + { + // Verify there's exactly one User-Agent header entry + Assert.Single(headers, h => h.Key == "User-Agent"); + if (headers.TryGetValues("User-Agent", out var values)) + { + capturedUserAgentValues = [.. values]; + } + }, + }; IChatClient chatClient = CreateChatClient(handler, "claude-haiku-4-5"); + ChatResponse response = await chatClient.GetResponseAsync( - "Run failing bash", + "Test", new(), TestContext.Current.CancellationToken ); - CodeInterpreterToolResultContent codeResult = - Assert.IsType(response.Messages[0].Contents[0]); - Assert.NotNull(codeResult.Outputs); - Assert.Single(codeResult.Outputs); - - ErrorContent errorOutput = Assert.IsType(codeResult.Outputs[0]); - Assert.Equal("bash: command not found: nonexistent", errorOutput.Message); - Assert.Equal("127", errorOutput.ErrorCode); + Assert.NotNull(response); + Assert.NotNull(capturedUserAgentValues); + // Verify the MEAI user-agent is present and has correct format (MEAI or MEAI/version) + Assert.Contains( + capturedUserAgentValues, + v => v.StartsWith("MEAI", StringComparison.Ordinal) + ); + Assert.Contains(capturedUserAgentValues, v => v.Contains("AnthropicClient")); } [Fact] - public async Task GetResponseAsync_BashCodeExecutionResult_WithFileOutputs() + public async Task GetResponseAsync_MeaiUserAgentHeader_PresentAlongsideDefaultHeaders() { + bool hasAnthropicVersion = false; + bool hasMeaiUserAgent = false; + bool hasDefaultUserAgent = false; + VerbatimHttpHandler handler = new( expectedRequest: """ { @@ -2079,33 +1617,20 @@ public async Task GetResponseAsync_BashCodeExecutionResult_WithFileOutputs() "role": "user", "content": [{ "type": "text", - "text": "Create files with bash" + "text": "Test" }] }] } """, actualResponse: """ { - "id": "msg_bash_files_01", + "id": "msg_headers_01", "type": "message", "role": "assistant", "model": "claude-haiku-4-5", "content": [{ - "type": "bash_code_execution_tool_result", - "tool_use_id": "bash_exec_3", - "content": { - "type": "bash_code_execution_result", - "stdout": "Files created successfully", - "stderr": "", - "return_code": 0, - "content": [{ - "type": "bash_code_execution_output", - "file_id": "file_bash_123" - }, { - "type": "bash_code_execution_output", - "file_id": "file_bash_456" - }] - } + "type": "text", + "text": "Response" }], "stop_reason": "end_turn", "usage": { @@ -2114,106 +1639,107 @@ public async Task GetResponseAsync_BashCodeExecutionResult_WithFileOutputs() } } """ - ); + ) + { + OnRequestHeaders = headers => + { + // Verify there's exactly one User-Agent header entry + Assert.Single(headers, h => h.Key == "User-Agent"); + hasAnthropicVersion = headers.Contains("anthropic-version"); + if (headers.TryGetValues("User-Agent", out var values)) + { + var valuesArray = values.ToArray(); + hasMeaiUserAgent = valuesArray.Any(v => v.Contains("MEAI")); + hasDefaultUserAgent = valuesArray.Any(v => v.Contains("AnthropicClient")); + } + }, + }; IChatClient chatClient = CreateChatClient(handler, "claude-haiku-4-5"); + ChatResponse response = await chatClient.GetResponseAsync( - "Create files with bash", + "Test", new(), TestContext.Current.CancellationToken ); - CodeInterpreterToolResultContent codeResult = - Assert.IsType(response.Messages[0].Contents[0]); - Assert.NotNull(codeResult.Outputs); - Assert.Equal(3, codeResult.Outputs.Count); - - TextContent textOutput = Assert.IsType(codeResult.Outputs[0]); - Assert.Equal("Files created successfully", textOutput.Text); - - HostedFileContent fileOutput1 = Assert.IsType(codeResult.Outputs[1]); - Assert.Equal("file_bash_123", fileOutput1.FileId); - - HostedFileContent fileOutput2 = Assert.IsType(codeResult.Outputs[2]); - Assert.Equal("file_bash_456", fileOutput2.FileId); + Assert.NotNull(response); + Assert.True(hasAnthropicVersion, "anthropic-version header should be present"); + Assert.True(hasMeaiUserAgent, "MEAI user-agent header should be present"); + Assert.True( + hasDefaultUserAgent, + "Default AnthropicClient user-agent header should be present" + ); } [Fact] - public async Task GetResponseAsync_WithHostedTools_AddsBetaHeaders() + public async Task GetResponseAsync_WithReasoningEffort_IgnoredWhenThinkingAlreadyConfigured() { - IEnumerable? capturedBetaHeaders = null; + // When RawRepresentationFactory pre-configures Thinking, the Reasoning option should be ignored. VerbatimHttpHandler handler = new( expectedRequest: """ { - "max_tokens": 1024, + "max_tokens": 50000, "model": "claude-haiku-4-5", "messages": [{ "role": "user", "content": [{ "type": "text", - "text": "Use both tools" + "text": "Think carefully" }] }], - "tools": [{ - "type": "code_execution_20250825", - "name": "code_execution" - }], - "mcp_servers": [{ - "name": "mcp", - "type": "url", - "url": "https://mcp.example.com/server" - }] + "thinking": { + "type": "enabled", + "budget_tokens": 5000 + } } """, actualResponse: """ { - "id": "msg_both_beta_01", + "id": "msg_reasoning_preconfigured", "type": "message", "role": "assistant", "model": "claude-haiku-4-5", "content": [{ "type": "text", - "text": "I have access to both tools." + "text": "Response" }], "stop_reason": "end_turn", "usage": { - "input_tokens": 25, - "output_tokens": 10 + "input_tokens": 10, + "output_tokens": 15 } } """ - ) - { - OnRequestHeaders = headers => - headers.TryGetValues("anthropic-beta", out capturedBetaHeaders), - }; + ); IChatClient chatClient = CreateChatClient(handler, "claude-haiku-4-5"); ChatOptions options = new() { - Tools = - [ - new HostedCodeInterpreterTool(), - new HostedMcpServerTool("my-mcp-server", new Uri("https://mcp.example.com/server")), - ], + // RawRepresentationFactory sets Thinking to enabled with 5000 budget. + // Reasoning.Effort should be ignored since Thinking is already configured. + RawRepresentationFactory = _ => new MessageCreateParams() + { + MaxTokens = 50000, + Model = "claude-haiku-4-5", + Messages = [], + Thinking = new BetaThinkingConfigParam(new BetaThinkingConfigEnabled(5000)), + }, + Reasoning = new() { Effort = ReasoningEffort.ExtraHigh }, }; ChatResponse response = await chatClient.GetResponseAsync( - "Use both tools", + "Think carefully", options, TestContext.Current.CancellationToken ); Assert.NotNull(response); - Assert.NotNull(capturedBetaHeaders); - Assert.Contains("code-execution-2025-08-25", capturedBetaHeaders); - Assert.Contains("mcp-client-2025-11-20", capturedBetaHeaders); } [Fact] - public async Task GetResponseAsync_WithHostedToolsAndExistingBetas_PreservesAndDeduplicatesBetas() + public async Task GetResponseAsync_WithAIFunctionTool_AllowedCallers_FlowsThrough() { - IEnumerable? capturedBetaHeaders = null; VerbatimHttpHandler handler = new( expectedRequest: """ { @@ -2223,340 +1749,474 @@ public async Task GetResponseAsync_WithHostedToolsAndExistingBetas_PreservesAndD "role": "user", "content": [{ "type": "text", - "text": "Test" + "text": "Use tool" }] }], "tools": [{ - "type": "code_execution_20250825", - "name": "code_execution" - }], - "mcp_servers": [{ - "name": "mcp", - "type": "url", - "url": "https://mcp.example.com/server" + "name": "callers_tool", + "description": "A tool with allowed callers", + "input_schema": { + "type": "object", + "properties": { + "value": { + "type": "integer" + } + }, + "required": ["value"], + "additionalProperties": false + }, + "allowed_callers": [ + "direct" + ] }] } """, actualResponse: """ { - "id": "msg_preserve_beta_01", + "id": "msg_callers_01", "type": "message", "role": "assistant", "model": "claude-haiku-4-5", "content": [{ "type": "text", - "text": "Response" + "text": "Done" }], "stop_reason": "end_turn", "usage": { - "input_tokens": 10, + "input_tokens": 30, "output_tokens": 5 } } """ - ) - { - OnRequestHeaders = headers => - headers.TryGetValues("anthropic-beta", out capturedBetaHeaders), - }; + ); IChatClient chatClient = CreateChatClient(handler, "claude-haiku-4-5"); - ChatOptions options = new() - { - RawRepresentationFactory = _ => new MessageCreateParams() + var function = AIFunctionFactory.Create( + (int value) => value, + new AIFunctionFactoryOptions { - MaxTokens = 1024, - Model = "claude-haiku-4-5", - Messages = [], - Betas = ["custom-beta-feature", "code-execution-2025-08-25"], - }, - Tools = - [ - new HostedCodeInterpreterTool(), - new HostedMcpServerTool("my-mcp-server", new Uri("https://mcp.example.com/server")), - ], - }; + Name = "callers_tool", + Description = "A tool with allowed callers", + AdditionalProperties = new Dictionary + { + [nameof(BetaTool.AllowedCallers)] = new List< + ApiEnum + > + { + new(JsonSerializer.SerializeToElement("direct")), + }, + }, + } + ); + + ChatOptions options = new() { Tools = [function] }; ChatResponse response = await chatClient.GetResponseAsync( - "Test", + "Use tool", options, TestContext.Current.CancellationToken ); Assert.NotNull(response); - Assert.NotNull(capturedBetaHeaders); - Assert.Equal(3, capturedBetaHeaders.Count()); - Assert.Contains("custom-beta-feature", capturedBetaHeaders); - Assert.Contains("code-execution-2025-08-25", capturedBetaHeaders); - Assert.Contains("mcp-client-2025-11-20", capturedBetaHeaders); } [Fact] - public async Task GetResponseAsync_IncludesMeaiUserAgentHeader() + public void AsIHostedFileClient_ReturnsInstance() { - string[]? capturedUserAgentValues = null; - VerbatimHttpHandler handler = new( - expectedRequest: """ - { - "max_tokens": 1024, - "model": "claude-haiku-4-5", - "messages": [{ - "role": "user", - "content": [{ - "type": "text", - "text": "Test" - }] - }] - } - """, - actualResponse: """ - { - "id": "msg_meai_header_01", - "type": "message", - "role": "assistant", - "model": "claude-haiku-4-5", - "content": [{ - "type": "text", - "text": "Response" - }], - "stop_reason": "end_turn", - "usage": { - "input_tokens": 10, - "output_tokens": 5 - } - } - """ - ) + var client = new AnthropicClient { ApiKey = "test-key" }.Beta; + IHostedFileClient fileClient = client.AsIHostedFileClient(); + Assert.NotNull(fileClient); + } + + [Fact] + public void AsIHostedFileClient_ReturnsHostedFileClientMetadata() + { + var client = new AnthropicClient { ApiKey = "test-key" }.Beta; + IHostedFileClient fileClient = client.AsIHostedFileClient(); + var metadata = fileClient.GetService(); + Assert.NotNull(metadata); + Assert.Equal("anthropic", metadata.ProviderName); + } + + [Fact] + public void AsIHostedFileClient_ThrowsOnNull() + { + Anthropic.Services.IBetaService betaService = null!; + Anthropic.Services.Beta.IFileService fileService = null!; + + Assert.Throws(() => betaService.AsIHostedFileClient()); + Assert.Throws(() => fileService.AsIHostedFileClient()); + } + + [Fact] + public async Task IHostedFileClient_UploadAsync_NullContent_Throws() + { + using IHostedFileClient fileClient = new AnthropicClient { - OnRequestHeaders = headers => - { - // Verify there's exactly one User-Agent header entry - Assert.Single(headers, h => h.Key == "User-Agent"); - if (headers.TryGetValues("User-Agent", out var values)) - { - capturedUserAgentValues = [.. values]; - } - }, - }; + ApiKey = "test-key", + }.Beta.AsIHostedFileClient(); + await Assert.ThrowsAsync( + "content", + () => + fileClient.UploadAsync( + null!, + cancellationToken: TestContext.Current.CancellationToken + ) + ); + } - IChatClient chatClient = CreateChatClient(handler, "claude-haiku-4-5"); + [Theory] + [InlineData(null)] + [InlineData("")] + public async Task IHostedFileClient_DownloadAsync_InvalidFileId_Throws(string? fileId) + { + using IHostedFileClient fileClient = new AnthropicClient + { + ApiKey = "test-key", + }.Beta.AsIHostedFileClient(); + ArgumentException ex = await Assert.ThrowsAnyAsync(() => + fileClient.DownloadAsync( + fileId!, + cancellationToken: TestContext.Current.CancellationToken + ) + ); + Assert.Equal("fileId", ex.ParamName); + } - ChatResponse response = await chatClient.GetResponseAsync( - "Test", - new(), - TestContext.Current.CancellationToken + [Theory] + [InlineData(null)] + [InlineData("")] + public async Task IHostedFileClient_GetFileInfoAsync_InvalidFileId_Throws(string? fileId) + { + using IHostedFileClient fileClient = new AnthropicClient + { + ApiKey = "test-key", + }.Beta.AsIHostedFileClient(); + ArgumentException ex = await Assert.ThrowsAnyAsync(() => + fileClient.GetFileInfoAsync( + fileId!, + cancellationToken: TestContext.Current.CancellationToken + ) ); + Assert.Equal("fileId", ex.ParamName); + } - Assert.NotNull(response); - Assert.NotNull(capturedUserAgentValues); - Assert.Contains(capturedUserAgentValues, v => v.Contains("MEAI")); - Assert.Contains(capturedUserAgentValues, v => v.Contains("AnthropicClient")); + [Theory] + [InlineData(null)] + [InlineData("")] + public async Task IHostedFileClient_DeleteAsync_InvalidFileId_Throws(string? fileId) + { + using IHostedFileClient fileClient = new AnthropicClient + { + ApiKey = "test-key", + }.Beta.AsIHostedFileClient(); + ArgumentException ex = await Assert.ThrowsAnyAsync(() => + fileClient.DeleteAsync( + fileId!, + cancellationToken: TestContext.Current.CancellationToken + ) + ); + Assert.Equal("fileId", ex.ParamName); } [Fact] - public async Task GetStreamingResponseAsync_IncludesMeaiUserAgentHeader() + public void IHostedFileClient_GetService_NullServiceType_Throws() + { + using IHostedFileClient fileClient = new AnthropicClient + { + ApiKey = "test-key", + }.Beta.AsIHostedFileClient(); + Assert.Throws("serviceType", () => fileClient.GetService(null!)); + } + + [Fact] + public void IHostedFileClient_GetService_NonNullServiceKey_ReturnsNull() + { + using IHostedFileClient fileClient = new AnthropicClient + { + ApiKey = "test-key", + }.Beta.AsIHostedFileClient(); + Assert.Null(fileClient.GetService(typeof(HostedFileClientMetadata), "key")); + } + + [Fact] + public void IHostedFileClient_GetService_ReturnsSelf() + { + using IHostedFileClient fileClient = new AnthropicClient + { + ApiKey = "test-key", + }.Beta.AsIHostedFileClient(); + Assert.Same(fileClient, fileClient.GetService()); + } + + [Fact] + public void IHostedFileClient_GetService_UnknownType_ReturnsNull() + { + using IHostedFileClient fileClient = new AnthropicClient + { + ApiKey = "test-key", + }.Beta.AsIHostedFileClient(); + Assert.Null(fileClient.GetService(typeof(string))); + } + + [Fact] + public void IHostedFileClient_GetService_Metadata_HasProviderUri() + { + using IHostedFileClient fileClient = new AnthropicClient + { + ApiKey = "test-key", + }.Beta.AsIHostedFileClient(); + var metadata = fileClient.GetService(); + Assert.NotNull(metadata); + Assert.NotNull(metadata.ProviderUri); + } + + [Fact] + public async Task IHostedFileClient_GetFileInfoAsync_ReturnsHostedFileContent() { - string[]? capturedUserAgentValues = null; VerbatimHttpHandler handler = new( - expectedRequest: """ + "", + """ { - "max_tokens": 1024, - "model": "claude-haiku-4-5", - "messages": [{ - "role": "user", - "content": [{ - "type": "text", - "text": "Test streaming" - }] - }], - "stream": true + "id": "file_abc123", + "created_at": "2024-01-01T00:00:00+00:00", + "filename": "test.txt", + "mime_type": "text/plain", + "size_bytes": 100, + "type": "file" } - """, - actualResponse: """ - event: message_start - data: {"type":"message_start","message":{"id":"msg_stream_meai_01","type":"message","role":"assistant","model":"claude-haiku-4-5","content":[],"stop_reason":null,"stop_sequence":null,"usage":{"input_tokens":10,"output_tokens":0}}} + """ + ); - event: content_block_start - data: {"type":"content_block_start","index":0,"content_block":{"type":"text","text":""}} + using IHostedFileClient fileClient = CreateAnthropicClient(handler) + .Beta.AsIHostedFileClient(); + HostedFileContent? result = await fileClient.GetFileInfoAsync( + "file_abc123", + cancellationToken: TestContext.Current.CancellationToken + ); - event: content_block_delta - data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":"Response"}} + Assert.NotNull(result); + Assert.Equal("file_abc123", result.FileId); + Assert.Equal("text/plain", result.MediaType); + Assert.Equal("test.txt", result.Name); + Assert.Equal(100, result.SizeInBytes); + Assert.NotNull(result.CreatedAt); + Assert.IsType(result.RawRepresentation); + } - event: content_block_stop - data: {"type":"content_block_stop","index":0} + [Fact] + public async Task IHostedFileClient_DeleteAsync_ReturnsTrue() + { + VerbatimHttpHandler handler = new( + "", + """ + { + "id": "file_abc123", + "type": "file_deleted" + } + """ + ); - event: message_delta - data: {"type":"message_delta","delta":{"stop_reason":"end_turn","stop_sequence":null},"usage":{"output_tokens":5}} + using IHostedFileClient fileClient = CreateAnthropicClient(handler) + .Beta.AsIHostedFileClient(); + bool result = await fileClient.DeleteAsync( + "file_abc123", + cancellationToken: TestContext.Current.CancellationToken + ); - event: message_stop - data: {"type":"message_stop"} + Assert.True(result); + } + [Fact] + public async Task IHostedFileClient_UploadAsync_ReturnsHostedFileContent() + { + VerbatimHttpHandler handler = new( + "", """ - ) - { - OnRequestHeaders = headers => { - // Verify there's exactly one User-Agent header entry - Assert.Single(headers, h => h.Key == "User-Agent"); - if (headers.TryGetValues("User-Agent", out var values)) - { - capturedUserAgentValues = [.. values]; - } - }, - }; + "id": "file_new123", + "created_at": "2024-06-15T10:30:00+00:00", + "filename": "report.pdf", + "mime_type": "application/pdf", + "size_bytes": 5000, + "type": "file" + } + """ + ); - IChatClient chatClient = CreateChatClient(handler, "claude-haiku-4-5"); + using IHostedFileClient fileClient = CreateAnthropicClient(handler) + .Beta.AsIHostedFileClient(); + using var stream = new MemoryStream([1, 2, 3]); - await foreach ( - var update in chatClient.GetStreamingResponseAsync( - "Test streaming", - new(), - TestContext.Current.CancellationToken - ) - ) - { - // Consume the stream - } + HostedFileContent result = await fileClient.UploadAsync( + stream, + "application/pdf", + "report.pdf", + cancellationToken: TestContext.Current.CancellationToken + ); - Assert.NotNull(capturedUserAgentValues); - Assert.Contains(capturedUserAgentValues, v => v.Contains("MEAI")); - Assert.Contains(capturedUserAgentValues, v => v.Contains("AnthropicClient")); + Assert.Equal("file_new123", result.FileId); + Assert.Equal("application/pdf", result.MediaType); + Assert.Equal("report.pdf", result.Name); + Assert.Equal(5000, result.SizeInBytes); } [Fact] - public async Task GetResponseAsync_MeaiUserAgentHeader_HasCorrectFormat() + public async Task IHostedFileClient_UploadAsync_NullMediaType_InfersFromFileName() { - string[]? capturedUserAgentValues = null; VerbatimHttpHandler handler = new( - expectedRequest: """ + "", + """ { - "max_tokens": 1024, - "model": "claude-haiku-4-5", - "messages": [{ - "role": "user", - "content": [{ - "type": "text", - "text": "Test" - }] - }] + "id": "file_inferred", + "created_at": "2024-06-15T10:30:00+00:00", + "filename": "data.csv", + "mime_type": "text/csv", + "size_bytes": 500, + "type": "file" } - """, - actualResponse: """ + """ + ); + + using IHostedFileClient fileClient = CreateAnthropicClient(handler) + .Beta.AsIHostedFileClient(); + using var stream = new MemoryStream([1, 2, 3]); + + HostedFileContent result = await fileClient.UploadAsync( + stream, + null, + "data.csv", + cancellationToken: TestContext.Current.CancellationToken + ); + + Assert.Equal("file_inferred", result.FileId); + } + + [Fact] + public async Task IHostedFileClient_UploadAsync_NullFileName_GeneratesFromMediaType() + { + VerbatimHttpHandler handler = new( + "", + """ { - "id": "msg_meai_format_01", - "type": "message", - "role": "assistant", - "model": "claude-haiku-4-5", - "content": [{ - "type": "text", - "text": "Response" - }], - "stop_reason": "end_turn", - "usage": { - "input_tokens": 10, - "output_tokens": 5 - } + "id": "file_gen", + "created_at": "2024-06-15T10:30:00+00:00", + "filename": "generated.pdf", + "mime_type": "application/pdf", + "size_bytes": 100, + "type": "file" } """ - ) - { - OnRequestHeaders = headers => - { - // Verify there's exactly one User-Agent header entry - Assert.Single(headers, h => h.Key == "User-Agent"); - if (headers.TryGetValues("User-Agent", out var values)) - { - capturedUserAgentValues = [.. values]; - } - }, - }; + ); - IChatClient chatClient = CreateChatClient(handler, "claude-haiku-4-5"); + using IHostedFileClient fileClient = CreateAnthropicClient(handler) + .Beta.AsIHostedFileClient(); + using var stream = new MemoryStream([1, 2, 3]); - ChatResponse response = await chatClient.GetResponseAsync( - "Test", - new(), - TestContext.Current.CancellationToken + HostedFileContent result = await fileClient.UploadAsync( + stream, + "application/pdf", + null, + cancellationToken: TestContext.Current.CancellationToken ); - Assert.NotNull(response); - Assert.NotNull(capturedUserAgentValues); - // Verify the MEAI user-agent is present and has correct format (MEAI or MEAI/version) - Assert.Contains( - capturedUserAgentValues, - v => v.StartsWith("MEAI", StringComparison.Ordinal) + Assert.Equal("file_gen", result.FileId); + } + + [Fact] + public async Task IHostedFileClient_DownloadAsync_ReturnsReadableStream() + { + VerbatimHttpHandler handler = new("", "test file content"); + + using IHostedFileClient fileClient = CreateAnthropicClient(handler) + .Beta.AsIHostedFileClient(); + using HostedFileDownloadStream downloadStream = await fileClient.DownloadAsync( + "file_abc123", + cancellationToken: TestContext.Current.CancellationToken ); - Assert.Contains(capturedUserAgentValues, v => v.Contains("AnthropicClient")); + + Assert.NotNull(downloadStream); + Assert.True(downloadStream.CanRead); + Assert.False(downloadStream.CanWrite); + + using var reader = new StreamReader(downloadStream); + string content = await reader.ReadToEndAsync( +#if NET + TestContext.Current.CancellationToken +#endif + ); + Assert.Contains("test file content", content); } [Fact] - public async Task GetResponseAsync_MeaiUserAgentHeader_PresentAlongsideDefaultHeaders() + public async Task IHostedFileClient_DownloadAsync_StreamWriteThrows() { - bool hasAnthropicVersion = false; - bool hasMeaiUserAgent = false; - bool hasDefaultUserAgent = false; + VerbatimHttpHandler handler = new("", "content"); + + using IHostedFileClient fileClient = CreateAnthropicClient(handler) + .Beta.AsIHostedFileClient(); + using HostedFileDownloadStream stream = await fileClient.DownloadAsync( + "file_abc123", + cancellationToken: TestContext.Current.CancellationToken + ); + Assert.Throws(() => stream.Write(new byte[1], 0, 1)); + Assert.Throws(() => stream.SetLength(0)); + } + + [Fact] + public async Task IHostedFileClient_ListFilesAsync_ReturnsFiles() + { VerbatimHttpHandler handler = new( - expectedRequest: """ - { - "max_tokens": 1024, - "model": "claude-haiku-4-5", - "messages": [{ - "role": "user", - "content": [{ - "type": "text", - "text": "Test" - }] - }] - } - """, - actualResponse: """ + "", + """ { - "id": "msg_headers_01", - "type": "message", - "role": "assistant", - "model": "claude-haiku-4-5", - "content": [{ - "type": "text", - "text": "Response" - }], - "stop_reason": "end_turn", - "usage": { - "input_tokens": 10, - "output_tokens": 5 - } + "data": [ + { + "id": "file_1", + "created_at": "2024-01-01T00:00:00+00:00", + "filename": "a.txt", + "mime_type": "text/plain", + "size_bytes": 10, + "type": "file" + }, + { + "id": "file_2", + "created_at": "2024-01-02T00:00:00+00:00", + "filename": "b.pdf", + "mime_type": "application/pdf", + "size_bytes": 2000, + "type": "file" + } + ], + "first_id": "file_1", + "has_more": false, + "last_id": null } """ - ) - { - OnRequestHeaders = headers => - { - // Verify there's exactly one User-Agent header entry - Assert.Single(headers, h => h.Key == "User-Agent"); - hasAnthropicVersion = headers.Contains("anthropic-version"); - if (headers.TryGetValues("User-Agent", out var values)) - { - var valuesArray = values.ToArray(); - hasMeaiUserAgent = valuesArray.Any(v => v.Contains("MEAI")); - hasDefaultUserAgent = valuesArray.Any(v => v.Contains("AnthropicClient")); - } - }, - }; + ); - IChatClient chatClient = CreateChatClient(handler, "claude-haiku-4-5"); + using IHostedFileClient fileClient = CreateAnthropicClient(handler) + .Beta.AsIHostedFileClient(); - ChatResponse response = await chatClient.GetResponseAsync( - "Test", - new(), - TestContext.Current.CancellationToken - ); + List files = new(); + await foreach ( + HostedFileContent file in fileClient.ListFilesAsync( + cancellationToken: TestContext.Current.CancellationToken + ) + ) + { + files.Add(file); + } - Assert.NotNull(response); - Assert.True(hasAnthropicVersion, "anthropic-version header should be present"); - Assert.True(hasMeaiUserAgent, "MEAI user-agent header should be present"); - Assert.True( - hasDefaultUserAgent, - "Default AnthropicClient user-agent header should be present" - ); + Assert.Equal(2, files.Count); + Assert.Equal("file_1", files[0].FileId); + Assert.Equal("a.txt", files[0].Name); + Assert.Equal("text/plain", files[0].MediaType); + Assert.Equal(10, files[0].SizeInBytes); + Assert.Equal("file_2", files[1].FileId); + Assert.Equal("b.pdf", files[1].Name); + Assert.Equal("application/pdf", files[1].MediaType); + Assert.Equal(2000, files[1].SizeInBytes); } } diff --git a/src/Anthropic.Tests/AnthropicClientExtensionsTests.cs b/src/Anthropic.Tests/AnthropicClientExtensionsTests.cs index bb9c50a14..cf5610586 100644 --- a/src/Anthropic.Tests/AnthropicClientExtensionsTests.cs +++ b/src/Anthropic.Tests/AnthropicClientExtensionsTests.cs @@ -1,8 +1,10 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Text.Json; using System.Threading.Tasks; using Anthropic; +using Anthropic.Core; using Anthropic.Models.Messages; #pragma warning disable IDE0130 // Namespace does not match folder structure @@ -336,6 +338,73 @@ [new TextBlockParam() { Text = "Preconfigured message" }] Assert.NotNull(response); } + [Fact] + public async Task GetResponseAsync_WithNonEmptyMessageParams_EmptyMessages() + { + VerbatimHttpHandler handler = new( + expectedRequest: """ + { + "max_tokens": 2048, + "model": "claude-haiku-4-5", + "messages": [ + { + "role": "user", + "content": [{ + "type": "text", + "text": "Preconfigured message" + }] + } + ] + } + """, + actualResponse: """ + { + "id": "msg_factory_02", + "type": "message", + "role": "assistant", + "model": "claude-haiku-4-5", + "content": [{ + "type": "text", + "text": "Response" + }], + "stop_reason": "end_turn", + "usage": { + "input_tokens": 10, + "output_tokens": 5 + } + } + """ + ); + + IChatClient chatClient = CreateChatClient(handler, "claude-haiku-4-5"); + + ChatOptions options = new() + { + RawRepresentationFactory = _ => new MessageCreateParams() + { + MaxTokens = 2048, + Model = "claude-haiku-4-5", + Messages = + [ + new MessageParam() + { + Role = Role.User, + Content = new MessageParamContent( + [new TextBlockParam() { Text = "Preconfigured message" }] + ), + }, + ], + }, + }; + + ChatResponse response = await chatClient.GetResponseAsync( + [], + options, + TestContext.Current.CancellationToken + ); + Assert.NotNull(response); + } + [Fact] public async Task GetResponseAsync_WithRawRepresentationFactory_SystemMessagesMerged() { @@ -816,4 +885,150 @@ public async Task GetResponseAsync_MeaiUserAgentHeader_PresentAlongsideDefaultHe "Default AnthropicClient user-agent header should be present" ); } + + [Fact] + public async Task GetResponseAsync_WithReasoningEffort_IgnoredWhenThinkingAlreadyConfigured() + { + // When RawRepresentationFactory pre-configures Thinking, the Reasoning option should be ignored. + VerbatimHttpHandler handler = new( + expectedRequest: """ + { + "max_tokens": 50000, + "model": "claude-haiku-4-5", + "messages": [{ + "role": "user", + "content": [{ + "type": "text", + "text": "Think carefully" + }] + }], + "thinking": { + "type": "enabled", + "budget_tokens": 5000 + } + } + """, + actualResponse: """ + { + "id": "msg_reasoning_preconfigured", + "type": "message", + "role": "assistant", + "model": "claude-haiku-4-5", + "content": [{ + "type": "text", + "text": "Response" + }], + "stop_reason": "end_turn", + "usage": { + "input_tokens": 10, + "output_tokens": 15 + } + } + """ + ); + + IChatClient chatClient = CreateChatClient(handler, "claude-haiku-4-5"); + + ChatOptions options = new() + { + // RawRepresentationFactory sets Thinking to enabled with 5000 budget. + // Reasoning.Effort should be ignored since Thinking is already configured. + RawRepresentationFactory = _ => new MessageCreateParams() + { + MaxTokens = 50000, + Model = "claude-haiku-4-5", + Messages = [], + Thinking = new ThinkingConfigParam(new ThinkingConfigEnabled(5000)), + }, + Reasoning = new() { Effort = ReasoningEffort.ExtraHigh }, + }; + + ChatResponse response = await chatClient.GetResponseAsync( + "Think carefully", + options, + TestContext.Current.CancellationToken + ); + Assert.NotNull(response); + } + + [Fact] + public async Task GetResponseAsync_WithAIFunctionTool_AllowedCallers_FlowsThrough() + { + VerbatimHttpHandler handler = new( + expectedRequest: """ + { + "max_tokens": 1024, + "model": "claude-haiku-4-5", + "messages": [{ + "role": "user", + "content": [{ + "type": "text", + "text": "Use tool" + }] + }], + "tools": [{ + "name": "callers_tool", + "description": "A tool with allowed callers", + "input_schema": { + "type": "object", + "properties": { + "value": { + "type": "integer" + } + }, + "required": ["value"], + "additionalProperties": false + }, + "allowed_callers": [ + "direct" + ] + }] + } + """, + actualResponse: """ + { + "id": "msg_callers_01", + "type": "message", + "role": "assistant", + "model": "claude-haiku-4-5", + "content": [{ + "type": "text", + "text": "Done" + }], + "stop_reason": "end_turn", + "usage": { + "input_tokens": 30, + "output_tokens": 5 + } + } + """ + ); + + IChatClient chatClient = CreateChatClient(handler, "claude-haiku-4-5"); + + var function = AIFunctionFactory.Create( + (int value) => value, + new AIFunctionFactoryOptions + { + Name = "callers_tool", + Description = "A tool with allowed callers", + AdditionalProperties = new Dictionary + { + [nameof(Tool.AllowedCallers)] = new List> + { + new(JsonSerializer.SerializeToElement("direct")), + }, + }, + } + ); + + ChatOptions options = new() { Tools = [function] }; + + ChatResponse response = await chatClient.GetResponseAsync( + "Use tool", + options, + TestContext.Current.CancellationToken + ); + Assert.NotNull(response); + } } diff --git a/src/Anthropic.Tests/AnthropicClientExtensionsTestsBase.cs b/src/Anthropic.Tests/AnthropicClientExtensionsTestsBase.cs index 5149aff65..940b376f6 100644 --- a/src/Anthropic.Tests/AnthropicClientExtensionsTestsBase.cs +++ b/src/Anthropic.Tests/AnthropicClientExtensionsTestsBase.cs @@ -9,7 +9,10 @@ using System.Threading; using System.Threading.Tasks; using Anthropic; +using Anthropic.Core; +using Anthropic.Models.Messages; +#pragma warning disable MEAI001 // [Experimental] APIs in Microsoft.Extensions.AI #pragma warning disable IDE0130 // Namespace does not match folder structure namespace Microsoft.Extensions.AI.Tests; @@ -903,7 +906,8 @@ public async Task GetResponseAsync_WithToolCalls() "location": { "type": "string", "description": "The city and state" }, "unit": { "type": "string", "description": "Temperature unit" } }, - "required": ["location", "unit"] + "required": ["location", "unit"], + "additionalProperties": false } }] } @@ -996,7 +1000,7 @@ public async Task GetResponseAsync_WithParameterlessToolCall() "input_schema": { "type": "object", "properties": {}, - "required": [] + "additionalProperties": false } }] } @@ -1452,7 +1456,8 @@ public async Task GetResponseAsync_WithToolModeAuto() "properties": { "location": { "type": "string", "description": "The location" } }, - "required": ["location"] + "required": ["location"], + "additionalProperties": false } }] } @@ -1530,7 +1535,8 @@ public async Task GetResponseAsync_WithToolModeRequired() "properties": { "location": { "type": "string", "description": "The location" } }, - "required": ["location"] + "required": ["location"], + "additionalProperties": false } }] } @@ -1615,7 +1621,8 @@ public async Task GetResponseAsync_WithToolModeNone() "properties": { "location": { "type": "string", "description": "The location" } }, - "required": ["location"] + "required": ["location"], + "additionalProperties": false } }] } @@ -1690,7 +1697,8 @@ public async Task GetResponseAsync_WithMultipleToolCalls() "properties": { "location": { "type": "string" } }, - "required": ["location"] + "required": ["location"], + "additionalProperties": false } }] } @@ -2709,54 +2717,48 @@ public async Task GetResponseAsync_WithNullFinishReason() Assert.Null(response.FinishReason); } - [Fact] - public async Task GetResponseAsync_SendsTextReasoningAsThinkingBlock() + [Theory] + [InlineData(ReasoningEffort.Low, 1024)] + [InlineData(ReasoningEffort.Medium, 8192)] + [InlineData(ReasoningEffort.High, 16384)] + [InlineData(ReasoningEffort.ExtraHigh, 32768)] + public async Task GetResponseAsync_WithReasoningEffort_SetsThinkingEnabled( + ReasoningEffort effort, + int expectedBudgetTokens + ) { VerbatimHttpHandler handler = new( - expectedRequest: """ + expectedRequest: $$""" { "model": "claude-haiku-4-5", - "messages": [ - { - "role": "user", - "content": [{ - "type": "text", - "text": "Think about this" - }] - }, - { - "role": "assistant", - "content": [{ - "type": "thinking", - "thinking": "My detailed reasoning...", - "signature": "sig_123" - }] - }, - { - "role": "user", - "content": [{ - "type": "text", - "text": "What did you conclude?" - }] - } - ], - "max_tokens": 1024 + "messages": [{ + "role": "user", + "content": [{ + "type": "text", + "text": "Think carefully" + }] + }], + "max_tokens": 100000, + "thinking": { + "type": "enabled", + "budget_tokens": {{expectedBudgetTokens}} + } } """, actualResponse: """ { - "id": "msg_reasoning_sent_01", + "id": "msg_reasoning_01", "type": "message", "role": "assistant", "model": "claude-haiku-4-5", "content": [{ "type": "text", - "text": "Response after thinking" + "text": "Here is my response" }], "stop_reason": "end_turn", "usage": { - "input_tokens": 30, - "output_tokens": 10 + "input_tokens": 10, + "output_tokens": 20 } } """ @@ -2764,71 +2766,54 @@ public async Task GetResponseAsync_SendsTextReasoningAsThinkingBlock() IChatClient chatClient = CreateChatClient(handler, "claude-haiku-4-5"); - List messages = - [ - new(ChatRole.User, "Think about this"), - new( - ChatRole.Assistant, - [new TextReasoningContent("My detailed reasoning...") { ProtectedData = "sig_123" }] - ), - new(ChatRole.User, "What did you conclude?"), - ]; + ChatOptions options = new() + { + MaxOutputTokens = 100000, + Reasoning = new() { Effort = effort }, + }; ChatResponse response = await chatClient.GetResponseAsync( - messages, - new(), + "Think carefully", + options, TestContext.Current.CancellationToken ); Assert.NotNull(response); } [Fact] - public async Task GetResponseAsync_SendsRedactedTextReasoningAsRedactedThinkingBlock() + public async Task GetResponseAsync_WithReasoningEffortNone_SetsThinkingDisabled() { VerbatimHttpHandler handler = new( expectedRequest: """ { "model": "claude-haiku-4-5", - "messages": [ - { - "role": "user", - "content": [{ - "type": "text", - "text": "Previous question" - }] - }, - { - "role": "assistant", - "content": [{ - "type": "redacted_thinking", - "data": "encrypted_data_xyz" - }] - }, - { - "role": "user", - "content": [{ - "type": "text", - "text": "Follow up question" - }] - } - ], - "max_tokens": 1024 + "messages": [{ + "role": "user", + "content": [{ + "type": "text", + "text": "Simple question" + }] + }], + "max_tokens": 1024, + "thinking": { + "type": "disabled" + } } """, actualResponse: """ { - "id": "msg_redacted_sent_01", + "id": "msg_reasoning_02", "type": "message", "role": "assistant", "model": "claude-haiku-4-5", "content": [{ "type": "text", - "text": "Response after redacted thinking" + "text": "Quick answer" }], "stop_reason": "end_turn", "usage": { - "input_tokens": 30, - "output_tokens": 10 + "input_tokens": 10, + "output_tokens": 5 } } """ @@ -2836,53 +2821,42 @@ public async Task GetResponseAsync_SendsRedactedTextReasoningAsRedactedThinkingB IChatClient chatClient = CreateChatClient(handler, "claude-haiku-4-5"); - List messages = - [ - new(ChatRole.User, "Previous question"), - new( - ChatRole.Assistant, - [new TextReasoningContent(string.Empty) { ProtectedData = "encrypted_data_xyz" }] - ), - new(ChatRole.User, "Follow up question"), - ]; + ChatOptions options = new() { Reasoning = new() { Effort = ReasoningEffort.None } }; ChatResponse response = await chatClient.GetResponseAsync( - messages, - new(), + "Simple question", + options, TestContext.Current.CancellationToken ); Assert.NotNull(response); } [Fact] - public async Task GetResponseAsync_SkipsEmptyTextReasoningContent() + public async Task GetResponseAsync_WithReasoningEffort_ClampsBudgetToExplicitMaxTokens() { + // High effort maps to 16384, but caller explicitly set max_tokens to 5000, + // so budget should clamp to 4999. VerbatimHttpHandler handler = new( expectedRequest: """ { - "max_tokens": 1024, "model": "claude-haiku-4-5", - "messages": [ - { - "role": "user", - "content": [{ - "type": "text", - "text": "Question" - }] - }, - { - "role": "user", - "content": [{ - "type": "text", - "text": "Follow up" - }] - } - ] + "messages": [{ + "role": "user", + "content": [{ + "type": "text", + "text": "Think carefully" + }] + }], + "max_tokens": 5000, + "thinking": { + "type": "enabled", + "budget_tokens": 4999 + } } """, actualResponse: """ { - "id": "msg_skip_empty_01", + "id": "msg_reasoning_03", "type": "message", "role": "assistant", "model": "claude-haiku-4-5", @@ -2892,8 +2866,8 @@ public async Task GetResponseAsync_SkipsEmptyTextReasoningContent() }], "stop_reason": "end_turn", "usage": { - "input_tokens": 20, - "output_tokens": 5 + "input_tokens": 10, + "output_tokens": 15 } } """ @@ -2901,245 +2875,187 @@ public async Task GetResponseAsync_SkipsEmptyTextReasoningContent() IChatClient chatClient = CreateChatClient(handler, "claude-haiku-4-5"); - List messages = - [ - new(ChatRole.User, "Question"), - new(ChatRole.Assistant, [new TextReasoningContent(null)]), - new(ChatRole.User, "Follow up"), - ]; + ChatOptions options = new() + { + MaxOutputTokens = 5000, + Reasoning = new() { Effort = ReasoningEffort.High }, + }; ChatResponse response = await chatClient.GetResponseAsync( - messages, - new(), + "Think carefully", + options, TestContext.Current.CancellationToken ); Assert.NotNull(response); } [Fact] - public async Task GetStreamingResponseAsync_WithThinkingBlockInStream() + public async Task GetResponseAsync_WithReasoningEffort_SkipsThinkingWhenExplicitMaxTokensTooSmall() { + // Medium effort maps to 8192, but caller explicitly set max_tokens to 1024, + // so after clamping budget would be 1023 which is < 1024 minimum. Thinking is skipped. VerbatimHttpHandler handler = new( expectedRequest: """ { - "max_tokens": 1024, "model": "claude-haiku-4-5", "messages": [{ "role": "user", "content": [{ "type": "text", - "text": "Analyze this problem" + "text": "Think carefully" }] }], - "stream": true + "max_tokens": 1024 } """, actualResponse: """ - event: message_start - data: {"type":"message_start","message":{"id":"msg_thinking_stream_01","type":"message","role":"assistant","model":"claude-haiku-4-5","content":[],"stop_reason":null,"stop_sequence":null,"usage":{"input_tokens":10,"output_tokens":0}}} - - event: content_block_start - data: {"type":"content_block_start","index":0,"content_block":{"type":"thinking","thinking":"","signature":"sig_123"}} - - event: content_block_delta - data: {"type":"content_block_delta","index":0,"delta":{"type":"thinking_delta","thinking":"Let me analyze this..."}} - - event: content_block_stop - data: {"type":"content_block_stop","index":0} - - event: content_block_start - data: {"type":"content_block_start","index":1,"content_block":{"type":"text","text":""}} - - event: content_block_delta - data: {"type":"content_block_delta","index":1,"delta":{"type":"text_delta","text":"Based on my analysis"}} - - event: content_block_stop - data: {"type":"content_block_stop","index":1} - - event: message_delta - data: {"type":"message_delta","delta":{"stop_reason":"end_turn","stop_sequence":null},"usage":{"output_tokens":10}} - - event: message_stop - data: {"type":"message_stop"} - + { + "id": "msg_reasoning_04", + "type": "message", + "role": "assistant", + "model": "claude-haiku-4-5", + "content": [{ + "type": "text", + "text": "Response" + }], + "stop_reason": "end_turn", + "usage": { + "input_tokens": 10, + "output_tokens": 15 + } + } """ ); IChatClient chatClient = CreateChatClient(handler, "claude-haiku-4-5"); - List updates = []; - await foreach ( - var update in chatClient.GetStreamingResponseAsync( - "Analyze this problem", - new(), - TestContext.Current.CancellationToken - ) - ) + ChatOptions options = new() { - updates.Add(update); - } - - Assert.NotEmpty(updates); - var reasoningUpdates = updates - .Where(u => u.Contents.Any(c => c is TextReasoningContent)) - .ToList(); - Assert.NotEmpty(reasoningUpdates); + MaxOutputTokens = 1024, + Reasoning = new() { Effort = ReasoningEffort.Medium }, + }; - var reasoningContent = reasoningUpdates - .SelectMany(u => u.Contents.OfType()) - .FirstOrDefault(); - Assert.NotNull(reasoningContent); + ChatResponse response = await chatClient.GetResponseAsync( + "Think carefully", + options, + TestContext.Current.CancellationToken + ); + Assert.NotNull(response); } [Fact] - public async Task GetStreamingResponseAsync_WithRedactedThinkingBlockInStream() + public async Task GetResponseAsync_WithReasoningEffort_AutoIncreasesMaxTokensFromDefault() { + // Medium effort maps to 8192. Default max_tokens is 1024, so max_tokens should + // auto-increase to budget (8192) + default (1024) = 9216. VerbatimHttpHandler handler = new( expectedRequest: """ { - "max_tokens": 1024, "model": "claude-haiku-4-5", "messages": [{ "role": "user", "content": [{ "type": "text", - "text": "Test redacted thinking" + "text": "Think carefully" }] }], - "stream": true + "max_tokens": 9216, + "thinking": { + "type": "enabled", + "budget_tokens": 8192 + } } """, actualResponse: """ - event: message_start - data: {"type":"message_start","message":{"id":"msg_redacted_stream_01","type":"message","role":"assistant","model":"claude-haiku-4-5","content":[],"stop_reason":null,"stop_sequence":null,"usage":{"input_tokens":10,"output_tokens":0}}} - - event: content_block_start - data: {"type":"content_block_start","index":0,"content_block":{"type":"redacted_thinking","data":"encrypted_xyz"}} + { + "id": "msg_reasoning_05", + "type": "message", + "role": "assistant", + "model": "claude-haiku-4-5", + "content": [{ + "type": "text", + "text": "Response" + }], + "stop_reason": "end_turn", + "usage": { + "input_tokens": 10, + "output_tokens": 15 + } + } + """ + ); - event: content_block_stop - data: {"type":"content_block_stop","index":0} + IChatClient chatClient = CreateChatClient(handler, "claude-haiku-4-5"); - event: content_block_start - data: {"type":"content_block_start","index":1,"content_block":{"type":"text","text":""}} + ChatOptions options = new() { Reasoning = new() { Effort = ReasoningEffort.Medium } }; - event: content_block_delta - data: {"type":"content_block_delta","index":1,"delta":{"type":"text_delta","text":"Response"}} - - event: content_block_stop - data: {"type":"content_block_stop","index":1} - - event: message_delta - data: {"type":"message_delta","delta":{"stop_reason":"end_turn","stop_sequence":null},"usage":{"output_tokens":5}} - - event: message_stop - data: {"type":"message_stop"} - - """ + ChatResponse response = await chatClient.GetResponseAsync( + "Think carefully", + options, + TestContext.Current.CancellationToken ); - - IChatClient chatClient = CreateChatClient(handler, "claude-haiku-4-5"); - - List updates = []; - await foreach ( - var update in chatClient.GetStreamingResponseAsync( - "Test redacted thinking", - new(), - TestContext.Current.CancellationToken - ) - ) - { - updates.Add(update); - } - - Assert.NotEmpty(updates); - var reasoningUpdates = updates - .Where(u => u.Contents.Any(c => c is TextReasoningContent)) - .ToList(); - Assert.NotEmpty(reasoningUpdates); - - var reasoningContent = reasoningUpdates - .SelectMany(u => u.Contents.OfType()) - .FirstOrDefault(); - Assert.NotNull(reasoningContent); - Assert.Equal(string.Empty, reasoningContent.Text); - Assert.Equal("encrypted_xyz", reasoningContent.ProtectedData); + Assert.NotNull(response); } [Fact] - public async Task GetStreamingResponseAsync_WithSignatureDeltaInStream() + public async Task GetResponseAsync_WithReasoningEffortLow_AutoIncreasesFromDefaultMaxTokens() { + // Low effort maps to 1024. Default max_tokens is also 1024, so 1024 <= 1024 + // triggers auto-increase to budget (1024) + default (1024) = 2048. VerbatimHttpHandler handler = new( expectedRequest: """ { - "max_tokens": 1024, "model": "claude-haiku-4-5", "messages": [{ "role": "user", "content": [{ "type": "text", - "text": "Test signature delta" + "text": "Think a little" }] }], - "stream": true + "max_tokens": 2048, + "thinking": { + "type": "enabled", + "budget_tokens": 1024 + } } """, actualResponse: """ - event: message_start - data: {"type":"message_start","message":{"id":"msg_sig_delta_01","type":"message","role":"assistant","model":"claude-haiku-4-5","content":[],"stop_reason":null,"stop_sequence":null,"usage":{"input_tokens":10,"output_tokens":0}}} - - event: content_block_start - data: {"type":"content_block_start","index":0,"content_block":{"type":"thinking","thinking":"","signature":""}} - - event: content_block_delta - data: {"type":"content_block_delta","index":0,"delta":{"type":"thinking_delta","thinking":"Analyzing..."}} - - event: content_block_delta - data: {"type":"content_block_delta","index":0,"delta":{"type":"signature_delta","signature":"sig_part1"}} - - event: content_block_delta - data: {"type":"content_block_delta","index":0,"delta":{"type":"signature_delta","signature":"sig_part2"}} - - event: content_block_stop - data: {"type":"content_block_stop","index":0} - - event: message_delta - data: {"type":"message_delta","delta":{"stop_reason":"end_turn","stop_sequence":null},"usage":{"output_tokens":5}} - - event: message_stop - data: {"type":"message_stop"} - + { + "id": "msg_reasoning_06", + "type": "message", + "role": "assistant", + "model": "claude-haiku-4-5", + "content": [{ + "type": "text", + "text": "Response" + }], + "stop_reason": "end_turn", + "usage": { + "input_tokens": 10, + "output_tokens": 15 + } + } """ ); IChatClient chatClient = CreateChatClient(handler, "claude-haiku-4-5"); - List updates = []; - await foreach ( - var update in chatClient.GetStreamingResponseAsync( - "Test signature delta", - new(), - TestContext.Current.CancellationToken - ) - ) - { - updates.Add(update); - } - - Assert.NotEmpty(updates); - var reasoningUpdates = updates - .Where(u => u.Contents.Any(c => c is TextReasoningContent)) - .ToList(); - Assert.NotEmpty(reasoningUpdates); + ChatOptions options = new() { Reasoning = new() { Effort = ReasoningEffort.Low } }; - var allReasoningContent = reasoningUpdates - .SelectMany(u => u.Contents.OfType()) - .ToList(); - Assert.NotEmpty(allReasoningContent); + ChatResponse response = await chatClient.GetResponseAsync( + "Think a little", + options, + TestContext.Current.CancellationToken + ); + Assert.NotNull(response); } [Fact] - public async Task GetResponseAsync_WithThinkingBlockInResponse() + public async Task GetResponseAsync_WithReasoningEffort_ExactFitMaxTokensOneBeyondBudget() { + // Low effort maps to 1024. MaxOutputTokens is 1025, so 1025 > 1024 means + // no auto-increase needed — budget fits exactly. VerbatimHttpHandler handler = new( expectedRequest: """ { @@ -3148,29 +3064,29 @@ public async Task GetResponseAsync_WithThinkingBlockInResponse() "role": "user", "content": [{ "type": "text", - "text": "What is the answer?" + "text": "Think a little" }] }], - "max_tokens": 1024 + "max_tokens": 1025, + "thinking": { + "type": "enabled", + "budget_tokens": 1024 + } } """, actualResponse: """ { - "id": "msg_thinking_resp_01", + "id": "msg_reasoning_07", "type": "message", "role": "assistant", "model": "claude-haiku-4-5", "content": [{ - "type": "thinking", - "thinking": "Let me think through this step by step...", - "signature": "sig_abc123" - }, { "type": "text", - "text": "Based on my analysis, the answer is 42." + "text": "Response" }], "stop_reason": "end_turn", "usage": { - "input_tokens": 20, + "input_tokens": 10, "output_tokens": 15 } } @@ -3179,31 +3095,25 @@ public async Task GetResponseAsync_WithThinkingBlockInResponse() IChatClient chatClient = CreateChatClient(handler, "claude-haiku-4-5"); + ChatOptions options = new() + { + MaxOutputTokens = 1025, + Reasoning = new() { Effort = ReasoningEffort.Low }, + }; + ChatResponse response = await chatClient.GetResponseAsync( - "What is the answer?", - new(), + "Think a little", + options, TestContext.Current.CancellationToken ); Assert.NotNull(response); - - Assert.Equal(2, response.Messages[0].Contents.Count); - - var reasoningContent = response - .Messages[0] - .Contents.OfType() - .FirstOrDefault(); - Assert.NotNull(reasoningContent); - Assert.Equal("Let me think through this step by step...", reasoningContent.Text); - Assert.Equal("sig_abc123", reasoningContent.ProtectedData); - - var textContent = response.Messages[0].Contents.OfType().FirstOrDefault(); - Assert.NotNull(textContent); - Assert.Equal("Based on my analysis, the answer is 42.", textContent.Text); } [Fact] - public async Task GetResponseAsync_WithRedactedThinkingBlockInResponse() + public async Task GetResponseAsync_WithReasoningEffort_NoAutoIncreaseWhenDefaultMaxTokensSufficient() { + // Low effort maps to 1024. Custom default max_tokens is 5000, so 5000 > 1024 means + // no auto-increase is needed. VerbatimHttpHandler handler = new( expectedRequest: """ { @@ -3212,60 +3122,53 @@ public async Task GetResponseAsync_WithRedactedThinkingBlockInResponse() "role": "user", "content": [{ "type": "text", - "text": "Tell me your conclusion" + "text": "Think a little" }] }], - "max_tokens": 1024 + "max_tokens": 5000, + "thinking": { + "type": "enabled", + "budget_tokens": 1024 + } } """, actualResponse: """ { - "id": "msg_redacted_resp_01", + "id": "msg_reasoning_08", "type": "message", "role": "assistant", "model": "claude-haiku-4-5", "content": [{ - "type": "redacted_thinking", - "data": "encrypted_thinking_data_xyz" - }, { "type": "text", - "text": "Here is my conclusion." + "text": "Response" }], "stop_reason": "end_turn", "usage": { - "input_tokens": 20, - "output_tokens": 10 + "input_tokens": 10, + "output_tokens": 15 } } """ ); - IChatClient chatClient = CreateChatClient(handler, "claude-haiku-4-5"); + IChatClient chatClient = CreateChatClient( + handler, + "claude-haiku-4-5", + defaultMaxOutputTokens: 5000 + ); + + ChatOptions options = new() { Reasoning = new() { Effort = ReasoningEffort.Low } }; ChatResponse response = await chatClient.GetResponseAsync( - "Tell me your conclusion", - new(), + "Think a little", + options, TestContext.Current.CancellationToken ); Assert.NotNull(response); - - Assert.Equal(2, response.Messages[0].Contents.Count); - - var reasoningContent = response - .Messages[0] - .Contents.OfType() - .FirstOrDefault(); - Assert.NotNull(reasoningContent); - Assert.Equal(string.Empty, reasoningContent.Text); - Assert.Equal("encrypted_thinking_data_xyz", reasoningContent.ProtectedData); - - var textContent = response.Messages[0].Contents.OfType().FirstOrDefault(); - Assert.NotNull(textContent); - Assert.Equal("Here is my conclusion.", textContent.Text); } [Fact] - public async Task GetResponseAsync_WithToolUseBlockInResponse() + public async Task GetResponseAsync_WithReasoningOutputNone_SetsThinkingDisplayOmitted() { VerbatimHttpHandler handler = new( expectedRequest: """ @@ -3275,33 +3178,35 @@ public async Task GetResponseAsync_WithToolUseBlockInResponse() "role": "user", "content": [{ "type": "text", - "text": "What is 6 times 7?" + "text": "Think carefully" }] }], - "max_tokens": 1024 + "max_tokens": 100000, + "thinking": { + "type": "enabled", + "budget_tokens": 16384, + "display": "omitted" + } } """, actualResponse: """ { - "id": "msg_tooluse_resp_01", + "id": "msg_reasoning_display_01", "type": "message", "role": "assistant", "model": "claude-haiku-4-5", "content": [{ - "type": "tool_use", - "id": "toolu_detailed_01", - "name": "calculate", - "input": { - "operation": "multiply", - "a": 6, - "b": 7 - }, - "caller": {"type": "direct"} + "type": "thinking", + "thinking": "", + "signature": "sig_abc123" + }, { + "type": "text", + "text": "Here is my response" }], - "stop_reason": "tool_use", + "stop_reason": "end_turn", "usage": { - "input_tokens": 30, - "output_tokens": 15 + "input_tokens": 10, + "output_tokens": 20 } } """ @@ -3309,42 +3214,28 @@ public async Task GetResponseAsync_WithToolUseBlockInResponse() IChatClient chatClient = CreateChatClient(handler, "claude-haiku-4-5"); - var calcFunction = AIFunctionFactory.Create( - (string operation, int a, int b) => - { - return operation == "multiply" ? a * b : 0; - }, - "calculate" - ); - - ChatOptions options = new() { Tools = [calcFunction] }; + ChatOptions options = new() + { + MaxOutputTokens = 100000, + Reasoning = new() { Effort = ReasoningEffort.High, Output = ReasoningOutput.None }, + }; ChatResponse response = await chatClient.GetResponseAsync( - "What is 6 times 7?", - new(), + "Think carefully", + options, TestContext.Current.CancellationToken ); Assert.NotNull(response); - - Assert.Equal(ChatFinishReason.ToolCalls, response.FinishReason); - - var functionCall = response - .Messages[0] - .Contents.OfType() - .FirstOrDefault(); - Assert.NotNull(functionCall); - Assert.Equal("calculate", functionCall.Name); - Assert.Equal("toolu_detailed_01", functionCall.CallId); - Assert.NotNull(functionCall.Arguments); - Assert.True(functionCall.Arguments.ContainsKey("operation")); - Assert.Equal("multiply", functionCall.Arguments["operation"]?.ToString()); - Assert.True(functionCall.Arguments.ContainsKey("a")); - Assert.True(functionCall.Arguments.ContainsKey("b")); } - [Fact] - public async Task GetResponseAsync_WithHostedWebSearchToolOptionTriggersConversion() + [Theory] + [InlineData(ReasoningOutput.Summary)] + [InlineData(ReasoningOutput.Full)] + public async Task GetResponseAsync_WithReasoningOutputSummaryOrFull_DoesNotSetDisplay( + ReasoningOutput output + ) { + // Summary and Full should not set display, letting the server default to "summarized". VerbatimHttpHandler handler = new( expectedRequest: """ { @@ -3353,43 +3244,45 @@ public async Task GetResponseAsync_WithHostedWebSearchToolOptionTriggersConversi "role": "user", "content": [{ "type": "text", - "text": "Find recent news about AI" + "text": "Think carefully" }] }], - "max_tokens": 1024, - "tools": [ - { - "name": "web_search", - "type": "web_search_20250305" - } - ] + "max_tokens": 100000, + "thinking": { + "type": "enabled", + "budget_tokens": 16384 + } } """, actualResponse: """ { - "id": "msg_websearch_opt_01", + "id": "msg_reasoning_display_02", "type": "message", "role": "assistant", "model": "claude-haiku-4-5", "content": [{ "type": "text", - "text": "I'll search for that information." + "text": "Here is my response" }], "stop_reason": "end_turn", "usage": { - "input_tokens": 20, - "output_tokens": 10 - } + "input_tokens": 10, + "output_tokens": 20 + } } """ ); IChatClient chatClient = CreateChatClient(handler, "claude-haiku-4-5"); - ChatOptions options = new() { Tools = [new HostedWebSearchTool()] }; + ChatOptions options = new() + { + MaxOutputTokens = 100000, + Reasoning = new() { Effort = ReasoningEffort.High, Output = output }, + }; ChatResponse response = await chatClient.GetResponseAsync( - "Find recent news about AI", + "Think carefully", options, TestContext.Current.CancellationToken ); @@ -3397,8 +3290,10 @@ public async Task GetResponseAsync_WithHostedWebSearchToolOptionTriggersConversi } [Fact] - public async Task GetResponseAsync_WithTextBlockWithoutCitations() + public async Task GetResponseAsync_WithReasoningOutputNone_OmittedThinkingYieldsRedactedReasoningContent() { + // When display is omitted, the response contains thinking blocks with empty thinking + // but a valid signature. These should map to TextReasoningContent with ProtectedData. VerbatimHttpHandler handler = new( expectedRequest: """ { @@ -3407,26 +3302,35 @@ public async Task GetResponseAsync_WithTextBlockWithoutCitations() "role": "user", "content": [{ "type": "text", - "text": "Tell me about AI" + "text": "Think carefully" }] }], - "max_tokens": 1024 + "max_tokens": 100000, + "thinking": { + "type": "enabled", + "budget_tokens": 16384, + "display": "omitted" + } } """, actualResponse: """ { - "id": "msg_no_citations_01", + "id": "msg_reasoning_display_03", "type": "message", "role": "assistant", "model": "claude-haiku-4-5", "content": [{ + "type": "thinking", + "thinking": "", + "signature": "sig_omitted_abc123" + }, { "type": "text", - "text": "AI is transforming the world." + "text": "Here is my response" }], "stop_reason": "end_turn", "usage": { - "input_tokens": 20, - "output_tokens": 10 + "input_tokens": 10, + "output_tokens": 20 } } """ @@ -3434,57 +3338,76 @@ public async Task GetResponseAsync_WithTextBlockWithoutCitations() IChatClient chatClient = CreateChatClient(handler, "claude-haiku-4-5"); + ChatOptions options = new() + { + MaxOutputTokens = 100000, + Reasoning = new() { Effort = ReasoningEffort.High, Output = ReasoningOutput.None }, + }; + ChatResponse response = await chatClient.GetResponseAsync( - "Tell me about AI", - new(), + "Think carefully", + options, TestContext.Current.CancellationToken ); Assert.NotNull(response); - var textContent = response.Messages[0].Contents.OfType().FirstOrDefault(); - Assert.NotNull(textContent); - Assert.Equal("AI is transforming the world.", textContent.Text); - Assert.True(textContent.Annotations is null || !textContent.Annotations.Any()); + // The response should contain a TextReasoningContent with the signature + // and empty text (since display was omitted). + TextReasoningContent thinkingContent = Assert.IsType( + response.Messages.SelectMany(m => m.Contents).OfType().Single() + ); + Assert.Equal(string.Empty, thinkingContent.Text); + Assert.Equal("sig_omitted_abc123", thinkingContent.ProtectedData); } [Fact] - public async Task GetResponseAsync_WithWebSearchCitationsInTextBlock() + public async Task GetResponseAsync_SendsTextReasoningAsThinkingBlock() { VerbatimHttpHandler handler = new( expectedRequest: """ { "model": "claude-haiku-4-5", - "messages": [{ - "role": "user", - "content": [{ - "type": "text", - "text": "Tell me about recent AI developments with sources" - }] - }], + "messages": [ + { + "role": "user", + "content": [{ + "type": "text", + "text": "Think about this" + }] + }, + { + "role": "assistant", + "content": [{ + "type": "thinking", + "thinking": "My detailed reasoning...", + "signature": "sig_123" + }] + }, + { + "role": "user", + "content": [{ + "type": "text", + "text": "What did you conclude?" + }] + } + ], "max_tokens": 1024 } """, actualResponse: """ { - "id": "msg_with_citations_01", + "id": "msg_reasoning_sent_01", "type": "message", "role": "assistant", "model": "claude-haiku-4-5", "content": [{ "type": "text", - "text": "According to recent research [1], artificial intelligence is rapidly advancing.", - "citations": [{ - "type": "web_search_result_location", - "cited_text": "artificial intelligence is rapidly advancing", - "title": "AI Research 2024", - "url": "https://example.com/ai-research", - "encrypted_index": "enc_idx_123" - }] + "text": "Response after thinking" }], "stop_reason": "end_turn", "usage": { - "input_tokens": 25, - "output_tokens": 18 + "input_tokens": 30, + "output_tokens": 10 } } """ @@ -3492,68 +3415,71 @@ public async Task GetResponseAsync_WithWebSearchCitationsInTextBlock() IChatClient chatClient = CreateChatClient(handler, "claude-haiku-4-5"); + List messages = + [ + new(ChatRole.User, "Think about this"), + new( + ChatRole.Assistant, + [new TextReasoningContent("My detailed reasoning...") { ProtectedData = "sig_123" }] + ), + new(ChatRole.User, "What did you conclude?"), + ]; + ChatResponse response = await chatClient.GetResponseAsync( - "Tell me about recent AI developments with sources", + messages, new(), TestContext.Current.CancellationToken ); Assert.NotNull(response); - - var textContent = response.Messages[0].Contents.OfType().FirstOrDefault(); - Assert.NotNull(textContent); - Assert.Contains("artificial intelligence", textContent.Text); - Assert.NotNull(textContent.Annotations); - Assert.NotEmpty(textContent.Annotations); - - var citation = textContent.Annotations.OfType().FirstOrDefault(); - Assert.NotNull(citation); - Assert.Equal("AI Research 2024", citation.Title); - Assert.Equal("artificial intelligence is rapidly advancing", citation.Snippet); - Assert.NotNull(citation.Url); - Assert.Equal("https://example.com/ai-research", citation.Url.ToString()); - Assert.Null(citation.AnnotatedRegions); } [Fact] - public async Task GetResponseAsync_WithContentBlockLocationCitationsInTextBlock() + public async Task GetResponseAsync_SendsRedactedTextReasoningAsRedactedThinkingBlock() { VerbatimHttpHandler handler = new( expectedRequest: """ { "model": "claude-haiku-4-5", - "messages": [{ - "role": "user", - "content": [{ - "type": "text", - "text": "What does the document say about ML?" - }] - }], + "messages": [ + { + "role": "user", + "content": [{ + "type": "text", + "text": "Previous question" + }] + }, + { + "role": "assistant", + "content": [{ + "type": "redacted_thinking", + "data": "encrypted_data_xyz" + }] + }, + { + "role": "user", + "content": [{ + "type": "text", + "text": "Follow up question" + }] + } + ], "max_tokens": 1024 } """, actualResponse: """ { - "id": "msg_doc_citations_01", + "id": "msg_redacted_sent_01", "type": "message", "role": "assistant", "model": "claude-haiku-4-5", "content": [{ "type": "text", - "text": "As stated in the document [1], machine learning requires large datasets.", - "citations": [{ - "type": "content_block_location", - "cited_text": "machine learning requires large datasets", - "document_title": "ML Fundamentals", - "file_id": "file_abc123", - "document_index": 0, - "start_block_index": 15, - "end_block_index": 45 - }] + "text": "Response after redacted thinking" }], "stop_reason": "end_turn", "usage": { "input_tokens": 30, - "output_tokens": 20 + "output_tokens": 10 } } """ @@ -3561,47 +3487,53 @@ public async Task GetResponseAsync_WithContentBlockLocationCitationsInTextBlock( IChatClient chatClient = CreateChatClient(handler, "claude-haiku-4-5"); + List messages = + [ + new(ChatRole.User, "Previous question"), + new( + ChatRole.Assistant, + [new TextReasoningContent(string.Empty) { ProtectedData = "encrypted_data_xyz" }] + ), + new(ChatRole.User, "Follow up question"), + ]; + ChatResponse response = await chatClient.GetResponseAsync( - "What does the document say about ML?", + messages, new(), TestContext.Current.CancellationToken ); Assert.NotNull(response); - - var textContent = response.Messages[0].Contents.OfType().FirstOrDefault(); - Assert.NotNull(textContent); - Assert.NotNull(textContent.Annotations); - Assert.NotEmpty(textContent.Annotations); - - var citation = textContent.Annotations.OfType().FirstOrDefault(); - Assert.NotNull(citation); - - Assert.Equal("machine learning requires large datasets", citation.Snippet); - Assert.Equal("file_abc123", citation.FileId); - Assert.Null(citation.Url); - Assert.Null(citation.AnnotatedRegions); } [Fact] - public async Task GetResponseAsync_FinishReasonNullHandling() + public async Task GetResponseAsync_SkipsEmptyTextReasoningContent() { VerbatimHttpHandler handler = new( expectedRequest: """ { "max_tokens": 1024, "model": "claude-haiku-4-5", - "messages": [{ - "role": "user", - "content": [{ - "type": "text", - "text": "Test" - }] - }] + "messages": [ + { + "role": "user", + "content": [{ + "type": "text", + "text": "Question" + }] + }, + { + "role": "user", + "content": [{ + "type": "text", + "text": "Follow up" + }] + } + ] } """, actualResponse: """ { - "id": "msg_no_finish_01", + "id": "msg_skip_empty_01", "type": "message", "role": "assistant", "model": "claude-haiku-4-5", @@ -3609,9 +3541,9 @@ public async Task GetResponseAsync_FinishReasonNullHandling() "type": "text", "text": "Response" }], - "stop_reason": null, + "stop_reason": "end_turn", "usage": { - "input_tokens": 10, + "input_tokens": 20, "output_tokens": 5 } } @@ -3620,18 +3552,23 @@ public async Task GetResponseAsync_FinishReasonNullHandling() IChatClient chatClient = CreateChatClient(handler, "claude-haiku-4-5"); + List messages = + [ + new(ChatRole.User, "Question"), + new(ChatRole.Assistant, [new TextReasoningContent(null)]), + new(ChatRole.User, "Follow up"), + ]; + ChatResponse response = await chatClient.GetResponseAsync( - "Test", + messages, new(), TestContext.Current.CancellationToken ); Assert.NotNull(response); - - Assert.Null(response.FinishReason); } [Fact] - public async Task GetStreamingResponseAsync_AccumulatesUsageFromMultipleMessageStartEvents() + public async Task GetStreamingResponseAsync_WithThinkingBlockInStream() { VerbatimHttpHandler handler = new( expectedRequest: """ @@ -3642,7 +3579,7 @@ public async Task GetStreamingResponseAsync_AccumulatesUsageFromMultipleMessageS "role": "user", "content": [{ "type": "text", - "text": "Test multiple message starts" + "text": "Analyze this problem" }] }], "stream": true @@ -3650,22 +3587,28 @@ public async Task GetStreamingResponseAsync_AccumulatesUsageFromMultipleMessageS """, actualResponse: """ event: message_start - data: {"type":"message_start","message":{"id":"msg_multi_start_01","type":"message","role":"assistant","model":"claude-haiku-4-5","content":[],"stop_reason":null,"stop_sequence":null,"usage":{"input_tokens":10,"output_tokens":0}}} - - event: message_start - data: {"type":"message_start","message":{"id":"msg_multi_start_01","type":"message","role":"assistant","model":"claude-haiku-4-5","content":[],"stop_reason":null,"stop_sequence":null,"usage":{"input_tokens":5,"output_tokens":0}}} + data: {"type":"message_start","message":{"id":"msg_thinking_stream_01","type":"message","role":"assistant","model":"claude-haiku-4-5","content":[],"stop_reason":null,"stop_sequence":null,"usage":{"input_tokens":10,"output_tokens":0}}} event: content_block_start - data: {"type":"content_block_start","index":0,"content_block":{"type":"text","text":""}} + data: {"type":"content_block_start","index":0,"content_block":{"type":"thinking","thinking":"","signature":"sig_123"}} event: content_block_delta - data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":"Response"}} + data: {"type":"content_block_delta","index":0,"delta":{"type":"thinking_delta","thinking":"Let me analyze this..."}} event: content_block_stop data: {"type":"content_block_stop","index":0} + event: content_block_start + data: {"type":"content_block_start","index":1,"content_block":{"type":"text","text":""}} + + event: content_block_delta + data: {"type":"content_block_delta","index":1,"delta":{"type":"text_delta","text":"Based on my analysis"}} + + event: content_block_stop + data: {"type":"content_block_stop","index":1} + event: message_delta - data: {"type":"message_delta","delta":{"stop_reason":"end_turn","stop_sequence":null},"usage":{"input_tokens":15,"output_tokens":2}} + data: {"type":"message_delta","delta":{"stop_reason":"end_turn","stop_sequence":null},"usage":{"output_tokens":10}} event: message_stop data: {"type":"message_stop"} @@ -3678,7 +3621,7 @@ public async Task GetStreamingResponseAsync_AccumulatesUsageFromMultipleMessageS List updates = []; await foreach ( var update in chatClient.GetStreamingResponseAsync( - "Test multiple message starts", + "Analyze this problem", new(), TestContext.Current.CancellationToken ) @@ -3688,20 +3631,19 @@ var update in chatClient.GetStreamingResponseAsync( } Assert.NotEmpty(updates); - var usageUpdates = updates.Where(u => u.Contents.Any(c => c is UsageContent)).ToList(); - Assert.NotEmpty(usageUpdates); + var reasoningUpdates = updates + .Where(u => u.Contents.Any(c => c is TextReasoningContent)) + .ToList(); + Assert.NotEmpty(reasoningUpdates); - var usageContent = usageUpdates - .SelectMany(u => u.Contents.OfType()) + var reasoningContent = reasoningUpdates + .SelectMany(u => u.Contents.OfType()) .FirstOrDefault(); - Assert.NotNull(usageContent); - - Assert.Equal(15, usageContent.Details.InputTokenCount); - Assert.Equal(2, usageContent.Details.OutputTokenCount); + Assert.NotNull(reasoningContent); } [Fact] - public async Task GetStreamingResponseAsync_UsageFromDeltaOverridesStartEvent() + public async Task GetStreamingResponseAsync_WithRedactedThinkingBlockInStream() { VerbatimHttpHandler handler = new( expectedRequest: """ @@ -3712,7 +3654,7 @@ public async Task GetStreamingResponseAsync_UsageFromDeltaOverridesStartEvent() "role": "user", "content": [{ "type": "text", - "text": "hello" + "text": "Test redacted thinking" }] }], "stream": true @@ -3720,19 +3662,25 @@ public async Task GetStreamingResponseAsync_UsageFromDeltaOverridesStartEvent() """, actualResponse: """ event: message_start - data: {"type":"message_start","message":{"id":"msg_01","type":"message","role":"assistant","model":"claude-haiku-4-5","content":[],"stop_reason":null,"stop_sequence":null,"usage":{"input_tokens":8,"cache_creation_input_tokens":0,"cache_read_input_tokens":0,"output_tokens":8}}} + data: {"type":"message_start","message":{"id":"msg_redacted_stream_01","type":"message","role":"assistant","model":"claude-haiku-4-5","content":[],"stop_reason":null,"stop_sequence":null,"usage":{"input_tokens":10,"output_tokens":0}}} event: content_block_start - data: {"type":"content_block_start","index":0,"content_block":{"type":"text","text":""}} + data: {"type":"content_block_start","index":0,"content_block":{"type":"redacted_thinking","data":"encrypted_xyz"}} + + event: content_block_stop + data: {"type":"content_block_stop","index":0} + + event: content_block_start + data: {"type":"content_block_start","index":1,"content_block":{"type":"text","text":""}} event: content_block_delta - data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":"Hello!"}} + data: {"type":"content_block_delta","index":1,"delta":{"type":"text_delta","text":"Response"}} event: content_block_stop - data: {"type":"content_block_stop","index":0} + data: {"type":"content_block_stop","index":1} event: message_delta - data: {"type":"message_delta","delta":{"stop_reason":"end_turn","stop_sequence":null},"usage":{"input_tokens":8,"cache_creation_input_tokens":0,"cache_read_input_tokens":0,"output_tokens":12}} + data: {"type":"message_delta","delta":{"stop_reason":"end_turn","stop_sequence":null},"usage":{"output_tokens":5}} event: message_stop data: {"type":"message_stop"} @@ -3745,7 +3693,7 @@ public async Task GetStreamingResponseAsync_UsageFromDeltaOverridesStartEvent() List updates = []; await foreach ( var update in chatClient.GetStreamingResponseAsync( - "hello", + "Test redacted thinking", new(), TestContext.Current.CancellationToken ) @@ -3755,184 +3703,126 @@ var update in chatClient.GetStreamingResponseAsync( } Assert.NotEmpty(updates); - var usageUpdates = updates.Where(u => u.Contents.Any(c => c is UsageContent)).ToList(); - Assert.NotEmpty(usageUpdates); + var reasoningUpdates = updates + .Where(u => u.Contents.Any(c => c is TextReasoningContent)) + .ToList(); + Assert.NotEmpty(reasoningUpdates); - var usageContent = usageUpdates - .SelectMany(u => u.Contents.OfType()) + var reasoningContent = reasoningUpdates + .SelectMany(u => u.Contents.OfType()) .FirstOrDefault(); - Assert.NotNull(usageContent); - - Assert.Equal(8, usageContent.Details.InputTokenCount); - Assert.Equal(12, usageContent.Details.OutputTokenCount); - Assert.Equal(20, usageContent.Details.TotalTokenCount); + Assert.NotNull(reasoningContent); + Assert.Equal(string.Empty, reasoningContent.Text); + Assert.Equal("encrypted_xyz", reasoningContent.ProtectedData); } [Fact] - public async Task GetResponseAsync_FunctionResult_WithSingleTextContent() + public async Task GetStreamingResponseAsync_WithSignatureDeltaInStream() { VerbatimHttpHandler handler = new( expectedRequest: """ { "max_tokens": 1024, "model": "claude-haiku-4-5", - "messages": [ - { - "role": "user", - "content": [{ - "type": "text", - "text": "What's the weather in Seattle?" - }] - }, - { - "role": "assistant", - "content": [{ - "type": "tool_use", - "id": "toolu_012ji4C9Dx9qiGwDPfWSjRVC", - "name": "get_weather", - "input": { - "location": "Seattle" - } - }] - }, - { - "role": "user", - "content": [{ - "type": "tool_result", - "tool_use_id": "toolu_012ji4C9Dx9qiGwDPfWSjRVC", - "is_error": false, - "content": [{ - "type": "text", - "text": "The weather in Seattle is sunny and 72�F" - }] - }] - } - ] + "messages": [{ + "role": "user", + "content": [{ + "type": "text", + "text": "Test signature delta" + }] + }], + "stream": true } """, actualResponse: """ - { - "id": "msg_01DzfU3ta5z9nrJo6EGamXqV", - "type": "message", - "role": "assistant", - "model": "claude-haiku-4-5-20251001", - "content": [{ - "type": "text", - "text": "The weather in Seattle is currently **sunny** with a temperature of **72�F** (about 22�C). Great weather for outdoor activities!" - }], - "stop_reason": "end_turn", - "usage": { - "input_tokens": 91, - "output_tokens": 34 - } - } + event: message_start + data: {"type":"message_start","message":{"id":"msg_sig_delta_01","type":"message","role":"assistant","model":"claude-haiku-4-5","content":[],"stop_reason":null,"stop_sequence":null,"usage":{"input_tokens":10,"output_tokens":0}}} + + event: content_block_start + data: {"type":"content_block_start","index":0,"content_block":{"type":"thinking","thinking":"","signature":""}} + + event: content_block_delta + data: {"type":"content_block_delta","index":0,"delta":{"type":"thinking_delta","thinking":"Analyzing..."}} + + event: content_block_delta + data: {"type":"content_block_delta","index":0,"delta":{"type":"signature_delta","signature":"sig_part1"}} + + event: content_block_delta + data: {"type":"content_block_delta","index":0,"delta":{"type":"signature_delta","signature":"sig_part2"}} + + event: content_block_stop + data: {"type":"content_block_stop","index":0} + + event: message_delta + data: {"type":"message_delta","delta":{"stop_reason":"end_turn","stop_sequence":null},"usage":{"output_tokens":5}} + + event: message_stop + data: {"type":"message_stop"} + """ ); IChatClient chatClient = CreateChatClient(handler, "claude-haiku-4-5"); - List messages = - [ - new(ChatRole.User, "What's the weather in Seattle?"), - new( - ChatRole.Assistant, - [ - new FunctionCallContent( - "toolu_012ji4C9Dx9qiGwDPfWSjRVC", - "get_weather", - new Dictionary { ["location"] = "Seattle" } - ), - ] - ), - new( - ChatRole.User, - [ - new FunctionResultContent( - "toolu_012ji4C9Dx9qiGwDPfWSjRVC", - new TextContent("The weather in Seattle is sunny and 72�F") - ), - ] - ), - ]; + List updates = []; + await foreach ( + var update in chatClient.GetStreamingResponseAsync( + "Test signature delta", + new(), + TestContext.Current.CancellationToken + ) + ) + { + updates.Add(update); + } - ChatResponse response = await chatClient.GetResponseAsync( - messages, - new(), - TestContext.Current.CancellationToken - ); + Assert.NotEmpty(updates); + var reasoningUpdates = updates + .Where(u => u.Contents.Any(c => c is TextReasoningContent)) + .ToList(); + Assert.NotEmpty(reasoningUpdates); - Assert.NotNull(response); - TextContent textContent = Assert.IsType(response.Messages[0].Contents[0]); - Assert.Contains("sunny", textContent.Text, StringComparison.OrdinalIgnoreCase); - Assert.Contains("72", textContent.Text); + var allReasoningContent = reasoningUpdates + .SelectMany(u => u.Contents.OfType()) + .ToList(); + Assert.NotEmpty(allReasoningContent); } [Fact] - public async Task GetResponseAsync_FunctionResult_WithMultipleTextContents() + public async Task GetResponseAsync_WithThinkingBlockInResponse() { VerbatimHttpHandler handler = new( expectedRequest: """ { - "max_tokens": 1024, "model": "claude-haiku-4-5", - "messages": [ - { - "role": "user", - "content": [{ - "type": "text", - "text": "Get me news about AI" - }] - }, - { - "role": "assistant", - "content": [{ - "type": "tool_use", - "id": "toolu_01TQkFntpAPUXLijpPu5Q1dT", - "name": "get_news", - "input": { - "topic": "AI" - } - }] - }, - { - "role": "user", - "content": [{ - "type": "tool_result", - "tool_use_id": "toolu_01TQkFntpAPUXLijpPu5Q1dT", - "is_error": false, - "content": [ - { - "type": "text", - "text": "Breaking: AI advances" - }, - { - "type": "text", - "text": "Research shows improvements" - }, - { - "type": "text", - "text": "Industry adoption grows" - } - ] - }] - } - ] + "messages": [{ + "role": "user", + "content": [{ + "type": "text", + "text": "What is the answer?" + }] + }], + "max_tokens": 1024 } """, actualResponse: """ { - "id": "msg_01G8oMUpScZWsMe5JsNuLkgJ", + "id": "msg_thinking_resp_01", "type": "message", "role": "assistant", - "model": "claude-haiku-4-5-20251001", + "model": "claude-haiku-4-5", "content": [{ + "type": "thinking", + "thinking": "Let me think through this step by step...", + "signature": "sig_abc123" + }, { "type": "text", - "text": "Here's the latest AI news:\\n\\n**Breaking: AI Advances**\\n- Researchers are demonstrating significant improvements in AI capabilities across various domains\\n\\n**Research Shows Improvements**\\n- Ongoing studies continue to push the boundaries of what AI systems can accomplish\\n\\n**Industry Adoption Grows**\\n- Companies across sectors are increasingly implementing AI solutions into their operations" + "text": "Based on my analysis, the answer is 42." }], "stop_reason": "end_turn", "usage": { - "input_tokens": 95, - "output_tokens": 100 + "input_tokens": 20, + "output_tokens": 15 } } """ @@ -3940,107 +3830,62 @@ public async Task GetResponseAsync_FunctionResult_WithMultipleTextContents() IChatClient chatClient = CreateChatClient(handler, "claude-haiku-4-5"); - List messages = - [ - new(ChatRole.User, "Get me news about AI"), - new( - ChatRole.Assistant, - [ - new FunctionCallContent( - "toolu_01TQkFntpAPUXLijpPu5Q1dT", - "get_news", - new Dictionary { ["topic"] = "AI" } - ), - ] - ), - new( - ChatRole.User, - [ - new FunctionResultContent( - "toolu_01TQkFntpAPUXLijpPu5Q1dT", - new AIContent[] - { - new TextContent("Breaking: AI advances"), - new TextContent("Research shows improvements"), - new TextContent("Industry adoption grows"), - } - ), - ] - ), - ]; - ChatResponse response = await chatClient.GetResponseAsync( - messages, + "What is the answer?", new(), TestContext.Current.CancellationToken ); - Assert.NotNull(response); - TextContent textContent = Assert.IsType(response.Messages[0].Contents[0]); - Assert.Contains("AI", textContent.Text); - Assert.Contains("advances", textContent.Text, StringComparison.OrdinalIgnoreCase); + + Assert.Equal(2, response.Messages[0].Contents.Count); + + var reasoningContent = response + .Messages[0] + .Contents.OfType() + .FirstOrDefault(); + Assert.NotNull(reasoningContent); + Assert.Equal("Let me think through this step by step...", reasoningContent.Text); + Assert.Equal("sig_abc123", reasoningContent.ProtectedData); + + var textContent = response.Messages[0].Contents.OfType().FirstOrDefault(); + Assert.NotNull(textContent); + Assert.Equal("Based on my analysis, the answer is 42.", textContent.Text); } [Fact] - public async Task GetResponseAsync_FunctionResult_WithImageDataContent() + public async Task GetResponseAsync_WithRedactedThinkingBlockInResponse() { VerbatimHttpHandler handler = new( expectedRequest: """ { - "max_tokens": 1024, "model": "claude-haiku-4-5", - "messages": [ - { - "role": "user", - "content": [{ - "type": "text", - "text": "Generate a bar chart" - }] - }, - { - "role": "assistant", - "content": [{ - "type": "tool_use", - "id": "toolu_01RFvjHBAxq1z9kgH7vtVioW", - "name": "generate_chart", - "input": { - "type": "bar" - } - }] - }, - { - "role": "user", - "content": [{ - "type": "tool_result", - "tool_use_id": "toolu_01RFvjHBAxq1z9kgH7vtVioW", - "is_error": false, - "content": [{ - "type": "image", - "source": { - "type": "base64", - "media_type": "image/png", - "data": "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg==" - } - }] - }] - } - ] + "messages": [{ + "role": "user", + "content": [{ + "type": "text", + "text": "Tell me your conclusion" + }] + }], + "max_tokens": 1024 } """, actualResponse: """ { - "id": "msg_01JVBwA4cipSnmopX4ywyZ36", + "id": "msg_redacted_resp_01", "type": "message", "role": "assistant", - "model": "claude-haiku-4-5-20251001", + "model": "claude-haiku-4-5", "content": [{ + "type": "redacted_thinking", + "data": "encrypted_thinking_data_xyz" + }, { "type": "text", - "text": "I've generated a simple bar chart for you! \\n\\nSince you didn't specify particular data, here's a basic example. If you'd like me to create a bar chart with specific data, categories, or a particular theme, please let me know." + "text": "Here is my conclusion." }], "stop_reason": "end_turn", "usage": { - "input_tokens": 92, - "output_tokens": 50 + "input_tokens": 20, + "output_tokens": 10 } } """ @@ -4048,105 +3893,66 @@ public async Task GetResponseAsync_FunctionResult_WithImageDataContent() IChatClient chatClient = CreateChatClient(handler, "claude-haiku-4-5"); - byte[] pngData = Convert.FromBase64String( - "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg==" - ); - - List messages = - [ - new(ChatRole.User, "Generate a bar chart"), - new( - ChatRole.Assistant, - [ - new FunctionCallContent( - "toolu_01RFvjHBAxq1z9kgH7vtVioW", - "generate_chart", - new Dictionary { ["type"] = "bar" } - ), - ] - ), - new( - ChatRole.User, - [ - new FunctionResultContent( - "toolu_01RFvjHBAxq1z9kgH7vtVioW", - new DataContent(pngData, "image/png") - ), - ] - ), - ]; - ChatResponse response = await chatClient.GetResponseAsync( - messages, + "Tell me your conclusion", new(), TestContext.Current.CancellationToken ); - Assert.NotNull(response); - TextContent textContent = Assert.IsType(response.Messages[0].Contents[0]); - Assert.Contains("chart", textContent.Text, StringComparison.OrdinalIgnoreCase); + + Assert.Equal(2, response.Messages[0].Contents.Count); + + var reasoningContent = response + .Messages[0] + .Contents.OfType() + .FirstOrDefault(); + Assert.NotNull(reasoningContent); + Assert.Equal(string.Empty, reasoningContent.Text); + Assert.Equal("encrypted_thinking_data_xyz", reasoningContent.ProtectedData); + + var textContent = response.Messages[0].Contents.OfType().FirstOrDefault(); + Assert.NotNull(textContent); + Assert.Equal("Here is my conclusion.", textContent.Text); } [Fact] - public async Task GetResponseAsync_FunctionResult_WithPdfDataContent() + public async Task GetResponseAsync_WithToolUseBlockInResponse() { VerbatimHttpHandler handler = new( expectedRequest: """ { - "max_tokens": 1024, "model": "claude-haiku-4-5", - "messages": [ - { - "role": "user", - "content": [{ - "type": "text", - "text": "Generate a sales report" - }] - }, - { - "role": "assistant", - "content": [{ - "type": "tool_use", - "id": "toolu_01Xp2XKeM6KcpCrGKbh96biN", - "name": "generate_report", - "input": { - "type": "sales" - } - }] - }, - { - "role": "user", - "content": [{ - "type": "tool_result", - "tool_use_id": "toolu_01Xp2XKeM6KcpCrGKbh96biN", - "is_error": false, - "content": [{ - "type": "document", - "source": { - "type": "base64", - "media_type": "application/pdf", - "data": "JVBERi0xLjQKMSAwIG9iajw8L1R5cGUvQ2F0YWxvZy9QYWdlcyAyIDAgUj4+ZW5kb2JqIDIgMCBvYmo8PC9UeXBlL1BhZ2VzL0tpZHNbMyAwIFJdL0NvdW50IDE+PmVuZG9iaiAzIDAgb2JqPDwvVHlwZS9QYWdlL01lZGlhQm94WzAgMCA2MTIgNzkyXS9QYXJlbnQgMiAwIFIvUmVzb3VyY2VzPDw+Pj4+ZW5kb2JqCnhyZWYKMCA0CjAwMDAwMDAwMDAgNjU1MzUgZgowMDAwMDAwMDA5IDAwMDAwIG4KMDAwMDAwMDA1MiAwMDAwMCBuCjAwMDAwMDAxMDEgMDAwMDAgbgp0cmFpbGVyPDwvU2l6ZSA0L1Jvb3QgMSAwIFI+PgpzdGFydHhyZWYKMTc4CiUlRU9G" - } - }] - }] - } - ] + "messages": [{ + "role": "user", + "content": [{ + "type": "text", + "text": "What is 6 times 7?" + }] + }], + "max_tokens": 1024 } """, actualResponse: """ { - "id": "msg_01WhQmBXmH4zHd1fB2VYGRWW", + "id": "msg_tooluse_resp_01", "type": "message", "role": "assistant", - "model": "claude-haiku-4-5-20251001", + "model": "claude-haiku-4-5", "content": [{ - "type": "text", - "text": "I attempted to generate a sales report, but the generated document appears to be blank. Let me provide you with a sample **Sales Report** instead with key metrics and insights." + "type": "tool_use", + "id": "toolu_detailed_01", + "name": "calculate", + "input": { + "operation": "multiply", + "a": 6, + "b": 7 + }, + "caller": {"type": "direct"} }], - "stop_reason": "end_turn", + "stop_reason": "tool_use", "usage": { - "input_tokens": 1653, - "output_tokens": 50 + "input_tokens": 30, + "output_tokens": 15 } } """ @@ -4154,111 +3960,76 @@ public async Task GetResponseAsync_FunctionResult_WithPdfDataContent() IChatClient chatClient = CreateChatClient(handler, "claude-haiku-4-5"); - string pdfContent = - "%PDF-1.4\n1 0 obj<>endobj 2 0 obj<>endobj 3 0 obj<>>>endobj\nxref\n0 4\n0000000000 65535 f\n0000000009 00000 n\n0000000052 00000 n\n0000000101 00000 n\ntrailer<>\nstartxref\n178\n%%EOF"; - byte[] pdfData = Encoding.UTF8.GetBytes(pdfContent); + var calcFunction = AIFunctionFactory.Create( + (string operation, int a, int b) => + { + return operation == "multiply" ? a * b : 0; + }, + "calculate" + ); - List messages = - [ - new(ChatRole.User, "Generate a sales report"), - new( - ChatRole.Assistant, - [ - new FunctionCallContent( - "toolu_01Xp2XKeM6KcpCrGKbh96biN", - "generate_report", - new Dictionary { ["type"] = "sales" } - ), - ] - ), - new( - ChatRole.User, - [ - new FunctionResultContent( - "toolu_01Xp2XKeM6KcpCrGKbh96biN", - new DataContent(pdfData, "application/pdf") - ), - ] - ), - ]; + ChatOptions options = new() { Tools = [calcFunction] }; ChatResponse response = await chatClient.GetResponseAsync( - messages, + "What is 6 times 7?", new(), TestContext.Current.CancellationToken ); - Assert.NotNull(response); - TextContent textContent = Assert.IsType(response.Messages[0].Contents[0]); - Assert.Contains("report", textContent.Text, StringComparison.OrdinalIgnoreCase); - } - [Fact] - public async Task GetResponseAsync_FunctionResult_WithTextPlainDataContent() - { + Assert.Equal(ChatFinishReason.ToolCalls, response.FinishReason); + + var functionCall = response + .Messages[0] + .Contents.OfType() + .FirstOrDefault(); + Assert.NotNull(functionCall); + Assert.Equal("calculate", functionCall.Name); + Assert.Equal("toolu_detailed_01", functionCall.CallId); + Assert.NotNull(functionCall.Arguments); + Assert.True(functionCall.Arguments.ContainsKey("operation")); + Assert.Equal("multiply", functionCall.Arguments["operation"]?.ToString()); + Assert.True(functionCall.Arguments.ContainsKey("a")); + Assert.True(functionCall.Arguments.ContainsKey("b")); + } + + [Fact] + public async Task GetResponseAsync_WithHostedWebSearchToolOptionTriggersConversion() + { VerbatimHttpHandler handler = new( expectedRequest: """ { - "max_tokens": 1024, "model": "claude-haiku-4-5", - "messages": [ - { - "role": "user", - "content": [{ - "type": "text", - "text": "Get the system logs" - }] - }, - { - "role": "assistant", - "content": [ - { - "type": "text", - "text": "I'll retrieve the system logs for you." - }, - { - "type": "tool_use", - "id": "toolu_01JqNHMtbwFQExUwDMWy3wHe", - "name": "get_logs", - "input": { - "type": "system" - } - } - ] - }, + "messages": [{ + "role": "user", + "content": [{ + "type": "text", + "text": "Find recent news about AI" + }] + }], + "max_tokens": 1024, + "tools": [ { - "role": "user", - "content": [{ - "type": "tool_result", - "tool_use_id": "toolu_01JqNHMtbwFQExUwDMWy3wHe", - "is_error": false, - "content": [{ - "type": "document", - "source": { - "type": "text", - "media_type": "text/plain", - "data": "Log Entry 1: System started\nLog Entry 2: Processing data\nLog Entry 3: Task completed" - } - }] - }] + "name": "web_search", + "type": "web_search_20250305" } ] } """, actualResponse: """ { - "id": "msg_01RxuuTbpsvFyNpim6uoXujV", + "id": "msg_websearch_opt_01", "type": "message", "role": "assistant", - "model": "claude-haiku-4-5-20251001", + "model": "claude-haiku-4-5", "content": [{ "type": "text", - "text": "Here are the system logs:\\n\\n**System Logs:**\\n1. System started\\n2. Processing data\\n3. Task completed\\n\\nThese are the current entries in the system log." + "text": "I'll search for that information." }], "stop_reason": "end_turn", "usage": { - "input_tokens": 148, - "output_tokens": 50 + "input_tokens": 20, + "output_tokens": 10 } } """ @@ -4266,113 +4037,47 @@ public async Task GetResponseAsync_FunctionResult_WithTextPlainDataContent() IChatClient chatClient = CreateChatClient(handler, "claude-haiku-4-5"); - string logContent = - "Log Entry 1: System started\nLog Entry 2: Processing data\nLog Entry 3: Task completed"; - byte[] logData = Encoding.UTF8.GetBytes(logContent); - - List messages = - [ - new(ChatRole.User, "Get the system logs"), - new( - ChatRole.Assistant, - [ - new TextContent("I'll retrieve the system logs for you."), - new FunctionCallContent( - "toolu_01JqNHMtbwFQExUwDMWy3wHe", - "get_logs", - new Dictionary { ["type"] = "system" } - ), - ] - ), - new( - ChatRole.User, - [ - new FunctionResultContent( - "toolu_01JqNHMtbwFQExUwDMWy3wHe", - new DataContent(logData, "text/plain") - ), - ] - ), - ]; + ChatOptions options = new() { Tools = [new HostedWebSearchTool()] }; ChatResponse response = await chatClient.GetResponseAsync( - messages, - new(), + "Find recent news about AI", + options, TestContext.Current.CancellationToken ); - Assert.NotNull(response); - TextContent textContent = Assert.IsType(response.Messages[0].Contents[0]); - Assert.Contains("System started", textContent.Text); - Assert.Contains("Task completed", textContent.Text); } [Fact] - public async Task GetResponseAsync_FunctionResult_WithMixedContent() + public async Task GetResponseAsync_WithTextBlockWithoutCitations() { VerbatimHttpHandler handler = new( expectedRequest: """ { - "max_tokens": 1024, "model": "claude-haiku-4-5", - "messages": [ - { - "role": "user", - "content": [{ - "type": "text", - "text": "Analyze the sales data" - }] - }, - { - "role": "assistant", - "content": [{ - "type": "tool_use", - "id": "toolu_01ABC123", - "name": "analyze_data", - "input": { - "dataset": "sales" - } - }] - }, - { - "role": "user", - "content": [{ - "type": "tool_result", - "tool_use_id": "toolu_01ABC123", - "is_error": false, - "content": [ - { - "type": "text", - "text": "Analysis: Mean=42.5, Median=40" - }, - { - "type": "image", - "source": { - "type": "base64", - "media_type": "image/png", - "data": "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg==" - } - } - ] - }] - } - ] + "messages": [{ + "role": "user", + "content": [{ + "type": "text", + "text": "Tell me about AI" + }] + }], + "max_tokens": 1024 } """, actualResponse: """ { - "id": "msg_01MixedContent", + "id": "msg_no_citations_01", "type": "message", "role": "assistant", - "model": "claude-haiku-4-5-20251001", + "model": "claude-haiku-4-5", "content": [{ "type": "text", - "text": "Based on the analysis, your sales data shows a mean of 42.5 and median of 40. The chart visualization helps illustrate the distribution." + "text": "AI is transforming the world." }], "stop_reason": "end_turn", "usage": { - "input_tokens": 120, - "output_tokens": 35 + "input_tokens": 20, + "output_tokens": 10 } } """ @@ -4380,107 +4085,3383 @@ public async Task GetResponseAsync_FunctionResult_WithMixedContent() IChatClient chatClient = CreateChatClient(handler, "claude-haiku-4-5"); - byte[] chartData = Convert.FromBase64String( - "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg==" - ); - - List messages = - [ - new(ChatRole.User, "Analyze the sales data"), - new( - ChatRole.Assistant, - [ - new FunctionCallContent( - "toolu_01ABC123", - "analyze_data", - new Dictionary { ["dataset"] = "sales" } - ), - ] - ), - new( - ChatRole.User, - [ - new FunctionResultContent( - "toolu_01ABC123", - new AIContent[] - { - new TextContent("Analysis: Mean=42.5, Median=40"), - new DataContent(chartData, "image/png"), - } - ), - ] - ), - ]; - ChatResponse response = await chatClient.GetResponseAsync( - messages, + "Tell me about AI", new(), TestContext.Current.CancellationToken ); - Assert.NotNull(response); - TextContent textContent = Assert.IsType(response.Messages[0].Contents[0]); - Assert.Contains("42.5", textContent.Text); - Assert.Contains("40", textContent.Text); + + var textContent = response.Messages[0].Contents.OfType().FirstOrDefault(); + Assert.NotNull(textContent); + Assert.Equal("AI is transforming the world.", textContent.Text); + Assert.True(textContent.Annotations is null || !textContent.Annotations.Any()); } [Fact] - public async Task GetResponseAsync_WithFunctionResultContent_UriContent_Image() + public async Task GetResponseAsync_WithWebSearchCitationsInTextBlock() { VerbatimHttpHandler handler = new( expectedRequest: """ { - "max_tokens": 1024, "model": "claude-haiku-4-5", - "messages": [ - { - "role": "user", - "content": [{ - "type": "text", - "text": "Get image URL" - }] - }, - { - "role": "assistant", - "content": [{ - "type": "tool_use", - "id": "tool_uri_img", - "name": "url_tool", - "input": {} - }] - }, - { - "role": "user", - "content": [{ - "type": "tool_result", - "tool_use_id": "tool_uri_img", - "is_error": false, - "content": [{ - "type": "image", - "source": { - "type": "url", - "url": "https://example.com/image.png" - } - }] + "messages": [{ + "role": "user", + "content": [{ + "type": "text", + "text": "Tell me about recent AI developments with sources" + }] + }], + "max_tokens": 1024 + } + """, + actualResponse: """ + { + "id": "msg_with_citations_01", + "type": "message", + "role": "assistant", + "model": "claude-haiku-4-5", + "content": [{ + "type": "text", + "text": "According to recent research [1], artificial intelligence is rapidly advancing.", + "citations": [{ + "type": "web_search_result_location", + "cited_text": "artificial intelligence is rapidly advancing", + "title": "AI Research 2024", + "url": "https://example.com/ai-research", + "encrypted_index": "enc_idx_123" + }] + }], + "stop_reason": "end_turn", + "usage": { + "input_tokens": 25, + "output_tokens": 18 + } + } + """ + ); + + IChatClient chatClient = CreateChatClient(handler, "claude-haiku-4-5"); + + ChatResponse response = await chatClient.GetResponseAsync( + "Tell me about recent AI developments with sources", + new(), + TestContext.Current.CancellationToken + ); + Assert.NotNull(response); + + var textContent = response.Messages[0].Contents.OfType().FirstOrDefault(); + Assert.NotNull(textContent); + Assert.Contains("artificial intelligence", textContent.Text); + Assert.NotNull(textContent.Annotations); + Assert.NotEmpty(textContent.Annotations); + + var citation = textContent.Annotations.OfType().FirstOrDefault(); + Assert.NotNull(citation); + Assert.Equal("AI Research 2024", citation.Title); + Assert.Equal("artificial intelligence is rapidly advancing", citation.Snippet); + Assert.NotNull(citation.Url); + Assert.Equal("https://example.com/ai-research", citation.Url.ToString()); + Assert.Null(citation.AnnotatedRegions); + } + + [Fact] + public async Task GetResponseAsync_WithContentBlockLocationCitationsInTextBlock() + { + VerbatimHttpHandler handler = new( + expectedRequest: """ + { + "model": "claude-haiku-4-5", + "messages": [{ + "role": "user", + "content": [{ + "type": "text", + "text": "What does the document say about ML?" + }] + }], + "max_tokens": 1024 + } + """, + actualResponse: """ + { + "id": "msg_doc_citations_01", + "type": "message", + "role": "assistant", + "model": "claude-haiku-4-5", + "content": [{ + "type": "text", + "text": "As stated in the document [1], machine learning requires large datasets.", + "citations": [{ + "type": "content_block_location", + "cited_text": "machine learning requires large datasets", + "document_title": "ML Fundamentals", + "file_id": "file_abc123", + "document_index": 0, + "start_block_index": 15, + "end_block_index": 45 + }] + }], + "stop_reason": "end_turn", + "usage": { + "input_tokens": 30, + "output_tokens": 20 + } + } + """ + ); + + IChatClient chatClient = CreateChatClient(handler, "claude-haiku-4-5"); + + ChatResponse response = await chatClient.GetResponseAsync( + "What does the document say about ML?", + new(), + TestContext.Current.CancellationToken + ); + Assert.NotNull(response); + + var textContent = response.Messages[0].Contents.OfType().FirstOrDefault(); + Assert.NotNull(textContent); + Assert.NotNull(textContent.Annotations); + Assert.NotEmpty(textContent.Annotations); + + var citation = textContent.Annotations.OfType().FirstOrDefault(); + Assert.NotNull(citation); + + Assert.Equal("machine learning requires large datasets", citation.Snippet); + Assert.Equal("file_abc123", citation.FileId); + Assert.Null(citation.Url); + Assert.Null(citation.AnnotatedRegions); + } + + [Fact] + public async Task GetResponseAsync_ServerToolUseBlock_WebSearch_MapsToWebSearchToolCallContent() + { + VerbatimHttpHandler handler = new( + expectedRequest: """ + { + "model": "claude-haiku-4-5", + "messages": [{ + "role": "user", + "content": [{ + "type": "text", + "text": "Search for AI news" + }] + }], + "max_tokens": 1024, + "tools": [{ + "name": "web_search", + "type": "web_search_20250305" + }] + } + """, + actualResponse: """ + { + "id": "msg_ws_01", + "type": "message", + "role": "assistant", + "model": "claude-haiku-4-5", + "content": [ + { + "type": "server_tool_use", + "id": "srvtoolu_ws_01", + "name": "web_search", + "caller": { "type": "direct" }, + "input": { "query": "latest AI news 2026" } + }, + { + "type": "web_search_tool_result", + "tool_use_id": "srvtoolu_ws_01", + "caller": { "type": "direct" }, + "content": [ + { + "type": "web_search_result", + "title": "AI Breakthroughs in 2026", + "url": "https://example.com/ai-news", + "encrypted_content": "enc_abc123", + "page_age": "2 days ago" + }, + { + "type": "web_search_result", + "title": "Latest AI Research", + "url": "https://example.com/ai-research", + "encrypted_content": "enc_def456", + "page_age": "1 week ago" + } + ] + }, + { + "type": "text", + "text": "Here are some recent AI news articles." + } + ], + "stop_reason": "end_turn", + "usage": { + "input_tokens": 30, + "output_tokens": 25 + } + } + """ + ); + + IChatClient chatClient = CreateChatClient(handler, "claude-haiku-4-5"); + + ChatResponse response = await chatClient.GetResponseAsync( + "Search for AI news", + new() { Tools = [new HostedWebSearchTool()] }, + TestContext.Current.CancellationToken + ); + Assert.NotNull(response); + + var contents = response.Messages[0].Contents; + + // Verify WebSearchToolCallContent + var wsCall = Assert.IsType(contents[0]); + Assert.Equal("srvtoolu_ws_01", wsCall.CallId); + Assert.NotNull(wsCall.Queries); + Assert.Single(wsCall.Queries); + Assert.Equal("latest AI news 2026", wsCall.Queries[0]); + + // Verify WebSearchToolResultContent + var wsResult = Assert.IsType(contents[1]); + Assert.Equal("srvtoolu_ws_01", wsResult.CallId); + Assert.NotNull(wsResult.Results); + Assert.Equal(2, wsResult.Results.Count); + + var firstResult = Assert.IsType(wsResult.Results[0]); + Assert.Equal(new Uri("https://example.com/ai-news"), firstResult.Uri); + + var secondResult = Assert.IsType(wsResult.Results[1]); + Assert.Equal(new Uri("https://example.com/ai-research"), secondResult.Uri); + + // Verify text content + var text = Assert.IsType(contents[2]); + Assert.Equal("Here are some recent AI news articles.", text.Text); + } + + [Fact] + public async Task GetResponseAsync_ServerToolUseBlock_CodeExecution_MapsToCodeInterpreterToolCallContent() + { + VerbatimHttpHandler handler = new( + expectedRequest: """ + { + "model": "claude-haiku-4-5", + "messages": [{ + "role": "user", + "content": [{ + "type": "text", + "text": "Compute 2**10" + }] + }], + "max_tokens": 1024, + "tools": [{ + "type": "code_execution_20250825", + "name": "code_execution" + }] + } + """, + actualResponse: """ + { + "id": "msg_ce_01", + "type": "message", + "role": "assistant", + "model": "claude-haiku-4-5", + "content": [ + { + "type": "server_tool_use", + "id": "srvtoolu_ce_01", + "name": "code_execution", + "caller": { "type": "direct" }, + "input": { "code": "print(2**10)" } + }, + { + "type": "code_execution_tool_result", + "tool_use_id": "srvtoolu_ce_01", + "content": { + "type": "code_execution_result", + "content": [], + "stdout": "1024\n", + "stderr": "", + "return_code": 0 + } + }, + { + "type": "text", + "text": "The result is 1024." + } + ], + "stop_reason": "end_turn", + "usage": { + "input_tokens": 25, + "output_tokens": 20 + } + } + """ + ); + + IChatClient chatClient = CreateChatClient(handler, "claude-haiku-4-5"); + + ChatResponse response = await chatClient.GetResponseAsync( + "Compute 2**10", + new() { Tools = [new HostedCodeInterpreterTool()] }, + TestContext.Current.CancellationToken + ); + Assert.NotNull(response); + + var contents = response.Messages[0].Contents; + + // Verify CodeInterpreterToolCallContent + var ciCall = Assert.IsType(contents[0]); + Assert.Equal("srvtoolu_ce_01", ciCall.CallId); + Assert.NotNull(ciCall.Inputs); + Assert.Single(ciCall.Inputs); + var codeInput = Assert.IsType(ciCall.Inputs[0]); + Assert.Equal("text/x-python", codeInput.MediaType); + Assert.Equal("print(2**10)", Encoding.UTF8.GetString(codeInput.Data.ToArray())); + + // Verify CodeInterpreterToolResultContent + var ciResult = Assert.IsType(contents[1]); + Assert.Equal("srvtoolu_ce_01", ciResult.CallId); + Assert.NotNull(ciResult.Outputs); + var stdoutOutput = Assert.IsType(ciResult.Outputs[0]); + Assert.Equal("1024\n", stdoutOutput.Text); + + // Verify text content + var text = Assert.IsType(contents[2]); + Assert.Equal("The result is 1024.", text.Text); + } + + [Fact] + public async Task GetResponseAsync_WebSearchToolResult_WithError_MapsToErrorContent() + { + VerbatimHttpHandler handler = new( + expectedRequest: """ + { + "model": "claude-haiku-4-5", + "messages": [{ + "role": "user", + "content": [{ + "type": "text", + "text": "Search for something" + }] + }], + "max_tokens": 1024, + "tools": [{ + "name": "web_search", + "type": "web_search_20250305" + }] + } + """, + actualResponse: """ + { + "id": "msg_ws_err_01", + "type": "message", + "role": "assistant", + "model": "claude-haiku-4-5", + "content": [ + { + "type": "server_tool_use", + "id": "srvtoolu_ws_err_01", + "name": "web_search", + "caller": { "type": "direct" }, + "input": { "query": "test query" } + }, + { + "type": "web_search_tool_result", + "tool_use_id": "srvtoolu_ws_err_01", + "caller": { "type": "direct" }, + "content": { + "type": "web_search_tool_result_error", + "error_code": "max_uses_exceeded" + } + }, + { + "type": "text", + "text": "Search encountered an error." + } + ], + "stop_reason": "end_turn", + "usage": { + "input_tokens": 20, + "output_tokens": 15 + } + } + """ + ); + + IChatClient chatClient = CreateChatClient(handler, "claude-haiku-4-5"); + + ChatResponse response = await chatClient.GetResponseAsync( + "Search for something", + new() { Tools = [new HostedWebSearchTool()] }, + TestContext.Current.CancellationToken + ); + Assert.NotNull(response); + + var contents = response.Messages[0].Contents; + + var wsResult = Assert.IsType(contents[1]); + Assert.Equal("srvtoolu_ws_err_01", wsResult.CallId); + Assert.NotNull(wsResult.Results); + Assert.Single(wsResult.Results); + var errorResult = Assert.IsType(wsResult.Results[0]); + Assert.Equal("MaxUsesExceeded", errorResult.ErrorCode); + } + + [Fact] + public async Task GetResponseAsync_FinishReasonNullHandling() + { + VerbatimHttpHandler handler = new( + expectedRequest: """ + { + "max_tokens": 1024, + "model": "claude-haiku-4-5", + "messages": [{ + "role": "user", + "content": [{ + "type": "text", + "text": "Test" + }] + }] + } + """, + actualResponse: """ + { + "id": "msg_no_finish_01", + "type": "message", + "role": "assistant", + "model": "claude-haiku-4-5", + "content": [{ + "type": "text", + "text": "Response" + }], + "stop_reason": null, + "usage": { + "input_tokens": 10, + "output_tokens": 5 + } + } + """ + ); + + IChatClient chatClient = CreateChatClient(handler, "claude-haiku-4-5"); + + ChatResponse response = await chatClient.GetResponseAsync( + "Test", + new(), + TestContext.Current.CancellationToken + ); + Assert.NotNull(response); + + Assert.Null(response.FinishReason); + } + + [Fact] + public async Task GetStreamingResponseAsync_AccumulatesUsageFromMultipleMessageStartEvents() + { + VerbatimHttpHandler handler = new( + expectedRequest: """ + { + "max_tokens": 1024, + "model": "claude-haiku-4-5", + "messages": [{ + "role": "user", + "content": [{ + "type": "text", + "text": "Test multiple message starts" + }] + }], + "stream": true + } + """, + actualResponse: """ + event: message_start + data: {"type":"message_start","message":{"id":"msg_multi_start_01","type":"message","role":"assistant","model":"claude-haiku-4-5","content":[],"stop_reason":null,"stop_sequence":null,"usage":{"input_tokens":10,"output_tokens":0}}} + + event: message_start + data: {"type":"message_start","message":{"id":"msg_multi_start_01","type":"message","role":"assistant","model":"claude-haiku-4-5","content":[],"stop_reason":null,"stop_sequence":null,"usage":{"input_tokens":5,"output_tokens":0}}} + + event: content_block_start + data: {"type":"content_block_start","index":0,"content_block":{"type":"text","text":""}} + + event: content_block_delta + data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":"Response"}} + + event: content_block_stop + data: {"type":"content_block_stop","index":0} + + event: message_delta + data: {"type":"message_delta","delta":{"stop_reason":"end_turn","stop_sequence":null},"usage":{"input_tokens":15,"output_tokens":2}} + + event: message_stop + data: {"type":"message_stop"} + + """ + ); + + IChatClient chatClient = CreateChatClient(handler, "claude-haiku-4-5"); + + List updates = []; + await foreach ( + var update in chatClient.GetStreamingResponseAsync( + "Test multiple message starts", + new(), + TestContext.Current.CancellationToken + ) + ) + { + updates.Add(update); + } + + Assert.NotEmpty(updates); + var usageUpdates = updates.Where(u => u.Contents.Any(c => c is UsageContent)).ToList(); + Assert.NotEmpty(usageUpdates); + + var usageContent = usageUpdates + .SelectMany(u => u.Contents.OfType()) + .FirstOrDefault(); + Assert.NotNull(usageContent); + + Assert.Equal(15, usageContent.Details.InputTokenCount); + Assert.Equal(2, usageContent.Details.OutputTokenCount); + } + + [Fact] + public async Task GetStreamingResponseAsync_UsageFromDeltaOverridesStartEvent() + { + VerbatimHttpHandler handler = new( + expectedRequest: """ + { + "max_tokens": 1024, + "model": "claude-haiku-4-5", + "messages": [{ + "role": "user", + "content": [{ + "type": "text", + "text": "hello" + }] + }], + "stream": true + } + """, + actualResponse: """ + event: message_start + data: {"type":"message_start","message":{"id":"msg_01","type":"message","role":"assistant","model":"claude-haiku-4-5","content":[],"stop_reason":null,"stop_sequence":null,"usage":{"input_tokens":8,"cache_creation_input_tokens":0,"cache_read_input_tokens":0,"output_tokens":8}}} + + event: content_block_start + data: {"type":"content_block_start","index":0,"content_block":{"type":"text","text":""}} + + event: content_block_delta + data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":"Hello!"}} + + event: content_block_stop + data: {"type":"content_block_stop","index":0} + + event: message_delta + data: {"type":"message_delta","delta":{"stop_reason":"end_turn","stop_sequence":null},"usage":{"input_tokens":8,"cache_creation_input_tokens":0,"cache_read_input_tokens":0,"output_tokens":12}} + + event: message_stop + data: {"type":"message_stop"} + + """ + ); + + IChatClient chatClient = CreateChatClient(handler, "claude-haiku-4-5"); + + List updates = []; + await foreach ( + var update in chatClient.GetStreamingResponseAsync( + "hello", + new(), + TestContext.Current.CancellationToken + ) + ) + { + updates.Add(update); + } + + Assert.NotEmpty(updates); + var usageUpdates = updates.Where(u => u.Contents.Any(c => c is UsageContent)).ToList(); + Assert.NotEmpty(usageUpdates); + + var usageContent = usageUpdates + .SelectMany(u => u.Contents.OfType()) + .FirstOrDefault(); + Assert.NotNull(usageContent); + + Assert.Equal(8, usageContent.Details.InputTokenCount); + Assert.Equal(12, usageContent.Details.OutputTokenCount); + Assert.Equal(20, usageContent.Details.TotalTokenCount); + } + + [Fact] + public async Task GetResponseAsync_FunctionResult_WithSingleTextContent() + { + VerbatimHttpHandler handler = new( + expectedRequest: """ + { + "max_tokens": 1024, + "model": "claude-haiku-4-5", + "messages": [ + { + "role": "user", + "content": [{ + "type": "text", + "text": "What's the weather in Seattle?" + }] + }, + { + "role": "assistant", + "content": [{ + "type": "tool_use", + "id": "toolu_012ji4C9Dx9qiGwDPfWSjRVC", + "name": "get_weather", + "input": { + "location": "Seattle" + } + }] + }, + { + "role": "user", + "content": [{ + "type": "tool_result", + "tool_use_id": "toolu_012ji4C9Dx9qiGwDPfWSjRVC", + "is_error": false, + "content": [{ + "type": "text", + "text": "The weather in Seattle is sunny and 72�F" + }] + }] + } + ] + } + """, + actualResponse: """ + { + "id": "msg_01DzfU3ta5z9nrJo6EGamXqV", + "type": "message", + "role": "assistant", + "model": "claude-haiku-4-5-20251001", + "content": [{ + "type": "text", + "text": "The weather in Seattle is currently **sunny** with a temperature of **72�F** (about 22�C). Great weather for outdoor activities!" + }], + "stop_reason": "end_turn", + "usage": { + "input_tokens": 91, + "output_tokens": 34 + } + } + """ + ); + + IChatClient chatClient = CreateChatClient(handler, "claude-haiku-4-5"); + + List messages = + [ + new(ChatRole.User, "What's the weather in Seattle?"), + new( + ChatRole.Assistant, + [ + new FunctionCallContent( + "toolu_012ji4C9Dx9qiGwDPfWSjRVC", + "get_weather", + new Dictionary { ["location"] = "Seattle" } + ), + ] + ), + new( + ChatRole.User, + [ + new FunctionResultContent( + "toolu_012ji4C9Dx9qiGwDPfWSjRVC", + new TextContent("The weather in Seattle is sunny and 72�F") + ), + ] + ), + ]; + + ChatResponse response = await chatClient.GetResponseAsync( + messages, + new(), + TestContext.Current.CancellationToken + ); + + Assert.NotNull(response); + TextContent textContent = Assert.IsType(response.Messages[0].Contents[0]); + Assert.Contains("sunny", textContent.Text, StringComparison.OrdinalIgnoreCase); + Assert.Contains("72", textContent.Text); + } + + [Fact] + public async Task GetResponseAsync_FunctionResult_WithMultipleTextContents() + { + VerbatimHttpHandler handler = new( + expectedRequest: """ + { + "max_tokens": 1024, + "model": "claude-haiku-4-5", + "messages": [ + { + "role": "user", + "content": [{ + "type": "text", + "text": "Get me news about AI" + }] + }, + { + "role": "assistant", + "content": [{ + "type": "tool_use", + "id": "toolu_01TQkFntpAPUXLijpPu5Q1dT", + "name": "get_news", + "input": { + "topic": "AI" + } + }] + }, + { + "role": "user", + "content": [{ + "type": "tool_result", + "tool_use_id": "toolu_01TQkFntpAPUXLijpPu5Q1dT", + "is_error": false, + "content": [ + { + "type": "text", + "text": "Breaking: AI advances" + }, + { + "type": "text", + "text": "Research shows improvements" + }, + { + "type": "text", + "text": "Industry adoption grows" + } + ] + }] + } + ] + } + """, + actualResponse: """ + { + "id": "msg_01G8oMUpScZWsMe5JsNuLkgJ", + "type": "message", + "role": "assistant", + "model": "claude-haiku-4-5-20251001", + "content": [{ + "type": "text", + "text": "Here's the latest AI news:\\n\\n**Breaking: AI Advances**\\n- Researchers are demonstrating significant improvements in AI capabilities across various domains\\n\\n**Research Shows Improvements**\\n- Ongoing studies continue to push the boundaries of what AI systems can accomplish\\n\\n**Industry Adoption Grows**\\n- Companies across sectors are increasingly implementing AI solutions into their operations" + }], + "stop_reason": "end_turn", + "usage": { + "input_tokens": 95, + "output_tokens": 100 + } + } + """ + ); + + IChatClient chatClient = CreateChatClient(handler, "claude-haiku-4-5"); + + List messages = + [ + new(ChatRole.User, "Get me news about AI"), + new( + ChatRole.Assistant, + [ + new FunctionCallContent( + "toolu_01TQkFntpAPUXLijpPu5Q1dT", + "get_news", + new Dictionary { ["topic"] = "AI" } + ), + ] + ), + new( + ChatRole.User, + [ + new FunctionResultContent( + "toolu_01TQkFntpAPUXLijpPu5Q1dT", + new AIContent[] + { + new TextContent("Breaking: AI advances"), + new TextContent("Research shows improvements"), + new TextContent("Industry adoption grows"), + } + ), + ] + ), + ]; + + ChatResponse response = await chatClient.GetResponseAsync( + messages, + new(), + TestContext.Current.CancellationToken + ); + + Assert.NotNull(response); + TextContent textContent = Assert.IsType(response.Messages[0].Contents[0]); + Assert.Contains("AI", textContent.Text); + Assert.Contains("advances", textContent.Text, StringComparison.OrdinalIgnoreCase); + } + + [Fact] + public async Task GetResponseAsync_FunctionResult_WithImageDataContent() + { + VerbatimHttpHandler handler = new( + expectedRequest: """ + { + "max_tokens": 1024, + "model": "claude-haiku-4-5", + "messages": [ + { + "role": "user", + "content": [{ + "type": "text", + "text": "Generate a bar chart" + }] + }, + { + "role": "assistant", + "content": [{ + "type": "tool_use", + "id": "toolu_01RFvjHBAxq1z9kgH7vtVioW", + "name": "generate_chart", + "input": { + "type": "bar" + } + }] + }, + { + "role": "user", + "content": [{ + "type": "tool_result", + "tool_use_id": "toolu_01RFvjHBAxq1z9kgH7vtVioW", + "is_error": false, + "content": [{ + "type": "image", + "source": { + "type": "base64", + "media_type": "image/png", + "data": "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg==" + } + }] + }] + } + ] + } + """, + actualResponse: """ + { + "id": "msg_01JVBwA4cipSnmopX4ywyZ36", + "type": "message", + "role": "assistant", + "model": "claude-haiku-4-5-20251001", + "content": [{ + "type": "text", + "text": "I've generated a simple bar chart for you! \\n\\nSince you didn't specify particular data, here's a basic example. If you'd like me to create a bar chart with specific data, categories, or a particular theme, please let me know." + }], + "stop_reason": "end_turn", + "usage": { + "input_tokens": 92, + "output_tokens": 50 + } + } + """ + ); + + IChatClient chatClient = CreateChatClient(handler, "claude-haiku-4-5"); + + byte[] pngData = Convert.FromBase64String( + "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg==" + ); + + List messages = + [ + new(ChatRole.User, "Generate a bar chart"), + new( + ChatRole.Assistant, + [ + new FunctionCallContent( + "toolu_01RFvjHBAxq1z9kgH7vtVioW", + "generate_chart", + new Dictionary { ["type"] = "bar" } + ), + ] + ), + new( + ChatRole.User, + [ + new FunctionResultContent( + "toolu_01RFvjHBAxq1z9kgH7vtVioW", + new DataContent(pngData, "image/png") + ), + ] + ), + ]; + + ChatResponse response = await chatClient.GetResponseAsync( + messages, + new(), + TestContext.Current.CancellationToken + ); + + Assert.NotNull(response); + TextContent textContent = Assert.IsType(response.Messages[0].Contents[0]); + Assert.Contains("chart", textContent.Text, StringComparison.OrdinalIgnoreCase); + } + + [Fact] + public async Task GetResponseAsync_FunctionResult_WithPdfDataContent() + { + VerbatimHttpHandler handler = new( + expectedRequest: """ + { + "max_tokens": 1024, + "model": "claude-haiku-4-5", + "messages": [ + { + "role": "user", + "content": [{ + "type": "text", + "text": "Generate a sales report" + }] + }, + { + "role": "assistant", + "content": [{ + "type": "tool_use", + "id": "toolu_01Xp2XKeM6KcpCrGKbh96biN", + "name": "generate_report", + "input": { + "type": "sales" + } + }] + }, + { + "role": "user", + "content": [{ + "type": "tool_result", + "tool_use_id": "toolu_01Xp2XKeM6KcpCrGKbh96biN", + "is_error": false, + "content": [{ + "type": "document", + "source": { + "type": "base64", + "media_type": "application/pdf", + "data": "JVBERi0xLjQKMSAwIG9iajw8L1R5cGUvQ2F0YWxvZy9QYWdlcyAyIDAgUj4+ZW5kb2JqIDIgMCBvYmo8PC9UeXBlL1BhZ2VzL0tpZHNbMyAwIFJdL0NvdW50IDE+PmVuZG9iaiAzIDAgb2JqPDwvVHlwZS9QYWdlL01lZGlhQm94WzAgMCA2MTIgNzkyXS9QYXJlbnQgMiAwIFIvUmVzb3VyY2VzPDw+Pj4+ZW5kb2JqCnhyZWYKMCA0CjAwMDAwMDAwMDAgNjU1MzUgZgowMDAwMDAwMDA5IDAwMDAwIG4KMDAwMDAwMDA1MiAwMDAwMCBuCjAwMDAwMDAxMDEgMDAwMDAgbgp0cmFpbGVyPDwvU2l6ZSA0L1Jvb3QgMSAwIFI+PgpzdGFydHhyZWYKMTc4CiUlRU9G" + } + }] + }] + } + ] + } + """, + actualResponse: """ + { + "id": "msg_01WhQmBXmH4zHd1fB2VYGRWW", + "type": "message", + "role": "assistant", + "model": "claude-haiku-4-5-20251001", + "content": [{ + "type": "text", + "text": "I attempted to generate a sales report, but the generated document appears to be blank. Let me provide you with a sample **Sales Report** instead with key metrics and insights." + }], + "stop_reason": "end_turn", + "usage": { + "input_tokens": 1653, + "output_tokens": 50 + } + } + """ + ); + + IChatClient chatClient = CreateChatClient(handler, "claude-haiku-4-5"); + + string pdfContent = + "%PDF-1.4\n1 0 obj<>endobj 2 0 obj<>endobj 3 0 obj<>>>endobj\nxref\n0 4\n0000000000 65535 f\n0000000009 00000 n\n0000000052 00000 n\n0000000101 00000 n\ntrailer<>\nstartxref\n178\n%%EOF"; + byte[] pdfData = Encoding.UTF8.GetBytes(pdfContent); + + List messages = + [ + new(ChatRole.User, "Generate a sales report"), + new( + ChatRole.Assistant, + [ + new FunctionCallContent( + "toolu_01Xp2XKeM6KcpCrGKbh96biN", + "generate_report", + new Dictionary { ["type"] = "sales" } + ), + ] + ), + new( + ChatRole.User, + [ + new FunctionResultContent( + "toolu_01Xp2XKeM6KcpCrGKbh96biN", + new DataContent(pdfData, "application/pdf") + ), + ] + ), + ]; + + ChatResponse response = await chatClient.GetResponseAsync( + messages, + new(), + TestContext.Current.CancellationToken + ); + + Assert.NotNull(response); + TextContent textContent = Assert.IsType(response.Messages[0].Contents[0]); + Assert.Contains("report", textContent.Text, StringComparison.OrdinalIgnoreCase); + } + + [Fact] + public async Task GetResponseAsync_FunctionResult_WithTextPlainDataContent() + { + VerbatimHttpHandler handler = new( + expectedRequest: """ + { + "max_tokens": 1024, + "model": "claude-haiku-4-5", + "messages": [ + { + "role": "user", + "content": [{ + "type": "text", + "text": "Get the system logs" + }] + }, + { + "role": "assistant", + "content": [ + { + "type": "text", + "text": "I'll retrieve the system logs for you." + }, + { + "type": "tool_use", + "id": "toolu_01JqNHMtbwFQExUwDMWy3wHe", + "name": "get_logs", + "input": { + "type": "system" + } + } + ] + }, + { + "role": "user", + "content": [{ + "type": "tool_result", + "tool_use_id": "toolu_01JqNHMtbwFQExUwDMWy3wHe", + "is_error": false, + "content": [{ + "type": "document", + "source": { + "type": "text", + "media_type": "text/plain", + "data": "Log Entry 1: System started\nLog Entry 2: Processing data\nLog Entry 3: Task completed" + } + }] + }] + } + ] + } + """, + actualResponse: """ + { + "id": "msg_01RxuuTbpsvFyNpim6uoXujV", + "type": "message", + "role": "assistant", + "model": "claude-haiku-4-5-20251001", + "content": [{ + "type": "text", + "text": "Here are the system logs:\\n\\n**System Logs:**\\n1. System started\\n2. Processing data\\n3. Task completed\\n\\nThese are the current entries in the system log." + }], + "stop_reason": "end_turn", + "usage": { + "input_tokens": 148, + "output_tokens": 50 + } + } + """ + ); + + IChatClient chatClient = CreateChatClient(handler, "claude-haiku-4-5"); + + string logContent = + "Log Entry 1: System started\nLog Entry 2: Processing data\nLog Entry 3: Task completed"; + byte[] logData = Encoding.UTF8.GetBytes(logContent); + + List messages = + [ + new(ChatRole.User, "Get the system logs"), + new( + ChatRole.Assistant, + [ + new TextContent("I'll retrieve the system logs for you."), + new FunctionCallContent( + "toolu_01JqNHMtbwFQExUwDMWy3wHe", + "get_logs", + new Dictionary { ["type"] = "system" } + ), + ] + ), + new( + ChatRole.User, + [ + new FunctionResultContent( + "toolu_01JqNHMtbwFQExUwDMWy3wHe", + new DataContent(logData, "text/plain") + ), + ] + ), + ]; + + ChatResponse response = await chatClient.GetResponseAsync( + messages, + new(), + TestContext.Current.CancellationToken + ); + + Assert.NotNull(response); + TextContent textContent = Assert.IsType(response.Messages[0].Contents[0]); + Assert.Contains("System started", textContent.Text); + Assert.Contains("Task completed", textContent.Text); + } + + [Fact] + public async Task GetResponseAsync_FunctionResult_WithMixedContent() + { + VerbatimHttpHandler handler = new( + expectedRequest: """ + { + "max_tokens": 1024, + "model": "claude-haiku-4-5", + "messages": [ + { + "role": "user", + "content": [{ + "type": "text", + "text": "Analyze the sales data" + }] + }, + { + "role": "assistant", + "content": [{ + "type": "tool_use", + "id": "toolu_01ABC123", + "name": "analyze_data", + "input": { + "dataset": "sales" + } + }] + }, + { + "role": "user", + "content": [{ + "type": "tool_result", + "tool_use_id": "toolu_01ABC123", + "is_error": false, + "content": [ + { + "type": "text", + "text": "Analysis: Mean=42.5, Median=40" + }, + { + "type": "image", + "source": { + "type": "base64", + "media_type": "image/png", + "data": "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg==" + } + } + ] + }] + } + ] + } + """, + actualResponse: """ + { + "id": "msg_01MixedContent", + "type": "message", + "role": "assistant", + "model": "claude-haiku-4-5-20251001", + "content": [{ + "type": "text", + "text": "Based on the analysis, your sales data shows a mean of 42.5 and median of 40. The chart visualization helps illustrate the distribution." + }], + "stop_reason": "end_turn", + "usage": { + "input_tokens": 120, + "output_tokens": 35 + } + } + """ + ); + + IChatClient chatClient = CreateChatClient(handler, "claude-haiku-4-5"); + + byte[] chartData = Convert.FromBase64String( + "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg==" + ); + + List messages = + [ + new(ChatRole.User, "Analyze the sales data"), + new( + ChatRole.Assistant, + [ + new FunctionCallContent( + "toolu_01ABC123", + "analyze_data", + new Dictionary { ["dataset"] = "sales" } + ), + ] + ), + new( + ChatRole.User, + [ + new FunctionResultContent( + "toolu_01ABC123", + new AIContent[] + { + new TextContent("Analysis: Mean=42.5, Median=40"), + new DataContent(chartData, "image/png"), + } + ), + ] + ), + ]; + + ChatResponse response = await chatClient.GetResponseAsync( + messages, + new(), + TestContext.Current.CancellationToken + ); + + Assert.NotNull(response); + TextContent textContent = Assert.IsType(response.Messages[0].Contents[0]); + Assert.Contains("42.5", textContent.Text); + Assert.Contains("40", textContent.Text); + } + + [Fact] + public async Task GetResponseAsync_WithFunctionResultContent_UriContent_Image() + { + VerbatimHttpHandler handler = new( + expectedRequest: """ + { + "max_tokens": 1024, + "model": "claude-haiku-4-5", + "messages": [ + { + "role": "user", + "content": [{ + "type": "text", + "text": "Get image URL" + }] + }, + { + "role": "assistant", + "content": [{ + "type": "tool_use", + "id": "tool_uri_img", + "name": "url_tool", + "input": {} + }] + }, + { + "role": "user", + "content": [{ + "type": "tool_result", + "tool_use_id": "tool_uri_img", + "is_error": false, + "content": [{ + "type": "image", + "source": { + "type": "url", + "url": "https://example.com/image.png" + } + }] + }] + } + ] + } + """, + actualResponse: """ + { + "id": "msg_uri_img_01", + "type": "message", + "role": "assistant", + "model": "claude-haiku-4-5", + "content": [{ + "type": "text", + "text": "Image URL received" + }], + "stop_reason": "end_turn", + "usage": { + "input_tokens": 32, + "output_tokens": 8 + } + } + """ + ); + + IChatClient chatClient = CreateChatClient(handler, "claude-haiku-4-5"); + + List messages = + [ + new ChatMessage(ChatRole.User, "Get image URL"), + new ChatMessage( + ChatRole.Assistant, + [ + new FunctionCallContent( + "tool_uri_img", + "url_tool", + new Dictionary() + ), + ] + ), + new ChatMessage( + ChatRole.User, + [ + new FunctionResultContent( + "tool_uri_img", + new UriContent(new Uri("https://example.com/image.png"), "image/png") + ), + ] + ), + ]; + + ChatResponse response = await chatClient.GetResponseAsync( + messages, + new(), + TestContext.Current.CancellationToken + ); + Assert.NotNull(response); + } + + [Fact] + public async Task GetResponseAsync_WithFunctionResultContent_UriContent_PDF() + { + VerbatimHttpHandler handler = new( + expectedRequest: """ + { + "max_tokens": 1024, + "model": "claude-haiku-4-5", + "messages": [ + { + "role": "user", + "content": [{ + "type": "text", + "text": "Get PDF URL" + }] + }, + { + "role": "assistant", + "content": [{ + "type": "tool_use", + "id": "tool_uri_pdf", + "name": "pdf_url_tool", + "input": {} + }] + }, + { + "role": "user", + "content": [{ + "type": "tool_result", + "tool_use_id": "tool_uri_pdf", + "is_error": false, + "content": [{ + "type": "document", + "source": { + "type": "url", + "url": "https://example.com/document.pdf" + } + }] + }] + } + ] + } + """, + actualResponse: """ + { + "id": "msg_uri_pdf_01", + "type": "message", + "role": "assistant", + "model": "claude-haiku-4-5", + "content": [{ + "type": "text", + "text": "PDF URL received" + }], + "stop_reason": "end_turn", + "usage": { + "input_tokens": 35, + "output_tokens": 9 + } + } + """ + ); + + IChatClient chatClient = CreateChatClient(handler, "claude-haiku-4-5"); + + List messages = + [ + new ChatMessage(ChatRole.User, "Get PDF URL"), + new ChatMessage( + ChatRole.Assistant, + [ + new FunctionCallContent( + "tool_uri_pdf", + "pdf_url_tool", + new Dictionary() + ), + ] + ), + new ChatMessage( + ChatRole.User, + [ + new FunctionResultContent( + "tool_uri_pdf", + new UriContent( + new Uri("https://example.com/document.pdf"), + "application/pdf" + ) + ), + ] + ), + ]; + + ChatResponse response = await chatClient.GetResponseAsync( + messages, + new(), + TestContext.Current.CancellationToken + ); + Assert.NotNull(response); + } + + [Fact] + public async Task GetResponseAsync_WithSimpleResponseFormat_ReturnsStructuredJSON() + { + VerbatimHttpHandler handler = new( + expectedRequest: """ + { + "max_tokens": 1024, + "model": "claude-sonnet-4-5-20250929", + "messages": [{ + "role": "user", + "content": [{ + "type": "text", + "text": "Tell me about Albert Einstein. Respond with his name and age at death." + }] + }], + "output_config": { + "format": { + "type": "json_schema", + "schema": { + "type": "object", + "properties": { + "name": { "type": "string" }, + "age": { "type": "integer" } + }, + "required": ["name", "age"], + "additionalProperties": false + } + } + } + } + """, + actualResponse: """ + { + "id": "msg_format_01", + "type": "message", + "role": "assistant", + "model": "claude-sonnet-4-5-20250929", + "content": [{ + "type": "text", + "text": "{\"name\":\"Albert Einstein\",\"age\":76}" + }], + "stop_reason": "end_turn", + "usage": { + "input_tokens": 25, + "output_tokens": 15 + } + } + """ + ); + + IChatClient chatClient = CreateChatClient(handler, "claude-sonnet-4-5-20250929"); + + ChatOptions options = new() + { + ResponseFormat = ChatResponseFormat.ForJsonSchema( + JsonElement.Parse( + """ + { + "type": "object", + "properties": { + "name": { "type": "string" }, + "age": { "type": "integer" } + }, + "required": ["name", "age"] + } + """ + ), + "person_info" + ), + }; + + ChatResponse response = await chatClient.GetResponseAsync( + "Tell me about Albert Einstein. Respond with his name and age at death.", + options, + TestContext.Current.CancellationToken + ); + + Assert.NotNull(response); + TextContent textContent = Assert.IsType(response.Messages[0].Contents[0]); + Assert.Contains("Einstein", textContent.Text); + Assert.Contains("76", textContent.Text); + } + + [Fact] + public async Task GetResponseAsync_WithNestedObjectSchema_ReturnsStructuredJSON() + { + VerbatimHttpHandler handler = new( + expectedRequest: """ + { + "max_tokens": 1024, + "model": "claude-sonnet-4-5-20250929", + "messages": [{ + "role": "user", + "content": [{ + "type": "text", + "text": "Tell me about the book '1984' by George Orwell." + }] + }], + "output_config": { + "format": { + "type": "json_schema", + "schema": { + "type": "object", + "properties": { + "title": { "type": "string" }, + "author": { + "type": "object", + "properties": { + "name": { "type": "string" }, + "birth_year": { "type": "integer" } + }, + "required": ["name", "birth_year"], + "additionalProperties": false + }, + "published_year": { + "type": "integer" + } + }, + "required": ["title", "author", "published_year"], + "additionalProperties": false + } + } + } + } + """, + actualResponse: """ + { + "id": "msg_format_02", + "type": "message", + "role": "assistant", + "model": "claude-sonnet-4-5-20250929", + "content": [{ + "type": "text", + "text": "{\"title\":\"1984\",\"author\":{\"name\":\"George Orwell\",\"birth_year\":1903},\"published_year\":1949}" + }], + "stop_reason": "end_turn", + "usage": { + "input_tokens": 30, + "output_tokens": 25 + } + } + """ + ); + + IChatClient chatClient = CreateChatClient(handler, "claude-sonnet-4-5-20250929"); + + ChatOptions options = new() + { + ResponseFormat = ChatResponseFormat.ForJsonSchema( + JsonElement.Parse( + """ + { + "type": "object", + "properties": { + "title": { "type": "string" }, + "author": { + "type": "object", + "properties": { + "name": { "type": "string" }, + "birth_year": { "type": "integer" } + }, + "required": ["name", "birth_year"] + }, + "published_year": { "type": "integer" } + }, + "required": ["title", "author", "published_year"] + } + """ + ), + "book_info" + ), + }; + + ChatResponse response = await chatClient.GetResponseAsync( + "Tell me about the book '1984' by George Orwell.", + options, + TestContext.Current.CancellationToken + ); + + Assert.NotNull(response); + TextContent textContent = Assert.IsType(response.Messages[0].Contents[0]); + Assert.Contains("1984", textContent.Text); + Assert.Contains("Orwell", textContent.Text); + Assert.Contains("1903", textContent.Text); + Assert.Contains("1949", textContent.Text); + } + + [Fact] + public async Task GetResponseAsync_WithArraySchema_ReturnsStructuredJSON() + { + VerbatimHttpHandler handler = new( + expectedRequest: """ + { + "max_tokens": 1024, + "model": "claude-sonnet-4-5-20250929", + "messages": [{ + "role": "user", + "content": [{ + "type": "text", + "text": "List 3 common fruits: apple, orange, and banana." + }] + }], + "output_config": { + "format": { + "type": "json_schema", + "schema": { + "type": "object", + "properties": { + "fruits": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { "type": "string" }, + "color": { "type": "string" }, + "is_citrus": { "type": "boolean" } + }, + "required": ["name", "color", "is_citrus"], + "additionalProperties": false + } + } + }, + "required": ["fruits"], + "additionalProperties": false + } + } + } + } + """, + actualResponse: """ + { + "id": "msg_format_03", + "type": "message", + "role": "assistant", + "model": "claude-sonnet-4-5-20250929", + "content": [{ + "type": "text", + "text": "{\"fruits\":[{\"name\":\"apple\",\"color\":\"red\",\"is_citrus\":false},{\"name\":\"orange\",\"color\":\"orange\",\"is_citrus\":true},{\"name\":\"banana\",\"color\":\"yellow\",\"is_citrus\":false}]}" + }], + "stop_reason": "end_turn", + "usage": { + "input_tokens": 35, + "output_tokens": 40 + } + } + """ + ); + + IChatClient chatClient = CreateChatClient(handler, "claude-sonnet-4-5-20250929"); + + ChatOptions options = new() + { + ResponseFormat = ChatResponseFormat.ForJsonSchema( + JsonElement.Parse( + """ + { + "type": "object", + "properties": { + "fruits": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { "type": "string" }, + "color": { "type": "string" }, + "is_citrus": { "type": "boolean" } + }, + "required": ["name", "color", "is_citrus"] + } + } + }, + "required": ["fruits"] + } + """ + ), + "fruit_list" + ), + }; + + ChatResponse response = await chatClient.GetResponseAsync( + "List 3 common fruits: apple, orange, and banana.", + options, + TestContext.Current.CancellationToken + ); + + Assert.NotNull(response); + TextContent textContent = Assert.IsType(response.Messages[0].Contents[0]); + Assert.Contains("apple", textContent.Text); + Assert.Contains("orange", textContent.Text); + Assert.Contains("banana", textContent.Text); + } + + [Fact] + public async Task GetResponseAsync_WithHostedCodeInterpreterTool() + { + VerbatimHttpHandler handler = new( + expectedRequest: """ + { + "max_tokens": 1024, + "model": "claude-haiku-4-5", + "messages": [{ + "role": "user", + "content": [{ + "type": "text", + "text": "Execute code" + }] + }], + "tools": [{ + "type": "code_execution_20250825", + "name": "code_execution" + }] + } + """, + actualResponse: """ + { + "id": "msg_code_exec_01", + "type": "message", + "role": "assistant", + "model": "claude-haiku-4-5", + "content": [{ + "type": "text", + "text": "I can execute code." + }], + "stop_reason": "end_turn", + "usage": { + "input_tokens": 15, + "output_tokens": 6 + } + } + """ + ); + + IChatClient chatClient = CreateChatClient(handler, "claude-haiku-4-5"); + + ChatOptions options = new() { Tools = [new HostedCodeInterpreterTool()] }; + + ChatResponse response = await chatClient.GetResponseAsync( + "Execute code", + options, + TestContext.Current.CancellationToken + ); + Assert.NotNull(response); + } + + [Fact] + public async Task GetResponseAsync_CodeExecutionToolResult_WithError() + { + VerbatimHttpHandler handler = new( + expectedRequest: """ + { + "max_tokens": 1024, + "model": "claude-haiku-4-5", + "messages": [{ + "role": "user", + "content": [{ + "type": "text", + "text": "Test code execution error" + }] + }] + } + """, + actualResponse: """ + { + "id": "msg_code_error_01", + "type": "message", + "role": "assistant", + "model": "claude-haiku-4-5", + "content": [{ + "type": "code_execution_tool_result", + "tool_use_id": "code_exec_error_1", + "content": { + "type": "code_execution_tool_result_error", + "error_code": "execution_time_exceeded" + } + }], + "stop_reason": "end_turn", + "usage": { + "input_tokens": 10, + "output_tokens": 5 + } + } + """ + ); + + IChatClient chatClient = CreateChatClient(handler, "claude-haiku-4-5"); + ChatResponse response = await chatClient.GetResponseAsync( + "Test code execution error", + new(), + TestContext.Current.CancellationToken + ); + + CodeInterpreterToolResultContent codeResult = + Assert.IsType(response.Messages[0].Contents[0]); + Assert.NotNull(codeResult); + Assert.Equal("code_exec_error_1", codeResult.CallId); + Assert.NotNull(codeResult.Outputs); + Assert.Single(codeResult.Outputs); + + ErrorContent errorContent = Assert.IsType(codeResult.Outputs[0]); + Assert.Equal("ExecutionTimeExceeded", errorContent.ErrorCode); + } + + [Theory] + [InlineData("code_execution")] + [InlineData("bash_code_execution")] + public async Task GetResponseAsync_CodeExecutionResult_WithStdout(string executionType) + { + VerbatimHttpHandler handler = new( + expectedRequest: """ + { + "max_tokens": 1024, + "model": "claude-haiku-4-5", + "messages": [{ + "role": "user", + "content": [{ + "type": "text", + "text": "Run code" + }] + }] + } + """, + actualResponse: $$""" + { + "id": "msg_stdout_01", + "type": "message", + "role": "assistant", + "model": "claude-haiku-4-5", + "content": [{ + "type": "{{executionType}}_tool_result", + "tool_use_id": "exec_1", + "content": { + "type": "{{executionType}}_result", + "stdout": "Hello World\n42\n", + "stderr": "", + "return_code": 0, + "content": [] + } + }], + "stop_reason": "end_turn", + "usage": { + "input_tokens": 10, + "output_tokens": 5 + } + } + """ + ); + + IChatClient chatClient = CreateChatClient(handler, "claude-haiku-4-5"); + ChatResponse response = await chatClient.GetResponseAsync( + "Run code", + new(), + TestContext.Current.CancellationToken + ); + + CodeInterpreterToolResultContent codeResult = + Assert.IsType(response.Messages[0].Contents[0]); + Assert.Equal("exec_1", codeResult.CallId); + Assert.NotNull(codeResult.Outputs); + Assert.Single(codeResult.Outputs); + + TextContent textOutput = Assert.IsType(codeResult.Outputs[0]); + Assert.Equal("Hello World\n42\n", textOutput.Text); + } + + [Theory] + [InlineData("code_execution", "Division by zero error", 1)] + [InlineData("bash_code_execution", "bash: command not found: nonexistent", 127)] + public async Task GetResponseAsync_CodeExecutionResult_WithStderrAndNonZeroReturnCode( + string executionType, + string stderrMessage, + int returnCode + ) + { + VerbatimHttpHandler handler = new( + expectedRequest: """ + { + "max_tokens": 1024, + "model": "claude-haiku-4-5", + "messages": [{ + "role": "user", + "content": [{ + "type": "text", + "text": "Run failing code" + }] + }] + } + """, + actualResponse: $$""" + { + "id": "msg_stderr_01", + "type": "message", + "role": "assistant", + "model": "claude-haiku-4-5", + "content": [{ + "type": "{{executionType}}_tool_result", + "tool_use_id": "exec_2", + "content": { + "type": "{{executionType}}_result", + "stdout": "", + "stderr": "{{stderrMessage}}", + "return_code": {{returnCode}}, + "content": [] + } + }], + "stop_reason": "end_turn", + "usage": { + "input_tokens": 10, + "output_tokens": 5 + } + } + """ + ); + + IChatClient chatClient = CreateChatClient(handler, "claude-haiku-4-5"); + ChatResponse response = await chatClient.GetResponseAsync( + "Run failing code", + new(), + TestContext.Current.CancellationToken + ); + + CodeInterpreterToolResultContent codeResult = + Assert.IsType(response.Messages[0].Contents[0]); + Assert.NotNull(codeResult.Outputs); + Assert.Single(codeResult.Outputs); + + ErrorContent errorOutput = Assert.IsType(codeResult.Outputs[0]); + Assert.Equal(stderrMessage, errorOutput.Message); + Assert.Equal( + returnCode.ToString(System.Globalization.CultureInfo.InvariantCulture), + errorOutput.ErrorCode + ); + } + + [Theory] + [InlineData("code_execution")] + [InlineData("bash_code_execution")] + public async Task GetResponseAsync_CodeExecutionResult_WithFileOutputs(string executionType) + { + VerbatimHttpHandler handler = new( + expectedRequest: """ + { + "max_tokens": 1024, + "model": "claude-haiku-4-5", + "messages": [{ + "role": "user", + "content": [{ + "type": "text", + "text": "Create file" + }] + }] + } + """, + actualResponse: $$""" + { + "id": "msg_files_01", + "type": "message", + "role": "assistant", + "model": "claude-haiku-4-5", + "content": [{ + "type": "{{executionType}}_tool_result", + "tool_use_id": "exec_3", + "content": { + "type": "{{executionType}}_result", + "stdout": "File created", + "stderr": "", + "return_code": 0, + "content": [{ + "type": "{{executionType}}_output", + "file_id": "file_output_123" + }, { + "type": "{{executionType}}_output", + "file_id": "file_output_456" }] } - ] + }], + "stop_reason": "end_turn", + "usage": { + "input_tokens": 10, + "output_tokens": 5 + } + } + """ + ); + + IChatClient chatClient = CreateChatClient(handler, "claude-haiku-4-5"); + ChatResponse response = await chatClient.GetResponseAsync( + "Create file", + new(), + TestContext.Current.CancellationToken + ); + + CodeInterpreterToolResultContent codeResult = + Assert.IsType(response.Messages[0].Contents[0]); + Assert.NotNull(codeResult.Outputs); + Assert.Equal(3, codeResult.Outputs.Count); + + TextContent textOutput = Assert.IsType(codeResult.Outputs[0]); + Assert.Equal("File created", textOutput.Text); + + HostedFileContent fileOutput1 = Assert.IsType(codeResult.Outputs[1]); + Assert.Equal("file_output_123", fileOutput1.FileId); + + HostedFileContent fileOutput2 = Assert.IsType(codeResult.Outputs[2]); + Assert.Equal("file_output_456", fileOutput2.FileId); + } + + [Fact] + public async Task GetResponseAsync_WithAIFunctionTool_AdditionalProperties_FlowsThrough() + { + VerbatimHttpHandler handler = new( + expectedRequest: """ + { + "max_tokens": 1024, + "model": "claude-haiku-4-5", + "messages": [{ + "role": "user", + "content": [{ + "type": "text", + "text": "Use enhanced tool" + }] + }], + "tools": [{ + "name": "enhanced_tool", + "description": "A tool with additional properties", + "input_schema": { + "type": "object", + "properties": { + "query": { + "type": "string" + } + }, + "required": ["query"], + "additionalProperties": false + }, + "defer_loading": true, + "strict": true, + "input_examples": [ + { + "query": "example query" + } + ] + }] + } + """, + actualResponse: """ + { + "id": "msg_enhanced_tool_01", + "type": "message", + "role": "assistant", + "model": "claude-haiku-4-5", + "content": [{ + "type": "text", + "text": "Tool is ready" + }], + "stop_reason": "end_turn", + "usage": { + "input_tokens": 40, + "output_tokens": 10 + } + } + """ + ); + + IChatClient chatClient = CreateChatClient(handler, "claude-haiku-4-5"); + + var enhancedFunction = AIFunctionFactory.Create( + (string query) => "result", + new AIFunctionFactoryOptions + { + Name = "enhanced_tool", + Description = "A tool with additional properties", + AdditionalProperties = new Dictionary + { + [nameof(Tool.DeferLoading)] = true, + [nameof(Tool.Strict)] = true, + [nameof(Tool.InputExamples)] = new List> + { + new() { ["query"] = JsonSerializer.SerializeToElement("example query") }, + }, + }, + } + ); + + ChatOptions options = new() { Tools = [enhancedFunction] }; + + ChatResponse response = await chatClient.GetResponseAsync( + "Use enhanced tool", + options, + TestContext.Current.CancellationToken + ); + Assert.NotNull(response); + } + + [Fact] + public async Task GetResponseAsync_WithAIFunctionTool_PartialAdditionalProperties() + { + VerbatimHttpHandler handler = new( + expectedRequest: """ + { + "max_tokens": 1024, + "model": "claude-haiku-4-5", + "messages": [{ + "role": "user", + "content": [{ + "type": "text", + "text": "Use strict tool" + }] + }], + "tools": [{ + "name": "strict_tool", + "description": "A tool with only strict property", + "input_schema": { + "type": "object", + "properties": { + "value": { + "type": "integer" + } + }, + "required": ["value"], + "additionalProperties": false + }, + "strict": true + }] + } + """, + actualResponse: """ + { + "id": "msg_strict_tool_01", + "type": "message", + "role": "assistant", + "model": "claude-haiku-4-5", + "content": [{ + "type": "text", + "text": "Strict mode enabled" + }], + "stop_reason": "end_turn", + "usage": { + "input_tokens": 35, + "output_tokens": 8 + } + } + """ + ); + + IChatClient chatClient = CreateChatClient(handler, "claude-haiku-4-5"); + + var strictFunction = AIFunctionFactory.Create( + (int value) => value * 2, + new AIFunctionFactoryOptions + { + Name = "strict_tool", + Description = "A tool with only strict property", + AdditionalProperties = new Dictionary + { + [nameof(Tool.Strict)] = true, + }, + } + ); + + ChatOptions options = new() { Tools = [strictFunction] }; + + ChatResponse response = await chatClient.GetResponseAsync( + "Use strict tool", + options, + TestContext.Current.CancellationToken + ); + Assert.NotNull(response); + } + + /// + /// Validates that all JSON schema transformations are applied correctly when using + /// ChatResponseFormat.ForJsonSchema. Tests: + /// + /// Numeric constraints (minimum, maximum, multipleOf) → description + /// String constraints (minLength, maxLength, pattern) → description + /// Unsupported string format → description + /// Supported string format (email) preserved + /// Array minItems > 1 → description + /// Array minItems ≤ 1 preserved + /// oneOf → anyOf conversion (with nested object getting additionalProperties: false) + /// enum preserved + /// const preserved + /// title preserved + /// Unsupported properties (default) → description + /// Nested object gets additionalProperties: false + /// Root object gets additionalProperties: false + /// + /// + [Fact] + public async Task GetResponseAsync_ResponseFormatSchema_AllTransformationsApplied() + { + string inputSchema = """ + { + "type": "object", + "properties": { + "score": { + "type": "integer", + "description": "A score", + "minimum": 0, + "maximum": 100, + "multipleOf": 5 + }, + "code": { + "type": "string", + "minLength": 3, + "maxLength": 10, + "pattern": "^[A-Z]+$" + }, + "phone": { + "type": "string", + "format": "phone" + }, + "email": { + "type": "string", + "format": "email" + }, + "tags": { + "type": "array", + "items": { "type": "string" }, + "minItems": 3 + }, + "ids": { + "type": "array", + "items": { "type": "string" }, + "minItems": 1 + }, + "value": { + "oneOf": [ + { "type": "string" }, + { + "type": "object", + "properties": { "x": { "type": "integer" } }, + "required": ["x"] + } + ] + }, + "status": { + "type": "string", + "enum": ["active", "inactive"] + }, + "level": { + "type": "string", + "const": "admin" + }, + "name": { + "type": "string", + "title": "Full Name" + }, + "note": { + "type": "string", + "default": "N/A" + }, + "nested": { + "type": "object", + "properties": { + "inner": { "type": "string" } + }, + "required": ["inner"] + } + }, + "required": ["score", "code", "phone", "email", "tags", "ids", "value", "status", "level", "name", "note", "nested"] + } + """; + + VerbatimHttpHandler handler = new( + expectedRequest: """ + { + "max_tokens": 1024, + "model": "claude-sonnet-4-5-20250929", + "messages": [{ + "role": "user", + "content": [{ + "type": "text", + "text": "test" + }] + }], + "output_config": { + "format": { + "type": "json_schema", + "schema": { + "type": "object", + "properties": { + "score": { + "type": "integer", + "description": "A score\n\n{minimum: 0, maximum: 100, multipleOf: 5}" + }, + "code": { + "type": "string", + "description": "{minLength: 3, maxLength: 10, pattern: \"^[A-Z]+$\"}" + }, + "phone": { + "type": "string", + "description": "{format: \"phone\"}" + }, + "email": { + "type": "string", + "format": "email" + }, + "tags": { + "type": "array", + "items": { "type": "string" }, + "description": "{minItems: 3}" + }, + "ids": { + "type": "array", + "items": { "type": "string" }, + "minItems": 1 + }, + "value": { + "anyOf": [ + { "type": "string" }, + { + "type": "object", + "properties": { "x": { "type": "integer" } }, + "required": ["x"], + "additionalProperties": false + } + ] + }, + "status": { + "type": "string", + "enum": ["active", "inactive"] + }, + "level": { + "type": "string", + "const": "admin" + }, + "name": { + "type": "string", + "title": "Full Name" + }, + "note": { + "type": "string", + "description": "{default: \"N/A\"}" + }, + "nested": { + "type": "object", + "properties": { + "inner": { "type": "string" } + }, + "required": ["inner"], + "additionalProperties": false + } + }, + "required": ["score", "code", "phone", "email", "tags", "ids", "value", "status", "level", "name", "note", "nested"], + "additionalProperties": false + } + } + } + } + """, + actualResponse: """ + { + "id": "msg_transform_01", + "type": "message", + "role": "assistant", + "model": "claude-sonnet-4-5-20250929", + "content": [{ + "type": "text", + "text": "{}" + }], + "stop_reason": "end_turn", + "usage": { + "input_tokens": 25, + "output_tokens": 10 + } + } + """ + ); + + IChatClient chatClient = CreateChatClient(handler, "claude-sonnet-4-5-20250929"); + + ChatOptions options = new() + { + ResponseFormat = ChatResponseFormat.ForJsonSchema( + JsonElement.Parse(inputSchema), + "test_schema" + ), + }; + + ChatResponse response = await chatClient.GetResponseAsync( + "test", + options, + TestContext.Current.CancellationToken + ); + Assert.NotNull(response); + } + + /// + /// Validates the same schema transformations as + /// but through + /// the tool path, ensuring both code paths apply the same + /// transform pipeline. + /// + [Fact] + public async Task GetResponseAsync_ToolDeclarationSchema_AllTransformationsApplied() + { + string inputSchema = """ + { + "type": "object", + "properties": { + "score": { + "type": "integer", + "description": "A score", + "minimum": 0, + "maximum": 100, + "multipleOf": 5 + }, + "code": { + "type": "string", + "minLength": 3, + "maxLength": 10, + "pattern": "^[A-Z]+$" + }, + "phone": { + "type": "string", + "format": "phone" + }, + "email": { + "type": "string", + "format": "email" + }, + "tags": { + "type": "array", + "items": { "type": "string" }, + "minItems": 3 + }, + "ids": { + "type": "array", + "items": { "type": "string" }, + "minItems": 1 + }, + "value": { + "oneOf": [ + { "type": "string" }, + { + "type": "object", + "properties": { "x": { "type": "integer" } }, + "required": ["x"] + } + ] + }, + "status": { + "type": "string", + "enum": ["active", "inactive"] + }, + "level": { + "type": "string", + "const": "admin" + }, + "name": { + "type": "string", + "title": "Full Name" + }, + "note": { + "type": "string", + "default": "N/A" + }, + "nested": { + "type": "object", + "properties": { + "inner": { "type": "string" } + }, + "required": ["inner"] + } + }, + "required": ["score", "code", "phone", "email", "tags", "ids", "value", "status", "level", "name", "note", "nested"] + } + """; + + VerbatimHttpHandler handler = new( + expectedRequest: """ + { + "max_tokens": 1024, + "model": "claude-sonnet-4-5-20250929", + "messages": [{ + "role": "user", + "content": [{ + "type": "text", + "text": "test" + }] + }], + "tools": [{ + "name": "test_tool", + "description": "A test tool", + "input_schema": { + "type": "object", + "properties": { + "score": { + "type": "integer", + "description": "A score\n\n{minimum: 0, maximum: 100, multipleOf: 5}" + }, + "code": { + "type": "string", + "description": "{minLength: 3, maxLength: 10, pattern: \"^[A-Z]+$\"}" + }, + "phone": { + "type": "string", + "description": "{format: \"phone\"}" + }, + "email": { + "type": "string", + "format": "email" + }, + "tags": { + "type": "array", + "items": { "type": "string" }, + "description": "{minItems: 3}" + }, + "ids": { + "type": "array", + "items": { "type": "string" }, + "minItems": 1 + }, + "value": { + "anyOf": [ + { "type": "string" }, + { + "type": "object", + "properties": { "x": { "type": "integer" } }, + "required": ["x"], + "additionalProperties": false + } + ] + }, + "status": { + "type": "string", + "enum": ["active", "inactive"] + }, + "level": { + "type": "string", + "const": "admin" + }, + "name": { + "type": "string", + "title": "Full Name" + }, + "note": { + "type": "string", + "description": "{default: \"N/A\"}" + }, + "nested": { + "type": "object", + "properties": { + "inner": { "type": "string" } + }, + "required": ["inner"], + "additionalProperties": false + } + }, + "required": ["score", "code", "phone", "email", "tags", "ids", "value", "status", "level", "name", "note", "nested"], + "additionalProperties": false + } + }] + } + """, + actualResponse: """ + { + "id": "msg_transform_02", + "type": "message", + "role": "assistant", + "model": "claude-sonnet-4-5-20250929", + "content": [{ + "type": "text", + "text": "ok" + }], + "stop_reason": "end_turn", + "usage": { + "input_tokens": 25, + "output_tokens": 10 + } + } + """ + ); + + IChatClient chatClient = CreateChatClient(handler, "claude-sonnet-4-5-20250929"); + + var declaration = AIFunctionFactory.CreateDeclaration( + "test_tool", + "A test tool", + JsonElement.Parse(inputSchema), + null + ); + + ChatOptions options = new() { Tools = [declaration] }; + + ChatResponse response = await chatClient.GetResponseAsync( + "test", + options, + TestContext.Current.CancellationToken + ); + Assert.NotNull(response); + } + + [Fact] + public async Task GetResponseAsync_ServerToolUseBlock_BashCodeExecution_MapsToDataContentWithShMediaType() + { + VerbatimHttpHandler handler = new( + expectedRequest: """ + { + "max_tokens": 1024, + "model": "claude-haiku-4-5", + "messages": [{ + "role": "user", + "content": [{ + "type": "text", + "text": "Run a bash command" + }] + }] + } + """, + actualResponse: """ + { + "id": "msg_bash_01", + "type": "message", + "role": "assistant", + "model": "claude-haiku-4-5", + "content": [ + { + "type": "server_tool_use", + "id": "srvtoolu_bash_01", + "name": "bash_code_execution", + "caller": { "type": "direct" }, + "input": { "command": "echo hello" } + } + ], + "stop_reason": "end_turn", + "usage": { + "input_tokens": 10, + "output_tokens": 5 + } + } + """ + ); + + IChatClient chatClient = CreateChatClient(handler, "claude-haiku-4-5"); + ChatResponse response = await chatClient.GetResponseAsync( + "Run a bash command", + new(), + TestContext.Current.CancellationToken + ); + Assert.NotNull(response); + + var contents = response.Messages[0].Contents; + var ciCall = Assert.IsType(contents[0]); + Assert.Equal("srvtoolu_bash_01", ciCall.CallId); + Assert.NotNull(ciCall.Inputs); + Assert.Single(ciCall.Inputs); + var codeInput = Assert.IsType(ciCall.Inputs[0]); + Assert.Equal("application/x-sh", codeInput.MediaType); + Assert.Equal("echo hello", Encoding.UTF8.GetString(codeInput.Data.ToArray())); + } + + [Fact] + public async Task GetResponseAsync_ServerToolUseBlock_TextEditorCodeExecution_MapsToDataContentWithTextPlainMediaType() + { + VerbatimHttpHandler handler = new( + expectedRequest: """ + { + "max_tokens": 1024, + "model": "claude-haiku-4-5", + "messages": [{ + "role": "user", + "content": [{ + "type": "text", + "text": "Create a file" + }] + }] + } + """, + actualResponse: """ + { + "id": "msg_te_01", + "type": "message", + "role": "assistant", + "model": "claude-haiku-4-5", + "content": [ + { + "type": "server_tool_use", + "id": "srvtoolu_te_01", + "name": "text_editor_code_execution", + "caller": { "type": "direct" }, + "input": { "command": "create" } + } + ], + "stop_reason": "end_turn", + "usage": { + "input_tokens": 10, + "output_tokens": 5 + } + } + """ + ); + + IChatClient chatClient = CreateChatClient(handler, "claude-haiku-4-5"); + ChatResponse response = await chatClient.GetResponseAsync( + "Create a file", + new(), + TestContext.Current.CancellationToken + ); + Assert.NotNull(response); + + var contents = response.Messages[0].Contents; + var ciCall = Assert.IsType(contents[0]); + Assert.Equal("srvtoolu_te_01", ciCall.CallId); + Assert.NotNull(ciCall.Inputs); + Assert.Single(ciCall.Inputs); + var codeInput = Assert.IsType(ciCall.Inputs[0]); + Assert.Equal("text/plain", codeInput.MediaType); + Assert.Equal("create", Encoding.UTF8.GetString(codeInput.Data.ToArray())); + } + + [Fact] + public async Task GetResponseAsync_ServerToolUseBlock_CodeExecution_WithMissingCodeKey_InputsNotPopulated() + { + VerbatimHttpHandler handler = new( + expectedRequest: """ + { + "max_tokens": 1024, + "model": "claude-haiku-4-5", + "messages": [{ + "role": "user", + "content": [{ + "type": "text", + "text": "Execute something" + }] + }] + } + """, + actualResponse: """ + { + "id": "msg_nocode_01", + "type": "message", + "role": "assistant", + "model": "claude-haiku-4-5", + "content": [ + { + "type": "server_tool_use", + "id": "srvtoolu_nocode_01", + "name": "code_execution", + "caller": { "type": "direct" }, + "input": { "language": "python" } + } + ], + "stop_reason": "end_turn", + "usage": { + "input_tokens": 10, + "output_tokens": 5 + } + } + """ + ); + + IChatClient chatClient = CreateChatClient(handler, "claude-haiku-4-5"); + ChatResponse response = await chatClient.GetResponseAsync( + "Execute something", + new(), + TestContext.Current.CancellationToken + ); + Assert.NotNull(response); + + var contents = response.Messages[0].Contents; + var ciCall = Assert.IsType(contents[0]); + Assert.Equal("srvtoolu_nocode_01", ciCall.CallId); + Assert.Null(ciCall.Inputs); + } + + [Fact] + public async Task GetResponseAsync_ServerToolUseBlock_UnknownName_MapsToToolCallContent() + { + VerbatimHttpHandler handler = new( + expectedRequest: """ + { + "max_tokens": 1024, + "model": "claude-haiku-4-5", + "messages": [{ + "role": "user", + "content": [{ + "type": "text", + "text": "Search tools" + }] + }] + } + """, + actualResponse: """ + { + "id": "msg_unknown_01", + "type": "message", + "role": "assistant", + "model": "claude-haiku-4-5", + "content": [ + { + "type": "server_tool_use", + "id": "srvtoolu_ts_01", + "name": "tool_search_tool_regex", + "caller": { "type": "direct" }, + "input": {} + } + ], + "stop_reason": "end_turn", + "usage": { + "input_tokens": 10, + "output_tokens": 5 + } + } + """ + ); + + IChatClient chatClient = CreateChatClient(handler, "claude-haiku-4-5"); + ChatResponse response = await chatClient.GetResponseAsync( + "Search tools", + new(), + TestContext.Current.CancellationToken + ); + Assert.NotNull(response); + + var contents = response.Messages[0].Contents; + var tc = Assert.IsType(contents[0]); + Assert.Equal("srvtoolu_ts_01", tc.CallId); + } + + [Fact] + public async Task GetResponseAsync_ServerToolUseBlock_WebSearch_WithoutQueryInput_QueriesNotPopulated() + { + VerbatimHttpHandler handler = new( + expectedRequest: """ + { + "max_tokens": 1024, + "model": "claude-haiku-4-5", + "messages": [{ + "role": "user", + "content": [{ + "type": "text", + "text": "Search the web" + }] + }] + } + """, + actualResponse: """ + { + "id": "msg_ws_noquery_01", + "type": "message", + "role": "assistant", + "model": "claude-haiku-4-5", + "content": [ + { + "type": "server_tool_use", + "id": "srvtoolu_ws_noq_01", + "name": "web_search", + "caller": { "type": "direct" }, + "input": {} + } + ], + "stop_reason": "end_turn", + "usage": { + "input_tokens": 10, + "output_tokens": 5 + } + } + """ + ); + + IChatClient chatClient = CreateChatClient(handler, "claude-haiku-4-5"); + ChatResponse response = await chatClient.GetResponseAsync( + "Search the web", + new(), + TestContext.Current.CancellationToken + ); + Assert.NotNull(response); + + var contents = response.Messages[0].Contents; + var wsc = Assert.IsType(contents[0]); + Assert.Equal("srvtoolu_ws_noq_01", wsc.CallId); + Assert.Null(wsc.Queries); + } + + [Fact] + public async Task GetResponseAsync_WebFetchToolResultBlock_MapsToWebSearchToolResultContent() + { + VerbatimHttpHandler handler = new( + expectedRequest: """ + { + "max_tokens": 1024, + "model": "claude-haiku-4-5", + "messages": [{ + "role": "user", + "content": [{ + "type": "text", + "text": "Fetch a page" + }] + }] + } + """, + actualResponse: """ + { + "id": "msg_wf_01", + "type": "message", + "role": "assistant", + "model": "claude-haiku-4-5", + "content": [ + { + "type": "web_fetch_tool_result", + "tool_use_id": "srvtoolu_wf_01", + "caller": { "type": "direct" }, + "content": { + "type": "web_fetch_result", + "url": "https://example.com/article.html", + "retrieved_at": "2025-01-01T00:00:00Z", + "content": { + "type": "document", + "citations": null, + "source": { + "type": "text", + "media_type": "text/plain", + "data": "fetched content" + }, + "title": "Article" + } + } + } + ], + "stop_reason": "end_turn", + "usage": { + "input_tokens": 10, + "output_tokens": 5 + } + } + """ + ); + + IChatClient chatClient = CreateChatClient(handler, "claude-haiku-4-5"); + ChatResponse response = await chatClient.GetResponseAsync( + "Fetch a page", + new(), + TestContext.Current.CancellationToken + ); + Assert.NotNull(response); + + var contents = response.Messages[0].Contents; + var result = Assert.IsType(contents[0]); + Assert.Equal("srvtoolu_wf_01", result.CallId); + Assert.NotNull(result.Results); + Assert.Single(result.Results); + var uriContent = Assert.IsType(result.Results[0]); + Assert.Equal(new Uri("https://example.com/article.html"), uriContent.Uri); + Assert.Equal("text/html", uriContent.MediaType); + } + + [Fact] + public async Task GetResponseAsync_TextEditorCodeExecutionResult_ViewOperation() + { + VerbatimHttpHandler handler = new( + expectedRequest: """ + { + "max_tokens": 1024, + "model": "claude-haiku-4-5", + "messages": [{ + "role": "user", + "content": [{ + "type": "text", + "text": "View a file" + }] + }] + } + """, + actualResponse: """ + { + "id": "msg_te_view_01", + "type": "message", + "role": "assistant", + "model": "claude-haiku-4-5", + "content": [ + { + "type": "text_editor_code_execution_tool_result", + "tool_use_id": "srvtoolu_te_01", + "content": { + "type": "text_editor_code_execution_view_result", + "file_type": "text", + "content": "print('hello')", + "num_lines": 1, + "start_line": 1, + "total_lines": 1 + } + } + ], + "stop_reason": "end_turn", + "usage": { + "input_tokens": 10, + "output_tokens": 5 + } + } + """ + ); + + IChatClient chatClient = CreateChatClient(handler, "claude-haiku-4-5"); + ChatResponse response = await chatClient.GetResponseAsync( + "View a file", + new(), + TestContext.Current.CancellationToken + ); + Assert.NotNull(response); + + var contents = response.Messages[0].Contents; + var ciResult = Assert.IsType(contents[0]); + Assert.Equal("srvtoolu_te_01", ciResult.CallId); + Assert.NotNull(ciResult.Outputs); + Assert.Single(ciResult.Outputs); + var textOutput = Assert.IsType(ciResult.Outputs[0]); + Assert.Equal("print('hello')", textOutput.Text); + } + + [Fact] + public async Task GetResponseAsync_TextEditorCodeExecutionResult_WithError() + { + VerbatimHttpHandler handler = new( + expectedRequest: """ + { + "max_tokens": 1024, + "model": "claude-haiku-4-5", + "messages": [{ + "role": "user", + "content": [{ + "type": "text", + "text": "Edit missing file" + }] + }] + } + """, + actualResponse: """ + { + "id": "msg_te_err_01", + "type": "message", + "role": "assistant", + "model": "claude-haiku-4-5", + "content": [ + { + "type": "text_editor_code_execution_tool_result", + "tool_use_id": "srvtoolu_te_02", + "content": { + "type": "text_editor_code_execution_tool_result_error", + "error_code": "file_not_found", + "error_message": "File not found: /tmp/missing.py" + } + } + ], + "stop_reason": "end_turn", + "usage": { + "input_tokens": 10, + "output_tokens": 5 + } + } + """ + ); + + IChatClient chatClient = CreateChatClient(handler, "claude-haiku-4-5"); + ChatResponse response = await chatClient.GetResponseAsync( + "Edit missing file", + new(), + TestContext.Current.CancellationToken + ); + Assert.NotNull(response); + + var contents = response.Messages[0].Contents; + var ciResult = Assert.IsType(contents[0]); + Assert.Equal("srvtoolu_te_02", ciResult.CallId); + Assert.NotNull(ciResult.Outputs); + Assert.Single(ciResult.Outputs); + var errorContent = Assert.IsType(ciResult.Outputs[0]); + Assert.Equal("File not found: /tmp/missing.py", errorContent.Message); + Assert.Equal("FileNotFound", errorContent.ErrorCode); + } + + [Fact] + public async Task GetResponseAsync_ToolSearchToolResultBlock_MapsToToolResultContent() + { + VerbatimHttpHandler handler = new( + expectedRequest: """ + { + "max_tokens": 1024, + "model": "claude-haiku-4-5", + "messages": [{ + "role": "user", + "content": [{ + "type": "text", + "text": "Find tools" + }] + }] + } + """, + actualResponse: """ + { + "id": "msg_ts_01", + "type": "message", + "role": "assistant", + "model": "claude-haiku-4-5", + "content": [ + { + "type": "tool_search_tool_result", + "tool_use_id": "srvtoolu_ts_01", + "content": { + "type": "tool_search_tool_search_result", + "tool_references": [] + } + } + ], + "stop_reason": "end_turn", + "usage": { + "input_tokens": 10, + "output_tokens": 5 + } + } + """ + ); + + IChatClient chatClient = CreateChatClient(handler, "claude-haiku-4-5"); + ChatResponse response = await chatClient.GetResponseAsync( + "Find tools", + new(), + TestContext.Current.CancellationToken + ); + Assert.NotNull(response); + + var contents = response.Messages[0].Contents; + var result = Assert.IsType(contents[0]); + Assert.Equal("srvtoolu_ts_01", result.CallId); + } + + [Fact] + public async Task GetStreamingResponseAsync_WithServerToolResultInContentBlockStart() + { + VerbatimHttpHandler handler = new( + expectedRequest: """ + { + "max_tokens": 1024, + "model": "claude-haiku-4-5", + "messages": [{ + "role": "user", + "content": [{ + "type": "text", + "text": "Search the web" + }] + }], + "stream": true + } + """, + actualResponse: """ + event: message_start + data: {"type":"message_start","message":{"id":"msg_stream_ws_01","type":"message","role":"assistant","model":"claude-haiku-4-5","content":[],"stop_reason":null,"stop_sequence":null,"usage":{"input_tokens":10,"output_tokens":0}}} + + event: content_block_start + data: {"type":"content_block_start","index":0,"content_block":{"type":"web_search_tool_result","tool_use_id":"srvtoolu_ws_stream_01","caller":{"type":"direct"},"content":[{"type":"web_search_result","title":"Stream Result","url":"https://example.com/stream","encrypted_content":"enc_stream","page_age":"1 day ago"}]}} + + event: content_block_stop + data: {"type":"content_block_stop","index":0} + + event: message_delta + data: {"type":"message_delta","delta":{"stop_reason":"end_turn","stop_sequence":null},"usage":{"output_tokens":5}} + + event: message_stop + data: {"type":"message_stop"} + + """ + ); + + IChatClient chatClient = CreateChatClient(handler, "claude-haiku-4-5"); + + List updates = []; + await foreach ( + var update in chatClient.GetStreamingResponseAsync( + "Search the web", + new(), + TestContext.Current.CancellationToken + ) + ) + { + updates.Add(update); + } + Assert.NotEmpty(updates); + + var wsResultUpdates = updates + .SelectMany(u => u.Contents.OfType()) + .ToList(); + Assert.Single(wsResultUpdates); + + var wsResult = wsResultUpdates[0]; + Assert.Equal("srvtoolu_ws_stream_01", wsResult.CallId); + Assert.NotNull(wsResult.Results); + Assert.Single(wsResult.Results); + + var uriContent = Assert.IsType(wsResult.Results[0]); + Assert.Equal(new Uri("https://example.com/stream"), uriContent.Uri); + } + + [Fact] + public async Task GetResponseAsync_EncryptedCodeExecutionResult_MapsStderrAndFiles() + { + VerbatimHttpHandler handler = new( + expectedRequest: """ + { + "model": "claude-haiku-4-5", + "messages": [{ + "role": "user", + "content": [{ + "type": "text", + "text": "Run code" + }] + }], + "max_tokens": 1024, + "tools": [{ + "type": "code_execution_20250825", + "name": "code_execution" + }] + } + """, + actualResponse: """ + { + "id": "msg_enc_01", + "type": "message", + "role": "assistant", + "model": "claude-haiku-4-5", + "content": [ + { + "type": "server_tool_use", + "id": "srvtoolu_enc_01", + "name": "code_execution", + "caller": { "type": "direct" }, + "input": { "code": "print('hello')" } + }, + { + "type": "code_execution_tool_result", + "tool_use_id": "srvtoolu_enc_01", + "content": { + "type": "encrypted_code_execution_result", + "encrypted_stdout": "base64encryptedstuff", + "stderr": "warning: something", + "return_code": 1, + "content": [ + { "type": "code_execution_output", "file_id": "file_out_01" }, + { "type": "code_execution_output", "file_id": "file_out_02" } + ] + } + } + ], + "stop_reason": "end_turn", + "usage": { + "input_tokens": 25, + "output_tokens": 20 + } + } + """ + ); + + IChatClient chatClient = CreateChatClient(handler, "claude-haiku-4-5"); + + ChatResponse response = await chatClient.GetResponseAsync( + "Run code", + new() { Tools = [new HostedCodeInterpreterTool()] }, + TestContext.Current.CancellationToken + ); + + var contents = response.Messages[0].Contents; + var ciResult = Assert.IsType(contents[1]); + Assert.Equal("srvtoolu_enc_01", ciResult.CallId); + Assert.NotNull(ciResult.Outputs); + + // Encrypted stdout is not surfaced, but stderr and files are + var errorOutput = Assert.IsType(ciResult.Outputs[0]); + Assert.Equal("warning: something", errorOutput.Message); + Assert.Equal("1", errorOutput.ErrorCode); + + var file1 = Assert.IsType(ciResult.Outputs[1]); + Assert.Equal("file_out_01", file1.FileId); + + var file2 = Assert.IsType(ciResult.Outputs[2]); + Assert.Equal("file_out_02", file2.FileId); + } + + [Fact] + public async Task GetResponseAsync_ContainerUploadBlock_MapsToHostedFileContent() + { + VerbatimHttpHandler handler = new( + expectedRequest: """ + { + "model": "claude-haiku-4-5", + "messages": [{ + "role": "user", + "content": [{ + "type": "text", + "text": "Upload a file" + }] + }], + "max_tokens": 1024, + "tools": [{ + "type": "code_execution_20250825", + "name": "code_execution" + }] } """, actualResponse: """ { - "id": "msg_uri_img_01", + "id": "msg_cu_01", "type": "message", "role": "assistant", "model": "claude-haiku-4-5", - "content": [{ - "type": "text", - "text": "Image URL received" - }], + "content": [ + { + "type": "container_upload", + "file_id": "file_container_01" + }, + { + "type": "text", + "text": "File uploaded." + } + ], "stop_reason": "end_turn", "usage": { - "input_tokens": 32, - "output_tokens": 8 + "input_tokens": 10, + "output_tokens": 5 } } """ @@ -4488,135 +7469,154 @@ public async Task GetResponseAsync_WithFunctionResultContent_UriContent_Image() IChatClient chatClient = CreateChatClient(handler, "claude-haiku-4-5"); - List messages = - [ - new ChatMessage(ChatRole.User, "Get image URL"), - new ChatMessage( - ChatRole.Assistant, - [ - new FunctionCallContent( - "tool_uri_img", - "url_tool", - new Dictionary() - ), - ] - ), - new ChatMessage( - ChatRole.User, - [ - new FunctionResultContent( - "tool_uri_img", - new UriContent(new Uri("https://example.com/image.png"), "image/png") - ), - ] - ), - ]; - ChatResponse response = await chatClient.GetResponseAsync( - messages, - new(), + "Upload a file", + new() { Tools = [new HostedCodeInterpreterTool()] }, TestContext.Current.CancellationToken ); - Assert.NotNull(response); + + var contents = response.Messages[0].Contents; + + var hostedFile = Assert.IsType(contents[0]); + Assert.Equal("file_container_01", hostedFile.FileId); + Assert.NotNull(hostedFile.RawRepresentation); + + var text = Assert.IsType(contents[1]); + Assert.Equal("File uploaded.", text.Text); } [Fact] - public async Task GetResponseAsync_WithFunctionResultContent_UriContent_PDF() + public async Task GetStreamingResponseAsync_ContainerUploadBlock_MapsToHostedFileContent() { VerbatimHttpHandler handler = new( expectedRequest: """ { "max_tokens": 1024, "model": "claude-haiku-4-5", - "messages": [ - { - "role": "user", - "content": [{ - "type": "text", - "text": "Get PDF URL" - }] - }, - { - "role": "assistant", - "content": [{ - "type": "tool_use", - "id": "tool_uri_pdf", - "name": "pdf_url_tool", - "input": {} - }] - }, - { - "role": "user", - "content": [{ - "type": "tool_result", - "tool_use_id": "tool_uri_pdf", - "is_error": false, - "content": [{ - "type": "document", - "source": { - "type": "url", - "url": "https://example.com/document.pdf" - } - }] - }] - } - ] + "messages": [{ + "role": "user", + "content": [{ + "type": "text", + "text": "Upload a file" + }] + }], + "stream": true } """, actualResponse: """ + event: message_start + data: {"type":"message_start","message":{"id":"msg_stream_cu_01","type":"message","role":"assistant","model":"claude-haiku-4-5","content":[],"stop_reason":null,"stop_sequence":null,"usage":{"input_tokens":10,"output_tokens":0}}} + + event: content_block_start + data: {"type":"content_block_start","index":0,"content_block":{"type":"container_upload","file_id":"file_stream_container_01"}} + + event: content_block_stop + data: {"type":"content_block_stop","index":0} + + event: message_delta + data: {"type":"message_delta","delta":{"stop_reason":"end_turn","stop_sequence":null},"usage":{"output_tokens":5}} + + event: message_stop + data: {"type":"message_stop"} + + """ + ); + + IChatClient chatClient = CreateChatClient(handler, "claude-haiku-4-5"); + + List updates = []; + await foreach ( + var update in chatClient.GetStreamingResponseAsync( + "Upload a file", + new(), + TestContext.Current.CancellationToken + ) + ) + { + updates.Add(update); + } + Assert.NotEmpty(updates); + + var hostedFiles = updates.SelectMany(u => u.Contents.OfType()).ToList(); + Assert.Single(hostedFiles); + Assert.Equal("file_stream_container_01", hostedFiles[0].FileId); + } + + [Fact] + public async Task GetStreamingResponseAsync_CitationsDelta_MapsToAnnotation() + { + VerbatimHttpHandler handler = new( + expectedRequest: """ { - "id": "msg_uri_pdf_01", - "type": "message", - "role": "assistant", + "max_tokens": 1024, "model": "claude-haiku-4-5", - "content": [{ - "type": "text", - "text": "PDF URL received" + "messages": [{ + "role": "user", + "content": [{ + "type": "text", + "text": "Search and cite" + }] }], - "stop_reason": "end_turn", - "usage": { - "input_tokens": 35, - "output_tokens": 9 - } + "stream": true } + """, + actualResponse: """ + event: message_start + data: {"type":"message_start","message":{"id":"msg_stream_cite_01","type":"message","role":"assistant","model":"claude-haiku-4-5","content":[],"stop_reason":null,"stop_sequence":null,"usage":{"input_tokens":10,"output_tokens":0}}} + + event: content_block_start + data: {"type":"content_block_start","index":0,"content_block":{"type":"text","text":""}} + + event: content_block_delta + data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":"The Eiffel Tower is 330m tall."}} + + event: content_block_delta + data: {"type":"content_block_delta","index":0,"delta":{"type":"citations_delta","citation":{"type":"web_search_result_location","cited_text":"330 meters tall","encrypted_index":"enc","title":"Eiffel Tower Facts","url":"https://example.com/eiffel"}}} + + event: content_block_stop + data: {"type":"content_block_stop","index":0} + + event: message_delta + data: {"type":"message_delta","delta":{"stop_reason":"end_turn","stop_sequence":null},"usage":{"output_tokens":15}} + + event: message_stop + data: {"type":"message_stop"} + """ ); IChatClient chatClient = CreateChatClient(handler, "claude-haiku-4-5"); - List messages = - [ - new ChatMessage(ChatRole.User, "Get PDF URL"), - new ChatMessage( - ChatRole.Assistant, - [ - new FunctionCallContent( - "tool_uri_pdf", - "pdf_url_tool", - new Dictionary() - ), - ] - ), - new ChatMessage( - ChatRole.User, - [ - new FunctionResultContent( - "tool_uri_pdf", - new UriContent( - new Uri("https://example.com/document.pdf"), - "application/pdf" - ) - ), - ] - ), - ]; + List updates = []; + await foreach ( + var update in chatClient.GetStreamingResponseAsync( + "Search and cite", + new(), + TestContext.Current.CancellationToken + ) + ) + { + updates.Add(update); + } + Assert.NotEmpty(updates); - ChatResponse response = await chatClient.GetResponseAsync( - messages, - new(), - TestContext.Current.CancellationToken + // Verify text came through + var allText = string.Concat( + updates.SelectMany(u => u.Contents.OfType()).Select(c => c.Text) ); - Assert.NotNull(response); + Assert.Contains("Eiffel Tower", allText); + + // Verify citation annotation came through + var annotatedContents = updates + .SelectMany(u => u.Contents.OfType()) + .Where(t => t.Annotations is { Count: > 0 }) + .ToList(); + Assert.Single(annotatedContents); + + var annotation = Assert.IsType(annotatedContents[0].Annotations![0]); + Assert.Equal("Eiffel Tower Facts", annotation.Title); + Assert.Equal("330 meters tall", annotation.Snippet); + Assert.Equal(new Uri("https://example.com/eiffel"), annotation.Url); } protected sealed class VerbatimHttpHandler(string expectedRequest, string actualResponse) @@ -4654,4 +7654,48 @@ CancellationToken cancellationToken }; } } + + [Theory] + [InlineData("https://example.com/doc.pdf", "application/pdf")] + [InlineData("https://example.com/page.html", "text/html")] + [InlineData("https://example.com/path/resource", "application/octet-stream")] + [InlineData("https://example.com/Photo.JPG", "image/jpeg")] + [InlineData("https://example.com/file.xyz123", "application/octet-stream")] + [InlineData(".py", "text/x-python")] + [InlineData(".sh", "application/x-sh")] + [InlineData(".js", "text/javascript")] + [InlineData(".pdf", "application/pdf")] + [InlineData(".PY", "text/x-python")] + [InlineData(".PNG", "image/png")] + [InlineData(".unknown", "application/octet-stream")] + public void InferMediaTypeFromExtension_ReturnsExpectedType( + string urlOrPath, + string expectedMediaType + ) + { + Assert.Equal( + expectedMediaType, + AnthropicClientExtensions.InferMediaTypeFromExtension(urlOrPath) + ); + } + + [Theory] + [InlineData(null, "")] + [InlineData("image/jpeg", ".jpg")] + [InlineData("text/x-python", ".py")] + [InlineData("application/pdf", ".pdf")] + [InlineData("application/yaml", ".yaml")] + [InlineData("text/javascript", ".js")] + [InlineData("text/typescript", ".ts")] + [InlineData("application/x-custom-unknown", "")] + public void InferExtensionFromMediaType_ReturnsExpectedExtension( + string? mediaType, + string expectedExtension + ) + { + Assert.Equal( + expectedExtension, + AnthropicClientExtensions.InferExtensionFromMediaType(mediaType) + ); + } } diff --git a/src/Anthropic.Tests/Models/Beta/AnthropicBetaTest.cs b/src/Anthropic.Tests/Models/Beta/AnthropicBetaTest.cs index e54d4d1a4..0df8d7b63 100644 --- a/src/Anthropic.Tests/Models/Beta/AnthropicBetaTest.cs +++ b/src/Anthropic.Tests/Models/Beta/AnthropicBetaTest.cs @@ -28,6 +28,7 @@ public class AnthropicBetaTest : TestBase [InlineData(AnthropicBeta.ModelContextWindowExceeded2025_08_26)] [InlineData(AnthropicBeta.Skills2025_10_02)] [InlineData(AnthropicBeta.FastMode2026_02_01)] + [InlineData(AnthropicBeta.Output300k2026_03_24)] public void Validation_Works(AnthropicBeta rawValue) { // force implicit conversion because Theory can't do that for us @@ -68,6 +69,7 @@ public void InvalidEnumValidationThrows_Works() [InlineData(AnthropicBeta.ModelContextWindowExceeded2025_08_26)] [InlineData(AnthropicBeta.Skills2025_10_02)] [InlineData(AnthropicBeta.FastMode2026_02_01)] + [InlineData(AnthropicBeta.Output300k2026_03_24)] public void SerializationRoundtrip_Works(AnthropicBeta rawValue) { // force implicit conversion because Theory can't do that for us diff --git a/src/Anthropic.Tests/Models/Beta/Files/DeletedFileTest.cs b/src/Anthropic.Tests/Models/Beta/Files/DeletedFileTest.cs index e0a4ace88..02d41524a 100644 --- a/src/Anthropic.Tests/Models/Beta/Files/DeletedFileTest.cs +++ b/src/Anthropic.Tests/Models/Beta/Files/DeletedFileTest.cs @@ -10,9 +10,13 @@ public class DeletedFileTest : TestBase [Fact] public void FieldRoundtrip_Works() { - var model = new DeletedFile { ID = "id", Type = Type.FileDeleted }; + var model = new DeletedFile + { + ID = "file_011CNha8iCJcU1wXNR6q4V8w", + Type = Type.FileDeleted, + }; - string expectedID = "id"; + string expectedID = "file_011CNha8iCJcU1wXNR6q4V8w"; ApiEnum expectedType = Type.FileDeleted; Assert.Equal(expectedID, model.ID); @@ -22,7 +26,11 @@ public void FieldRoundtrip_Works() [Fact] public void SerializationRoundtrip_Works() { - var model = new DeletedFile { ID = "id", Type = Type.FileDeleted }; + var model = new DeletedFile + { + ID = "file_011CNha8iCJcU1wXNR6q4V8w", + Type = Type.FileDeleted, + }; string json = JsonSerializer.Serialize(model, ModelBase.SerializerOptions); var deserialized = JsonSerializer.Deserialize( @@ -36,7 +44,11 @@ public void SerializationRoundtrip_Works() [Fact] public void FieldRoundtripThroughSerialization_Works() { - var model = new DeletedFile { ID = "id", Type = Type.FileDeleted }; + var model = new DeletedFile + { + ID = "file_011CNha8iCJcU1wXNR6q4V8w", + Type = Type.FileDeleted, + }; string element = JsonSerializer.Serialize(model, ModelBase.SerializerOptions); var deserialized = JsonSerializer.Deserialize( @@ -45,7 +57,7 @@ public void FieldRoundtripThroughSerialization_Works() ); Assert.NotNull(deserialized); - string expectedID = "id"; + string expectedID = "file_011CNha8iCJcU1wXNR6q4V8w"; ApiEnum expectedType = Type.FileDeleted; Assert.Equal(expectedID, deserialized.ID); @@ -55,7 +67,11 @@ public void FieldRoundtripThroughSerialization_Works() [Fact] public void Validation_Works() { - var model = new DeletedFile { ID = "id", Type = Type.FileDeleted }; + var model = new DeletedFile + { + ID = "file_011CNha8iCJcU1wXNR6q4V8w", + Type = Type.FileDeleted, + }; model.Validate(); } @@ -63,7 +79,7 @@ public void Validation_Works() [Fact] public void OptionalNonNullablePropertiesUnsetAreNotSet_Works() { - var model = new DeletedFile { ID = "id" }; + var model = new DeletedFile { ID = "file_011CNha8iCJcU1wXNR6q4V8w" }; Assert.Null(model.Type); Assert.False(model.RawData.ContainsKey("type")); @@ -72,7 +88,7 @@ public void OptionalNonNullablePropertiesUnsetAreNotSet_Works() [Fact] public void OptionalNonNullablePropertiesUnsetValidation_Works() { - var model = new DeletedFile { ID = "id" }; + var model = new DeletedFile { ID = "file_011CNha8iCJcU1wXNR6q4V8w" }; model.Validate(); } @@ -82,7 +98,7 @@ public void OptionalNonNullablePropertiesSetToNullAreNotSet_Works() { var model = new DeletedFile { - ID = "id", + ID = "file_011CNha8iCJcU1wXNR6q4V8w", // Null should be interpreted as omitted for these properties Type = null, @@ -97,7 +113,7 @@ public void OptionalNonNullablePropertiesSetToNullValidation_Works() { var model = new DeletedFile { - ID = "id", + ID = "file_011CNha8iCJcU1wXNR6q4V8w", // Null should be interpreted as omitted for these properties Type = null, @@ -109,7 +125,11 @@ public void OptionalNonNullablePropertiesSetToNullValidation_Works() [Fact] public void CopyConstructor_Works() { - var model = new DeletedFile { ID = "id", Type = Type.FileDeleted }; + var model = new DeletedFile + { + ID = "file_011CNha8iCJcU1wXNR6q4V8w", + Type = Type.FileDeleted, + }; DeletedFile copied = new(model); diff --git a/src/Anthropic.Tests/Models/Beta/Files/FileListPageResponseTest.cs b/src/Anthropic.Tests/Models/Beta/Files/FileListPageResponseTest.cs index 40eaad61a..303081c86 100644 --- a/src/Anthropic.Tests/Models/Beta/Files/FileListPageResponseTest.cs +++ b/src/Anthropic.Tests/Models/Beta/Files/FileListPageResponseTest.cs @@ -17,34 +17,34 @@ public void FieldRoundtrip_Works() [ new() { - ID = "id", - CreatedAt = DateTimeOffset.Parse("2019-12-27T18:11:19.117Z"), - Filename = "x", - MimeType = "x", - SizeBytes = 0, - Downloadable = true, + ID = "file_011CNha8iCJcU1wXNR6q4V8w", + CreatedAt = DateTimeOffset.Parse("2025-04-15T18:37:24.100435Z"), + Filename = "document.pdf", + MimeType = "application/pdf", + SizeBytes = 102400, + Downloadable = false, }, ], - FirstID = "first_id", + FirstID = "file_011CNha8iCJcU1wXNR6q4V8w", HasMore = true, - LastID = "last_id", + LastID = "file_013Zva2CMHLNnXjNJJKqJ2EF", }; List expectedData = [ new() { - ID = "id", - CreatedAt = DateTimeOffset.Parse("2019-12-27T18:11:19.117Z"), - Filename = "x", - MimeType = "x", - SizeBytes = 0, - Downloadable = true, + ID = "file_011CNha8iCJcU1wXNR6q4V8w", + CreatedAt = DateTimeOffset.Parse("2025-04-15T18:37:24.100435Z"), + Filename = "document.pdf", + MimeType = "application/pdf", + SizeBytes = 102400, + Downloadable = false, }, ]; - string expectedFirstID = "first_id"; + string expectedFirstID = "file_011CNha8iCJcU1wXNR6q4V8w"; bool expectedHasMore = true; - string expectedLastID = "last_id"; + string expectedLastID = "file_013Zva2CMHLNnXjNJJKqJ2EF"; Assert.Equal(expectedData.Count, model.Data.Count); for (int i = 0; i < expectedData.Count; i++) @@ -65,17 +65,17 @@ public void SerializationRoundtrip_Works() [ new() { - ID = "id", - CreatedAt = DateTimeOffset.Parse("2019-12-27T18:11:19.117Z"), - Filename = "x", - MimeType = "x", - SizeBytes = 0, - Downloadable = true, + ID = "file_011CNha8iCJcU1wXNR6q4V8w", + CreatedAt = DateTimeOffset.Parse("2025-04-15T18:37:24.100435Z"), + Filename = "document.pdf", + MimeType = "application/pdf", + SizeBytes = 102400, + Downloadable = false, }, ], - FirstID = "first_id", + FirstID = "file_011CNha8iCJcU1wXNR6q4V8w", HasMore = true, - LastID = "last_id", + LastID = "file_013Zva2CMHLNnXjNJJKqJ2EF", }; string json = JsonSerializer.Serialize(model, ModelBase.SerializerOptions); @@ -96,17 +96,17 @@ public void FieldRoundtripThroughSerialization_Works() [ new() { - ID = "id", - CreatedAt = DateTimeOffset.Parse("2019-12-27T18:11:19.117Z"), - Filename = "x", - MimeType = "x", - SizeBytes = 0, - Downloadable = true, + ID = "file_011CNha8iCJcU1wXNR6q4V8w", + CreatedAt = DateTimeOffset.Parse("2025-04-15T18:37:24.100435Z"), + Filename = "document.pdf", + MimeType = "application/pdf", + SizeBytes = 102400, + Downloadable = false, }, ], - FirstID = "first_id", + FirstID = "file_011CNha8iCJcU1wXNR6q4V8w", HasMore = true, - LastID = "last_id", + LastID = "file_013Zva2CMHLNnXjNJJKqJ2EF", }; string element = JsonSerializer.Serialize(model, ModelBase.SerializerOptions); @@ -120,17 +120,17 @@ public void FieldRoundtripThroughSerialization_Works() [ new() { - ID = "id", - CreatedAt = DateTimeOffset.Parse("2019-12-27T18:11:19.117Z"), - Filename = "x", - MimeType = "x", - SizeBytes = 0, - Downloadable = true, + ID = "file_011CNha8iCJcU1wXNR6q4V8w", + CreatedAt = DateTimeOffset.Parse("2025-04-15T18:37:24.100435Z"), + Filename = "document.pdf", + MimeType = "application/pdf", + SizeBytes = 102400, + Downloadable = false, }, ]; - string expectedFirstID = "first_id"; + string expectedFirstID = "file_011CNha8iCJcU1wXNR6q4V8w"; bool expectedHasMore = true; - string expectedLastID = "last_id"; + string expectedLastID = "file_013Zva2CMHLNnXjNJJKqJ2EF"; Assert.Equal(expectedData.Count, deserialized.Data.Count); for (int i = 0; i < expectedData.Count; i++) @@ -151,17 +151,17 @@ public void Validation_Works() [ new() { - ID = "id", - CreatedAt = DateTimeOffset.Parse("2019-12-27T18:11:19.117Z"), - Filename = "x", - MimeType = "x", - SizeBytes = 0, - Downloadable = true, + ID = "file_011CNha8iCJcU1wXNR6q4V8w", + CreatedAt = DateTimeOffset.Parse("2025-04-15T18:37:24.100435Z"), + Filename = "document.pdf", + MimeType = "application/pdf", + SizeBytes = 102400, + Downloadable = false, }, ], - FirstID = "first_id", + FirstID = "file_011CNha8iCJcU1wXNR6q4V8w", HasMore = true, - LastID = "last_id", + LastID = "file_013Zva2CMHLNnXjNJJKqJ2EF", }; model.Validate(); @@ -176,16 +176,16 @@ public void OptionalNonNullablePropertiesUnsetAreNotSet_Works() [ new() { - ID = "id", - CreatedAt = DateTimeOffset.Parse("2019-12-27T18:11:19.117Z"), - Filename = "x", - MimeType = "x", - SizeBytes = 0, - Downloadable = true, + ID = "file_011CNha8iCJcU1wXNR6q4V8w", + CreatedAt = DateTimeOffset.Parse("2025-04-15T18:37:24.100435Z"), + Filename = "document.pdf", + MimeType = "application/pdf", + SizeBytes = 102400, + Downloadable = false, }, ], - FirstID = "first_id", - LastID = "last_id", + FirstID = "file_011CNha8iCJcU1wXNR6q4V8w", + LastID = "file_013Zva2CMHLNnXjNJJKqJ2EF", }; Assert.Null(model.HasMore); @@ -201,16 +201,16 @@ public void OptionalNonNullablePropertiesUnsetValidation_Works() [ new() { - ID = "id", - CreatedAt = DateTimeOffset.Parse("2019-12-27T18:11:19.117Z"), - Filename = "x", - MimeType = "x", - SizeBytes = 0, - Downloadable = true, + ID = "file_011CNha8iCJcU1wXNR6q4V8w", + CreatedAt = DateTimeOffset.Parse("2025-04-15T18:37:24.100435Z"), + Filename = "document.pdf", + MimeType = "application/pdf", + SizeBytes = 102400, + Downloadable = false, }, ], - FirstID = "first_id", - LastID = "last_id", + FirstID = "file_011CNha8iCJcU1wXNR6q4V8w", + LastID = "file_013Zva2CMHLNnXjNJJKqJ2EF", }; model.Validate(); @@ -225,16 +225,16 @@ public void OptionalNonNullablePropertiesSetToNullAreNotSet_Works() [ new() { - ID = "id", - CreatedAt = DateTimeOffset.Parse("2019-12-27T18:11:19.117Z"), - Filename = "x", - MimeType = "x", - SizeBytes = 0, - Downloadable = true, + ID = "file_011CNha8iCJcU1wXNR6q4V8w", + CreatedAt = DateTimeOffset.Parse("2025-04-15T18:37:24.100435Z"), + Filename = "document.pdf", + MimeType = "application/pdf", + SizeBytes = 102400, + Downloadable = false, }, ], - FirstID = "first_id", - LastID = "last_id", + FirstID = "file_011CNha8iCJcU1wXNR6q4V8w", + LastID = "file_013Zva2CMHLNnXjNJJKqJ2EF", // Null should be interpreted as omitted for these properties HasMore = null, @@ -253,16 +253,16 @@ public void OptionalNonNullablePropertiesSetToNullValidation_Works() [ new() { - ID = "id", - CreatedAt = DateTimeOffset.Parse("2019-12-27T18:11:19.117Z"), - Filename = "x", - MimeType = "x", - SizeBytes = 0, - Downloadable = true, + ID = "file_011CNha8iCJcU1wXNR6q4V8w", + CreatedAt = DateTimeOffset.Parse("2025-04-15T18:37:24.100435Z"), + Filename = "document.pdf", + MimeType = "application/pdf", + SizeBytes = 102400, + Downloadable = false, }, ], - FirstID = "first_id", - LastID = "last_id", + FirstID = "file_011CNha8iCJcU1wXNR6q4V8w", + LastID = "file_013Zva2CMHLNnXjNJJKqJ2EF", // Null should be interpreted as omitted for these properties HasMore = null, @@ -280,12 +280,12 @@ public void OptionalNullablePropertiesUnsetAreNotSet_Works() [ new() { - ID = "id", - CreatedAt = DateTimeOffset.Parse("2019-12-27T18:11:19.117Z"), - Filename = "x", - MimeType = "x", - SizeBytes = 0, - Downloadable = true, + ID = "file_011CNha8iCJcU1wXNR6q4V8w", + CreatedAt = DateTimeOffset.Parse("2025-04-15T18:37:24.100435Z"), + Filename = "document.pdf", + MimeType = "application/pdf", + SizeBytes = 102400, + Downloadable = false, }, ], HasMore = true, @@ -306,12 +306,12 @@ public void OptionalNullablePropertiesUnsetValidation_Works() [ new() { - ID = "id", - CreatedAt = DateTimeOffset.Parse("2019-12-27T18:11:19.117Z"), - Filename = "x", - MimeType = "x", - SizeBytes = 0, - Downloadable = true, + ID = "file_011CNha8iCJcU1wXNR6q4V8w", + CreatedAt = DateTimeOffset.Parse("2025-04-15T18:37:24.100435Z"), + Filename = "document.pdf", + MimeType = "application/pdf", + SizeBytes = 102400, + Downloadable = false, }, ], HasMore = true, @@ -329,12 +329,12 @@ public void OptionalNullablePropertiesSetToNullAreSetToNull_Works() [ new() { - ID = "id", - CreatedAt = DateTimeOffset.Parse("2019-12-27T18:11:19.117Z"), - Filename = "x", - MimeType = "x", - SizeBytes = 0, - Downloadable = true, + ID = "file_011CNha8iCJcU1wXNR6q4V8w", + CreatedAt = DateTimeOffset.Parse("2025-04-15T18:37:24.100435Z"), + Filename = "document.pdf", + MimeType = "application/pdf", + SizeBytes = 102400, + Downloadable = false, }, ], HasMore = true, @@ -358,12 +358,12 @@ public void OptionalNullablePropertiesSetToNullValidation_Works() [ new() { - ID = "id", - CreatedAt = DateTimeOffset.Parse("2019-12-27T18:11:19.117Z"), - Filename = "x", - MimeType = "x", - SizeBytes = 0, - Downloadable = true, + ID = "file_011CNha8iCJcU1wXNR6q4V8w", + CreatedAt = DateTimeOffset.Parse("2025-04-15T18:37:24.100435Z"), + Filename = "document.pdf", + MimeType = "application/pdf", + SizeBytes = 102400, + Downloadable = false, }, ], HasMore = true, @@ -384,17 +384,17 @@ public void CopyConstructor_Works() [ new() { - ID = "id", - CreatedAt = DateTimeOffset.Parse("2019-12-27T18:11:19.117Z"), - Filename = "x", - MimeType = "x", - SizeBytes = 0, - Downloadable = true, + ID = "file_011CNha8iCJcU1wXNR6q4V8w", + CreatedAt = DateTimeOffset.Parse("2025-04-15T18:37:24.100435Z"), + Filename = "document.pdf", + MimeType = "application/pdf", + SizeBytes = 102400, + Downloadable = false, }, ], - FirstID = "first_id", + FirstID = "file_011CNha8iCJcU1wXNR6q4V8w", HasMore = true, - LastID = "last_id", + LastID = "file_013Zva2CMHLNnXjNJJKqJ2EF", }; FileListPageResponse copied = new(model); diff --git a/src/Anthropic.Tests/Models/Beta/Files/FileMetadataTest.cs b/src/Anthropic.Tests/Models/Beta/Files/FileMetadataTest.cs index a02048493..10feabfca 100644 --- a/src/Anthropic.Tests/Models/Beta/Files/FileMetadataTest.cs +++ b/src/Anthropic.Tests/Models/Beta/Files/FileMetadataTest.cs @@ -12,21 +12,21 @@ public void FieldRoundtrip_Works() { var model = new FileMetadata { - ID = "id", - CreatedAt = DateTimeOffset.Parse("2019-12-27T18:11:19.117Z"), - Filename = "x", - MimeType = "x", - SizeBytes = 0, - Downloadable = true, + ID = "file_011CNha8iCJcU1wXNR6q4V8w", + CreatedAt = DateTimeOffset.Parse("2025-04-15T18:37:24.100435Z"), + Filename = "document.pdf", + MimeType = "application/pdf", + SizeBytes = 102400, + Downloadable = false, }; - string expectedID = "id"; - DateTimeOffset expectedCreatedAt = DateTimeOffset.Parse("2019-12-27T18:11:19.117Z"); - string expectedFilename = "x"; - string expectedMimeType = "x"; - long expectedSizeBytes = 0; + string expectedID = "file_011CNha8iCJcU1wXNR6q4V8w"; + DateTimeOffset expectedCreatedAt = DateTimeOffset.Parse("2025-04-15T18:37:24.100435Z"); + string expectedFilename = "document.pdf"; + string expectedMimeType = "application/pdf"; + long expectedSizeBytes = 102400; JsonElement expectedType = JsonSerializer.SerializeToElement("file"); - bool expectedDownloadable = true; + bool expectedDownloadable = false; Assert.Equal(expectedID, model.ID); Assert.Equal(expectedCreatedAt, model.CreatedAt); @@ -42,12 +42,12 @@ public void SerializationRoundtrip_Works() { var model = new FileMetadata { - ID = "id", - CreatedAt = DateTimeOffset.Parse("2019-12-27T18:11:19.117Z"), - Filename = "x", - MimeType = "x", - SizeBytes = 0, - Downloadable = true, + ID = "file_011CNha8iCJcU1wXNR6q4V8w", + CreatedAt = DateTimeOffset.Parse("2025-04-15T18:37:24.100435Z"), + Filename = "document.pdf", + MimeType = "application/pdf", + SizeBytes = 102400, + Downloadable = false, }; string json = JsonSerializer.Serialize(model, ModelBase.SerializerOptions); @@ -64,12 +64,12 @@ public void FieldRoundtripThroughSerialization_Works() { var model = new FileMetadata { - ID = "id", - CreatedAt = DateTimeOffset.Parse("2019-12-27T18:11:19.117Z"), - Filename = "x", - MimeType = "x", - SizeBytes = 0, - Downloadable = true, + ID = "file_011CNha8iCJcU1wXNR6q4V8w", + CreatedAt = DateTimeOffset.Parse("2025-04-15T18:37:24.100435Z"), + Filename = "document.pdf", + MimeType = "application/pdf", + SizeBytes = 102400, + Downloadable = false, }; string element = JsonSerializer.Serialize(model, ModelBase.SerializerOptions); @@ -79,13 +79,13 @@ public void FieldRoundtripThroughSerialization_Works() ); Assert.NotNull(deserialized); - string expectedID = "id"; - DateTimeOffset expectedCreatedAt = DateTimeOffset.Parse("2019-12-27T18:11:19.117Z"); - string expectedFilename = "x"; - string expectedMimeType = "x"; - long expectedSizeBytes = 0; + string expectedID = "file_011CNha8iCJcU1wXNR6q4V8w"; + DateTimeOffset expectedCreatedAt = DateTimeOffset.Parse("2025-04-15T18:37:24.100435Z"); + string expectedFilename = "document.pdf"; + string expectedMimeType = "application/pdf"; + long expectedSizeBytes = 102400; JsonElement expectedType = JsonSerializer.SerializeToElement("file"); - bool expectedDownloadable = true; + bool expectedDownloadable = false; Assert.Equal(expectedID, deserialized.ID); Assert.Equal(expectedCreatedAt, deserialized.CreatedAt); @@ -101,12 +101,12 @@ public void Validation_Works() { var model = new FileMetadata { - ID = "id", - CreatedAt = DateTimeOffset.Parse("2019-12-27T18:11:19.117Z"), - Filename = "x", - MimeType = "x", - SizeBytes = 0, - Downloadable = true, + ID = "file_011CNha8iCJcU1wXNR6q4V8w", + CreatedAt = DateTimeOffset.Parse("2025-04-15T18:37:24.100435Z"), + Filename = "document.pdf", + MimeType = "application/pdf", + SizeBytes = 102400, + Downloadable = false, }; model.Validate(); @@ -117,11 +117,11 @@ public void OptionalNonNullablePropertiesUnsetAreNotSet_Works() { var model = new FileMetadata { - ID = "id", - CreatedAt = DateTimeOffset.Parse("2019-12-27T18:11:19.117Z"), - Filename = "x", - MimeType = "x", - SizeBytes = 0, + ID = "file_011CNha8iCJcU1wXNR6q4V8w", + CreatedAt = DateTimeOffset.Parse("2025-04-15T18:37:24.100435Z"), + Filename = "document.pdf", + MimeType = "application/pdf", + SizeBytes = 102400, }; Assert.Null(model.Downloadable); @@ -133,11 +133,11 @@ public void OptionalNonNullablePropertiesUnsetValidation_Works() { var model = new FileMetadata { - ID = "id", - CreatedAt = DateTimeOffset.Parse("2019-12-27T18:11:19.117Z"), - Filename = "x", - MimeType = "x", - SizeBytes = 0, + ID = "file_011CNha8iCJcU1wXNR6q4V8w", + CreatedAt = DateTimeOffset.Parse("2025-04-15T18:37:24.100435Z"), + Filename = "document.pdf", + MimeType = "application/pdf", + SizeBytes = 102400, }; model.Validate(); @@ -148,11 +148,11 @@ public void OptionalNonNullablePropertiesSetToNullAreNotSet_Works() { var model = new FileMetadata { - ID = "id", - CreatedAt = DateTimeOffset.Parse("2019-12-27T18:11:19.117Z"), - Filename = "x", - MimeType = "x", - SizeBytes = 0, + ID = "file_011CNha8iCJcU1wXNR6q4V8w", + CreatedAt = DateTimeOffset.Parse("2025-04-15T18:37:24.100435Z"), + Filename = "document.pdf", + MimeType = "application/pdf", + SizeBytes = 102400, // Null should be interpreted as omitted for these properties Downloadable = null, @@ -167,11 +167,11 @@ public void OptionalNonNullablePropertiesSetToNullValidation_Works() { var model = new FileMetadata { - ID = "id", - CreatedAt = DateTimeOffset.Parse("2019-12-27T18:11:19.117Z"), - Filename = "x", - MimeType = "x", - SizeBytes = 0, + ID = "file_011CNha8iCJcU1wXNR6q4V8w", + CreatedAt = DateTimeOffset.Parse("2025-04-15T18:37:24.100435Z"), + Filename = "document.pdf", + MimeType = "application/pdf", + SizeBytes = 102400, // Null should be interpreted as omitted for these properties Downloadable = null, @@ -185,12 +185,12 @@ public void CopyConstructor_Works() { var model = new FileMetadata { - ID = "id", - CreatedAt = DateTimeOffset.Parse("2019-12-27T18:11:19.117Z"), - Filename = "x", - MimeType = "x", - SizeBytes = 0, - Downloadable = true, + ID = "file_011CNha8iCJcU1wXNR6q4V8w", + CreatedAt = DateTimeOffset.Parse("2025-04-15T18:37:24.100435Z"), + Filename = "document.pdf", + MimeType = "application/pdf", + SizeBytes = 102400, + Downloadable = false, }; FileMetadata copied = new(model); diff --git a/src/Anthropic.Tests/Models/Beta/Messages/Batches/BatchCreateParamsTest.cs b/src/Anthropic.Tests/Models/Beta/Messages/Batches/BatchCreateParamsTest.cs index e48a4fced..78e51fbfe 100644 --- a/src/Anthropic.Tests/Models/Beta/Messages/Batches/BatchCreateParamsTest.cs +++ b/src/Anthropic.Tests/Models/Beta/Messages/Batches/BatchCreateParamsTest.cs @@ -36,9 +36,9 @@ public void FieldRoundtrip_Works() [ new() { - SkillID = "x", + SkillID = "pdf", Type = Messages::BetaSkillParamsType.Anthropic, - Version = "x", + Version = "latest", }, ], }, @@ -115,7 +115,10 @@ public void FieldRoundtrip_Works() ] ), Temperature = 1, - Thinking = new Messages::BetaThinkingConfigEnabled(1024), + Thinking = new Messages::BetaThinkingConfigAdaptive() + { + Display = Messages::Display.Summarized, + }, ToolChoice = new Messages::BetaToolChoiceAuto() { DisableParallelToolUse = true, @@ -176,9 +179,9 @@ public void FieldRoundtrip_Works() [ new() { - SkillID = "x", + SkillID = "pdf", Type = Messages::BetaSkillParamsType.Anthropic, - Version = "x", + Version = "latest", }, ], }, @@ -251,7 +254,10 @@ public void FieldRoundtrip_Works() ] ), Temperature = 1, - Thinking = new Messages::BetaThinkingConfigEnabled(1024), + Thinking = new Messages::BetaThinkingConfigAdaptive() + { + Display = Messages::Display.Summarized, + }, ToolChoice = new Messages::BetaToolChoiceAuto() { DisableParallelToolUse = true, @@ -332,9 +338,9 @@ public void OptionalNonNullableParamsUnsetAreNotSet_Works() [ new() { - SkillID = "x", + SkillID = "pdf", Type = Messages::BetaSkillParamsType.Anthropic, - Version = "x", + Version = "latest", }, ], }, @@ -411,7 +417,10 @@ public void OptionalNonNullableParamsUnsetAreNotSet_Works() ] ), Temperature = 1, - Thinking = new Messages::BetaThinkingConfigEnabled(1024), + Thinking = new Messages::BetaThinkingConfigAdaptive() + { + Display = Messages::Display.Summarized, + }, ToolChoice = new Messages::BetaToolChoiceAuto() { DisableParallelToolUse = true, @@ -480,9 +489,9 @@ public void OptionalNonNullableParamsSetToNullAreNotSet_Works() [ new() { - SkillID = "x", + SkillID = "pdf", Type = Messages::BetaSkillParamsType.Anthropic, - Version = "x", + Version = "latest", }, ], }, @@ -559,7 +568,10 @@ public void OptionalNonNullableParamsSetToNullAreNotSet_Works() ] ), Temperature = 1, - Thinking = new Messages::BetaThinkingConfigEnabled(1024), + Thinking = new Messages::BetaThinkingConfigAdaptive() + { + Display = Messages::Display.Summarized, + }, ToolChoice = new Messages::BetaToolChoiceAuto() { DisableParallelToolUse = true, @@ -631,9 +643,9 @@ public void Url_Works() [ new() { - SkillID = "x", + SkillID = "pdf", Type = Messages::BetaSkillParamsType.Anthropic, - Version = "x", + Version = "latest", }, ], }, @@ -710,7 +722,10 @@ public void Url_Works() ] ), Temperature = 1, - Thinking = new Messages::BetaThinkingConfigEnabled(1024), + Thinking = new Messages::BetaThinkingConfigAdaptive() + { + Display = Messages::Display.Summarized, + }, ToolChoice = new Messages::BetaToolChoiceAuto() { DisableParallelToolUse = true, @@ -781,9 +796,9 @@ public void AddHeadersToRequest_Works() [ new() { - SkillID = "x", + SkillID = "pdf", Type = Messages::BetaSkillParamsType.Anthropic, - Version = "x", + Version = "latest", }, ], }, @@ -860,7 +875,10 @@ public void AddHeadersToRequest_Works() ] ), Temperature = 1, - Thinking = new Messages::BetaThinkingConfigEnabled(1024), + Thinking = new Messages::BetaThinkingConfigAdaptive() + { + Display = Messages::Display.Summarized, + }, ToolChoice = new Messages::BetaToolChoiceAuto() { DisableParallelToolUse = true, @@ -934,9 +952,9 @@ public void CopyConstructor_Works() [ new() { - SkillID = "x", + SkillID = "pdf", Type = Messages::BetaSkillParamsType.Anthropic, - Version = "x", + Version = "latest", }, ], }, @@ -1013,7 +1031,10 @@ public void CopyConstructor_Works() ] ), Temperature = 1, - Thinking = new Messages::BetaThinkingConfigEnabled(1024), + Thinking = new Messages::BetaThinkingConfigAdaptive() + { + Display = Messages::Display.Summarized, + }, ToolChoice = new Messages::BetaToolChoiceAuto() { DisableParallelToolUse = true, @@ -1083,9 +1104,9 @@ public void FieldRoundtrip_Works() [ new() { - SkillID = "x", + SkillID = "pdf", Type = Messages::BetaSkillParamsType.Anthropic, - Version = "x", + Version = "latest", }, ], }, @@ -1158,7 +1179,10 @@ public void FieldRoundtrip_Works() ] ), Temperature = 1, - Thinking = new Messages::BetaThinkingConfigEnabled(1024), + Thinking = new Messages::BetaThinkingConfigAdaptive() + { + Display = Messages::Display.Summarized, + }, ToolChoice = new Messages::BetaToolChoiceAuto() { DisableParallelToolUse = true }, Tools = [ @@ -1209,9 +1233,9 @@ public void FieldRoundtrip_Works() [ new() { - SkillID = "x", + SkillID = "pdf", Type = Messages::BetaSkillParamsType.Anthropic, - Version = "x", + Version = "latest", }, ], }, @@ -1284,7 +1308,10 @@ public void FieldRoundtrip_Works() ] ), Temperature = 1, - Thinking = new Messages::BetaThinkingConfigEnabled(1024), + Thinking = new Messages::BetaThinkingConfigAdaptive() + { + Display = Messages::Display.Summarized, + }, ToolChoice = new Messages::BetaToolChoiceAuto() { DisableParallelToolUse = true }, Tools = [ @@ -1343,9 +1370,9 @@ public void SerializationRoundtrip_Works() [ new() { - SkillID = "x", + SkillID = "pdf", Type = Messages::BetaSkillParamsType.Anthropic, - Version = "x", + Version = "latest", }, ], }, @@ -1418,7 +1445,10 @@ public void SerializationRoundtrip_Works() ] ), Temperature = 1, - Thinking = new Messages::BetaThinkingConfigEnabled(1024), + Thinking = new Messages::BetaThinkingConfigAdaptive() + { + Display = Messages::Display.Summarized, + }, ToolChoice = new Messages::BetaToolChoiceAuto() { DisableParallelToolUse = true }, Tools = [ @@ -1480,9 +1510,9 @@ public void FieldRoundtripThroughSerialization_Works() [ new() { - SkillID = "x", + SkillID = "pdf", Type = Messages::BetaSkillParamsType.Anthropic, - Version = "x", + Version = "latest", }, ], }, @@ -1555,7 +1585,10 @@ public void FieldRoundtripThroughSerialization_Works() ] ), Temperature = 1, - Thinking = new Messages::BetaThinkingConfigEnabled(1024), + Thinking = new Messages::BetaThinkingConfigAdaptive() + { + Display = Messages::Display.Summarized, + }, ToolChoice = new Messages::BetaToolChoiceAuto() { DisableParallelToolUse = true }, Tools = [ @@ -1613,9 +1646,9 @@ public void FieldRoundtripThroughSerialization_Works() [ new() { - SkillID = "x", + SkillID = "pdf", Type = Messages::BetaSkillParamsType.Anthropic, - Version = "x", + Version = "latest", }, ], }, @@ -1688,7 +1721,10 @@ public void FieldRoundtripThroughSerialization_Works() ] ), Temperature = 1, - Thinking = new Messages::BetaThinkingConfigEnabled(1024), + Thinking = new Messages::BetaThinkingConfigAdaptive() + { + Display = Messages::Display.Summarized, + }, ToolChoice = new Messages::BetaToolChoiceAuto() { DisableParallelToolUse = true }, Tools = [ @@ -1747,9 +1783,9 @@ public void Validation_Works() [ new() { - SkillID = "x", + SkillID = "pdf", Type = Messages::BetaSkillParamsType.Anthropic, - Version = "x", + Version = "latest", }, ], }, @@ -1822,7 +1858,10 @@ public void Validation_Works() ] ), Temperature = 1, - Thinking = new Messages::BetaThinkingConfigEnabled(1024), + Thinking = new Messages::BetaThinkingConfigAdaptive() + { + Display = Messages::Display.Summarized, + }, ToolChoice = new Messages::BetaToolChoiceAuto() { DisableParallelToolUse = true }, Tools = [ @@ -1881,9 +1920,9 @@ public void CopyConstructor_Works() [ new() { - SkillID = "x", + SkillID = "pdf", Type = Messages::BetaSkillParamsType.Anthropic, - Version = "x", + Version = "latest", }, ], }, @@ -1956,7 +1995,10 @@ public void CopyConstructor_Works() ] ), Temperature = 1, - Thinking = new Messages::BetaThinkingConfigEnabled(1024), + Thinking = new Messages::BetaThinkingConfigAdaptive() + { + Display = Messages::Display.Summarized, + }, ToolChoice = new Messages::BetaToolChoiceAuto() { DisableParallelToolUse = true }, Tools = [ @@ -2017,9 +2059,9 @@ public void FieldRoundtrip_Works() [ new() { - SkillID = "x", + SkillID = "pdf", Type = Messages::BetaSkillParamsType.Anthropic, - Version = "x", + Version = "latest", }, ], }, @@ -2092,7 +2134,10 @@ public void FieldRoundtrip_Works() ] ), Temperature = 1, - Thinking = new Messages::BetaThinkingConfigEnabled(1024), + Thinking = new Messages::BetaThinkingConfigAdaptive() + { + Display = Messages::Display.Summarized, + }, ToolChoice = new Messages::BetaToolChoiceAuto() { DisableParallelToolUse = true }, Tools = [ @@ -2145,9 +2190,9 @@ public void FieldRoundtrip_Works() [ new() { - SkillID = "x", + SkillID = "pdf", Type = Messages::BetaSkillParamsType.Anthropic, - Version = "x", + Version = "latest", }, ], }; @@ -2224,7 +2269,7 @@ public void FieldRoundtrip_Works() ); double expectedTemperature = 1; Messages::BetaThinkingConfigParam expectedThinking = - new Messages::BetaThinkingConfigEnabled(1024); + new Messages::BetaThinkingConfigAdaptive() { Display = Messages::Display.Summarized }; Messages::BetaToolChoice expectedToolChoice = new Messages::BetaToolChoiceAuto() { DisableParallelToolUse = true, @@ -2321,9 +2366,9 @@ public void SerializationRoundtrip_Works() [ new() { - SkillID = "x", + SkillID = "pdf", Type = Messages::BetaSkillParamsType.Anthropic, - Version = "x", + Version = "latest", }, ], }, @@ -2396,7 +2441,10 @@ public void SerializationRoundtrip_Works() ] ), Temperature = 1, - Thinking = new Messages::BetaThinkingConfigEnabled(1024), + Thinking = new Messages::BetaThinkingConfigAdaptive() + { + Display = Messages::Display.Summarized, + }, ToolChoice = new Messages::BetaToolChoiceAuto() { DisableParallelToolUse = true }, Tools = [ @@ -2454,9 +2502,9 @@ public void FieldRoundtripThroughSerialization_Works() [ new() { - SkillID = "x", + SkillID = "pdf", Type = Messages::BetaSkillParamsType.Anthropic, - Version = "x", + Version = "latest", }, ], }, @@ -2529,7 +2577,10 @@ public void FieldRoundtripThroughSerialization_Works() ] ), Temperature = 1, - Thinking = new Messages::BetaThinkingConfigEnabled(1024), + Thinking = new Messages::BetaThinkingConfigAdaptive() + { + Display = Messages::Display.Summarized, + }, ToolChoice = new Messages::BetaToolChoiceAuto() { DisableParallelToolUse = true }, Tools = [ @@ -2586,9 +2637,9 @@ public void FieldRoundtripThroughSerialization_Works() [ new() { - SkillID = "x", + SkillID = "pdf", Type = Messages::BetaSkillParamsType.Anthropic, - Version = "x", + Version = "latest", }, ], }; @@ -2665,7 +2716,7 @@ public void FieldRoundtripThroughSerialization_Works() ); double expectedTemperature = 1; Messages::BetaThinkingConfigParam expectedThinking = - new Messages::BetaThinkingConfigEnabled(1024); + new Messages::BetaThinkingConfigAdaptive() { Display = Messages::Display.Summarized }; Messages::BetaToolChoice expectedToolChoice = new Messages::BetaToolChoiceAuto() { DisableParallelToolUse = true, @@ -2762,9 +2813,9 @@ public void Validation_Works() [ new() { - SkillID = "x", + SkillID = "pdf", Type = Messages::BetaSkillParamsType.Anthropic, - Version = "x", + Version = "latest", }, ], }, @@ -2837,7 +2888,10 @@ public void Validation_Works() ] ), Temperature = 1, - Thinking = new Messages::BetaThinkingConfigEnabled(1024), + Thinking = new Messages::BetaThinkingConfigAdaptive() + { + Display = Messages::Display.Summarized, + }, ToolChoice = new Messages::BetaToolChoiceAuto() { DisableParallelToolUse = true }, Tools = [ @@ -2892,9 +2946,9 @@ public void OptionalNonNullablePropertiesUnsetAreNotSet_Works() [ new() { - SkillID = "x", + SkillID = "pdf", Type = Messages::BetaSkillParamsType.Anthropic, - Version = "x", + Version = "latest", }, ], }, @@ -2967,9 +3021,9 @@ public void OptionalNonNullablePropertiesUnsetValidation_Works() [ new() { - SkillID = "x", + SkillID = "pdf", Type = Messages::BetaSkillParamsType.Anthropic, - Version = "x", + Version = "latest", }, ], }, @@ -3017,9 +3071,9 @@ public void OptionalNonNullablePropertiesSetToNullAreNotSet_Works() [ new() { - SkillID = "x", + SkillID = "pdf", Type = Messages::BetaSkillParamsType.Anthropic, - Version = "x", + Version = "latest", }, ], }, @@ -3107,9 +3161,9 @@ public void OptionalNonNullablePropertiesSetToNullValidation_Works() [ new() { - SkillID = "x", + SkillID = "pdf", Type = Messages::BetaSkillParamsType.Anthropic, - Version = "x", + Version = "latest", }, ], }, @@ -3210,7 +3264,10 @@ public void OptionalNullablePropertiesUnsetAreNotSet_Works() ] ), Temperature = 1, - Thinking = new Messages::BetaThinkingConfigEnabled(1024), + Thinking = new Messages::BetaThinkingConfigAdaptive() + { + Display = Messages::Display.Summarized, + }, ToolChoice = new Messages::BetaToolChoiceAuto() { DisableParallelToolUse = true }, Tools = [ @@ -3314,7 +3371,10 @@ public void OptionalNullablePropertiesUnsetValidation_Works() ] ), Temperature = 1, - Thinking = new Messages::BetaThinkingConfigEnabled(1024), + Thinking = new Messages::BetaThinkingConfigAdaptive() + { + Display = Messages::Display.Summarized, + }, ToolChoice = new Messages::BetaToolChoiceAuto() { DisableParallelToolUse = true }, Tools = [ @@ -3407,7 +3467,10 @@ public void OptionalNullablePropertiesSetToNullAreSetToNull_Works() ] ), Temperature = 1, - Thinking = new Messages::BetaThinkingConfigEnabled(1024), + Thinking = new Messages::BetaThinkingConfigAdaptive() + { + Display = Messages::Display.Summarized, + }, ToolChoice = new Messages::BetaToolChoiceAuto() { DisableParallelToolUse = true }, Tools = [ @@ -3518,7 +3581,10 @@ public void OptionalNullablePropertiesSetToNullValidation_Works() ] ), Temperature = 1, - Thinking = new Messages::BetaThinkingConfigEnabled(1024), + Thinking = new Messages::BetaThinkingConfigAdaptive() + { + Display = Messages::Display.Summarized, + }, ToolChoice = new Messages::BetaToolChoiceAuto() { DisableParallelToolUse = true }, Tools = [ @@ -3580,9 +3646,9 @@ public void CopyConstructor_Works() [ new() { - SkillID = "x", + SkillID = "pdf", Type = Messages::BetaSkillParamsType.Anthropic, - Version = "x", + Version = "latest", }, ], }, @@ -3655,7 +3721,10 @@ public void CopyConstructor_Works() ] ), Temperature = 1, - Thinking = new Messages::BetaThinkingConfigEnabled(1024), + Thinking = new Messages::BetaThinkingConfigAdaptive() + { + Display = Messages::Display.Summarized, + }, ToolChoice = new Messages::BetaToolChoiceAuto() { DisableParallelToolUse = true }, Tools = [ @@ -3709,9 +3778,9 @@ public void BetaContainerParamsValidationWorks() [ new() { - SkillID = "x", + SkillID = "pdf", Type = Messages::BetaSkillParamsType.Anthropic, - Version = "x", + Version = "latest", }, ], }; @@ -3735,9 +3804,9 @@ public void BetaContainerParamsSerializationRoundtripWorks() [ new() { - SkillID = "x", + SkillID = "pdf", Type = Messages::BetaSkillParamsType.Anthropic, - Version = "x", + Version = "latest", }, ], }; diff --git a/src/Anthropic.Tests/Models/Beta/Messages/Batches/BetaMessageBatchIndividualResponseTest.cs b/src/Anthropic.Tests/Models/Beta/Messages/Batches/BetaMessageBatchIndividualResponseTest.cs index 5c2360bf7..1ac9c15e1 100644 --- a/src/Anthropic.Tests/Models/Beta/Messages/Batches/BetaMessageBatchIndividualResponseTest.cs +++ b/src/Anthropic.Tests/Models/Beta/Messages/Batches/BetaMessageBatchIndividualResponseTest.cs @@ -27,9 +27,9 @@ public void FieldRoundtrip_Works() [ new() { - SkillID = "x", + SkillID = "pdf", Type = Messages::Type.Anthropic, - Version = "x", + Version = "latest", }, ], }, @@ -112,9 +112,9 @@ public void FieldRoundtrip_Works() [ new() { - SkillID = "x", + SkillID = "pdf", Type = Messages::Type.Anthropic, - Version = "x", + Version = "latest", }, ], }, @@ -205,9 +205,9 @@ public void SerializationRoundtrip_Works() [ new() { - SkillID = "x", + SkillID = "pdf", Type = Messages::Type.Anthropic, - Version = "x", + Version = "latest", }, ], }, @@ -304,9 +304,9 @@ public void FieldRoundtripThroughSerialization_Works() [ new() { - SkillID = "x", + SkillID = "pdf", Type = Messages::Type.Anthropic, - Version = "x", + Version = "latest", }, ], }, @@ -396,9 +396,9 @@ public void FieldRoundtripThroughSerialization_Works() [ new() { - SkillID = "x", + SkillID = "pdf", Type = Messages::Type.Anthropic, - Version = "x", + Version = "latest", }, ], }, @@ -489,9 +489,9 @@ public void Validation_Works() [ new() { - SkillID = "x", + SkillID = "pdf", Type = Messages::Type.Anthropic, - Version = "x", + Version = "latest", }, ], }, @@ -582,9 +582,9 @@ public void CopyConstructor_Works() [ new() { - SkillID = "x", + SkillID = "pdf", Type = Messages::Type.Anthropic, - Version = "x", + Version = "latest", }, ], }, diff --git a/src/Anthropic.Tests/Models/Beta/Messages/Batches/BetaMessageBatchResultTest.cs b/src/Anthropic.Tests/Models/Beta/Messages/Batches/BetaMessageBatchResultTest.cs index 6cbf6cc7f..7a36b5e4c 100644 --- a/src/Anthropic.Tests/Models/Beta/Messages/Batches/BetaMessageBatchResultTest.cs +++ b/src/Anthropic.Tests/Models/Beta/Messages/Batches/BetaMessageBatchResultTest.cs @@ -25,9 +25,9 @@ public void SucceededValidationWorks() [ new() { - SkillID = "x", + SkillID = "pdf", Type = Messages::Type.Anthropic, - Version = "x", + Version = "latest", }, ], }, @@ -140,9 +140,9 @@ public void SucceededSerializationRoundtripWorks() [ new() { - SkillID = "x", + SkillID = "pdf", Type = Messages::Type.Anthropic, - Version = "x", + Version = "latest", }, ], }, diff --git a/src/Anthropic.Tests/Models/Beta/Messages/Batches/BetaMessageBatchSucceededResultTest.cs b/src/Anthropic.Tests/Models/Beta/Messages/Batches/BetaMessageBatchSucceededResultTest.cs index e851b9f32..aaface610 100644 --- a/src/Anthropic.Tests/Models/Beta/Messages/Batches/BetaMessageBatchSucceededResultTest.cs +++ b/src/Anthropic.Tests/Models/Beta/Messages/Batches/BetaMessageBatchSucceededResultTest.cs @@ -25,9 +25,9 @@ public void FieldRoundtrip_Works() [ new() { - SkillID = "x", + SkillID = "pdf", Type = Messages::Type.Anthropic, - Version = "x", + Version = "latest", }, ], }, @@ -107,9 +107,9 @@ public void FieldRoundtrip_Works() [ new() { - SkillID = "x", + SkillID = "pdf", Type = Messages::Type.Anthropic, - Version = "x", + Version = "latest", }, ], }, @@ -194,9 +194,9 @@ public void SerializationRoundtrip_Works() [ new() { - SkillID = "x", + SkillID = "pdf", Type = Messages::Type.Anthropic, - Version = "x", + Version = "latest", }, ], }, @@ -290,9 +290,9 @@ public void FieldRoundtripThroughSerialization_Works() [ new() { - SkillID = "x", + SkillID = "pdf", Type = Messages::Type.Anthropic, - Version = "x", + Version = "latest", }, ], }, @@ -379,9 +379,9 @@ public void FieldRoundtripThroughSerialization_Works() [ new() { - SkillID = "x", + SkillID = "pdf", Type = Messages::Type.Anthropic, - Version = "x", + Version = "latest", }, ], }, @@ -466,9 +466,9 @@ public void Validation_Works() [ new() { - SkillID = "x", + SkillID = "pdf", Type = Messages::Type.Anthropic, - Version = "x", + Version = "latest", }, ], }, @@ -556,9 +556,9 @@ public void CopyConstructor_Works() [ new() { - SkillID = "x", + SkillID = "pdf", Type = Messages::Type.Anthropic, - Version = "x", + Version = "latest", }, ], }, diff --git a/src/Anthropic.Tests/Models/Beta/Messages/BetaContainerParamsTest.cs b/src/Anthropic.Tests/Models/Beta/Messages/BetaContainerParamsTest.cs index 0d28b42a3..508e71ab1 100644 --- a/src/Anthropic.Tests/Models/Beta/Messages/BetaContainerParamsTest.cs +++ b/src/Anthropic.Tests/Models/Beta/Messages/BetaContainerParamsTest.cs @@ -17,9 +17,9 @@ public void FieldRoundtrip_Works() [ new() { - SkillID = "x", + SkillID = "pdf", Type = BetaSkillParamsType.Anthropic, - Version = "x", + Version = "latest", }, ], }; @@ -29,9 +29,9 @@ public void FieldRoundtrip_Works() [ new() { - SkillID = "x", + SkillID = "pdf", Type = BetaSkillParamsType.Anthropic, - Version = "x", + Version = "latest", }, ]; @@ -54,9 +54,9 @@ public void SerializationRoundtrip_Works() [ new() { - SkillID = "x", + SkillID = "pdf", Type = BetaSkillParamsType.Anthropic, - Version = "x", + Version = "latest", }, ], }; @@ -80,9 +80,9 @@ public void FieldRoundtripThroughSerialization_Works() [ new() { - SkillID = "x", + SkillID = "pdf", Type = BetaSkillParamsType.Anthropic, - Version = "x", + Version = "latest", }, ], }; @@ -99,9 +99,9 @@ public void FieldRoundtripThroughSerialization_Works() [ new() { - SkillID = "x", + SkillID = "pdf", Type = BetaSkillParamsType.Anthropic, - Version = "x", + Version = "latest", }, ]; @@ -124,9 +124,9 @@ public void Validation_Works() [ new() { - SkillID = "x", + SkillID = "pdf", Type = BetaSkillParamsType.Anthropic, - Version = "x", + Version = "latest", }, ], }; @@ -182,9 +182,9 @@ public void CopyConstructor_Works() [ new() { - SkillID = "x", + SkillID = "pdf", Type = BetaSkillParamsType.Anthropic, - Version = "x", + Version = "latest", }, ], }; diff --git a/src/Anthropic.Tests/Models/Beta/Messages/BetaContainerTest.cs b/src/Anthropic.Tests/Models/Beta/Messages/BetaContainerTest.cs index b01c7e7d4..60a6aedbd 100644 --- a/src/Anthropic.Tests/Models/Beta/Messages/BetaContainerTest.cs +++ b/src/Anthropic.Tests/Models/Beta/Messages/BetaContainerTest.cs @@ -19,9 +19,9 @@ public void FieldRoundtrip_Works() [ new() { - SkillID = "x", + SkillID = "pdf", Type = Messages::Type.Anthropic, - Version = "x", + Version = "latest", }, ], }; @@ -32,9 +32,9 @@ public void FieldRoundtrip_Works() [ new() { - SkillID = "x", + SkillID = "pdf", Type = Messages::Type.Anthropic, - Version = "x", + Version = "latest", }, ]; @@ -59,9 +59,9 @@ public void SerializationRoundtrip_Works() [ new() { - SkillID = "x", + SkillID = "pdf", Type = Messages::Type.Anthropic, - Version = "x", + Version = "latest", }, ], }; @@ -86,9 +86,9 @@ public void FieldRoundtripThroughSerialization_Works() [ new() { - SkillID = "x", + SkillID = "pdf", Type = Messages::Type.Anthropic, - Version = "x", + Version = "latest", }, ], }; @@ -106,9 +106,9 @@ public void FieldRoundtripThroughSerialization_Works() [ new() { - SkillID = "x", + SkillID = "pdf", Type = Messages::Type.Anthropic, - Version = "x", + Version = "latest", }, ]; @@ -133,9 +133,9 @@ public void Validation_Works() [ new() { - SkillID = "x", + SkillID = "pdf", Type = Messages::Type.Anthropic, - Version = "x", + Version = "latest", }, ], }; @@ -154,9 +154,9 @@ public void CopyConstructor_Works() [ new() { - SkillID = "x", + SkillID = "pdf", Type = Messages::Type.Anthropic, - Version = "x", + Version = "latest", }, ], }; diff --git a/src/Anthropic.Tests/Models/Beta/Messages/BetaMessageTest.cs b/src/Anthropic.Tests/Models/Beta/Messages/BetaMessageTest.cs index 2f340621e..83ddb52a1 100644 --- a/src/Anthropic.Tests/Models/Beta/Messages/BetaMessageTest.cs +++ b/src/Anthropic.Tests/Models/Beta/Messages/BetaMessageTest.cs @@ -23,9 +23,9 @@ public void FieldRoundtrip_Works() [ new() { - SkillID = "x", + SkillID = "pdf", Type = Messages::Type.Anthropic, - Version = "x", + Version = "latest", }, ], }, @@ -98,9 +98,9 @@ public void FieldRoundtrip_Works() [ new() { - SkillID = "x", + SkillID = "pdf", Type = Messages::Type.Anthropic, - Version = "x", + Version = "latest", }, ], }; @@ -195,9 +195,9 @@ public void SerializationRoundtrip_Works() [ new() { - SkillID = "x", + SkillID = "pdf", Type = Messages::Type.Anthropic, - Version = "x", + Version = "latest", }, ], }, @@ -284,9 +284,9 @@ public void FieldRoundtripThroughSerialization_Works() [ new() { - SkillID = "x", + SkillID = "pdf", Type = Messages::Type.Anthropic, - Version = "x", + Version = "latest", }, ], }, @@ -366,9 +366,9 @@ public void FieldRoundtripThroughSerialization_Works() [ new() { - SkillID = "x", + SkillID = "pdf", Type = Messages::Type.Anthropic, - Version = "x", + Version = "latest", }, ], }; @@ -463,9 +463,9 @@ public void Validation_Works() [ new() { - SkillID = "x", + SkillID = "pdf", Type = Messages::Type.Anthropic, - Version = "x", + Version = "latest", }, ], }, @@ -546,9 +546,9 @@ public void CopyConstructor_Works() [ new() { - SkillID = "x", + SkillID = "pdf", Type = Messages::Type.Anthropic, - Version = "x", + Version = "latest", }, ], }, diff --git a/src/Anthropic.Tests/Models/Beta/Messages/BetaRawMessageDeltaEventTest.cs b/src/Anthropic.Tests/Models/Beta/Messages/BetaRawMessageDeltaEventTest.cs index 9df0f297d..a59dcc659 100644 --- a/src/Anthropic.Tests/Models/Beta/Messages/BetaRawMessageDeltaEventTest.cs +++ b/src/Anthropic.Tests/Models/Beta/Messages/BetaRawMessageDeltaEventTest.cs @@ -31,9 +31,9 @@ public void FieldRoundtrip_Works() [ new() { - SkillID = "x", + SkillID = "pdf", Type = Messages::Type.Anthropic, - Version = "x", + Version = "latest", }, ], }, @@ -84,9 +84,9 @@ public void FieldRoundtrip_Works() [ new() { - SkillID = "x", + SkillID = "pdf", Type = Messages::Type.Anthropic, - Version = "x", + Version = "latest", }, ], }, @@ -148,9 +148,9 @@ public void SerializationRoundtrip_Works() [ new() { - SkillID = "x", + SkillID = "pdf", Type = Messages::Type.Anthropic, - Version = "x", + Version = "latest", }, ], }, @@ -215,9 +215,9 @@ public void FieldRoundtripThroughSerialization_Works() [ new() { - SkillID = "x", + SkillID = "pdf", Type = Messages::Type.Anthropic, - Version = "x", + Version = "latest", }, ], }, @@ -275,9 +275,9 @@ public void FieldRoundtripThroughSerialization_Works() [ new() { - SkillID = "x", + SkillID = "pdf", Type = Messages::Type.Anthropic, - Version = "x", + Version = "latest", }, ], }, @@ -339,9 +339,9 @@ public void Validation_Works() [ new() { - SkillID = "x", + SkillID = "pdf", Type = Messages::Type.Anthropic, - Version = "x", + Version = "latest", }, ], }, @@ -400,9 +400,9 @@ public void CopyConstructor_Works() [ new() { - SkillID = "x", + SkillID = "pdf", Type = Messages::Type.Anthropic, - Version = "x", + Version = "latest", }, ], }, @@ -455,9 +455,9 @@ public void FieldRoundtrip_Works() [ new() { - SkillID = "x", + SkillID = "pdf", Type = Messages::Type.Anthropic, - Version = "x", + Version = "latest", }, ], }, @@ -473,9 +473,9 @@ public void FieldRoundtrip_Works() [ new() { - SkillID = "x", + SkillID = "pdf", Type = Messages::Type.Anthropic, - Version = "x", + Version = "latest", }, ], }; @@ -501,9 +501,9 @@ public void SerializationRoundtrip_Works() [ new() { - SkillID = "x", + SkillID = "pdf", Type = Messages::Type.Anthropic, - Version = "x", + Version = "latest", }, ], }, @@ -533,9 +533,9 @@ public void FieldRoundtripThroughSerialization_Works() [ new() { - SkillID = "x", + SkillID = "pdf", Type = Messages::Type.Anthropic, - Version = "x", + Version = "latest", }, ], }, @@ -558,9 +558,9 @@ public void FieldRoundtripThroughSerialization_Works() [ new() { - SkillID = "x", + SkillID = "pdf", Type = Messages::Type.Anthropic, - Version = "x", + Version = "latest", }, ], }; @@ -586,9 +586,9 @@ public void Validation_Works() [ new() { - SkillID = "x", + SkillID = "pdf", Type = Messages::Type.Anthropic, - Version = "x", + Version = "latest", }, ], }, @@ -612,9 +612,9 @@ public void CopyConstructor_Works() [ new() { - SkillID = "x", + SkillID = "pdf", Type = Messages::Type.Anthropic, - Version = "x", + Version = "latest", }, ], }, diff --git a/src/Anthropic.Tests/Models/Beta/Messages/BetaRawMessageStartEventTest.cs b/src/Anthropic.Tests/Models/Beta/Messages/BetaRawMessageStartEventTest.cs index 27edfb53d..3b9a01d6b 100644 --- a/src/Anthropic.Tests/Models/Beta/Messages/BetaRawMessageStartEventTest.cs +++ b/src/Anthropic.Tests/Models/Beta/Messages/BetaRawMessageStartEventTest.cs @@ -24,9 +24,9 @@ public void FieldRoundtrip_Works() [ new() { - SkillID = "x", + SkillID = "pdf", Type = Messages::Type.Anthropic, - Version = "x", + Version = "latest", }, ], }, @@ -106,9 +106,9 @@ public void FieldRoundtrip_Works() [ new() { - SkillID = "x", + SkillID = "pdf", Type = Messages::Type.Anthropic, - Version = "x", + Version = "latest", }, ], }, @@ -193,9 +193,9 @@ public void SerializationRoundtrip_Works() [ new() { - SkillID = "x", + SkillID = "pdf", Type = Messages::Type.Anthropic, - Version = "x", + Version = "latest", }, ], }, @@ -289,9 +289,9 @@ public void FieldRoundtripThroughSerialization_Works() [ new() { - SkillID = "x", + SkillID = "pdf", Type = Messages::Type.Anthropic, - Version = "x", + Version = "latest", }, ], }, @@ -378,9 +378,9 @@ public void FieldRoundtripThroughSerialization_Works() [ new() { - SkillID = "x", + SkillID = "pdf", Type = Messages::Type.Anthropic, - Version = "x", + Version = "latest", }, ], }, @@ -465,9 +465,9 @@ public void Validation_Works() [ new() { - SkillID = "x", + SkillID = "pdf", Type = Messages::Type.Anthropic, - Version = "x", + Version = "latest", }, ], }, @@ -555,9 +555,9 @@ public void CopyConstructor_Works() [ new() { - SkillID = "x", + SkillID = "pdf", Type = Messages::Type.Anthropic, - Version = "x", + Version = "latest", }, ], }, diff --git a/src/Anthropic.Tests/Models/Beta/Messages/BetaRawMessageStreamEventTest.cs b/src/Anthropic.Tests/Models/Beta/Messages/BetaRawMessageStreamEventTest.cs index be9d23273..af0f940cc 100644 --- a/src/Anthropic.Tests/Models/Beta/Messages/BetaRawMessageStreamEventTest.cs +++ b/src/Anthropic.Tests/Models/Beta/Messages/BetaRawMessageStreamEventTest.cs @@ -23,9 +23,9 @@ public void StartValidationWorks() [ new() { - SkillID = "x", + SkillID = "pdf", Type = Messages::Type.Anthropic, - Version = "x", + Version = "latest", }, ], }, @@ -120,9 +120,9 @@ public void DeltaValidationWorks() [ new() { - SkillID = "x", + SkillID = "pdf", Type = Messages::Type.Anthropic, - Version = "x", + Version = "latest", }, ], }, @@ -222,9 +222,9 @@ public void StartSerializationRoundtripWorks() [ new() { - SkillID = "x", + SkillID = "pdf", Type = Messages::Type.Anthropic, - Version = "x", + Version = "latest", }, ], }, @@ -325,9 +325,9 @@ public void DeltaSerializationRoundtripWorks() [ new() { - SkillID = "x", + SkillID = "pdf", Type = Messages::Type.Anthropic, - Version = "x", + Version = "latest", }, ], }, diff --git a/src/Anthropic.Tests/Models/Beta/Messages/BetaSkillParamsTest.cs b/src/Anthropic.Tests/Models/Beta/Messages/BetaSkillParamsTest.cs index ad55042e1..11c4c29fa 100644 --- a/src/Anthropic.Tests/Models/Beta/Messages/BetaSkillParamsTest.cs +++ b/src/Anthropic.Tests/Models/Beta/Messages/BetaSkillParamsTest.cs @@ -12,14 +12,14 @@ public void FieldRoundtrip_Works() { var model = new BetaSkillParams { - SkillID = "x", + SkillID = "pdf", Type = BetaSkillParamsType.Anthropic, - Version = "x", + Version = "latest", }; - string expectedSkillID = "x"; + string expectedSkillID = "pdf"; ApiEnum expectedType = BetaSkillParamsType.Anthropic; - string expectedVersion = "x"; + string expectedVersion = "latest"; Assert.Equal(expectedSkillID, model.SkillID); Assert.Equal(expectedType, model.Type); @@ -31,9 +31,9 @@ public void SerializationRoundtrip_Works() { var model = new BetaSkillParams { - SkillID = "x", + SkillID = "pdf", Type = BetaSkillParamsType.Anthropic, - Version = "x", + Version = "latest", }; string json = JsonSerializer.Serialize(model, ModelBase.SerializerOptions); @@ -50,9 +50,9 @@ public void FieldRoundtripThroughSerialization_Works() { var model = new BetaSkillParams { - SkillID = "x", + SkillID = "pdf", Type = BetaSkillParamsType.Anthropic, - Version = "x", + Version = "latest", }; string element = JsonSerializer.Serialize(model, ModelBase.SerializerOptions); @@ -62,9 +62,9 @@ public void FieldRoundtripThroughSerialization_Works() ); Assert.NotNull(deserialized); - string expectedSkillID = "x"; + string expectedSkillID = "pdf"; ApiEnum expectedType = BetaSkillParamsType.Anthropic; - string expectedVersion = "x"; + string expectedVersion = "latest"; Assert.Equal(expectedSkillID, deserialized.SkillID); Assert.Equal(expectedType, deserialized.Type); @@ -76,9 +76,9 @@ public void Validation_Works() { var model = new BetaSkillParams { - SkillID = "x", + SkillID = "pdf", Type = BetaSkillParamsType.Anthropic, - Version = "x", + Version = "latest", }; model.Validate(); @@ -87,7 +87,7 @@ public void Validation_Works() [Fact] public void OptionalNonNullablePropertiesUnsetAreNotSet_Works() { - var model = new BetaSkillParams { SkillID = "x", Type = BetaSkillParamsType.Anthropic }; + var model = new BetaSkillParams { SkillID = "pdf", Type = BetaSkillParamsType.Anthropic }; Assert.Null(model.Version); Assert.False(model.RawData.ContainsKey("version")); @@ -96,7 +96,7 @@ public void OptionalNonNullablePropertiesUnsetAreNotSet_Works() [Fact] public void OptionalNonNullablePropertiesUnsetValidation_Works() { - var model = new BetaSkillParams { SkillID = "x", Type = BetaSkillParamsType.Anthropic }; + var model = new BetaSkillParams { SkillID = "pdf", Type = BetaSkillParamsType.Anthropic }; model.Validate(); } @@ -106,7 +106,7 @@ public void OptionalNonNullablePropertiesSetToNullAreNotSet_Works() { var model = new BetaSkillParams { - SkillID = "x", + SkillID = "pdf", Type = BetaSkillParamsType.Anthropic, // Null should be interpreted as omitted for these properties @@ -122,7 +122,7 @@ public void OptionalNonNullablePropertiesSetToNullValidation_Works() { var model = new BetaSkillParams { - SkillID = "x", + SkillID = "pdf", Type = BetaSkillParamsType.Anthropic, // Null should be interpreted as omitted for these properties @@ -137,9 +137,9 @@ public void CopyConstructor_Works() { var model = new BetaSkillParams { - SkillID = "x", + SkillID = "pdf", Type = BetaSkillParamsType.Anthropic, - Version = "x", + Version = "latest", }; BetaSkillParams copied = new(model); diff --git a/src/Anthropic.Tests/Models/Beta/Messages/BetaSkillTest.cs b/src/Anthropic.Tests/Models/Beta/Messages/BetaSkillTest.cs index a71419003..4e731a844 100644 --- a/src/Anthropic.Tests/Models/Beta/Messages/BetaSkillTest.cs +++ b/src/Anthropic.Tests/Models/Beta/Messages/BetaSkillTest.cs @@ -12,14 +12,14 @@ public void FieldRoundtrip_Works() { var model = new BetaSkill { - SkillID = "x", + SkillID = "pdf", Type = Type.Anthropic, - Version = "x", + Version = "latest", }; - string expectedSkillID = "x"; + string expectedSkillID = "pdf"; ApiEnum expectedType = Type.Anthropic; - string expectedVersion = "x"; + string expectedVersion = "latest"; Assert.Equal(expectedSkillID, model.SkillID); Assert.Equal(expectedType, model.Type); @@ -31,9 +31,9 @@ public void SerializationRoundtrip_Works() { var model = new BetaSkill { - SkillID = "x", + SkillID = "pdf", Type = Type.Anthropic, - Version = "x", + Version = "latest", }; string json = JsonSerializer.Serialize(model, ModelBase.SerializerOptions); @@ -47,9 +47,9 @@ public void FieldRoundtripThroughSerialization_Works() { var model = new BetaSkill { - SkillID = "x", + SkillID = "pdf", Type = Type.Anthropic, - Version = "x", + Version = "latest", }; string element = JsonSerializer.Serialize(model, ModelBase.SerializerOptions); @@ -59,9 +59,9 @@ public void FieldRoundtripThroughSerialization_Works() ); Assert.NotNull(deserialized); - string expectedSkillID = "x"; + string expectedSkillID = "pdf"; ApiEnum expectedType = Type.Anthropic; - string expectedVersion = "x"; + string expectedVersion = "latest"; Assert.Equal(expectedSkillID, deserialized.SkillID); Assert.Equal(expectedType, deserialized.Type); @@ -73,9 +73,9 @@ public void Validation_Works() { var model = new BetaSkill { - SkillID = "x", + SkillID = "pdf", Type = Type.Anthropic, - Version = "x", + Version = "latest", }; model.Validate(); @@ -86,9 +86,9 @@ public void CopyConstructor_Works() { var model = new BetaSkill { - SkillID = "x", + SkillID = "pdf", Type = Type.Anthropic, - Version = "x", + Version = "latest", }; BetaSkill copied = new(model); diff --git a/src/Anthropic.Tests/Models/Beta/Messages/BetaThinkingConfigAdaptiveTest.cs b/src/Anthropic.Tests/Models/Beta/Messages/BetaThinkingConfigAdaptiveTest.cs index 999c6f693..4247ab8d7 100644 --- a/src/Anthropic.Tests/Models/Beta/Messages/BetaThinkingConfigAdaptiveTest.cs +++ b/src/Anthropic.Tests/Models/Beta/Messages/BetaThinkingConfigAdaptiveTest.cs @@ -1,5 +1,6 @@ using System.Text.Json; using Anthropic.Core; +using Anthropic.Exceptions; using Anthropic.Models.Beta.Messages; namespace Anthropic.Tests.Models.Beta.Messages; @@ -9,17 +10,19 @@ public class BetaThinkingConfigAdaptiveTest : TestBase [Fact] public void FieldRoundtrip_Works() { - var model = new BetaThinkingConfigAdaptive { }; + var model = new BetaThinkingConfigAdaptive { Display = Display.Summarized }; JsonElement expectedType = JsonSerializer.SerializeToElement("adaptive"); + ApiEnum expectedDisplay = Display.Summarized; Assert.True(JsonElement.DeepEquals(expectedType, model.Type)); + Assert.Equal(expectedDisplay, model.Display); } [Fact] public void SerializationRoundtrip_Works() { - var model = new BetaThinkingConfigAdaptive { }; + var model = new BetaThinkingConfigAdaptive { Display = Display.Summarized }; string json = JsonSerializer.Serialize(model, ModelBase.SerializerOptions); var deserialized = JsonSerializer.Deserialize( @@ -33,7 +36,7 @@ public void SerializationRoundtrip_Works() [Fact] public void FieldRoundtripThroughSerialization_Works() { - var model = new BetaThinkingConfigAdaptive { }; + var model = new BetaThinkingConfigAdaptive { Display = Display.Summarized }; string element = JsonSerializer.Serialize(model, ModelBase.SerializerOptions); var deserialized = JsonSerializer.Deserialize( @@ -43,25 +46,119 @@ public void FieldRoundtripThroughSerialization_Works() Assert.NotNull(deserialized); JsonElement expectedType = JsonSerializer.SerializeToElement("adaptive"); + ApiEnum expectedDisplay = Display.Summarized; Assert.True(JsonElement.DeepEquals(expectedType, deserialized.Type)); + Assert.Equal(expectedDisplay, deserialized.Display); } [Fact] public void Validation_Works() + { + var model = new BetaThinkingConfigAdaptive { Display = Display.Summarized }; + + model.Validate(); + } + + [Fact] + public void OptionalNullablePropertiesUnsetAreNotSet_Works() + { + var model = new BetaThinkingConfigAdaptive { }; + + Assert.Null(model.Display); + Assert.False(model.RawData.ContainsKey("display")); + } + + [Fact] + public void OptionalNullablePropertiesUnsetValidation_Works() { var model = new BetaThinkingConfigAdaptive { }; model.Validate(); } + [Fact] + public void OptionalNullablePropertiesSetToNullAreSetToNull_Works() + { + var model = new BetaThinkingConfigAdaptive { Display = null }; + + Assert.Null(model.Display); + Assert.True(model.RawData.ContainsKey("display")); + } + + [Fact] + public void OptionalNullablePropertiesSetToNullValidation_Works() + { + var model = new BetaThinkingConfigAdaptive { Display = null }; + + model.Validate(); + } + [Fact] public void CopyConstructor_Works() { - var model = new BetaThinkingConfigAdaptive { }; + var model = new BetaThinkingConfigAdaptive { Display = Display.Summarized }; BetaThinkingConfigAdaptive copied = new(model); Assert.Equal(model, copied); } } + +public class DisplayTest : TestBase +{ + [Theory] + [InlineData(Display.Summarized)] + [InlineData(Display.Omitted)] + public void Validation_Works(Display rawValue) + { + // force implicit conversion because Theory can't do that for us + ApiEnum value = rawValue; + value.Validate(); + } + + [Fact] + public void InvalidEnumValidationThrows_Works() + { + var value = JsonSerializer.Deserialize>( + JsonSerializer.SerializeToElement("invalid value"), + ModelBase.SerializerOptions + ); + + Assert.NotNull(value); + Assert.Throws(() => value.Validate()); + } + + [Theory] + [InlineData(Display.Summarized)] + [InlineData(Display.Omitted)] + public void SerializationRoundtrip_Works(Display rawValue) + { + // force implicit conversion because Theory can't do that for us + ApiEnum value = rawValue; + + string json = JsonSerializer.Serialize(value, ModelBase.SerializerOptions); + var deserialized = JsonSerializer.Deserialize>( + json, + ModelBase.SerializerOptions + ); + + Assert.Equal(value, deserialized); + } + + [Fact] + public void InvalidEnumSerializationRoundtrip_Works() + { + var value = JsonSerializer.Deserialize>( + JsonSerializer.SerializeToElement("invalid value"), + ModelBase.SerializerOptions + ); + string json = JsonSerializer.Serialize(value, ModelBase.SerializerOptions); + var deserialized = JsonSerializer.Deserialize>( + json, + ModelBase.SerializerOptions + ); + + Assert.Equal(value, deserialized); + } +} diff --git a/src/Anthropic.Tests/Models/Beta/Messages/BetaThinkingConfigEnabledTest.cs b/src/Anthropic.Tests/Models/Beta/Messages/BetaThinkingConfigEnabledTest.cs index 5b7d375ef..7784d66c7 100644 --- a/src/Anthropic.Tests/Models/Beta/Messages/BetaThinkingConfigEnabledTest.cs +++ b/src/Anthropic.Tests/Models/Beta/Messages/BetaThinkingConfigEnabledTest.cs @@ -1,5 +1,6 @@ using System.Text.Json; using Anthropic.Core; +using Anthropic.Exceptions; using Anthropic.Models.Beta.Messages; namespace Anthropic.Tests.Models.Beta.Messages; @@ -9,19 +10,30 @@ public class BetaThinkingConfigEnabledTest : TestBase [Fact] public void FieldRoundtrip_Works() { - var model = new BetaThinkingConfigEnabled { BudgetTokens = 1024 }; + var model = new BetaThinkingConfigEnabled + { + BudgetTokens = 1024, + Display = BetaThinkingConfigEnabledDisplay.Summarized, + }; long expectedBudgetTokens = 1024; JsonElement expectedType = JsonSerializer.SerializeToElement("enabled"); + ApiEnum expectedDisplay = + BetaThinkingConfigEnabledDisplay.Summarized; Assert.Equal(expectedBudgetTokens, model.BudgetTokens); Assert.True(JsonElement.DeepEquals(expectedType, model.Type)); + Assert.Equal(expectedDisplay, model.Display); } [Fact] public void SerializationRoundtrip_Works() { - var model = new BetaThinkingConfigEnabled { BudgetTokens = 1024 }; + var model = new BetaThinkingConfigEnabled + { + BudgetTokens = 1024, + Display = BetaThinkingConfigEnabledDisplay.Summarized, + }; string json = JsonSerializer.Serialize(model, ModelBase.SerializerOptions); var deserialized = JsonSerializer.Deserialize( @@ -35,7 +47,11 @@ public void SerializationRoundtrip_Works() [Fact] public void FieldRoundtripThroughSerialization_Works() { - var model = new BetaThinkingConfigEnabled { BudgetTokens = 1024 }; + var model = new BetaThinkingConfigEnabled + { + BudgetTokens = 1024, + Display = BetaThinkingConfigEnabledDisplay.Summarized, + }; string element = JsonSerializer.Serialize(model, ModelBase.SerializerOptions); var deserialized = JsonSerializer.Deserialize( @@ -46,26 +62,137 @@ public void FieldRoundtripThroughSerialization_Works() long expectedBudgetTokens = 1024; JsonElement expectedType = JsonSerializer.SerializeToElement("enabled"); + ApiEnum expectedDisplay = + BetaThinkingConfigEnabledDisplay.Summarized; Assert.Equal(expectedBudgetTokens, deserialized.BudgetTokens); Assert.True(JsonElement.DeepEquals(expectedType, deserialized.Type)); + Assert.Equal(expectedDisplay, deserialized.Display); } [Fact] public void Validation_Works() + { + var model = new BetaThinkingConfigEnabled + { + BudgetTokens = 1024, + Display = BetaThinkingConfigEnabledDisplay.Summarized, + }; + + model.Validate(); + } + + [Fact] + public void OptionalNullablePropertiesUnsetAreNotSet_Works() + { + var model = new BetaThinkingConfigEnabled { BudgetTokens = 1024 }; + + Assert.Null(model.Display); + Assert.False(model.RawData.ContainsKey("display")); + } + + [Fact] + public void OptionalNullablePropertiesUnsetValidation_Works() { var model = new BetaThinkingConfigEnabled { BudgetTokens = 1024 }; model.Validate(); } + [Fact] + public void OptionalNullablePropertiesSetToNullAreSetToNull_Works() + { + var model = new BetaThinkingConfigEnabled + { + BudgetTokens = 1024, + + Display = null, + }; + + Assert.Null(model.Display); + Assert.True(model.RawData.ContainsKey("display")); + } + + [Fact] + public void OptionalNullablePropertiesSetToNullValidation_Works() + { + var model = new BetaThinkingConfigEnabled + { + BudgetTokens = 1024, + + Display = null, + }; + + model.Validate(); + } + [Fact] public void CopyConstructor_Works() { - var model = new BetaThinkingConfigEnabled { BudgetTokens = 1024 }; + var model = new BetaThinkingConfigEnabled + { + BudgetTokens = 1024, + Display = BetaThinkingConfigEnabledDisplay.Summarized, + }; BetaThinkingConfigEnabled copied = new(model); Assert.Equal(model, copied); } } + +public class BetaThinkingConfigEnabledDisplayTest : TestBase +{ + [Theory] + [InlineData(BetaThinkingConfigEnabledDisplay.Summarized)] + [InlineData(BetaThinkingConfigEnabledDisplay.Omitted)] + public void Validation_Works(BetaThinkingConfigEnabledDisplay rawValue) + { + // force implicit conversion because Theory can't do that for us + ApiEnum value = rawValue; + value.Validate(); + } + + [Fact] + public void InvalidEnumValidationThrows_Works() + { + var value = JsonSerializer.Deserialize>( + JsonSerializer.SerializeToElement("invalid value"), + ModelBase.SerializerOptions + ); + + Assert.NotNull(value); + Assert.Throws(() => value.Validate()); + } + + [Theory] + [InlineData(BetaThinkingConfigEnabledDisplay.Summarized)] + [InlineData(BetaThinkingConfigEnabledDisplay.Omitted)] + public void SerializationRoundtrip_Works(BetaThinkingConfigEnabledDisplay rawValue) + { + // force implicit conversion because Theory can't do that for us + ApiEnum value = rawValue; + + string json = JsonSerializer.Serialize(value, ModelBase.SerializerOptions); + var deserialized = JsonSerializer.Deserialize< + ApiEnum + >(json, ModelBase.SerializerOptions); + + Assert.Equal(value, deserialized); + } + + [Fact] + public void InvalidEnumSerializationRoundtrip_Works() + { + var value = JsonSerializer.Deserialize>( + JsonSerializer.SerializeToElement("invalid value"), + ModelBase.SerializerOptions + ); + string json = JsonSerializer.Serialize(value, ModelBase.SerializerOptions); + var deserialized = JsonSerializer.Deserialize< + ApiEnum + >(json, ModelBase.SerializerOptions); + + Assert.Equal(value, deserialized); + } +} diff --git a/src/Anthropic.Tests/Models/Beta/Messages/BetaThinkingConfigParamTest.cs b/src/Anthropic.Tests/Models/Beta/Messages/BetaThinkingConfigParamTest.cs index 1730ccf05..2c102a6ae 100644 --- a/src/Anthropic.Tests/Models/Beta/Messages/BetaThinkingConfigParamTest.cs +++ b/src/Anthropic.Tests/Models/Beta/Messages/BetaThinkingConfigParamTest.cs @@ -9,7 +9,11 @@ public class BetaThinkingConfigParamTest : TestBase [Fact] public void EnabledValidationWorks() { - BetaThinkingConfigParam value = new BetaThinkingConfigEnabled(1024); + BetaThinkingConfigParam value = new BetaThinkingConfigEnabled() + { + BudgetTokens = 1024, + Display = BetaThinkingConfigEnabledDisplay.Summarized, + }; value.Validate(); } @@ -23,14 +27,21 @@ public void DisabledValidationWorks() [Fact] public void AdaptiveValidationWorks() { - BetaThinkingConfigParam value = new BetaThinkingConfigAdaptive(); + BetaThinkingConfigParam value = new BetaThinkingConfigAdaptive() + { + Display = Display.Summarized, + }; value.Validate(); } [Fact] public void EnabledSerializationRoundtripWorks() { - BetaThinkingConfigParam value = new BetaThinkingConfigEnabled(1024); + BetaThinkingConfigParam value = new BetaThinkingConfigEnabled() + { + BudgetTokens = 1024, + Display = BetaThinkingConfigEnabledDisplay.Summarized, + }; string element = JsonSerializer.Serialize(value, ModelBase.SerializerOptions); var deserialized = JsonSerializer.Deserialize( element, @@ -56,7 +67,10 @@ public void DisabledSerializationRoundtripWorks() [Fact] public void AdaptiveSerializationRoundtripWorks() { - BetaThinkingConfigParam value = new BetaThinkingConfigAdaptive(); + BetaThinkingConfigParam value = new BetaThinkingConfigAdaptive() + { + Display = Display.Summarized, + }; string element = JsonSerializer.Serialize(value, ModelBase.SerializerOptions); var deserialized = JsonSerializer.Deserialize( element, diff --git a/src/Anthropic.Tests/Models/Beta/Messages/BetaToolUnionTest.cs b/src/Anthropic.Tests/Models/Beta/Messages/BetaToolUnionTest.cs index 167548cec..c5c188281 100644 --- a/src/Anthropic.Tests/Models/Beta/Messages/BetaToolUnionTest.cs +++ b/src/Anthropic.Tests/Models/Beta/Messages/BetaToolUnionTest.cs @@ -372,6 +372,25 @@ public void WebFetchTool20260209ValidationWorks() value.Validate(); } + [Fact] + public void WebFetchTool20260309ValidationWorks() + { + BetaToolUnion value = new BetaWebFetchTool20260309() + { + AllowedCallers = [BetaWebFetchTool20260309AllowedCaller.Direct], + AllowedDomains = ["string"], + BlockedDomains = ["string"], + CacheControl = new() { Ttl = Ttl.Ttl5m }, + Citations = new() { Enabled = true }, + DeferLoading = true, + MaxContentTokens = 1, + MaxUses = 1, + Strict = true, + UseCache = true, + }; + value.Validate(); + } + [Fact] public void SearchToolBm25_20251119ValidationWorks() { @@ -892,6 +911,31 @@ public void WebFetchTool20260209SerializationRoundtripWorks() Assert.Equal(value, deserialized); } + [Fact] + public void WebFetchTool20260309SerializationRoundtripWorks() + { + BetaToolUnion value = new BetaWebFetchTool20260309() + { + AllowedCallers = [BetaWebFetchTool20260309AllowedCaller.Direct], + AllowedDomains = ["string"], + BlockedDomains = ["string"], + CacheControl = new() { Ttl = Ttl.Ttl5m }, + Citations = new() { Enabled = true }, + DeferLoading = true, + MaxContentTokens = 1, + MaxUses = 1, + Strict = true, + UseCache = true, + }; + string element = JsonSerializer.Serialize(value, ModelBase.SerializerOptions); + var deserialized = JsonSerializer.Deserialize( + element, + ModelBase.SerializerOptions + ); + + Assert.Equal(value, deserialized); + } + [Fact] public void SearchToolBm25_20251119SerializationRoundtripWorks() { diff --git a/src/Anthropic.Tests/Models/Beta/Messages/BetaWebFetchTool20260309Test.cs b/src/Anthropic.Tests/Models/Beta/Messages/BetaWebFetchTool20260309Test.cs new file mode 100644 index 000000000..a4572c0bb --- /dev/null +++ b/src/Anthropic.Tests/Models/Beta/Messages/BetaWebFetchTool20260309Test.cs @@ -0,0 +1,447 @@ +using System.Collections.Generic; +using System.Text.Json; +using Anthropic.Core; +using Anthropic.Exceptions; +using Anthropic.Models.Beta.Messages; + +namespace Anthropic.Tests.Models.Beta.Messages; + +public class BetaWebFetchTool20260309Test : TestBase +{ + [Fact] + public void FieldRoundtrip_Works() + { + var model = new BetaWebFetchTool20260309 + { + AllowedCallers = [BetaWebFetchTool20260309AllowedCaller.Direct], + AllowedDomains = ["string"], + BlockedDomains = ["string"], + CacheControl = new() { Ttl = Ttl.Ttl5m }, + Citations = new() { Enabled = true }, + DeferLoading = true, + MaxContentTokens = 1, + MaxUses = 1, + Strict = true, + UseCache = true, + }; + + JsonElement expectedName = JsonSerializer.SerializeToElement("web_fetch"); + JsonElement expectedType = JsonSerializer.SerializeToElement("web_fetch_20260309"); + List> expectedAllowedCallers = + [ + BetaWebFetchTool20260309AllowedCaller.Direct, + ]; + List expectedAllowedDomains = ["string"]; + List expectedBlockedDomains = ["string"]; + BetaCacheControlEphemeral expectedCacheControl = new() { Ttl = Ttl.Ttl5m }; + BetaCitationsConfigParam expectedCitations = new() { Enabled = true }; + bool expectedDeferLoading = true; + long expectedMaxContentTokens = 1; + long expectedMaxUses = 1; + bool expectedStrict = true; + bool expectedUseCache = true; + + Assert.True(JsonElement.DeepEquals(expectedName, model.Name)); + Assert.True(JsonElement.DeepEquals(expectedType, model.Type)); + Assert.NotNull(model.AllowedCallers); + Assert.Equal(expectedAllowedCallers.Count, model.AllowedCallers.Count); + for (int i = 0; i < expectedAllowedCallers.Count; i++) + { + Assert.Equal(expectedAllowedCallers[i], model.AllowedCallers[i]); + } + Assert.NotNull(model.AllowedDomains); + Assert.Equal(expectedAllowedDomains.Count, model.AllowedDomains.Count); + for (int i = 0; i < expectedAllowedDomains.Count; i++) + { + Assert.Equal(expectedAllowedDomains[i], model.AllowedDomains[i]); + } + Assert.NotNull(model.BlockedDomains); + Assert.Equal(expectedBlockedDomains.Count, model.BlockedDomains.Count); + for (int i = 0; i < expectedBlockedDomains.Count; i++) + { + Assert.Equal(expectedBlockedDomains[i], model.BlockedDomains[i]); + } + Assert.Equal(expectedCacheControl, model.CacheControl); + Assert.Equal(expectedCitations, model.Citations); + Assert.Equal(expectedDeferLoading, model.DeferLoading); + Assert.Equal(expectedMaxContentTokens, model.MaxContentTokens); + Assert.Equal(expectedMaxUses, model.MaxUses); + Assert.Equal(expectedStrict, model.Strict); + Assert.Equal(expectedUseCache, model.UseCache); + } + + [Fact] + public void SerializationRoundtrip_Works() + { + var model = new BetaWebFetchTool20260309 + { + AllowedCallers = [BetaWebFetchTool20260309AllowedCaller.Direct], + AllowedDomains = ["string"], + BlockedDomains = ["string"], + CacheControl = new() { Ttl = Ttl.Ttl5m }, + Citations = new() { Enabled = true }, + DeferLoading = true, + MaxContentTokens = 1, + MaxUses = 1, + Strict = true, + UseCache = true, + }; + + string json = JsonSerializer.Serialize(model, ModelBase.SerializerOptions); + var deserialized = JsonSerializer.Deserialize( + json, + ModelBase.SerializerOptions + ); + + Assert.Equal(model, deserialized); + } + + [Fact] + public void FieldRoundtripThroughSerialization_Works() + { + var model = new BetaWebFetchTool20260309 + { + AllowedCallers = [BetaWebFetchTool20260309AllowedCaller.Direct], + AllowedDomains = ["string"], + BlockedDomains = ["string"], + CacheControl = new() { Ttl = Ttl.Ttl5m }, + Citations = new() { Enabled = true }, + DeferLoading = true, + MaxContentTokens = 1, + MaxUses = 1, + Strict = true, + UseCache = true, + }; + + string element = JsonSerializer.Serialize(model, ModelBase.SerializerOptions); + var deserialized = JsonSerializer.Deserialize( + element, + ModelBase.SerializerOptions + ); + Assert.NotNull(deserialized); + + JsonElement expectedName = JsonSerializer.SerializeToElement("web_fetch"); + JsonElement expectedType = JsonSerializer.SerializeToElement("web_fetch_20260309"); + List> expectedAllowedCallers = + [ + BetaWebFetchTool20260309AllowedCaller.Direct, + ]; + List expectedAllowedDomains = ["string"]; + List expectedBlockedDomains = ["string"]; + BetaCacheControlEphemeral expectedCacheControl = new() { Ttl = Ttl.Ttl5m }; + BetaCitationsConfigParam expectedCitations = new() { Enabled = true }; + bool expectedDeferLoading = true; + long expectedMaxContentTokens = 1; + long expectedMaxUses = 1; + bool expectedStrict = true; + bool expectedUseCache = true; + + Assert.True(JsonElement.DeepEquals(expectedName, deserialized.Name)); + Assert.True(JsonElement.DeepEquals(expectedType, deserialized.Type)); + Assert.NotNull(deserialized.AllowedCallers); + Assert.Equal(expectedAllowedCallers.Count, deserialized.AllowedCallers.Count); + for (int i = 0; i < expectedAllowedCallers.Count; i++) + { + Assert.Equal(expectedAllowedCallers[i], deserialized.AllowedCallers[i]); + } + Assert.NotNull(deserialized.AllowedDomains); + Assert.Equal(expectedAllowedDomains.Count, deserialized.AllowedDomains.Count); + for (int i = 0; i < expectedAllowedDomains.Count; i++) + { + Assert.Equal(expectedAllowedDomains[i], deserialized.AllowedDomains[i]); + } + Assert.NotNull(deserialized.BlockedDomains); + Assert.Equal(expectedBlockedDomains.Count, deserialized.BlockedDomains.Count); + for (int i = 0; i < expectedBlockedDomains.Count; i++) + { + Assert.Equal(expectedBlockedDomains[i], deserialized.BlockedDomains[i]); + } + Assert.Equal(expectedCacheControl, deserialized.CacheControl); + Assert.Equal(expectedCitations, deserialized.Citations); + Assert.Equal(expectedDeferLoading, deserialized.DeferLoading); + Assert.Equal(expectedMaxContentTokens, deserialized.MaxContentTokens); + Assert.Equal(expectedMaxUses, deserialized.MaxUses); + Assert.Equal(expectedStrict, deserialized.Strict); + Assert.Equal(expectedUseCache, deserialized.UseCache); + } + + [Fact] + public void Validation_Works() + { + var model = new BetaWebFetchTool20260309 + { + AllowedCallers = [BetaWebFetchTool20260309AllowedCaller.Direct], + AllowedDomains = ["string"], + BlockedDomains = ["string"], + CacheControl = new() { Ttl = Ttl.Ttl5m }, + Citations = new() { Enabled = true }, + DeferLoading = true, + MaxContentTokens = 1, + MaxUses = 1, + Strict = true, + UseCache = true, + }; + + model.Validate(); + } + + [Fact] + public void OptionalNonNullablePropertiesUnsetAreNotSet_Works() + { + var model = new BetaWebFetchTool20260309 + { + AllowedDomains = ["string"], + BlockedDomains = ["string"], + CacheControl = new() { Ttl = Ttl.Ttl5m }, + Citations = new() { Enabled = true }, + MaxContentTokens = 1, + MaxUses = 1, + }; + + Assert.Null(model.AllowedCallers); + Assert.False(model.RawData.ContainsKey("allowed_callers")); + Assert.Null(model.DeferLoading); + Assert.False(model.RawData.ContainsKey("defer_loading")); + Assert.Null(model.Strict); + Assert.False(model.RawData.ContainsKey("strict")); + Assert.Null(model.UseCache); + Assert.False(model.RawData.ContainsKey("use_cache")); + } + + [Fact] + public void OptionalNonNullablePropertiesUnsetValidation_Works() + { + var model = new BetaWebFetchTool20260309 + { + AllowedDomains = ["string"], + BlockedDomains = ["string"], + CacheControl = new() { Ttl = Ttl.Ttl5m }, + Citations = new() { Enabled = true }, + MaxContentTokens = 1, + MaxUses = 1, + }; + + model.Validate(); + } + + [Fact] + public void OptionalNonNullablePropertiesSetToNullAreNotSet_Works() + { + var model = new BetaWebFetchTool20260309 + { + AllowedDomains = ["string"], + BlockedDomains = ["string"], + CacheControl = new() { Ttl = Ttl.Ttl5m }, + Citations = new() { Enabled = true }, + MaxContentTokens = 1, + MaxUses = 1, + + // Null should be interpreted as omitted for these properties + AllowedCallers = null, + DeferLoading = null, + Strict = null, + UseCache = null, + }; + + Assert.Null(model.AllowedCallers); + Assert.False(model.RawData.ContainsKey("allowed_callers")); + Assert.Null(model.DeferLoading); + Assert.False(model.RawData.ContainsKey("defer_loading")); + Assert.Null(model.Strict); + Assert.False(model.RawData.ContainsKey("strict")); + Assert.Null(model.UseCache); + Assert.False(model.RawData.ContainsKey("use_cache")); + } + + [Fact] + public void OptionalNonNullablePropertiesSetToNullValidation_Works() + { + var model = new BetaWebFetchTool20260309 + { + AllowedDomains = ["string"], + BlockedDomains = ["string"], + CacheControl = new() { Ttl = Ttl.Ttl5m }, + Citations = new() { Enabled = true }, + MaxContentTokens = 1, + MaxUses = 1, + + // Null should be interpreted as omitted for these properties + AllowedCallers = null, + DeferLoading = null, + Strict = null, + UseCache = null, + }; + + model.Validate(); + } + + [Fact] + public void OptionalNullablePropertiesUnsetAreNotSet_Works() + { + var model = new BetaWebFetchTool20260309 + { + AllowedCallers = [BetaWebFetchTool20260309AllowedCaller.Direct], + DeferLoading = true, + Strict = true, + UseCache = true, + }; + + Assert.Null(model.AllowedDomains); + Assert.False(model.RawData.ContainsKey("allowed_domains")); + Assert.Null(model.BlockedDomains); + Assert.False(model.RawData.ContainsKey("blocked_domains")); + Assert.Null(model.CacheControl); + Assert.False(model.RawData.ContainsKey("cache_control")); + Assert.Null(model.Citations); + Assert.False(model.RawData.ContainsKey("citations")); + Assert.Null(model.MaxContentTokens); + Assert.False(model.RawData.ContainsKey("max_content_tokens")); + Assert.Null(model.MaxUses); + Assert.False(model.RawData.ContainsKey("max_uses")); + } + + [Fact] + public void OptionalNullablePropertiesUnsetValidation_Works() + { + var model = new BetaWebFetchTool20260309 + { + AllowedCallers = [BetaWebFetchTool20260309AllowedCaller.Direct], + DeferLoading = true, + Strict = true, + UseCache = true, + }; + + model.Validate(); + } + + [Fact] + public void OptionalNullablePropertiesSetToNullAreSetToNull_Works() + { + var model = new BetaWebFetchTool20260309 + { + AllowedCallers = [BetaWebFetchTool20260309AllowedCaller.Direct], + DeferLoading = true, + Strict = true, + UseCache = true, + + AllowedDomains = null, + BlockedDomains = null, + CacheControl = null, + Citations = null, + MaxContentTokens = null, + MaxUses = null, + }; + + Assert.Null(model.AllowedDomains); + Assert.True(model.RawData.ContainsKey("allowed_domains")); + Assert.Null(model.BlockedDomains); + Assert.True(model.RawData.ContainsKey("blocked_domains")); + Assert.Null(model.CacheControl); + Assert.True(model.RawData.ContainsKey("cache_control")); + Assert.Null(model.Citations); + Assert.True(model.RawData.ContainsKey("citations")); + Assert.Null(model.MaxContentTokens); + Assert.True(model.RawData.ContainsKey("max_content_tokens")); + Assert.Null(model.MaxUses); + Assert.True(model.RawData.ContainsKey("max_uses")); + } + + [Fact] + public void OptionalNullablePropertiesSetToNullValidation_Works() + { + var model = new BetaWebFetchTool20260309 + { + AllowedCallers = [BetaWebFetchTool20260309AllowedCaller.Direct], + DeferLoading = true, + Strict = true, + UseCache = true, + + AllowedDomains = null, + BlockedDomains = null, + CacheControl = null, + Citations = null, + MaxContentTokens = null, + MaxUses = null, + }; + + model.Validate(); + } + + [Fact] + public void CopyConstructor_Works() + { + var model = new BetaWebFetchTool20260309 + { + AllowedCallers = [BetaWebFetchTool20260309AllowedCaller.Direct], + AllowedDomains = ["string"], + BlockedDomains = ["string"], + CacheControl = new() { Ttl = Ttl.Ttl5m }, + Citations = new() { Enabled = true }, + DeferLoading = true, + MaxContentTokens = 1, + MaxUses = 1, + Strict = true, + UseCache = true, + }; + + BetaWebFetchTool20260309 copied = new(model); + + Assert.Equal(model, copied); + } +} + +public class BetaWebFetchTool20260309AllowedCallerTest : TestBase +{ + [Theory] + [InlineData(BetaWebFetchTool20260309AllowedCaller.Direct)] + [InlineData(BetaWebFetchTool20260309AllowedCaller.CodeExecution20250825)] + [InlineData(BetaWebFetchTool20260309AllowedCaller.CodeExecution20260120)] + public void Validation_Works(BetaWebFetchTool20260309AllowedCaller rawValue) + { + // force implicit conversion because Theory can't do that for us + ApiEnum value = rawValue; + value.Validate(); + } + + [Fact] + public void InvalidEnumValidationThrows_Works() + { + var value = JsonSerializer.Deserialize< + ApiEnum + >(JsonSerializer.SerializeToElement("invalid value"), ModelBase.SerializerOptions); + + Assert.NotNull(value); + Assert.Throws(() => value.Validate()); + } + + [Theory] + [InlineData(BetaWebFetchTool20260309AllowedCaller.Direct)] + [InlineData(BetaWebFetchTool20260309AllowedCaller.CodeExecution20250825)] + [InlineData(BetaWebFetchTool20260309AllowedCaller.CodeExecution20260120)] + public void SerializationRoundtrip_Works(BetaWebFetchTool20260309AllowedCaller rawValue) + { + // force implicit conversion because Theory can't do that for us + ApiEnum value = rawValue; + + string json = JsonSerializer.Serialize(value, ModelBase.SerializerOptions); + var deserialized = JsonSerializer.Deserialize< + ApiEnum + >(json, ModelBase.SerializerOptions); + + Assert.Equal(value, deserialized); + } + + [Fact] + public void InvalidEnumSerializationRoundtrip_Works() + { + var value = JsonSerializer.Deserialize< + ApiEnum + >(JsonSerializer.SerializeToElement("invalid value"), ModelBase.SerializerOptions); + string json = JsonSerializer.Serialize(value, ModelBase.SerializerOptions); + var deserialized = JsonSerializer.Deserialize< + ApiEnum + >(json, ModelBase.SerializerOptions); + + Assert.Equal(value, deserialized); + } +} diff --git a/src/Anthropic.Tests/Models/Beta/Messages/MessageCountTokensParamsTest.cs b/src/Anthropic.Tests/Models/Beta/Messages/MessageCountTokensParamsTest.cs index 52b3c7edc..75c6d970d 100644 --- a/src/Anthropic.Tests/Models/Beta/Messages/MessageCountTokensParamsTest.cs +++ b/src/Anthropic.Tests/Models/Beta/Messages/MessageCountTokensParamsTest.cs @@ -83,7 +83,7 @@ public void FieldRoundtrip_Works() }, ] ), - Thinking = new BetaThinkingConfigEnabled(1024), + Thinking = new BetaThinkingConfigAdaptive() { Display = Display.Summarized }, ToolChoice = new BetaToolChoiceAuto() { DisableParallelToolUse = true }, Tools = [ @@ -185,7 +185,10 @@ public void FieldRoundtrip_Works() }, ] ); - BetaThinkingConfigParam expectedThinking = new BetaThinkingConfigEnabled(1024); + BetaThinkingConfigParam expectedThinking = new BetaThinkingConfigAdaptive() + { + Display = Display.Summarized, + }; BetaToolChoice expectedToolChoice = new BetaToolChoiceAuto() { DisableParallelToolUse = true, @@ -412,7 +415,7 @@ public void OptionalNullableParamsUnsetAreNotSet_Works() }, ] ), - Thinking = new BetaThinkingConfigEnabled(1024), + Thinking = new BetaThinkingConfigAdaptive() { Display = Display.Summarized }, ToolChoice = new BetaToolChoiceAuto() { DisableParallelToolUse = true }, Tools = [ @@ -505,7 +508,7 @@ public void OptionalNullableParamsSetToNullAreSetToNull_Works() }, ] ), - Thinking = new BetaThinkingConfigEnabled(1024), + Thinking = new BetaThinkingConfigAdaptive() { Display = Display.Summarized }, ToolChoice = new BetaToolChoiceAuto() { DisableParallelToolUse = true }, Tools = [ @@ -659,7 +662,7 @@ public void CopyConstructor_Works() }, ] ), - Thinking = new BetaThinkingConfigEnabled(1024), + Thinking = new BetaThinkingConfigAdaptive() { Display = Display.Summarized }, ToolChoice = new BetaToolChoiceAuto() { DisableParallelToolUse = true }, Tools = [ @@ -1204,6 +1207,25 @@ public void BetaWebFetchTool20260209ValidationWorks() value.Validate(); } + [Fact] + public void BetaWebFetchTool20260309ValidationWorks() + { + Tool value = new BetaWebFetchTool20260309() + { + AllowedCallers = [BetaWebFetchTool20260309AllowedCaller.Direct], + AllowedDomains = ["string"], + BlockedDomains = ["string"], + CacheControl = new() { Ttl = Ttl.Ttl5m }, + Citations = new() { Enabled = true }, + DeferLoading = true, + MaxContentTokens = 1, + MaxUses = 1, + Strict = true, + UseCache = true, + }; + value.Validate(); + } + [Fact] public void BetaToolSearchToolBm25_20251119ValidationWorks() { @@ -1670,6 +1692,28 @@ public void BetaWebFetchTool20260209SerializationRoundtripWorks() Assert.Equal(value, deserialized); } + [Fact] + public void BetaWebFetchTool20260309SerializationRoundtripWorks() + { + Tool value = new BetaWebFetchTool20260309() + { + AllowedCallers = [BetaWebFetchTool20260309AllowedCaller.Direct], + AllowedDomains = ["string"], + BlockedDomains = ["string"], + CacheControl = new() { Ttl = Ttl.Ttl5m }, + Citations = new() { Enabled = true }, + DeferLoading = true, + MaxContentTokens = 1, + MaxUses = 1, + Strict = true, + UseCache = true, + }; + string element = JsonSerializer.Serialize(value, ModelBase.SerializerOptions); + var deserialized = JsonSerializer.Deserialize(element, ModelBase.SerializerOptions); + + Assert.Equal(value, deserialized); + } + [Fact] public void BetaToolSearchToolBm25_20251119SerializationRoundtripWorks() { diff --git a/src/Anthropic.Tests/Models/Beta/Messages/MessageCreateParamsTest.cs b/src/Anthropic.Tests/Models/Beta/Messages/MessageCreateParamsTest.cs index e219b63c1..a7451875d 100644 --- a/src/Anthropic.Tests/Models/Beta/Messages/MessageCreateParamsTest.cs +++ b/src/Anthropic.Tests/Models/Beta/Messages/MessageCreateParamsTest.cs @@ -28,9 +28,9 @@ public void FieldRoundtrip_Works() [ new() { - SkillID = "x", + SkillID = "pdf", Type = BetaSkillParamsType.Anthropic, - Version = "x", + Version = "latest", }, ], }, @@ -102,7 +102,7 @@ public void FieldRoundtrip_Works() ] ), Temperature = 1, - Thinking = new BetaThinkingConfigEnabled(1024), + Thinking = new BetaThinkingConfigAdaptive() { Display = Display.Summarized }, ToolChoice = new BetaToolChoiceAuto() { DisableParallelToolUse = true }, Tools = [ @@ -153,9 +153,9 @@ public void FieldRoundtrip_Works() [ new() { - SkillID = "x", + SkillID = "pdf", Type = BetaSkillParamsType.Anthropic, - Version = "x", + Version = "latest", }, ], }; @@ -227,7 +227,10 @@ public void FieldRoundtrip_Works() ] ); double expectedTemperature = 1; - BetaThinkingConfigParam expectedThinking = new BetaThinkingConfigEnabled(1024); + BetaThinkingConfigParam expectedThinking = new BetaThinkingConfigAdaptive() + { + Display = Display.Summarized, + }; BetaToolChoice expectedToolChoice = new BetaToolChoiceAuto() { DisableParallelToolUse = true, @@ -333,9 +336,9 @@ public void OptionalNonNullableParamsUnsetAreNotSet_Works() [ new() { - SkillID = "x", + SkillID = "pdf", Type = BetaSkillParamsType.Anthropic, - Version = "x", + Version = "latest", }, ], }, @@ -408,9 +411,9 @@ public void OptionalNonNullableParamsSetToNullAreNotSet_Works() [ new() { - SkillID = "x", + SkillID = "pdf", Type = BetaSkillParamsType.Anthropic, - Version = "x", + Version = "latest", }, ], }, @@ -535,7 +538,7 @@ public void OptionalNullableParamsUnsetAreNotSet_Works() ] ), Temperature = 1, - Thinking = new BetaThinkingConfigEnabled(1024), + Thinking = new BetaThinkingConfigAdaptive() { Display = Display.Summarized }, ToolChoice = new BetaToolChoiceAuto() { DisableParallelToolUse = true }, Tools = [ @@ -639,7 +642,7 @@ public void OptionalNullableParamsSetToNullAreSetToNull_Works() ] ), Temperature = 1, - Thinking = new BetaThinkingConfigEnabled(1024), + Thinking = new BetaThinkingConfigAdaptive() { Display = Display.Summarized }, ToolChoice = new BetaToolChoiceAuto() { DisableParallelToolUse = true }, Tools = [ @@ -748,9 +751,9 @@ public void CopyConstructor_Works() [ new() { - SkillID = "x", + SkillID = "pdf", Type = BetaSkillParamsType.Anthropic, - Version = "x", + Version = "latest", }, ], }, @@ -822,7 +825,7 @@ public void CopyConstructor_Works() ] ), Temperature = 1, - Thinking = new BetaThinkingConfigEnabled(1024), + Thinking = new BetaThinkingConfigAdaptive() { Display = Display.Summarized }, ToolChoice = new BetaToolChoiceAuto() { DisableParallelToolUse = true }, Tools = [ @@ -877,9 +880,9 @@ public void BetaContainerParamsValidationWorks() [ new() { - SkillID = "x", + SkillID = "pdf", Type = BetaSkillParamsType.Anthropic, - Version = "x", + Version = "latest", }, ], }; @@ -903,9 +906,9 @@ public void BetaContainerParamsSerializationRoundtripWorks() [ new() { - SkillID = "x", + SkillID = "pdf", Type = BetaSkillParamsType.Anthropic, - Version = "x", + Version = "latest", }, ], }; diff --git a/src/Anthropic.Tests/Models/Beta/Models/BetaCapabilitySupportTest.cs b/src/Anthropic.Tests/Models/Beta/Models/BetaCapabilitySupportTest.cs new file mode 100644 index 000000000..a8b732bab --- /dev/null +++ b/src/Anthropic.Tests/Models/Beta/Models/BetaCapabilitySupportTest.cs @@ -0,0 +1,67 @@ +using System.Text.Json; +using Anthropic.Core; +using Anthropic.Models.Beta.Models; + +namespace Anthropic.Tests.Models.Beta.Models; + +public class BetaCapabilitySupportTest : TestBase +{ + [Fact] + public void FieldRoundtrip_Works() + { + var model = new BetaCapabilitySupport { Supported = true }; + + bool expectedSupported = true; + + Assert.Equal(expectedSupported, model.Supported); + } + + [Fact] + public void SerializationRoundtrip_Works() + { + var model = new BetaCapabilitySupport { Supported = true }; + + string json = JsonSerializer.Serialize(model, ModelBase.SerializerOptions); + var deserialized = JsonSerializer.Deserialize( + json, + ModelBase.SerializerOptions + ); + + Assert.Equal(model, deserialized); + } + + [Fact] + public void FieldRoundtripThroughSerialization_Works() + { + var model = new BetaCapabilitySupport { Supported = true }; + + string element = JsonSerializer.Serialize(model, ModelBase.SerializerOptions); + var deserialized = JsonSerializer.Deserialize( + element, + ModelBase.SerializerOptions + ); + Assert.NotNull(deserialized); + + bool expectedSupported = true; + + Assert.Equal(expectedSupported, deserialized.Supported); + } + + [Fact] + public void Validation_Works() + { + var model = new BetaCapabilitySupport { Supported = true }; + + model.Validate(); + } + + [Fact] + public void CopyConstructor_Works() + { + var model = new BetaCapabilitySupport { Supported = true }; + + BetaCapabilitySupport copied = new(model); + + Assert.Equal(model, copied); + } +} diff --git a/src/Anthropic.Tests/Models/Beta/Models/BetaContextManagementCapabilityTest.cs b/src/Anthropic.Tests/Models/Beta/Models/BetaContextManagementCapabilityTest.cs new file mode 100644 index 000000000..dae87d286 --- /dev/null +++ b/src/Anthropic.Tests/Models/Beta/Models/BetaContextManagementCapabilityTest.cs @@ -0,0 +1,109 @@ +using System.Text.Json; +using Anthropic.Core; +using Anthropic.Models.Beta.Models; + +namespace Anthropic.Tests.Models.Beta.Models; + +public class BetaContextManagementCapabilityTest : TestBase +{ + [Fact] + public void FieldRoundtrip_Works() + { + var model = new BetaContextManagementCapability + { + ClearThinking20251015 = new(true), + ClearToolUses20250919 = new(true), + Compact20260112 = new(true), + Supported = true, + }; + + BetaCapabilitySupport expectedClearThinking20251015 = new(true); + BetaCapabilitySupport expectedClearToolUses20250919 = new(true); + BetaCapabilitySupport expectedCompact20260112 = new(true); + bool expectedSupported = true; + + Assert.Equal(expectedClearThinking20251015, model.ClearThinking20251015); + Assert.Equal(expectedClearToolUses20250919, model.ClearToolUses20250919); + Assert.Equal(expectedCompact20260112, model.Compact20260112); + Assert.Equal(expectedSupported, model.Supported); + } + + [Fact] + public void SerializationRoundtrip_Works() + { + var model = new BetaContextManagementCapability + { + ClearThinking20251015 = new(true), + ClearToolUses20250919 = new(true), + Compact20260112 = new(true), + Supported = true, + }; + + string json = JsonSerializer.Serialize(model, ModelBase.SerializerOptions); + var deserialized = JsonSerializer.Deserialize( + json, + ModelBase.SerializerOptions + ); + + Assert.Equal(model, deserialized); + } + + [Fact] + public void FieldRoundtripThroughSerialization_Works() + { + var model = new BetaContextManagementCapability + { + ClearThinking20251015 = new(true), + ClearToolUses20250919 = new(true), + Compact20260112 = new(true), + Supported = true, + }; + + string element = JsonSerializer.Serialize(model, ModelBase.SerializerOptions); + var deserialized = JsonSerializer.Deserialize( + element, + ModelBase.SerializerOptions + ); + Assert.NotNull(deserialized); + + BetaCapabilitySupport expectedClearThinking20251015 = new(true); + BetaCapabilitySupport expectedClearToolUses20250919 = new(true); + BetaCapabilitySupport expectedCompact20260112 = new(true); + bool expectedSupported = true; + + Assert.Equal(expectedClearThinking20251015, deserialized.ClearThinking20251015); + Assert.Equal(expectedClearToolUses20250919, deserialized.ClearToolUses20250919); + Assert.Equal(expectedCompact20260112, deserialized.Compact20260112); + Assert.Equal(expectedSupported, deserialized.Supported); + } + + [Fact] + public void Validation_Works() + { + var model = new BetaContextManagementCapability + { + ClearThinking20251015 = new(true), + ClearToolUses20250919 = new(true), + Compact20260112 = new(true), + Supported = true, + }; + + model.Validate(); + } + + [Fact] + public void CopyConstructor_Works() + { + var model = new BetaContextManagementCapability + { + ClearThinking20251015 = new(true), + ClearToolUses20250919 = new(true), + Compact20260112 = new(true), + Supported = true, + }; + + BetaContextManagementCapability copied = new(model); + + Assert.Equal(model, copied); + } +} diff --git a/src/Anthropic.Tests/Models/Beta/Models/BetaEffortCapabilityTest.cs b/src/Anthropic.Tests/Models/Beta/Models/BetaEffortCapabilityTest.cs new file mode 100644 index 000000000..64d2df148 --- /dev/null +++ b/src/Anthropic.Tests/Models/Beta/Models/BetaEffortCapabilityTest.cs @@ -0,0 +1,118 @@ +using System.Text.Json; +using Anthropic.Core; +using Anthropic.Models.Beta.Models; + +namespace Anthropic.Tests.Models.Beta.Models; + +public class BetaEffortCapabilityTest : TestBase +{ + [Fact] + public void FieldRoundtrip_Works() + { + var model = new BetaEffortCapability + { + High = new(true), + Low = new(true), + Max = new(true), + Medium = new(true), + Supported = true, + }; + + BetaCapabilitySupport expectedHigh = new(true); + BetaCapabilitySupport expectedLow = new(true); + BetaCapabilitySupport expectedMax = new(true); + BetaCapabilitySupport expectedMedium = new(true); + bool expectedSupported = true; + + Assert.Equal(expectedHigh, model.High); + Assert.Equal(expectedLow, model.Low); + Assert.Equal(expectedMax, model.Max); + Assert.Equal(expectedMedium, model.Medium); + Assert.Equal(expectedSupported, model.Supported); + } + + [Fact] + public void SerializationRoundtrip_Works() + { + var model = new BetaEffortCapability + { + High = new(true), + Low = new(true), + Max = new(true), + Medium = new(true), + Supported = true, + }; + + string json = JsonSerializer.Serialize(model, ModelBase.SerializerOptions); + var deserialized = JsonSerializer.Deserialize( + json, + ModelBase.SerializerOptions + ); + + Assert.Equal(model, deserialized); + } + + [Fact] + public void FieldRoundtripThroughSerialization_Works() + { + var model = new BetaEffortCapability + { + High = new(true), + Low = new(true), + Max = new(true), + Medium = new(true), + Supported = true, + }; + + string element = JsonSerializer.Serialize(model, ModelBase.SerializerOptions); + var deserialized = JsonSerializer.Deserialize( + element, + ModelBase.SerializerOptions + ); + Assert.NotNull(deserialized); + + BetaCapabilitySupport expectedHigh = new(true); + BetaCapabilitySupport expectedLow = new(true); + BetaCapabilitySupport expectedMax = new(true); + BetaCapabilitySupport expectedMedium = new(true); + bool expectedSupported = true; + + Assert.Equal(expectedHigh, deserialized.High); + Assert.Equal(expectedLow, deserialized.Low); + Assert.Equal(expectedMax, deserialized.Max); + Assert.Equal(expectedMedium, deserialized.Medium); + Assert.Equal(expectedSupported, deserialized.Supported); + } + + [Fact] + public void Validation_Works() + { + var model = new BetaEffortCapability + { + High = new(true), + Low = new(true), + Max = new(true), + Medium = new(true), + Supported = true, + }; + + model.Validate(); + } + + [Fact] + public void CopyConstructor_Works() + { + var model = new BetaEffortCapability + { + High = new(true), + Low = new(true), + Max = new(true), + Medium = new(true), + Supported = true, + }; + + BetaEffortCapability copied = new(model); + + Assert.Equal(model, copied); + } +} diff --git a/src/Anthropic.Tests/Models/Beta/Models/BetaModelCapabilitiesTest.cs b/src/Anthropic.Tests/Models/Beta/Models/BetaModelCapabilitiesTest.cs new file mode 100644 index 000000000..f7fadfcd1 --- /dev/null +++ b/src/Anthropic.Tests/Models/Beta/Models/BetaModelCapabilitiesTest.cs @@ -0,0 +1,273 @@ +using System.Text.Json; +using Anthropic.Core; +using Anthropic.Models.Beta.Models; + +namespace Anthropic.Tests.Models.Beta.Models; + +public class BetaModelCapabilitiesTest : TestBase +{ + [Fact] + public void FieldRoundtrip_Works() + { + var model = new BetaModelCapabilities + { + Batch = new(true), + Citations = new(true), + CodeExecution = new(true), + ContextManagement = new() + { + ClearThinking20251015 = new(true), + ClearToolUses20250919 = new(true), + Compact20260112 = new(true), + Supported = true, + }, + Effort = new() + { + High = new(true), + Low = new(true), + Max = new(true), + Medium = new(true), + Supported = true, + }, + ImageInput = new(true), + PdfInput = new(true), + StructuredOutputs = new(true), + Thinking = new() + { + Supported = true, + Types = new() { Adaptive = new(true), Enabled = new(true) }, + }, + }; + + BetaCapabilitySupport expectedBatch = new(true); + BetaCapabilitySupport expectedCitations = new(true); + BetaCapabilitySupport expectedCodeExecution = new(true); + BetaContextManagementCapability expectedContextManagement = new() + { + ClearThinking20251015 = new(true), + ClearToolUses20250919 = new(true), + Compact20260112 = new(true), + Supported = true, + }; + BetaEffortCapability expectedEffort = new() + { + High = new(true), + Low = new(true), + Max = new(true), + Medium = new(true), + Supported = true, + }; + BetaCapabilitySupport expectedImageInput = new(true); + BetaCapabilitySupport expectedPdfInput = new(true); + BetaCapabilitySupport expectedStructuredOutputs = new(true); + BetaThinkingCapability expectedThinking = new() + { + Supported = true, + Types = new() { Adaptive = new(true), Enabled = new(true) }, + }; + + Assert.Equal(expectedBatch, model.Batch); + Assert.Equal(expectedCitations, model.Citations); + Assert.Equal(expectedCodeExecution, model.CodeExecution); + Assert.Equal(expectedContextManagement, model.ContextManagement); + Assert.Equal(expectedEffort, model.Effort); + Assert.Equal(expectedImageInput, model.ImageInput); + Assert.Equal(expectedPdfInput, model.PdfInput); + Assert.Equal(expectedStructuredOutputs, model.StructuredOutputs); + Assert.Equal(expectedThinking, model.Thinking); + } + + [Fact] + public void SerializationRoundtrip_Works() + { + var model = new BetaModelCapabilities + { + Batch = new(true), + Citations = new(true), + CodeExecution = new(true), + ContextManagement = new() + { + ClearThinking20251015 = new(true), + ClearToolUses20250919 = new(true), + Compact20260112 = new(true), + Supported = true, + }, + Effort = new() + { + High = new(true), + Low = new(true), + Max = new(true), + Medium = new(true), + Supported = true, + }, + ImageInput = new(true), + PdfInput = new(true), + StructuredOutputs = new(true), + Thinking = new() + { + Supported = true, + Types = new() { Adaptive = new(true), Enabled = new(true) }, + }, + }; + + string json = JsonSerializer.Serialize(model, ModelBase.SerializerOptions); + var deserialized = JsonSerializer.Deserialize( + json, + ModelBase.SerializerOptions + ); + + Assert.Equal(model, deserialized); + } + + [Fact] + public void FieldRoundtripThroughSerialization_Works() + { + var model = new BetaModelCapabilities + { + Batch = new(true), + Citations = new(true), + CodeExecution = new(true), + ContextManagement = new() + { + ClearThinking20251015 = new(true), + ClearToolUses20250919 = new(true), + Compact20260112 = new(true), + Supported = true, + }, + Effort = new() + { + High = new(true), + Low = new(true), + Max = new(true), + Medium = new(true), + Supported = true, + }, + ImageInput = new(true), + PdfInput = new(true), + StructuredOutputs = new(true), + Thinking = new() + { + Supported = true, + Types = new() { Adaptive = new(true), Enabled = new(true) }, + }, + }; + + string element = JsonSerializer.Serialize(model, ModelBase.SerializerOptions); + var deserialized = JsonSerializer.Deserialize( + element, + ModelBase.SerializerOptions + ); + Assert.NotNull(deserialized); + + BetaCapabilitySupport expectedBatch = new(true); + BetaCapabilitySupport expectedCitations = new(true); + BetaCapabilitySupport expectedCodeExecution = new(true); + BetaContextManagementCapability expectedContextManagement = new() + { + ClearThinking20251015 = new(true), + ClearToolUses20250919 = new(true), + Compact20260112 = new(true), + Supported = true, + }; + BetaEffortCapability expectedEffort = new() + { + High = new(true), + Low = new(true), + Max = new(true), + Medium = new(true), + Supported = true, + }; + BetaCapabilitySupport expectedImageInput = new(true); + BetaCapabilitySupport expectedPdfInput = new(true); + BetaCapabilitySupport expectedStructuredOutputs = new(true); + BetaThinkingCapability expectedThinking = new() + { + Supported = true, + Types = new() { Adaptive = new(true), Enabled = new(true) }, + }; + + Assert.Equal(expectedBatch, deserialized.Batch); + Assert.Equal(expectedCitations, deserialized.Citations); + Assert.Equal(expectedCodeExecution, deserialized.CodeExecution); + Assert.Equal(expectedContextManagement, deserialized.ContextManagement); + Assert.Equal(expectedEffort, deserialized.Effort); + Assert.Equal(expectedImageInput, deserialized.ImageInput); + Assert.Equal(expectedPdfInput, deserialized.PdfInput); + Assert.Equal(expectedStructuredOutputs, deserialized.StructuredOutputs); + Assert.Equal(expectedThinking, deserialized.Thinking); + } + + [Fact] + public void Validation_Works() + { + var model = new BetaModelCapabilities + { + Batch = new(true), + Citations = new(true), + CodeExecution = new(true), + ContextManagement = new() + { + ClearThinking20251015 = new(true), + ClearToolUses20250919 = new(true), + Compact20260112 = new(true), + Supported = true, + }, + Effort = new() + { + High = new(true), + Low = new(true), + Max = new(true), + Medium = new(true), + Supported = true, + }, + ImageInput = new(true), + PdfInput = new(true), + StructuredOutputs = new(true), + Thinking = new() + { + Supported = true, + Types = new() { Adaptive = new(true), Enabled = new(true) }, + }, + }; + + model.Validate(); + } + + [Fact] + public void CopyConstructor_Works() + { + var model = new BetaModelCapabilities + { + Batch = new(true), + Citations = new(true), + CodeExecution = new(true), + ContextManagement = new() + { + ClearThinking20251015 = new(true), + ClearToolUses20250919 = new(true), + Compact20260112 = new(true), + Supported = true, + }, + Effort = new() + { + High = new(true), + Low = new(true), + Max = new(true), + Medium = new(true), + Supported = true, + }, + ImageInput = new(true), + PdfInput = new(true), + StructuredOutputs = new(true), + Thinking = new() + { + Supported = true, + Types = new() { Adaptive = new(true), Enabled = new(true) }, + }, + }; + + BetaModelCapabilities copied = new(model); + + Assert.Equal(model, copied); + } +} diff --git a/src/Anthropic.Tests/Models/Beta/Models/BetaModelInfoTest.cs b/src/Anthropic.Tests/Models/Beta/Models/BetaModelInfoTest.cs index e623ae2af..f5c840778 100644 --- a/src/Anthropic.Tests/Models/Beta/Models/BetaModelInfoTest.cs +++ b/src/Anthropic.Tests/Models/Beta/Models/BetaModelInfoTest.cs @@ -13,18 +13,83 @@ public void FieldRoundtrip_Works() var model = new BetaModelInfo { ID = "claude-opus-4-6", + Capabilities = new() + { + Batch = new(true), + Citations = new(true), + CodeExecution = new(true), + ContextManagement = new() + { + ClearThinking20251015 = new(true), + ClearToolUses20250919 = new(true), + Compact20260112 = new(true), + Supported = true, + }, + Effort = new() + { + High = new(true), + Low = new(true), + Max = new(true), + Medium = new(true), + Supported = true, + }, + ImageInput = new(true), + PdfInput = new(true), + StructuredOutputs = new(true), + Thinking = new() + { + Supported = true, + Types = new() { Adaptive = new(true), Enabled = new(true) }, + }, + }, CreatedAt = DateTimeOffset.Parse("2026-02-04T00:00:00Z"), DisplayName = "Claude Opus 4.6", + MaxInputTokens = 0, + MaxTokens = 0, }; string expectedID = "claude-opus-4-6"; + BetaModelCapabilities expectedCapabilities = new() + { + Batch = new(true), + Citations = new(true), + CodeExecution = new(true), + ContextManagement = new() + { + ClearThinking20251015 = new(true), + ClearToolUses20250919 = new(true), + Compact20260112 = new(true), + Supported = true, + }, + Effort = new() + { + High = new(true), + Low = new(true), + Max = new(true), + Medium = new(true), + Supported = true, + }, + ImageInput = new(true), + PdfInput = new(true), + StructuredOutputs = new(true), + Thinking = new() + { + Supported = true, + Types = new() { Adaptive = new(true), Enabled = new(true) }, + }, + }; DateTimeOffset expectedCreatedAt = DateTimeOffset.Parse("2026-02-04T00:00:00Z"); string expectedDisplayName = "Claude Opus 4.6"; + long expectedMaxInputTokens = 0; + long expectedMaxTokens = 0; JsonElement expectedType = JsonSerializer.SerializeToElement("model"); Assert.Equal(expectedID, model.ID); + Assert.Equal(expectedCapabilities, model.Capabilities); Assert.Equal(expectedCreatedAt, model.CreatedAt); Assert.Equal(expectedDisplayName, model.DisplayName); + Assert.Equal(expectedMaxInputTokens, model.MaxInputTokens); + Assert.Equal(expectedMaxTokens, model.MaxTokens); Assert.True(JsonElement.DeepEquals(expectedType, model.Type)); } @@ -34,8 +99,39 @@ public void SerializationRoundtrip_Works() var model = new BetaModelInfo { ID = "claude-opus-4-6", + Capabilities = new() + { + Batch = new(true), + Citations = new(true), + CodeExecution = new(true), + ContextManagement = new() + { + ClearThinking20251015 = new(true), + ClearToolUses20250919 = new(true), + Compact20260112 = new(true), + Supported = true, + }, + Effort = new() + { + High = new(true), + Low = new(true), + Max = new(true), + Medium = new(true), + Supported = true, + }, + ImageInput = new(true), + PdfInput = new(true), + StructuredOutputs = new(true), + Thinking = new() + { + Supported = true, + Types = new() { Adaptive = new(true), Enabled = new(true) }, + }, + }, CreatedAt = DateTimeOffset.Parse("2026-02-04T00:00:00Z"), DisplayName = "Claude Opus 4.6", + MaxInputTokens = 0, + MaxTokens = 0, }; string json = JsonSerializer.Serialize(model, ModelBase.SerializerOptions); @@ -53,8 +149,39 @@ public void FieldRoundtripThroughSerialization_Works() var model = new BetaModelInfo { ID = "claude-opus-4-6", + Capabilities = new() + { + Batch = new(true), + Citations = new(true), + CodeExecution = new(true), + ContextManagement = new() + { + ClearThinking20251015 = new(true), + ClearToolUses20250919 = new(true), + Compact20260112 = new(true), + Supported = true, + }, + Effort = new() + { + High = new(true), + Low = new(true), + Max = new(true), + Medium = new(true), + Supported = true, + }, + ImageInput = new(true), + PdfInput = new(true), + StructuredOutputs = new(true), + Thinking = new() + { + Supported = true, + Types = new() { Adaptive = new(true), Enabled = new(true) }, + }, + }, CreatedAt = DateTimeOffset.Parse("2026-02-04T00:00:00Z"), DisplayName = "Claude Opus 4.6", + MaxInputTokens = 0, + MaxTokens = 0, }; string element = JsonSerializer.Serialize(model, ModelBase.SerializerOptions); @@ -65,13 +192,47 @@ public void FieldRoundtripThroughSerialization_Works() Assert.NotNull(deserialized); string expectedID = "claude-opus-4-6"; + BetaModelCapabilities expectedCapabilities = new() + { + Batch = new(true), + Citations = new(true), + CodeExecution = new(true), + ContextManagement = new() + { + ClearThinking20251015 = new(true), + ClearToolUses20250919 = new(true), + Compact20260112 = new(true), + Supported = true, + }, + Effort = new() + { + High = new(true), + Low = new(true), + Max = new(true), + Medium = new(true), + Supported = true, + }, + ImageInput = new(true), + PdfInput = new(true), + StructuredOutputs = new(true), + Thinking = new() + { + Supported = true, + Types = new() { Adaptive = new(true), Enabled = new(true) }, + }, + }; DateTimeOffset expectedCreatedAt = DateTimeOffset.Parse("2026-02-04T00:00:00Z"); string expectedDisplayName = "Claude Opus 4.6"; + long expectedMaxInputTokens = 0; + long expectedMaxTokens = 0; JsonElement expectedType = JsonSerializer.SerializeToElement("model"); Assert.Equal(expectedID, deserialized.ID); + Assert.Equal(expectedCapabilities, deserialized.Capabilities); Assert.Equal(expectedCreatedAt, deserialized.CreatedAt); Assert.Equal(expectedDisplayName, deserialized.DisplayName); + Assert.Equal(expectedMaxInputTokens, deserialized.MaxInputTokens); + Assert.Equal(expectedMaxTokens, deserialized.MaxTokens); Assert.True(JsonElement.DeepEquals(expectedType, deserialized.Type)); } @@ -81,8 +242,39 @@ public void Validation_Works() var model = new BetaModelInfo { ID = "claude-opus-4-6", + Capabilities = new() + { + Batch = new(true), + Citations = new(true), + CodeExecution = new(true), + ContextManagement = new() + { + ClearThinking20251015 = new(true), + ClearToolUses20250919 = new(true), + Compact20260112 = new(true), + Supported = true, + }, + Effort = new() + { + High = new(true), + Low = new(true), + Max = new(true), + Medium = new(true), + Supported = true, + }, + ImageInput = new(true), + PdfInput = new(true), + StructuredOutputs = new(true), + Thinking = new() + { + Supported = true, + Types = new() { Adaptive = new(true), Enabled = new(true) }, + }, + }, CreatedAt = DateTimeOffset.Parse("2026-02-04T00:00:00Z"), DisplayName = "Claude Opus 4.6", + MaxInputTokens = 0, + MaxTokens = 0, }; model.Validate(); @@ -94,8 +286,39 @@ public void CopyConstructor_Works() var model = new BetaModelInfo { ID = "claude-opus-4-6", + Capabilities = new() + { + Batch = new(true), + Citations = new(true), + CodeExecution = new(true), + ContextManagement = new() + { + ClearThinking20251015 = new(true), + ClearToolUses20250919 = new(true), + Compact20260112 = new(true), + Supported = true, + }, + Effort = new() + { + High = new(true), + Low = new(true), + Max = new(true), + Medium = new(true), + Supported = true, + }, + ImageInput = new(true), + PdfInput = new(true), + StructuredOutputs = new(true), + Thinking = new() + { + Supported = true, + Types = new() { Adaptive = new(true), Enabled = new(true) }, + }, + }, CreatedAt = DateTimeOffset.Parse("2026-02-04T00:00:00Z"), DisplayName = "Claude Opus 4.6", + MaxInputTokens = 0, + MaxTokens = 0, }; BetaModelInfo copied = new(model); diff --git a/src/Anthropic.Tests/Models/Beta/Models/BetaThinkingCapabilityTest.cs b/src/Anthropic.Tests/Models/Beta/Models/BetaThinkingCapabilityTest.cs new file mode 100644 index 000000000..eac92b27b --- /dev/null +++ b/src/Anthropic.Tests/Models/Beta/Models/BetaThinkingCapabilityTest.cs @@ -0,0 +1,91 @@ +using System.Text.Json; +using Anthropic.Core; +using Anthropic.Models.Beta.Models; + +namespace Anthropic.Tests.Models.Beta.Models; + +public class BetaThinkingCapabilityTest : TestBase +{ + [Fact] + public void FieldRoundtrip_Works() + { + var model = new BetaThinkingCapability + { + Supported = true, + Types = new() { Adaptive = new(true), Enabled = new(true) }, + }; + + bool expectedSupported = true; + BetaThinkingTypes expectedTypes = new() { Adaptive = new(true), Enabled = new(true) }; + + Assert.Equal(expectedSupported, model.Supported); + Assert.Equal(expectedTypes, model.Types); + } + + [Fact] + public void SerializationRoundtrip_Works() + { + var model = new BetaThinkingCapability + { + Supported = true, + Types = new() { Adaptive = new(true), Enabled = new(true) }, + }; + + string json = JsonSerializer.Serialize(model, ModelBase.SerializerOptions); + var deserialized = JsonSerializer.Deserialize( + json, + ModelBase.SerializerOptions + ); + + Assert.Equal(model, deserialized); + } + + [Fact] + public void FieldRoundtripThroughSerialization_Works() + { + var model = new BetaThinkingCapability + { + Supported = true, + Types = new() { Adaptive = new(true), Enabled = new(true) }, + }; + + string element = JsonSerializer.Serialize(model, ModelBase.SerializerOptions); + var deserialized = JsonSerializer.Deserialize( + element, + ModelBase.SerializerOptions + ); + Assert.NotNull(deserialized); + + bool expectedSupported = true; + BetaThinkingTypes expectedTypes = new() { Adaptive = new(true), Enabled = new(true) }; + + Assert.Equal(expectedSupported, deserialized.Supported); + Assert.Equal(expectedTypes, deserialized.Types); + } + + [Fact] + public void Validation_Works() + { + var model = new BetaThinkingCapability + { + Supported = true, + Types = new() { Adaptive = new(true), Enabled = new(true) }, + }; + + model.Validate(); + } + + [Fact] + public void CopyConstructor_Works() + { + var model = new BetaThinkingCapability + { + Supported = true, + Types = new() { Adaptive = new(true), Enabled = new(true) }, + }; + + BetaThinkingCapability copied = new(model); + + Assert.Equal(model, copied); + } +} diff --git a/src/Anthropic.Tests/Models/Beta/Models/BetaThinkingTypesTest.cs b/src/Anthropic.Tests/Models/Beta/Models/BetaThinkingTypesTest.cs new file mode 100644 index 000000000..54af67fea --- /dev/null +++ b/src/Anthropic.Tests/Models/Beta/Models/BetaThinkingTypesTest.cs @@ -0,0 +1,71 @@ +using System.Text.Json; +using Anthropic.Core; +using Anthropic.Models.Beta.Models; + +namespace Anthropic.Tests.Models.Beta.Models; + +public class BetaThinkingTypesTest : TestBase +{ + [Fact] + public void FieldRoundtrip_Works() + { + var model = new BetaThinkingTypes { Adaptive = new(true), Enabled = new(true) }; + + BetaCapabilitySupport expectedAdaptive = new(true); + BetaCapabilitySupport expectedEnabled = new(true); + + Assert.Equal(expectedAdaptive, model.Adaptive); + Assert.Equal(expectedEnabled, model.Enabled); + } + + [Fact] + public void SerializationRoundtrip_Works() + { + var model = new BetaThinkingTypes { Adaptive = new(true), Enabled = new(true) }; + + string json = JsonSerializer.Serialize(model, ModelBase.SerializerOptions); + var deserialized = JsonSerializer.Deserialize( + json, + ModelBase.SerializerOptions + ); + + Assert.Equal(model, deserialized); + } + + [Fact] + public void FieldRoundtripThroughSerialization_Works() + { + var model = new BetaThinkingTypes { Adaptive = new(true), Enabled = new(true) }; + + string element = JsonSerializer.Serialize(model, ModelBase.SerializerOptions); + var deserialized = JsonSerializer.Deserialize( + element, + ModelBase.SerializerOptions + ); + Assert.NotNull(deserialized); + + BetaCapabilitySupport expectedAdaptive = new(true); + BetaCapabilitySupport expectedEnabled = new(true); + + Assert.Equal(expectedAdaptive, deserialized.Adaptive); + Assert.Equal(expectedEnabled, deserialized.Enabled); + } + + [Fact] + public void Validation_Works() + { + var model = new BetaThinkingTypes { Adaptive = new(true), Enabled = new(true) }; + + model.Validate(); + } + + [Fact] + public void CopyConstructor_Works() + { + var model = new BetaThinkingTypes { Adaptive = new(true), Enabled = new(true) }; + + BetaThinkingTypes copied = new(model); + + Assert.Equal(model, copied); + } +} diff --git a/src/Anthropic.Tests/Models/Beta/Models/ModelListPageResponseTest.cs b/src/Anthropic.Tests/Models/Beta/Models/ModelListPageResponseTest.cs index 23a840926..5a421888e 100644 --- a/src/Anthropic.Tests/Models/Beta/Models/ModelListPageResponseTest.cs +++ b/src/Anthropic.Tests/Models/Beta/Models/ModelListPageResponseTest.cs @@ -18,8 +18,39 @@ public void FieldRoundtrip_Works() new() { ID = "claude-opus-4-6", + Capabilities = new() + { + Batch = new(true), + Citations = new(true), + CodeExecution = new(true), + ContextManagement = new() + { + ClearThinking20251015 = new(true), + ClearToolUses20250919 = new(true), + Compact20260112 = new(true), + Supported = true, + }, + Effort = new() + { + High = new(true), + Low = new(true), + Max = new(true), + Medium = new(true), + Supported = true, + }, + ImageInput = new(true), + PdfInput = new(true), + StructuredOutputs = new(true), + Thinking = new() + { + Supported = true, + Types = new() { Adaptive = new(true), Enabled = new(true) }, + }, + }, CreatedAt = DateTimeOffset.Parse("2026-02-04T00:00:00Z"), DisplayName = "Claude Opus 4.6", + MaxInputTokens = 0, + MaxTokens = 0, }, ], FirstID = "first_id", @@ -32,8 +63,39 @@ public void FieldRoundtrip_Works() new() { ID = "claude-opus-4-6", + Capabilities = new() + { + Batch = new(true), + Citations = new(true), + CodeExecution = new(true), + ContextManagement = new() + { + ClearThinking20251015 = new(true), + ClearToolUses20250919 = new(true), + Compact20260112 = new(true), + Supported = true, + }, + Effort = new() + { + High = new(true), + Low = new(true), + Max = new(true), + Medium = new(true), + Supported = true, + }, + ImageInput = new(true), + PdfInput = new(true), + StructuredOutputs = new(true), + Thinking = new() + { + Supported = true, + Types = new() { Adaptive = new(true), Enabled = new(true) }, + }, + }, CreatedAt = DateTimeOffset.Parse("2026-02-04T00:00:00Z"), DisplayName = "Claude Opus 4.6", + MaxInputTokens = 0, + MaxTokens = 0, }, ]; string expectedFirstID = "first_id"; @@ -60,8 +122,39 @@ public void SerializationRoundtrip_Works() new() { ID = "claude-opus-4-6", + Capabilities = new() + { + Batch = new(true), + Citations = new(true), + CodeExecution = new(true), + ContextManagement = new() + { + ClearThinking20251015 = new(true), + ClearToolUses20250919 = new(true), + Compact20260112 = new(true), + Supported = true, + }, + Effort = new() + { + High = new(true), + Low = new(true), + Max = new(true), + Medium = new(true), + Supported = true, + }, + ImageInput = new(true), + PdfInput = new(true), + StructuredOutputs = new(true), + Thinking = new() + { + Supported = true, + Types = new() { Adaptive = new(true), Enabled = new(true) }, + }, + }, CreatedAt = DateTimeOffset.Parse("2026-02-04T00:00:00Z"), DisplayName = "Claude Opus 4.6", + MaxInputTokens = 0, + MaxTokens = 0, }, ], FirstID = "first_id", @@ -88,8 +181,39 @@ public void FieldRoundtripThroughSerialization_Works() new() { ID = "claude-opus-4-6", + Capabilities = new() + { + Batch = new(true), + Citations = new(true), + CodeExecution = new(true), + ContextManagement = new() + { + ClearThinking20251015 = new(true), + ClearToolUses20250919 = new(true), + Compact20260112 = new(true), + Supported = true, + }, + Effort = new() + { + High = new(true), + Low = new(true), + Max = new(true), + Medium = new(true), + Supported = true, + }, + ImageInput = new(true), + PdfInput = new(true), + StructuredOutputs = new(true), + Thinking = new() + { + Supported = true, + Types = new() { Adaptive = new(true), Enabled = new(true) }, + }, + }, CreatedAt = DateTimeOffset.Parse("2026-02-04T00:00:00Z"), DisplayName = "Claude Opus 4.6", + MaxInputTokens = 0, + MaxTokens = 0, }, ], FirstID = "first_id", @@ -109,8 +233,39 @@ public void FieldRoundtripThroughSerialization_Works() new() { ID = "claude-opus-4-6", + Capabilities = new() + { + Batch = new(true), + Citations = new(true), + CodeExecution = new(true), + ContextManagement = new() + { + ClearThinking20251015 = new(true), + ClearToolUses20250919 = new(true), + Compact20260112 = new(true), + Supported = true, + }, + Effort = new() + { + High = new(true), + Low = new(true), + Max = new(true), + Medium = new(true), + Supported = true, + }, + ImageInput = new(true), + PdfInput = new(true), + StructuredOutputs = new(true), + Thinking = new() + { + Supported = true, + Types = new() { Adaptive = new(true), Enabled = new(true) }, + }, + }, CreatedAt = DateTimeOffset.Parse("2026-02-04T00:00:00Z"), DisplayName = "Claude Opus 4.6", + MaxInputTokens = 0, + MaxTokens = 0, }, ]; string expectedFirstID = "first_id"; @@ -137,8 +292,39 @@ public void Validation_Works() new() { ID = "claude-opus-4-6", + Capabilities = new() + { + Batch = new(true), + Citations = new(true), + CodeExecution = new(true), + ContextManagement = new() + { + ClearThinking20251015 = new(true), + ClearToolUses20250919 = new(true), + Compact20260112 = new(true), + Supported = true, + }, + Effort = new() + { + High = new(true), + Low = new(true), + Max = new(true), + Medium = new(true), + Supported = true, + }, + ImageInput = new(true), + PdfInput = new(true), + StructuredOutputs = new(true), + Thinking = new() + { + Supported = true, + Types = new() { Adaptive = new(true), Enabled = new(true) }, + }, + }, CreatedAt = DateTimeOffset.Parse("2026-02-04T00:00:00Z"), DisplayName = "Claude Opus 4.6", + MaxInputTokens = 0, + MaxTokens = 0, }, ], FirstID = "first_id", @@ -159,8 +345,39 @@ public void CopyConstructor_Works() new() { ID = "claude-opus-4-6", + Capabilities = new() + { + Batch = new(true), + Citations = new(true), + CodeExecution = new(true), + ContextManagement = new() + { + ClearThinking20251015 = new(true), + ClearToolUses20250919 = new(true), + Compact20260112 = new(true), + Supported = true, + }, + Effort = new() + { + High = new(true), + Low = new(true), + Max = new(true), + Medium = new(true), + Supported = true, + }, + ImageInput = new(true), + PdfInput = new(true), + StructuredOutputs = new(true), + Thinking = new() + { + Supported = true, + Types = new() { Adaptive = new(true), Enabled = new(true) }, + }, + }, CreatedAt = DateTimeOffset.Parse("2026-02-04T00:00:00Z"), DisplayName = "Claude Opus 4.6", + MaxInputTokens = 0, + MaxTokens = 0, }, ], FirstID = "first_id", diff --git a/src/Anthropic.Tests/Models/ErrorTypeTest.cs b/src/Anthropic.Tests/Models/ErrorTypeTest.cs new file mode 100644 index 000000000..88d56eaf3 --- /dev/null +++ b/src/Anthropic.Tests/Models/ErrorTypeTest.cs @@ -0,0 +1,78 @@ +using System.Text.Json; +using Anthropic.Core; +using Anthropic.Exceptions; +using Anthropic.Models; + +namespace Anthropic.Tests.Models; + +public class ErrorTypeTest : TestBase +{ + [Theory] + [InlineData(ErrorType.InvalidRequestError)] + [InlineData(ErrorType.AuthenticationError)] + [InlineData(ErrorType.PermissionError)] + [InlineData(ErrorType.NotFoundError)] + [InlineData(ErrorType.RateLimitError)] + [InlineData(ErrorType.TimeoutError)] + [InlineData(ErrorType.OverloadedError)] + [InlineData(ErrorType.ApiError)] + [InlineData(ErrorType.BillingError)] + public void Validation_Works(ErrorType rawValue) + { + // force implicit conversion because Theory can't do that for us + ApiEnum value = rawValue; + value.Validate(); + } + + [Fact] + public void InvalidEnumValidationThrows_Works() + { + var value = JsonSerializer.Deserialize>( + JsonSerializer.SerializeToElement("invalid value"), + ModelBase.SerializerOptions + ); + + Assert.NotNull(value); + Assert.Throws(() => value.Validate()); + } + + [Theory] + [InlineData(ErrorType.InvalidRequestError)] + [InlineData(ErrorType.AuthenticationError)] + [InlineData(ErrorType.PermissionError)] + [InlineData(ErrorType.NotFoundError)] + [InlineData(ErrorType.RateLimitError)] + [InlineData(ErrorType.TimeoutError)] + [InlineData(ErrorType.OverloadedError)] + [InlineData(ErrorType.ApiError)] + [InlineData(ErrorType.BillingError)] + public void SerializationRoundtrip_Works(ErrorType rawValue) + { + // force implicit conversion because Theory can't do that for us + ApiEnum value = rawValue; + + string json = JsonSerializer.Serialize(value, ModelBase.SerializerOptions); + var deserialized = JsonSerializer.Deserialize>( + json, + ModelBase.SerializerOptions + ); + + Assert.Equal(value, deserialized); + } + + [Fact] + public void InvalidEnumSerializationRoundtrip_Works() + { + var value = JsonSerializer.Deserialize>( + JsonSerializer.SerializeToElement("invalid value"), + ModelBase.SerializerOptions + ); + string json = JsonSerializer.Serialize(value, ModelBase.SerializerOptions); + var deserialized = JsonSerializer.Deserialize>( + json, + ModelBase.SerializerOptions + ); + + Assert.Equal(value, deserialized); + } +} diff --git a/src/Anthropic.Tests/Models/Messages/Batches/BatchCreateParamsTest.cs b/src/Anthropic.Tests/Models/Messages/Batches/BatchCreateParamsTest.cs index 89047cde3..b720654ac 100644 --- a/src/Anthropic.Tests/Models/Messages/Batches/BatchCreateParamsTest.cs +++ b/src/Anthropic.Tests/Models/Messages/Batches/BatchCreateParamsTest.cs @@ -64,7 +64,10 @@ public void FieldRoundtrip_Works() ] ), Temperature = 1, - Thinking = new Messages::ThinkingConfigEnabled(1024), + Thinking = new Messages::ThinkingConfigAdaptive() + { + Display = Messages::Display.Summarized, + }, ToolChoice = new Messages::ToolChoiceAuto() { DisableParallelToolUse = true, @@ -155,7 +158,10 @@ public void FieldRoundtrip_Works() ] ), Temperature = 1, - Thinking = new Messages::ThinkingConfigEnabled(1024), + Thinking = new Messages::ThinkingConfigAdaptive() + { + Display = Messages::Display.Summarized, + }, ToolChoice = new Messages::ToolChoiceAuto() { DisableParallelToolUse = true }, Tools = [ @@ -254,7 +260,10 @@ public void Url_Works() ] ), Temperature = 1, - Thinking = new Messages::ThinkingConfigEnabled(1024), + Thinking = new Messages::ThinkingConfigAdaptive() + { + Display = Messages::Display.Summarized, + }, ToolChoice = new Messages::ToolChoiceAuto() { DisableParallelToolUse = true, @@ -355,7 +364,10 @@ public void CopyConstructor_Works() ] ), Temperature = 1, - Thinking = new Messages::ThinkingConfigEnabled(1024), + Thinking = new Messages::ThinkingConfigAdaptive() + { + Display = Messages::Display.Summarized, + }, ToolChoice = new Messages::ToolChoiceAuto() { DisableParallelToolUse = true, @@ -455,7 +467,10 @@ public void FieldRoundtrip_Works() ] ), Temperature = 1, - Thinking = new Messages::ThinkingConfigEnabled(1024), + Thinking = new Messages::ThinkingConfigAdaptive() + { + Display = Messages::Display.Summarized, + }, ToolChoice = new Messages::ToolChoiceAuto() { DisableParallelToolUse = true }, Tools = [ @@ -537,7 +552,10 @@ public void FieldRoundtrip_Works() ] ), Temperature = 1, - Thinking = new Messages::ThinkingConfigEnabled(1024), + Thinking = new Messages::ThinkingConfigAdaptive() + { + Display = Messages::Display.Summarized, + }, ToolChoice = new Messages::ToolChoiceAuto() { DisableParallelToolUse = true }, Tools = [ @@ -627,7 +645,10 @@ public void SerializationRoundtrip_Works() ] ), Temperature = 1, - Thinking = new Messages::ThinkingConfigEnabled(1024), + Thinking = new Messages::ThinkingConfigAdaptive() + { + Display = Messages::Display.Summarized, + }, ToolChoice = new Messages::ToolChoiceAuto() { DisableParallelToolUse = true }, Tools = [ @@ -720,7 +741,10 @@ public void FieldRoundtripThroughSerialization_Works() ] ), Temperature = 1, - Thinking = new Messages::ThinkingConfigEnabled(1024), + Thinking = new Messages::ThinkingConfigAdaptive() + { + Display = Messages::Display.Summarized, + }, ToolChoice = new Messages::ToolChoiceAuto() { DisableParallelToolUse = true }, Tools = [ @@ -809,7 +833,10 @@ public void FieldRoundtripThroughSerialization_Works() ] ), Temperature = 1, - Thinking = new Messages::ThinkingConfigEnabled(1024), + Thinking = new Messages::ThinkingConfigAdaptive() + { + Display = Messages::Display.Summarized, + }, ToolChoice = new Messages::ToolChoiceAuto() { DisableParallelToolUse = true }, Tools = [ @@ -899,7 +926,10 @@ public void Validation_Works() ] ), Temperature = 1, - Thinking = new Messages::ThinkingConfigEnabled(1024), + Thinking = new Messages::ThinkingConfigAdaptive() + { + Display = Messages::Display.Summarized, + }, ToolChoice = new Messages::ToolChoiceAuto() { DisableParallelToolUse = true }, Tools = [ @@ -989,7 +1019,10 @@ public void CopyConstructor_Works() ] ), Temperature = 1, - Thinking = new Messages::ThinkingConfigEnabled(1024), + Thinking = new Messages::ThinkingConfigAdaptive() + { + Display = Messages::Display.Summarized, + }, ToolChoice = new Messages::ToolChoiceAuto() { DisableParallelToolUse = true }, Tools = [ @@ -1081,7 +1114,10 @@ public void FieldRoundtrip_Works() ] ), Temperature = 1, - Thinking = new Messages::ThinkingConfigEnabled(1024), + Thinking = new Messages::ThinkingConfigAdaptive() + { + Display = Messages::Display.Summarized, + }, ToolChoice = new Messages::ToolChoiceAuto() { DisableParallelToolUse = true }, Tools = [ @@ -1165,7 +1201,10 @@ public void FieldRoundtrip_Works() ] ); double expectedTemperature = 1; - Messages::ThinkingConfigParam expectedThinking = new Messages::ThinkingConfigEnabled(1024); + Messages::ThinkingConfigParam expectedThinking = new Messages::ThinkingConfigAdaptive() + { + Display = Messages::Display.Summarized, + }; Messages::ToolChoice expectedToolChoice = new Messages::ToolChoiceAuto() { DisableParallelToolUse = true, @@ -1284,7 +1323,10 @@ public void SerializationRoundtrip_Works() ] ), Temperature = 1, - Thinking = new Messages::ThinkingConfigEnabled(1024), + Thinking = new Messages::ThinkingConfigAdaptive() + { + Display = Messages::Display.Summarized, + }, ToolChoice = new Messages::ToolChoiceAuto() { DisableParallelToolUse = true }, Tools = [ @@ -1373,7 +1415,10 @@ public void FieldRoundtripThroughSerialization_Works() ] ), Temperature = 1, - Thinking = new Messages::ThinkingConfigEnabled(1024), + Thinking = new Messages::ThinkingConfigAdaptive() + { + Display = Messages::Display.Summarized, + }, ToolChoice = new Messages::ToolChoiceAuto() { DisableParallelToolUse = true }, Tools = [ @@ -1461,7 +1506,10 @@ public void FieldRoundtripThroughSerialization_Works() ] ); double expectedTemperature = 1; - Messages::ThinkingConfigParam expectedThinking = new Messages::ThinkingConfigEnabled(1024); + Messages::ThinkingConfigParam expectedThinking = new Messages::ThinkingConfigAdaptive() + { + Display = Messages::Display.Summarized, + }; Messages::ToolChoice expectedToolChoice = new Messages::ToolChoiceAuto() { DisableParallelToolUse = true, @@ -1580,7 +1628,10 @@ public void Validation_Works() ] ), Temperature = 1, - Thinking = new Messages::ThinkingConfigEnabled(1024), + Thinking = new Messages::ThinkingConfigAdaptive() + { + Display = Messages::Display.Summarized, + }, ToolChoice = new Messages::ToolChoiceAuto() { DisableParallelToolUse = true }, Tools = [ @@ -1801,7 +1852,10 @@ public void OptionalNullablePropertiesUnsetAreNotSet_Works() ] ), Temperature = 1, - Thinking = new Messages::ThinkingConfigEnabled(1024), + Thinking = new Messages::ThinkingConfigAdaptive() + { + Display = Messages::Display.Summarized, + }, ToolChoice = new Messages::ToolChoiceAuto() { DisableParallelToolUse = true }, Tools = [ @@ -1889,7 +1943,10 @@ public void OptionalNullablePropertiesUnsetValidation_Works() ] ), Temperature = 1, - Thinking = new Messages::ThinkingConfigEnabled(1024), + Thinking = new Messages::ThinkingConfigAdaptive() + { + Display = Messages::Display.Summarized, + }, ToolChoice = new Messages::ToolChoiceAuto() { DisableParallelToolUse = true }, Tools = [ @@ -1972,7 +2029,10 @@ public void OptionalNullablePropertiesSetToNullAreSetToNull_Works() ] ), Temperature = 1, - Thinking = new Messages::ThinkingConfigEnabled(1024), + Thinking = new Messages::ThinkingConfigAdaptive() + { + Display = Messages::Display.Summarized, + }, ToolChoice = new Messages::ToolChoiceAuto() { DisableParallelToolUse = true }, Tools = [ @@ -2064,7 +2124,10 @@ public void OptionalNullablePropertiesSetToNullValidation_Works() ] ), Temperature = 1, - Thinking = new Messages::ThinkingConfigEnabled(1024), + Thinking = new Messages::ThinkingConfigAdaptive() + { + Display = Messages::Display.Summarized, + }, ToolChoice = new Messages::ToolChoiceAuto() { DisableParallelToolUse = true }, Tools = [ @@ -2154,7 +2217,10 @@ public void CopyConstructor_Works() ] ), Temperature = 1, - Thinking = new Messages::ThinkingConfigEnabled(1024), + Thinking = new Messages::ThinkingConfigAdaptive() + { + Display = Messages::Display.Summarized, + }, ToolChoice = new Messages::ToolChoiceAuto() { DisableParallelToolUse = true }, Tools = [ diff --git a/src/Anthropic.Tests/Models/Messages/MessageCountTokensParamsTest.cs b/src/Anthropic.Tests/Models/Messages/MessageCountTokensParamsTest.cs index c4906f875..c31f90c47 100644 --- a/src/Anthropic.Tests/Models/Messages/MessageCountTokensParamsTest.cs +++ b/src/Anthropic.Tests/Models/Messages/MessageCountTokensParamsTest.cs @@ -47,7 +47,10 @@ public void FieldRoundtrip_Works() }, ] ), - Thinking = new Messages::ThinkingConfigEnabled(1024), + Thinking = new Messages::ThinkingConfigAdaptive() + { + Display = Messages::Display.Summarized, + }, ToolChoice = new Messages::ToolChoiceAuto() { DisableParallelToolUse = true }, Tools = [ @@ -118,7 +121,10 @@ public void FieldRoundtrip_Works() }, ] ); - Messages::ThinkingConfigParam expectedThinking = new Messages::ThinkingConfigEnabled(1024); + Messages::ThinkingConfigParam expectedThinking = new Messages::ThinkingConfigAdaptive() + { + Display = Messages::Display.Summarized, + }; Messages::ToolChoice expectedToolChoice = new Messages::ToolChoiceAuto() { DisableParallelToolUse = true, @@ -262,7 +268,10 @@ public void OptionalNullableParamsUnsetAreNotSet_Works() }, ] ), - Thinking = new Messages::ThinkingConfigEnabled(1024), + Thinking = new Messages::ThinkingConfigAdaptive() + { + Display = Messages::Display.Summarized, + }, ToolChoice = new Messages::ToolChoiceAuto() { DisableParallelToolUse = true }, Tools = [ @@ -338,7 +347,10 @@ public void OptionalNullableParamsSetToNullAreSetToNull_Works() }, ] ), - Thinking = new Messages::ThinkingConfigEnabled(1024), + Thinking = new Messages::ThinkingConfigAdaptive() + { + Display = Messages::Display.Summarized, + }, ToolChoice = new Messages::ToolChoiceAuto() { DisableParallelToolUse = true }, Tools = [ @@ -431,7 +443,10 @@ public void CopyConstructor_Works() }, ] ), - Thinking = new Messages::ThinkingConfigEnabled(1024), + Thinking = new Messages::ThinkingConfigAdaptive() + { + Display = Messages::Display.Summarized, + }, ToolChoice = new Messages::ToolChoiceAuto() { DisableParallelToolUse = true }, Tools = [ diff --git a/src/Anthropic.Tests/Models/Messages/MessageCountTokensToolTest.cs b/src/Anthropic.Tests/Models/Messages/MessageCountTokensToolTest.cs index eb3a3e7af..f17176536 100644 --- a/src/Anthropic.Tests/Models/Messages/MessageCountTokensToolTest.cs +++ b/src/Anthropic.Tests/Models/Messages/MessageCountTokensToolTest.cs @@ -262,6 +262,25 @@ public void WebFetchTool20260209ValidationWorks() value.Validate(); } + [Fact] + public void WebFetchTool20260309ValidationWorks() + { + MessageCountTokensTool value = new WebFetchTool20260309() + { + AllowedCallers = [WebFetchTool20260309AllowedCaller.Direct], + AllowedDomains = ["string"], + BlockedDomains = ["string"], + CacheControl = new() { Ttl = Ttl.Ttl5m }, + Citations = new() { Enabled = true }, + DeferLoading = true, + MaxContentTokens = 1, + MaxUses = 1, + Strict = true, + UseCache = true, + }; + value.Validate(); + } + [Fact] public void ToolSearchToolBm25_20251119ValidationWorks() { @@ -623,6 +642,31 @@ public void WebFetchTool20260209SerializationRoundtripWorks() Assert.Equal(value, deserialized); } + [Fact] + public void WebFetchTool20260309SerializationRoundtripWorks() + { + MessageCountTokensTool value = new WebFetchTool20260309() + { + AllowedCallers = [WebFetchTool20260309AllowedCaller.Direct], + AllowedDomains = ["string"], + BlockedDomains = ["string"], + CacheControl = new() { Ttl = Ttl.Ttl5m }, + Citations = new() { Enabled = true }, + DeferLoading = true, + MaxContentTokens = 1, + MaxUses = 1, + Strict = true, + UseCache = true, + }; + string element = JsonSerializer.Serialize(value, ModelBase.SerializerOptions); + var deserialized = JsonSerializer.Deserialize( + element, + ModelBase.SerializerOptions + ); + + Assert.Equal(value, deserialized); + } + [Fact] public void ToolSearchToolBm25_20251119SerializationRoundtripWorks() { diff --git a/src/Anthropic.Tests/Models/Messages/MessageCreateParamsTest.cs b/src/Anthropic.Tests/Models/Messages/MessageCreateParamsTest.cs index 626ad3ec1..ef9c00195 100644 --- a/src/Anthropic.Tests/Models/Messages/MessageCreateParamsTest.cs +++ b/src/Anthropic.Tests/Models/Messages/MessageCreateParamsTest.cs @@ -55,7 +55,10 @@ public void FieldRoundtrip_Works() ] ), Temperature = 1, - Thinking = new Messages::ThinkingConfigEnabled(1024), + Thinking = new Messages::ThinkingConfigAdaptive() + { + Display = Messages::Display.Summarized, + }, ToolChoice = new Messages::ToolChoiceAuto() { DisableParallelToolUse = true }, Tools = [ @@ -138,7 +141,10 @@ public void FieldRoundtrip_Works() ] ); double expectedTemperature = 1; - Messages::ThinkingConfigParam expectedThinking = new Messages::ThinkingConfigEnabled(1024); + Messages::ThinkingConfigParam expectedThinking = new Messages::ThinkingConfigAdaptive() + { + Display = Messages::Display.Summarized, + }; Messages::ToolChoice expectedToolChoice = new Messages::ToolChoiceAuto() { DisableParallelToolUse = true, @@ -339,7 +345,10 @@ public void OptionalNullableParamsUnsetAreNotSet_Works() ] ), Temperature = 1, - Thinking = new Messages::ThinkingConfigEnabled(1024), + Thinking = new Messages::ThinkingConfigAdaptive() + { + Display = Messages::Display.Summarized, + }, ToolChoice = new Messages::ToolChoiceAuto() { DisableParallelToolUse = true }, Tools = [ @@ -426,7 +435,10 @@ public void OptionalNullableParamsSetToNullAreSetToNull_Works() ] ), Temperature = 1, - Thinking = new Messages::ThinkingConfigEnabled(1024), + Thinking = new Messages::ThinkingConfigAdaptive() + { + Display = Messages::Display.Summarized, + }, ToolChoice = new Messages::ToolChoiceAuto() { DisableParallelToolUse = true }, Tools = [ @@ -535,7 +547,10 @@ public void CopyConstructor_Works() ] ), Temperature = 1, - Thinking = new Messages::ThinkingConfigEnabled(1024), + Thinking = new Messages::ThinkingConfigAdaptive() + { + Display = Messages::Display.Summarized, + }, ToolChoice = new Messages::ToolChoiceAuto() { DisableParallelToolUse = true }, Tools = [ diff --git a/src/Anthropic.Tests/Models/Messages/ThinkingConfigAdaptiveTest.cs b/src/Anthropic.Tests/Models/Messages/ThinkingConfigAdaptiveTest.cs index 88196c75c..14117da8a 100644 --- a/src/Anthropic.Tests/Models/Messages/ThinkingConfigAdaptiveTest.cs +++ b/src/Anthropic.Tests/Models/Messages/ThinkingConfigAdaptiveTest.cs @@ -1,5 +1,6 @@ using System.Text.Json; using Anthropic.Core; +using Anthropic.Exceptions; using Anthropic.Models.Messages; namespace Anthropic.Tests.Models.Messages; @@ -9,17 +10,19 @@ public class ThinkingConfigAdaptiveTest : TestBase [Fact] public void FieldRoundtrip_Works() { - var model = new ThinkingConfigAdaptive { }; + var model = new ThinkingConfigAdaptive { Display = Display.Summarized }; JsonElement expectedType = JsonSerializer.SerializeToElement("adaptive"); + ApiEnum expectedDisplay = Display.Summarized; Assert.True(JsonElement.DeepEquals(expectedType, model.Type)); + Assert.Equal(expectedDisplay, model.Display); } [Fact] public void SerializationRoundtrip_Works() { - var model = new ThinkingConfigAdaptive { }; + var model = new ThinkingConfigAdaptive { Display = Display.Summarized }; string json = JsonSerializer.Serialize(model, ModelBase.SerializerOptions); var deserialized = JsonSerializer.Deserialize( @@ -33,7 +36,7 @@ public void SerializationRoundtrip_Works() [Fact] public void FieldRoundtripThroughSerialization_Works() { - var model = new ThinkingConfigAdaptive { }; + var model = new ThinkingConfigAdaptive { Display = Display.Summarized }; string element = JsonSerializer.Serialize(model, ModelBase.SerializerOptions); var deserialized = JsonSerializer.Deserialize( @@ -43,25 +46,119 @@ public void FieldRoundtripThroughSerialization_Works() Assert.NotNull(deserialized); JsonElement expectedType = JsonSerializer.SerializeToElement("adaptive"); + ApiEnum expectedDisplay = Display.Summarized; Assert.True(JsonElement.DeepEquals(expectedType, deserialized.Type)); + Assert.Equal(expectedDisplay, deserialized.Display); } [Fact] public void Validation_Works() + { + var model = new ThinkingConfigAdaptive { Display = Display.Summarized }; + + model.Validate(); + } + + [Fact] + public void OptionalNullablePropertiesUnsetAreNotSet_Works() + { + var model = new ThinkingConfigAdaptive { }; + + Assert.Null(model.Display); + Assert.False(model.RawData.ContainsKey("display")); + } + + [Fact] + public void OptionalNullablePropertiesUnsetValidation_Works() { var model = new ThinkingConfigAdaptive { }; model.Validate(); } + [Fact] + public void OptionalNullablePropertiesSetToNullAreSetToNull_Works() + { + var model = new ThinkingConfigAdaptive { Display = null }; + + Assert.Null(model.Display); + Assert.True(model.RawData.ContainsKey("display")); + } + + [Fact] + public void OptionalNullablePropertiesSetToNullValidation_Works() + { + var model = new ThinkingConfigAdaptive { Display = null }; + + model.Validate(); + } + [Fact] public void CopyConstructor_Works() { - var model = new ThinkingConfigAdaptive { }; + var model = new ThinkingConfigAdaptive { Display = Display.Summarized }; ThinkingConfigAdaptive copied = new(model); Assert.Equal(model, copied); } } + +public class DisplayTest : TestBase +{ + [Theory] + [InlineData(Display.Summarized)] + [InlineData(Display.Omitted)] + public void Validation_Works(Display rawValue) + { + // force implicit conversion because Theory can't do that for us + ApiEnum value = rawValue; + value.Validate(); + } + + [Fact] + public void InvalidEnumValidationThrows_Works() + { + var value = JsonSerializer.Deserialize>( + JsonSerializer.SerializeToElement("invalid value"), + ModelBase.SerializerOptions + ); + + Assert.NotNull(value); + Assert.Throws(() => value.Validate()); + } + + [Theory] + [InlineData(Display.Summarized)] + [InlineData(Display.Omitted)] + public void SerializationRoundtrip_Works(Display rawValue) + { + // force implicit conversion because Theory can't do that for us + ApiEnum value = rawValue; + + string json = JsonSerializer.Serialize(value, ModelBase.SerializerOptions); + var deserialized = JsonSerializer.Deserialize>( + json, + ModelBase.SerializerOptions + ); + + Assert.Equal(value, deserialized); + } + + [Fact] + public void InvalidEnumSerializationRoundtrip_Works() + { + var value = JsonSerializer.Deserialize>( + JsonSerializer.SerializeToElement("invalid value"), + ModelBase.SerializerOptions + ); + string json = JsonSerializer.Serialize(value, ModelBase.SerializerOptions); + var deserialized = JsonSerializer.Deserialize>( + json, + ModelBase.SerializerOptions + ); + + Assert.Equal(value, deserialized); + } +} diff --git a/src/Anthropic.Tests/Models/Messages/ThinkingConfigEnabledTest.cs b/src/Anthropic.Tests/Models/Messages/ThinkingConfigEnabledTest.cs index 96cedbdd2..4f4bc8e1f 100644 --- a/src/Anthropic.Tests/Models/Messages/ThinkingConfigEnabledTest.cs +++ b/src/Anthropic.Tests/Models/Messages/ThinkingConfigEnabledTest.cs @@ -1,5 +1,6 @@ using System.Text.Json; using Anthropic.Core; +using Anthropic.Exceptions; using Anthropic.Models.Messages; namespace Anthropic.Tests.Models.Messages; @@ -9,19 +10,30 @@ public class ThinkingConfigEnabledTest : TestBase [Fact] public void FieldRoundtrip_Works() { - var model = new ThinkingConfigEnabled { BudgetTokens = 1024 }; + var model = new ThinkingConfigEnabled + { + BudgetTokens = 1024, + Display = ThinkingConfigEnabledDisplay.Summarized, + }; long expectedBudgetTokens = 1024; JsonElement expectedType = JsonSerializer.SerializeToElement("enabled"); + ApiEnum expectedDisplay = + ThinkingConfigEnabledDisplay.Summarized; Assert.Equal(expectedBudgetTokens, model.BudgetTokens); Assert.True(JsonElement.DeepEquals(expectedType, model.Type)); + Assert.Equal(expectedDisplay, model.Display); } [Fact] public void SerializationRoundtrip_Works() { - var model = new ThinkingConfigEnabled { BudgetTokens = 1024 }; + var model = new ThinkingConfigEnabled + { + BudgetTokens = 1024, + Display = ThinkingConfigEnabledDisplay.Summarized, + }; string json = JsonSerializer.Serialize(model, ModelBase.SerializerOptions); var deserialized = JsonSerializer.Deserialize( @@ -35,7 +47,11 @@ public void SerializationRoundtrip_Works() [Fact] public void FieldRoundtripThroughSerialization_Works() { - var model = new ThinkingConfigEnabled { BudgetTokens = 1024 }; + var model = new ThinkingConfigEnabled + { + BudgetTokens = 1024, + Display = ThinkingConfigEnabledDisplay.Summarized, + }; string element = JsonSerializer.Serialize(model, ModelBase.SerializerOptions); var deserialized = JsonSerializer.Deserialize( @@ -46,26 +62,137 @@ public void FieldRoundtripThroughSerialization_Works() long expectedBudgetTokens = 1024; JsonElement expectedType = JsonSerializer.SerializeToElement("enabled"); + ApiEnum expectedDisplay = + ThinkingConfigEnabledDisplay.Summarized; Assert.Equal(expectedBudgetTokens, deserialized.BudgetTokens); Assert.True(JsonElement.DeepEquals(expectedType, deserialized.Type)); + Assert.Equal(expectedDisplay, deserialized.Display); } [Fact] public void Validation_Works() + { + var model = new ThinkingConfigEnabled + { + BudgetTokens = 1024, + Display = ThinkingConfigEnabledDisplay.Summarized, + }; + + model.Validate(); + } + + [Fact] + public void OptionalNullablePropertiesUnsetAreNotSet_Works() + { + var model = new ThinkingConfigEnabled { BudgetTokens = 1024 }; + + Assert.Null(model.Display); + Assert.False(model.RawData.ContainsKey("display")); + } + + [Fact] + public void OptionalNullablePropertiesUnsetValidation_Works() { var model = new ThinkingConfigEnabled { BudgetTokens = 1024 }; model.Validate(); } + [Fact] + public void OptionalNullablePropertiesSetToNullAreSetToNull_Works() + { + var model = new ThinkingConfigEnabled + { + BudgetTokens = 1024, + + Display = null, + }; + + Assert.Null(model.Display); + Assert.True(model.RawData.ContainsKey("display")); + } + + [Fact] + public void OptionalNullablePropertiesSetToNullValidation_Works() + { + var model = new ThinkingConfigEnabled + { + BudgetTokens = 1024, + + Display = null, + }; + + model.Validate(); + } + [Fact] public void CopyConstructor_Works() { - var model = new ThinkingConfigEnabled { BudgetTokens = 1024 }; + var model = new ThinkingConfigEnabled + { + BudgetTokens = 1024, + Display = ThinkingConfigEnabledDisplay.Summarized, + }; ThinkingConfigEnabled copied = new(model); Assert.Equal(model, copied); } } + +public class ThinkingConfigEnabledDisplayTest : TestBase +{ + [Theory] + [InlineData(ThinkingConfigEnabledDisplay.Summarized)] + [InlineData(ThinkingConfigEnabledDisplay.Omitted)] + public void Validation_Works(ThinkingConfigEnabledDisplay rawValue) + { + // force implicit conversion because Theory can't do that for us + ApiEnum value = rawValue; + value.Validate(); + } + + [Fact] + public void InvalidEnumValidationThrows_Works() + { + var value = JsonSerializer.Deserialize>( + JsonSerializer.SerializeToElement("invalid value"), + ModelBase.SerializerOptions + ); + + Assert.NotNull(value); + Assert.Throws(() => value.Validate()); + } + + [Theory] + [InlineData(ThinkingConfigEnabledDisplay.Summarized)] + [InlineData(ThinkingConfigEnabledDisplay.Omitted)] + public void SerializationRoundtrip_Works(ThinkingConfigEnabledDisplay rawValue) + { + // force implicit conversion because Theory can't do that for us + ApiEnum value = rawValue; + + string json = JsonSerializer.Serialize(value, ModelBase.SerializerOptions); + var deserialized = JsonSerializer.Deserialize< + ApiEnum + >(json, ModelBase.SerializerOptions); + + Assert.Equal(value, deserialized); + } + + [Fact] + public void InvalidEnumSerializationRoundtrip_Works() + { + var value = JsonSerializer.Deserialize>( + JsonSerializer.SerializeToElement("invalid value"), + ModelBase.SerializerOptions + ); + string json = JsonSerializer.Serialize(value, ModelBase.SerializerOptions); + var deserialized = JsonSerializer.Deserialize< + ApiEnum + >(json, ModelBase.SerializerOptions); + + Assert.Equal(value, deserialized); + } +} diff --git a/src/Anthropic.Tests/Models/Messages/ThinkingConfigParamTest.cs b/src/Anthropic.Tests/Models/Messages/ThinkingConfigParamTest.cs index ee58083af..d8f40dde9 100644 --- a/src/Anthropic.Tests/Models/Messages/ThinkingConfigParamTest.cs +++ b/src/Anthropic.Tests/Models/Messages/ThinkingConfigParamTest.cs @@ -9,7 +9,11 @@ public class ThinkingConfigParamTest : TestBase [Fact] public void EnabledValidationWorks() { - ThinkingConfigParam value = new ThinkingConfigEnabled(1024); + ThinkingConfigParam value = new ThinkingConfigEnabled() + { + BudgetTokens = 1024, + Display = ThinkingConfigEnabledDisplay.Summarized, + }; value.Validate(); } @@ -23,14 +27,18 @@ public void DisabledValidationWorks() [Fact] public void AdaptiveValidationWorks() { - ThinkingConfigParam value = new ThinkingConfigAdaptive(); + ThinkingConfigParam value = new ThinkingConfigAdaptive() { Display = Display.Summarized }; value.Validate(); } [Fact] public void EnabledSerializationRoundtripWorks() { - ThinkingConfigParam value = new ThinkingConfigEnabled(1024); + ThinkingConfigParam value = new ThinkingConfigEnabled() + { + BudgetTokens = 1024, + Display = ThinkingConfigEnabledDisplay.Summarized, + }; string element = JsonSerializer.Serialize(value, ModelBase.SerializerOptions); var deserialized = JsonSerializer.Deserialize( element, @@ -56,7 +64,7 @@ public void DisabledSerializationRoundtripWorks() [Fact] public void AdaptiveSerializationRoundtripWorks() { - ThinkingConfigParam value = new ThinkingConfigAdaptive(); + ThinkingConfigParam value = new ThinkingConfigAdaptive() { Display = Display.Summarized }; string element = JsonSerializer.Serialize(value, ModelBase.SerializerOptions); var deserialized = JsonSerializer.Deserialize( element, diff --git a/src/Anthropic.Tests/Models/Messages/ToolUnionTest.cs b/src/Anthropic.Tests/Models/Messages/ToolUnionTest.cs index 09fe6c4a1..b010d562f 100644 --- a/src/Anthropic.Tests/Models/Messages/ToolUnionTest.cs +++ b/src/Anthropic.Tests/Models/Messages/ToolUnionTest.cs @@ -262,6 +262,25 @@ public void WebFetchTool20260209ValidationWorks() value.Validate(); } + [Fact] + public void WebFetchTool20260309ValidationWorks() + { + ToolUnion value = new WebFetchTool20260309() + { + AllowedCallers = [WebFetchTool20260309AllowedCaller.Direct], + AllowedDomains = ["string"], + BlockedDomains = ["string"], + CacheControl = new() { Ttl = Ttl.Ttl5m }, + Citations = new() { Enabled = true }, + DeferLoading = true, + MaxContentTokens = 1, + MaxUses = 1, + Strict = true, + UseCache = true, + }; + value.Validate(); + } + [Fact] public void SearchToolBm25_20251119ValidationWorks() { @@ -623,6 +642,31 @@ public void WebFetchTool20260209SerializationRoundtripWorks() Assert.Equal(value, deserialized); } + [Fact] + public void WebFetchTool20260309SerializationRoundtripWorks() + { + ToolUnion value = new WebFetchTool20260309() + { + AllowedCallers = [WebFetchTool20260309AllowedCaller.Direct], + AllowedDomains = ["string"], + BlockedDomains = ["string"], + CacheControl = new() { Ttl = Ttl.Ttl5m }, + Citations = new() { Enabled = true }, + DeferLoading = true, + MaxContentTokens = 1, + MaxUses = 1, + Strict = true, + UseCache = true, + }; + string element = JsonSerializer.Serialize(value, ModelBase.SerializerOptions); + var deserialized = JsonSerializer.Deserialize( + element, + ModelBase.SerializerOptions + ); + + Assert.Equal(value, deserialized); + } + [Fact] public void SearchToolBm25_20251119SerializationRoundtripWorks() { diff --git a/src/Anthropic.Tests/Models/Messages/WebFetchTool20260309Test.cs b/src/Anthropic.Tests/Models/Messages/WebFetchTool20260309Test.cs new file mode 100644 index 000000000..405d46e97 --- /dev/null +++ b/src/Anthropic.Tests/Models/Messages/WebFetchTool20260309Test.cs @@ -0,0 +1,449 @@ +using System.Collections.Generic; +using System.Text.Json; +using Anthropic.Core; +using Anthropic.Exceptions; +using Anthropic.Models.Messages; + +namespace Anthropic.Tests.Models.Messages; + +public class WebFetchTool20260309Test : TestBase +{ + [Fact] + public void FieldRoundtrip_Works() + { + var model = new WebFetchTool20260309 + { + AllowedCallers = [WebFetchTool20260309AllowedCaller.Direct], + AllowedDomains = ["string"], + BlockedDomains = ["string"], + CacheControl = new() { Ttl = Ttl.Ttl5m }, + Citations = new() { Enabled = true }, + DeferLoading = true, + MaxContentTokens = 1, + MaxUses = 1, + Strict = true, + UseCache = true, + }; + + JsonElement expectedName = JsonSerializer.SerializeToElement("web_fetch"); + JsonElement expectedType = JsonSerializer.SerializeToElement("web_fetch_20260309"); + List> expectedAllowedCallers = + [ + WebFetchTool20260309AllowedCaller.Direct, + ]; + List expectedAllowedDomains = ["string"]; + List expectedBlockedDomains = ["string"]; + CacheControlEphemeral expectedCacheControl = new() { Ttl = Ttl.Ttl5m }; + CitationsConfigParam expectedCitations = new() { Enabled = true }; + bool expectedDeferLoading = true; + long expectedMaxContentTokens = 1; + long expectedMaxUses = 1; + bool expectedStrict = true; + bool expectedUseCache = true; + + Assert.True(JsonElement.DeepEquals(expectedName, model.Name)); + Assert.True(JsonElement.DeepEquals(expectedType, model.Type)); + Assert.NotNull(model.AllowedCallers); + Assert.Equal(expectedAllowedCallers.Count, model.AllowedCallers.Count); + for (int i = 0; i < expectedAllowedCallers.Count; i++) + { + Assert.Equal(expectedAllowedCallers[i], model.AllowedCallers[i]); + } + Assert.NotNull(model.AllowedDomains); + Assert.Equal(expectedAllowedDomains.Count, model.AllowedDomains.Count); + for (int i = 0; i < expectedAllowedDomains.Count; i++) + { + Assert.Equal(expectedAllowedDomains[i], model.AllowedDomains[i]); + } + Assert.NotNull(model.BlockedDomains); + Assert.Equal(expectedBlockedDomains.Count, model.BlockedDomains.Count); + for (int i = 0; i < expectedBlockedDomains.Count; i++) + { + Assert.Equal(expectedBlockedDomains[i], model.BlockedDomains[i]); + } + Assert.Equal(expectedCacheControl, model.CacheControl); + Assert.Equal(expectedCitations, model.Citations); + Assert.Equal(expectedDeferLoading, model.DeferLoading); + Assert.Equal(expectedMaxContentTokens, model.MaxContentTokens); + Assert.Equal(expectedMaxUses, model.MaxUses); + Assert.Equal(expectedStrict, model.Strict); + Assert.Equal(expectedUseCache, model.UseCache); + } + + [Fact] + public void SerializationRoundtrip_Works() + { + var model = new WebFetchTool20260309 + { + AllowedCallers = [WebFetchTool20260309AllowedCaller.Direct], + AllowedDomains = ["string"], + BlockedDomains = ["string"], + CacheControl = new() { Ttl = Ttl.Ttl5m }, + Citations = new() { Enabled = true }, + DeferLoading = true, + MaxContentTokens = 1, + MaxUses = 1, + Strict = true, + UseCache = true, + }; + + string json = JsonSerializer.Serialize(model, ModelBase.SerializerOptions); + var deserialized = JsonSerializer.Deserialize( + json, + ModelBase.SerializerOptions + ); + + Assert.Equal(model, deserialized); + } + + [Fact] + public void FieldRoundtripThroughSerialization_Works() + { + var model = new WebFetchTool20260309 + { + AllowedCallers = [WebFetchTool20260309AllowedCaller.Direct], + AllowedDomains = ["string"], + BlockedDomains = ["string"], + CacheControl = new() { Ttl = Ttl.Ttl5m }, + Citations = new() { Enabled = true }, + DeferLoading = true, + MaxContentTokens = 1, + MaxUses = 1, + Strict = true, + UseCache = true, + }; + + string element = JsonSerializer.Serialize(model, ModelBase.SerializerOptions); + var deserialized = JsonSerializer.Deserialize( + element, + ModelBase.SerializerOptions + ); + Assert.NotNull(deserialized); + + JsonElement expectedName = JsonSerializer.SerializeToElement("web_fetch"); + JsonElement expectedType = JsonSerializer.SerializeToElement("web_fetch_20260309"); + List> expectedAllowedCallers = + [ + WebFetchTool20260309AllowedCaller.Direct, + ]; + List expectedAllowedDomains = ["string"]; + List expectedBlockedDomains = ["string"]; + CacheControlEphemeral expectedCacheControl = new() { Ttl = Ttl.Ttl5m }; + CitationsConfigParam expectedCitations = new() { Enabled = true }; + bool expectedDeferLoading = true; + long expectedMaxContentTokens = 1; + long expectedMaxUses = 1; + bool expectedStrict = true; + bool expectedUseCache = true; + + Assert.True(JsonElement.DeepEquals(expectedName, deserialized.Name)); + Assert.True(JsonElement.DeepEquals(expectedType, deserialized.Type)); + Assert.NotNull(deserialized.AllowedCallers); + Assert.Equal(expectedAllowedCallers.Count, deserialized.AllowedCallers.Count); + for (int i = 0; i < expectedAllowedCallers.Count; i++) + { + Assert.Equal(expectedAllowedCallers[i], deserialized.AllowedCallers[i]); + } + Assert.NotNull(deserialized.AllowedDomains); + Assert.Equal(expectedAllowedDomains.Count, deserialized.AllowedDomains.Count); + for (int i = 0; i < expectedAllowedDomains.Count; i++) + { + Assert.Equal(expectedAllowedDomains[i], deserialized.AllowedDomains[i]); + } + Assert.NotNull(deserialized.BlockedDomains); + Assert.Equal(expectedBlockedDomains.Count, deserialized.BlockedDomains.Count); + for (int i = 0; i < expectedBlockedDomains.Count; i++) + { + Assert.Equal(expectedBlockedDomains[i], deserialized.BlockedDomains[i]); + } + Assert.Equal(expectedCacheControl, deserialized.CacheControl); + Assert.Equal(expectedCitations, deserialized.Citations); + Assert.Equal(expectedDeferLoading, deserialized.DeferLoading); + Assert.Equal(expectedMaxContentTokens, deserialized.MaxContentTokens); + Assert.Equal(expectedMaxUses, deserialized.MaxUses); + Assert.Equal(expectedStrict, deserialized.Strict); + Assert.Equal(expectedUseCache, deserialized.UseCache); + } + + [Fact] + public void Validation_Works() + { + var model = new WebFetchTool20260309 + { + AllowedCallers = [WebFetchTool20260309AllowedCaller.Direct], + AllowedDomains = ["string"], + BlockedDomains = ["string"], + CacheControl = new() { Ttl = Ttl.Ttl5m }, + Citations = new() { Enabled = true }, + DeferLoading = true, + MaxContentTokens = 1, + MaxUses = 1, + Strict = true, + UseCache = true, + }; + + model.Validate(); + } + + [Fact] + public void OptionalNonNullablePropertiesUnsetAreNotSet_Works() + { + var model = new WebFetchTool20260309 + { + AllowedDomains = ["string"], + BlockedDomains = ["string"], + CacheControl = new() { Ttl = Ttl.Ttl5m }, + Citations = new() { Enabled = true }, + MaxContentTokens = 1, + MaxUses = 1, + }; + + Assert.Null(model.AllowedCallers); + Assert.False(model.RawData.ContainsKey("allowed_callers")); + Assert.Null(model.DeferLoading); + Assert.False(model.RawData.ContainsKey("defer_loading")); + Assert.Null(model.Strict); + Assert.False(model.RawData.ContainsKey("strict")); + Assert.Null(model.UseCache); + Assert.False(model.RawData.ContainsKey("use_cache")); + } + + [Fact] + public void OptionalNonNullablePropertiesUnsetValidation_Works() + { + var model = new WebFetchTool20260309 + { + AllowedDomains = ["string"], + BlockedDomains = ["string"], + CacheControl = new() { Ttl = Ttl.Ttl5m }, + Citations = new() { Enabled = true }, + MaxContentTokens = 1, + MaxUses = 1, + }; + + model.Validate(); + } + + [Fact] + public void OptionalNonNullablePropertiesSetToNullAreNotSet_Works() + { + var model = new WebFetchTool20260309 + { + AllowedDomains = ["string"], + BlockedDomains = ["string"], + CacheControl = new() { Ttl = Ttl.Ttl5m }, + Citations = new() { Enabled = true }, + MaxContentTokens = 1, + MaxUses = 1, + + // Null should be interpreted as omitted for these properties + AllowedCallers = null, + DeferLoading = null, + Strict = null, + UseCache = null, + }; + + Assert.Null(model.AllowedCallers); + Assert.False(model.RawData.ContainsKey("allowed_callers")); + Assert.Null(model.DeferLoading); + Assert.False(model.RawData.ContainsKey("defer_loading")); + Assert.Null(model.Strict); + Assert.False(model.RawData.ContainsKey("strict")); + Assert.Null(model.UseCache); + Assert.False(model.RawData.ContainsKey("use_cache")); + } + + [Fact] + public void OptionalNonNullablePropertiesSetToNullValidation_Works() + { + var model = new WebFetchTool20260309 + { + AllowedDomains = ["string"], + BlockedDomains = ["string"], + CacheControl = new() { Ttl = Ttl.Ttl5m }, + Citations = new() { Enabled = true }, + MaxContentTokens = 1, + MaxUses = 1, + + // Null should be interpreted as omitted for these properties + AllowedCallers = null, + DeferLoading = null, + Strict = null, + UseCache = null, + }; + + model.Validate(); + } + + [Fact] + public void OptionalNullablePropertiesUnsetAreNotSet_Works() + { + var model = new WebFetchTool20260309 + { + AllowedCallers = [WebFetchTool20260309AllowedCaller.Direct], + DeferLoading = true, + Strict = true, + UseCache = true, + }; + + Assert.Null(model.AllowedDomains); + Assert.False(model.RawData.ContainsKey("allowed_domains")); + Assert.Null(model.BlockedDomains); + Assert.False(model.RawData.ContainsKey("blocked_domains")); + Assert.Null(model.CacheControl); + Assert.False(model.RawData.ContainsKey("cache_control")); + Assert.Null(model.Citations); + Assert.False(model.RawData.ContainsKey("citations")); + Assert.Null(model.MaxContentTokens); + Assert.False(model.RawData.ContainsKey("max_content_tokens")); + Assert.Null(model.MaxUses); + Assert.False(model.RawData.ContainsKey("max_uses")); + } + + [Fact] + public void OptionalNullablePropertiesUnsetValidation_Works() + { + var model = new WebFetchTool20260309 + { + AllowedCallers = [WebFetchTool20260309AllowedCaller.Direct], + DeferLoading = true, + Strict = true, + UseCache = true, + }; + + model.Validate(); + } + + [Fact] + public void OptionalNullablePropertiesSetToNullAreSetToNull_Works() + { + var model = new WebFetchTool20260309 + { + AllowedCallers = [WebFetchTool20260309AllowedCaller.Direct], + DeferLoading = true, + Strict = true, + UseCache = true, + + AllowedDomains = null, + BlockedDomains = null, + CacheControl = null, + Citations = null, + MaxContentTokens = null, + MaxUses = null, + }; + + Assert.Null(model.AllowedDomains); + Assert.True(model.RawData.ContainsKey("allowed_domains")); + Assert.Null(model.BlockedDomains); + Assert.True(model.RawData.ContainsKey("blocked_domains")); + Assert.Null(model.CacheControl); + Assert.True(model.RawData.ContainsKey("cache_control")); + Assert.Null(model.Citations); + Assert.True(model.RawData.ContainsKey("citations")); + Assert.Null(model.MaxContentTokens); + Assert.True(model.RawData.ContainsKey("max_content_tokens")); + Assert.Null(model.MaxUses); + Assert.True(model.RawData.ContainsKey("max_uses")); + } + + [Fact] + public void OptionalNullablePropertiesSetToNullValidation_Works() + { + var model = new WebFetchTool20260309 + { + AllowedCallers = [WebFetchTool20260309AllowedCaller.Direct], + DeferLoading = true, + Strict = true, + UseCache = true, + + AllowedDomains = null, + BlockedDomains = null, + CacheControl = null, + Citations = null, + MaxContentTokens = null, + MaxUses = null, + }; + + model.Validate(); + } + + [Fact] + public void CopyConstructor_Works() + { + var model = new WebFetchTool20260309 + { + AllowedCallers = [WebFetchTool20260309AllowedCaller.Direct], + AllowedDomains = ["string"], + BlockedDomains = ["string"], + CacheControl = new() { Ttl = Ttl.Ttl5m }, + Citations = new() { Enabled = true }, + DeferLoading = true, + MaxContentTokens = 1, + MaxUses = 1, + Strict = true, + UseCache = true, + }; + + WebFetchTool20260309 copied = new(model); + + Assert.Equal(model, copied); + } +} + +public class WebFetchTool20260309AllowedCallerTest : TestBase +{ + [Theory] + [InlineData(WebFetchTool20260309AllowedCaller.Direct)] + [InlineData(WebFetchTool20260309AllowedCaller.CodeExecution20250825)] + [InlineData(WebFetchTool20260309AllowedCaller.CodeExecution20260120)] + public void Validation_Works(WebFetchTool20260309AllowedCaller rawValue) + { + // force implicit conversion because Theory can't do that for us + ApiEnum value = rawValue; + value.Validate(); + } + + [Fact] + public void InvalidEnumValidationThrows_Works() + { + var value = JsonSerializer.Deserialize>( + JsonSerializer.SerializeToElement("invalid value"), + ModelBase.SerializerOptions + ); + + Assert.NotNull(value); + Assert.Throws(() => value.Validate()); + } + + [Theory] + [InlineData(WebFetchTool20260309AllowedCaller.Direct)] + [InlineData(WebFetchTool20260309AllowedCaller.CodeExecution20250825)] + [InlineData(WebFetchTool20260309AllowedCaller.CodeExecution20260120)] + public void SerializationRoundtrip_Works(WebFetchTool20260309AllowedCaller rawValue) + { + // force implicit conversion because Theory can't do that for us + ApiEnum value = rawValue; + + string json = JsonSerializer.Serialize(value, ModelBase.SerializerOptions); + var deserialized = JsonSerializer.Deserialize< + ApiEnum + >(json, ModelBase.SerializerOptions); + + Assert.Equal(value, deserialized); + } + + [Fact] + public void InvalidEnumSerializationRoundtrip_Works() + { + var value = JsonSerializer.Deserialize>( + JsonSerializer.SerializeToElement("invalid value"), + ModelBase.SerializerOptions + ); + string json = JsonSerializer.Serialize(value, ModelBase.SerializerOptions); + var deserialized = JsonSerializer.Deserialize< + ApiEnum + >(json, ModelBase.SerializerOptions); + + Assert.Equal(value, deserialized); + } +} diff --git a/src/Anthropic.Tests/Models/Models/CapabilitySupportTest.cs b/src/Anthropic.Tests/Models/Models/CapabilitySupportTest.cs new file mode 100644 index 000000000..cf94a29b0 --- /dev/null +++ b/src/Anthropic.Tests/Models/Models/CapabilitySupportTest.cs @@ -0,0 +1,67 @@ +using System.Text.Json; +using Anthropic.Core; +using Anthropic.Models.Models; + +namespace Anthropic.Tests.Models.Models; + +public class CapabilitySupportTest : TestBase +{ + [Fact] + public void FieldRoundtrip_Works() + { + var model = new CapabilitySupport { Supported = true }; + + bool expectedSupported = true; + + Assert.Equal(expectedSupported, model.Supported); + } + + [Fact] + public void SerializationRoundtrip_Works() + { + var model = new CapabilitySupport { Supported = true }; + + string json = JsonSerializer.Serialize(model, ModelBase.SerializerOptions); + var deserialized = JsonSerializer.Deserialize( + json, + ModelBase.SerializerOptions + ); + + Assert.Equal(model, deserialized); + } + + [Fact] + public void FieldRoundtripThroughSerialization_Works() + { + var model = new CapabilitySupport { Supported = true }; + + string element = JsonSerializer.Serialize(model, ModelBase.SerializerOptions); + var deserialized = JsonSerializer.Deserialize( + element, + ModelBase.SerializerOptions + ); + Assert.NotNull(deserialized); + + bool expectedSupported = true; + + Assert.Equal(expectedSupported, deserialized.Supported); + } + + [Fact] + public void Validation_Works() + { + var model = new CapabilitySupport { Supported = true }; + + model.Validate(); + } + + [Fact] + public void CopyConstructor_Works() + { + var model = new CapabilitySupport { Supported = true }; + + CapabilitySupport copied = new(model); + + Assert.Equal(model, copied); + } +} diff --git a/src/Anthropic.Tests/Models/Models/ContextManagementCapabilityTest.cs b/src/Anthropic.Tests/Models/Models/ContextManagementCapabilityTest.cs new file mode 100644 index 000000000..428886e37 --- /dev/null +++ b/src/Anthropic.Tests/Models/Models/ContextManagementCapabilityTest.cs @@ -0,0 +1,109 @@ +using System.Text.Json; +using Anthropic.Core; +using Anthropic.Models.Models; + +namespace Anthropic.Tests.Models.Models; + +public class ContextManagementCapabilityTest : TestBase +{ + [Fact] + public void FieldRoundtrip_Works() + { + var model = new ContextManagementCapability + { + ClearThinking20251015 = new(true), + ClearToolUses20250919 = new(true), + Compact20260112 = new(true), + Supported = true, + }; + + CapabilitySupport expectedClearThinking20251015 = new(true); + CapabilitySupport expectedClearToolUses20250919 = new(true); + CapabilitySupport expectedCompact20260112 = new(true); + bool expectedSupported = true; + + Assert.Equal(expectedClearThinking20251015, model.ClearThinking20251015); + Assert.Equal(expectedClearToolUses20250919, model.ClearToolUses20250919); + Assert.Equal(expectedCompact20260112, model.Compact20260112); + Assert.Equal(expectedSupported, model.Supported); + } + + [Fact] + public void SerializationRoundtrip_Works() + { + var model = new ContextManagementCapability + { + ClearThinking20251015 = new(true), + ClearToolUses20250919 = new(true), + Compact20260112 = new(true), + Supported = true, + }; + + string json = JsonSerializer.Serialize(model, ModelBase.SerializerOptions); + var deserialized = JsonSerializer.Deserialize( + json, + ModelBase.SerializerOptions + ); + + Assert.Equal(model, deserialized); + } + + [Fact] + public void FieldRoundtripThroughSerialization_Works() + { + var model = new ContextManagementCapability + { + ClearThinking20251015 = new(true), + ClearToolUses20250919 = new(true), + Compact20260112 = new(true), + Supported = true, + }; + + string element = JsonSerializer.Serialize(model, ModelBase.SerializerOptions); + var deserialized = JsonSerializer.Deserialize( + element, + ModelBase.SerializerOptions + ); + Assert.NotNull(deserialized); + + CapabilitySupport expectedClearThinking20251015 = new(true); + CapabilitySupport expectedClearToolUses20250919 = new(true); + CapabilitySupport expectedCompact20260112 = new(true); + bool expectedSupported = true; + + Assert.Equal(expectedClearThinking20251015, deserialized.ClearThinking20251015); + Assert.Equal(expectedClearToolUses20250919, deserialized.ClearToolUses20250919); + Assert.Equal(expectedCompact20260112, deserialized.Compact20260112); + Assert.Equal(expectedSupported, deserialized.Supported); + } + + [Fact] + public void Validation_Works() + { + var model = new ContextManagementCapability + { + ClearThinking20251015 = new(true), + ClearToolUses20250919 = new(true), + Compact20260112 = new(true), + Supported = true, + }; + + model.Validate(); + } + + [Fact] + public void CopyConstructor_Works() + { + var model = new ContextManagementCapability + { + ClearThinking20251015 = new(true), + ClearToolUses20250919 = new(true), + Compact20260112 = new(true), + Supported = true, + }; + + ContextManagementCapability copied = new(model); + + Assert.Equal(model, copied); + } +} diff --git a/src/Anthropic.Tests/Models/Models/EffortCapabilityTest.cs b/src/Anthropic.Tests/Models/Models/EffortCapabilityTest.cs new file mode 100644 index 000000000..e15d11747 --- /dev/null +++ b/src/Anthropic.Tests/Models/Models/EffortCapabilityTest.cs @@ -0,0 +1,118 @@ +using System.Text.Json; +using Anthropic.Core; +using Anthropic.Models.Models; + +namespace Anthropic.Tests.Models.Models; + +public class EffortCapabilityTest : TestBase +{ + [Fact] + public void FieldRoundtrip_Works() + { + var model = new EffortCapability + { + High = new(true), + Low = new(true), + Max = new(true), + Medium = new(true), + Supported = true, + }; + + CapabilitySupport expectedHigh = new(true); + CapabilitySupport expectedLow = new(true); + CapabilitySupport expectedMax = new(true); + CapabilitySupport expectedMedium = new(true); + bool expectedSupported = true; + + Assert.Equal(expectedHigh, model.High); + Assert.Equal(expectedLow, model.Low); + Assert.Equal(expectedMax, model.Max); + Assert.Equal(expectedMedium, model.Medium); + Assert.Equal(expectedSupported, model.Supported); + } + + [Fact] + public void SerializationRoundtrip_Works() + { + var model = new EffortCapability + { + High = new(true), + Low = new(true), + Max = new(true), + Medium = new(true), + Supported = true, + }; + + string json = JsonSerializer.Serialize(model, ModelBase.SerializerOptions); + var deserialized = JsonSerializer.Deserialize( + json, + ModelBase.SerializerOptions + ); + + Assert.Equal(model, deserialized); + } + + [Fact] + public void FieldRoundtripThroughSerialization_Works() + { + var model = new EffortCapability + { + High = new(true), + Low = new(true), + Max = new(true), + Medium = new(true), + Supported = true, + }; + + string element = JsonSerializer.Serialize(model, ModelBase.SerializerOptions); + var deserialized = JsonSerializer.Deserialize( + element, + ModelBase.SerializerOptions + ); + Assert.NotNull(deserialized); + + CapabilitySupport expectedHigh = new(true); + CapabilitySupport expectedLow = new(true); + CapabilitySupport expectedMax = new(true); + CapabilitySupport expectedMedium = new(true); + bool expectedSupported = true; + + Assert.Equal(expectedHigh, deserialized.High); + Assert.Equal(expectedLow, deserialized.Low); + Assert.Equal(expectedMax, deserialized.Max); + Assert.Equal(expectedMedium, deserialized.Medium); + Assert.Equal(expectedSupported, deserialized.Supported); + } + + [Fact] + public void Validation_Works() + { + var model = new EffortCapability + { + High = new(true), + Low = new(true), + Max = new(true), + Medium = new(true), + Supported = true, + }; + + model.Validate(); + } + + [Fact] + public void CopyConstructor_Works() + { + var model = new EffortCapability + { + High = new(true), + Low = new(true), + Max = new(true), + Medium = new(true), + Supported = true, + }; + + EffortCapability copied = new(model); + + Assert.Equal(model, copied); + } +} diff --git a/src/Anthropic.Tests/Models/Models/ModelCapabilitiesTest.cs b/src/Anthropic.Tests/Models/Models/ModelCapabilitiesTest.cs new file mode 100644 index 000000000..1cecdb4e6 --- /dev/null +++ b/src/Anthropic.Tests/Models/Models/ModelCapabilitiesTest.cs @@ -0,0 +1,273 @@ +using System.Text.Json; +using Anthropic.Core; +using Anthropic.Models.Models; + +namespace Anthropic.Tests.Models.Models; + +public class ModelCapabilitiesTest : TestBase +{ + [Fact] + public void FieldRoundtrip_Works() + { + var model = new ModelCapabilities + { + Batch = new(true), + Citations = new(true), + CodeExecution = new(true), + ContextManagement = new() + { + ClearThinking20251015 = new(true), + ClearToolUses20250919 = new(true), + Compact20260112 = new(true), + Supported = true, + }, + Effort = new() + { + High = new(true), + Low = new(true), + Max = new(true), + Medium = new(true), + Supported = true, + }, + ImageInput = new(true), + PdfInput = new(true), + StructuredOutputs = new(true), + Thinking = new() + { + Supported = true, + Types = new() { Adaptive = new(true), Enabled = new(true) }, + }, + }; + + CapabilitySupport expectedBatch = new(true); + CapabilitySupport expectedCitations = new(true); + CapabilitySupport expectedCodeExecution = new(true); + ContextManagementCapability expectedContextManagement = new() + { + ClearThinking20251015 = new(true), + ClearToolUses20250919 = new(true), + Compact20260112 = new(true), + Supported = true, + }; + EffortCapability expectedEffort = new() + { + High = new(true), + Low = new(true), + Max = new(true), + Medium = new(true), + Supported = true, + }; + CapabilitySupport expectedImageInput = new(true); + CapabilitySupport expectedPdfInput = new(true); + CapabilitySupport expectedStructuredOutputs = new(true); + ThinkingCapability expectedThinking = new() + { + Supported = true, + Types = new() { Adaptive = new(true), Enabled = new(true) }, + }; + + Assert.Equal(expectedBatch, model.Batch); + Assert.Equal(expectedCitations, model.Citations); + Assert.Equal(expectedCodeExecution, model.CodeExecution); + Assert.Equal(expectedContextManagement, model.ContextManagement); + Assert.Equal(expectedEffort, model.Effort); + Assert.Equal(expectedImageInput, model.ImageInput); + Assert.Equal(expectedPdfInput, model.PdfInput); + Assert.Equal(expectedStructuredOutputs, model.StructuredOutputs); + Assert.Equal(expectedThinking, model.Thinking); + } + + [Fact] + public void SerializationRoundtrip_Works() + { + var model = new ModelCapabilities + { + Batch = new(true), + Citations = new(true), + CodeExecution = new(true), + ContextManagement = new() + { + ClearThinking20251015 = new(true), + ClearToolUses20250919 = new(true), + Compact20260112 = new(true), + Supported = true, + }, + Effort = new() + { + High = new(true), + Low = new(true), + Max = new(true), + Medium = new(true), + Supported = true, + }, + ImageInput = new(true), + PdfInput = new(true), + StructuredOutputs = new(true), + Thinking = new() + { + Supported = true, + Types = new() { Adaptive = new(true), Enabled = new(true) }, + }, + }; + + string json = JsonSerializer.Serialize(model, ModelBase.SerializerOptions); + var deserialized = JsonSerializer.Deserialize( + json, + ModelBase.SerializerOptions + ); + + Assert.Equal(model, deserialized); + } + + [Fact] + public void FieldRoundtripThroughSerialization_Works() + { + var model = new ModelCapabilities + { + Batch = new(true), + Citations = new(true), + CodeExecution = new(true), + ContextManagement = new() + { + ClearThinking20251015 = new(true), + ClearToolUses20250919 = new(true), + Compact20260112 = new(true), + Supported = true, + }, + Effort = new() + { + High = new(true), + Low = new(true), + Max = new(true), + Medium = new(true), + Supported = true, + }, + ImageInput = new(true), + PdfInput = new(true), + StructuredOutputs = new(true), + Thinking = new() + { + Supported = true, + Types = new() { Adaptive = new(true), Enabled = new(true) }, + }, + }; + + string element = JsonSerializer.Serialize(model, ModelBase.SerializerOptions); + var deserialized = JsonSerializer.Deserialize( + element, + ModelBase.SerializerOptions + ); + Assert.NotNull(deserialized); + + CapabilitySupport expectedBatch = new(true); + CapabilitySupport expectedCitations = new(true); + CapabilitySupport expectedCodeExecution = new(true); + ContextManagementCapability expectedContextManagement = new() + { + ClearThinking20251015 = new(true), + ClearToolUses20250919 = new(true), + Compact20260112 = new(true), + Supported = true, + }; + EffortCapability expectedEffort = new() + { + High = new(true), + Low = new(true), + Max = new(true), + Medium = new(true), + Supported = true, + }; + CapabilitySupport expectedImageInput = new(true); + CapabilitySupport expectedPdfInput = new(true); + CapabilitySupport expectedStructuredOutputs = new(true); + ThinkingCapability expectedThinking = new() + { + Supported = true, + Types = new() { Adaptive = new(true), Enabled = new(true) }, + }; + + Assert.Equal(expectedBatch, deserialized.Batch); + Assert.Equal(expectedCitations, deserialized.Citations); + Assert.Equal(expectedCodeExecution, deserialized.CodeExecution); + Assert.Equal(expectedContextManagement, deserialized.ContextManagement); + Assert.Equal(expectedEffort, deserialized.Effort); + Assert.Equal(expectedImageInput, deserialized.ImageInput); + Assert.Equal(expectedPdfInput, deserialized.PdfInput); + Assert.Equal(expectedStructuredOutputs, deserialized.StructuredOutputs); + Assert.Equal(expectedThinking, deserialized.Thinking); + } + + [Fact] + public void Validation_Works() + { + var model = new ModelCapabilities + { + Batch = new(true), + Citations = new(true), + CodeExecution = new(true), + ContextManagement = new() + { + ClearThinking20251015 = new(true), + ClearToolUses20250919 = new(true), + Compact20260112 = new(true), + Supported = true, + }, + Effort = new() + { + High = new(true), + Low = new(true), + Max = new(true), + Medium = new(true), + Supported = true, + }, + ImageInput = new(true), + PdfInput = new(true), + StructuredOutputs = new(true), + Thinking = new() + { + Supported = true, + Types = new() { Adaptive = new(true), Enabled = new(true) }, + }, + }; + + model.Validate(); + } + + [Fact] + public void CopyConstructor_Works() + { + var model = new ModelCapabilities + { + Batch = new(true), + Citations = new(true), + CodeExecution = new(true), + ContextManagement = new() + { + ClearThinking20251015 = new(true), + ClearToolUses20250919 = new(true), + Compact20260112 = new(true), + Supported = true, + }, + Effort = new() + { + High = new(true), + Low = new(true), + Max = new(true), + Medium = new(true), + Supported = true, + }, + ImageInput = new(true), + PdfInput = new(true), + StructuredOutputs = new(true), + Thinking = new() + { + Supported = true, + Types = new() { Adaptive = new(true), Enabled = new(true) }, + }, + }; + + ModelCapabilities copied = new(model); + + Assert.Equal(model, copied); + } +} diff --git a/src/Anthropic.Tests/Models/Models/ModelInfoTest.cs b/src/Anthropic.Tests/Models/Models/ModelInfoTest.cs index a8c6df7c2..0bea9e142 100644 --- a/src/Anthropic.Tests/Models/Models/ModelInfoTest.cs +++ b/src/Anthropic.Tests/Models/Models/ModelInfoTest.cs @@ -13,18 +13,83 @@ public void FieldRoundtrip_Works() var model = new ModelInfo { ID = "claude-opus-4-6", + Capabilities = new() + { + Batch = new(true), + Citations = new(true), + CodeExecution = new(true), + ContextManagement = new() + { + ClearThinking20251015 = new(true), + ClearToolUses20250919 = new(true), + Compact20260112 = new(true), + Supported = true, + }, + Effort = new() + { + High = new(true), + Low = new(true), + Max = new(true), + Medium = new(true), + Supported = true, + }, + ImageInput = new(true), + PdfInput = new(true), + StructuredOutputs = new(true), + Thinking = new() + { + Supported = true, + Types = new() { Adaptive = new(true), Enabled = new(true) }, + }, + }, CreatedAt = DateTimeOffset.Parse("2026-02-04T00:00:00Z"), DisplayName = "Claude Opus 4.6", + MaxInputTokens = 0, + MaxTokens = 0, }; string expectedID = "claude-opus-4-6"; + ModelCapabilities expectedCapabilities = new() + { + Batch = new(true), + Citations = new(true), + CodeExecution = new(true), + ContextManagement = new() + { + ClearThinking20251015 = new(true), + ClearToolUses20250919 = new(true), + Compact20260112 = new(true), + Supported = true, + }, + Effort = new() + { + High = new(true), + Low = new(true), + Max = new(true), + Medium = new(true), + Supported = true, + }, + ImageInput = new(true), + PdfInput = new(true), + StructuredOutputs = new(true), + Thinking = new() + { + Supported = true, + Types = new() { Adaptive = new(true), Enabled = new(true) }, + }, + }; DateTimeOffset expectedCreatedAt = DateTimeOffset.Parse("2026-02-04T00:00:00Z"); string expectedDisplayName = "Claude Opus 4.6"; + long expectedMaxInputTokens = 0; + long expectedMaxTokens = 0; JsonElement expectedType = JsonSerializer.SerializeToElement("model"); Assert.Equal(expectedID, model.ID); + Assert.Equal(expectedCapabilities, model.Capabilities); Assert.Equal(expectedCreatedAt, model.CreatedAt); Assert.Equal(expectedDisplayName, model.DisplayName); + Assert.Equal(expectedMaxInputTokens, model.MaxInputTokens); + Assert.Equal(expectedMaxTokens, model.MaxTokens); Assert.True(JsonElement.DeepEquals(expectedType, model.Type)); } @@ -34,8 +99,39 @@ public void SerializationRoundtrip_Works() var model = new ModelInfo { ID = "claude-opus-4-6", + Capabilities = new() + { + Batch = new(true), + Citations = new(true), + CodeExecution = new(true), + ContextManagement = new() + { + ClearThinking20251015 = new(true), + ClearToolUses20250919 = new(true), + Compact20260112 = new(true), + Supported = true, + }, + Effort = new() + { + High = new(true), + Low = new(true), + Max = new(true), + Medium = new(true), + Supported = true, + }, + ImageInput = new(true), + PdfInput = new(true), + StructuredOutputs = new(true), + Thinking = new() + { + Supported = true, + Types = new() { Adaptive = new(true), Enabled = new(true) }, + }, + }, CreatedAt = DateTimeOffset.Parse("2026-02-04T00:00:00Z"), DisplayName = "Claude Opus 4.6", + MaxInputTokens = 0, + MaxTokens = 0, }; string json = JsonSerializer.Serialize(model, ModelBase.SerializerOptions); @@ -50,8 +146,39 @@ public void FieldRoundtripThroughSerialization_Works() var model = new ModelInfo { ID = "claude-opus-4-6", + Capabilities = new() + { + Batch = new(true), + Citations = new(true), + CodeExecution = new(true), + ContextManagement = new() + { + ClearThinking20251015 = new(true), + ClearToolUses20250919 = new(true), + Compact20260112 = new(true), + Supported = true, + }, + Effort = new() + { + High = new(true), + Low = new(true), + Max = new(true), + Medium = new(true), + Supported = true, + }, + ImageInput = new(true), + PdfInput = new(true), + StructuredOutputs = new(true), + Thinking = new() + { + Supported = true, + Types = new() { Adaptive = new(true), Enabled = new(true) }, + }, + }, CreatedAt = DateTimeOffset.Parse("2026-02-04T00:00:00Z"), DisplayName = "Claude Opus 4.6", + MaxInputTokens = 0, + MaxTokens = 0, }; string element = JsonSerializer.Serialize(model, ModelBase.SerializerOptions); @@ -62,13 +189,47 @@ public void FieldRoundtripThroughSerialization_Works() Assert.NotNull(deserialized); string expectedID = "claude-opus-4-6"; + ModelCapabilities expectedCapabilities = new() + { + Batch = new(true), + Citations = new(true), + CodeExecution = new(true), + ContextManagement = new() + { + ClearThinking20251015 = new(true), + ClearToolUses20250919 = new(true), + Compact20260112 = new(true), + Supported = true, + }, + Effort = new() + { + High = new(true), + Low = new(true), + Max = new(true), + Medium = new(true), + Supported = true, + }, + ImageInput = new(true), + PdfInput = new(true), + StructuredOutputs = new(true), + Thinking = new() + { + Supported = true, + Types = new() { Adaptive = new(true), Enabled = new(true) }, + }, + }; DateTimeOffset expectedCreatedAt = DateTimeOffset.Parse("2026-02-04T00:00:00Z"); string expectedDisplayName = "Claude Opus 4.6"; + long expectedMaxInputTokens = 0; + long expectedMaxTokens = 0; JsonElement expectedType = JsonSerializer.SerializeToElement("model"); Assert.Equal(expectedID, deserialized.ID); + Assert.Equal(expectedCapabilities, deserialized.Capabilities); Assert.Equal(expectedCreatedAt, deserialized.CreatedAt); Assert.Equal(expectedDisplayName, deserialized.DisplayName); + Assert.Equal(expectedMaxInputTokens, deserialized.MaxInputTokens); + Assert.Equal(expectedMaxTokens, deserialized.MaxTokens); Assert.True(JsonElement.DeepEquals(expectedType, deserialized.Type)); } @@ -78,8 +239,39 @@ public void Validation_Works() var model = new ModelInfo { ID = "claude-opus-4-6", + Capabilities = new() + { + Batch = new(true), + Citations = new(true), + CodeExecution = new(true), + ContextManagement = new() + { + ClearThinking20251015 = new(true), + ClearToolUses20250919 = new(true), + Compact20260112 = new(true), + Supported = true, + }, + Effort = new() + { + High = new(true), + Low = new(true), + Max = new(true), + Medium = new(true), + Supported = true, + }, + ImageInput = new(true), + PdfInput = new(true), + StructuredOutputs = new(true), + Thinking = new() + { + Supported = true, + Types = new() { Adaptive = new(true), Enabled = new(true) }, + }, + }, CreatedAt = DateTimeOffset.Parse("2026-02-04T00:00:00Z"), DisplayName = "Claude Opus 4.6", + MaxInputTokens = 0, + MaxTokens = 0, }; model.Validate(); @@ -91,8 +283,39 @@ public void CopyConstructor_Works() var model = new ModelInfo { ID = "claude-opus-4-6", + Capabilities = new() + { + Batch = new(true), + Citations = new(true), + CodeExecution = new(true), + ContextManagement = new() + { + ClearThinking20251015 = new(true), + ClearToolUses20250919 = new(true), + Compact20260112 = new(true), + Supported = true, + }, + Effort = new() + { + High = new(true), + Low = new(true), + Max = new(true), + Medium = new(true), + Supported = true, + }, + ImageInput = new(true), + PdfInput = new(true), + StructuredOutputs = new(true), + Thinking = new() + { + Supported = true, + Types = new() { Adaptive = new(true), Enabled = new(true) }, + }, + }, CreatedAt = DateTimeOffset.Parse("2026-02-04T00:00:00Z"), DisplayName = "Claude Opus 4.6", + MaxInputTokens = 0, + MaxTokens = 0, }; ModelInfo copied = new(model); diff --git a/src/Anthropic.Tests/Models/Models/ModelListPageResponseTest.cs b/src/Anthropic.Tests/Models/Models/ModelListPageResponseTest.cs index 7094a0475..faf9d0144 100644 --- a/src/Anthropic.Tests/Models/Models/ModelListPageResponseTest.cs +++ b/src/Anthropic.Tests/Models/Models/ModelListPageResponseTest.cs @@ -18,8 +18,39 @@ public void FieldRoundtrip_Works() new() { ID = "claude-opus-4-6", + Capabilities = new() + { + Batch = new(true), + Citations = new(true), + CodeExecution = new(true), + ContextManagement = new() + { + ClearThinking20251015 = new(true), + ClearToolUses20250919 = new(true), + Compact20260112 = new(true), + Supported = true, + }, + Effort = new() + { + High = new(true), + Low = new(true), + Max = new(true), + Medium = new(true), + Supported = true, + }, + ImageInput = new(true), + PdfInput = new(true), + StructuredOutputs = new(true), + Thinking = new() + { + Supported = true, + Types = new() { Adaptive = new(true), Enabled = new(true) }, + }, + }, CreatedAt = DateTimeOffset.Parse("2026-02-04T00:00:00Z"), DisplayName = "Claude Opus 4.6", + MaxInputTokens = 0, + MaxTokens = 0, }, ], FirstID = "first_id", @@ -32,8 +63,39 @@ public void FieldRoundtrip_Works() new() { ID = "claude-opus-4-6", + Capabilities = new() + { + Batch = new(true), + Citations = new(true), + CodeExecution = new(true), + ContextManagement = new() + { + ClearThinking20251015 = new(true), + ClearToolUses20250919 = new(true), + Compact20260112 = new(true), + Supported = true, + }, + Effort = new() + { + High = new(true), + Low = new(true), + Max = new(true), + Medium = new(true), + Supported = true, + }, + ImageInput = new(true), + PdfInput = new(true), + StructuredOutputs = new(true), + Thinking = new() + { + Supported = true, + Types = new() { Adaptive = new(true), Enabled = new(true) }, + }, + }, CreatedAt = DateTimeOffset.Parse("2026-02-04T00:00:00Z"), DisplayName = "Claude Opus 4.6", + MaxInputTokens = 0, + MaxTokens = 0, }, ]; string expectedFirstID = "first_id"; @@ -60,8 +122,39 @@ public void SerializationRoundtrip_Works() new() { ID = "claude-opus-4-6", + Capabilities = new() + { + Batch = new(true), + Citations = new(true), + CodeExecution = new(true), + ContextManagement = new() + { + ClearThinking20251015 = new(true), + ClearToolUses20250919 = new(true), + Compact20260112 = new(true), + Supported = true, + }, + Effort = new() + { + High = new(true), + Low = new(true), + Max = new(true), + Medium = new(true), + Supported = true, + }, + ImageInput = new(true), + PdfInput = new(true), + StructuredOutputs = new(true), + Thinking = new() + { + Supported = true, + Types = new() { Adaptive = new(true), Enabled = new(true) }, + }, + }, CreatedAt = DateTimeOffset.Parse("2026-02-04T00:00:00Z"), DisplayName = "Claude Opus 4.6", + MaxInputTokens = 0, + MaxTokens = 0, }, ], FirstID = "first_id", @@ -88,8 +181,39 @@ public void FieldRoundtripThroughSerialization_Works() new() { ID = "claude-opus-4-6", + Capabilities = new() + { + Batch = new(true), + Citations = new(true), + CodeExecution = new(true), + ContextManagement = new() + { + ClearThinking20251015 = new(true), + ClearToolUses20250919 = new(true), + Compact20260112 = new(true), + Supported = true, + }, + Effort = new() + { + High = new(true), + Low = new(true), + Max = new(true), + Medium = new(true), + Supported = true, + }, + ImageInput = new(true), + PdfInput = new(true), + StructuredOutputs = new(true), + Thinking = new() + { + Supported = true, + Types = new() { Adaptive = new(true), Enabled = new(true) }, + }, + }, CreatedAt = DateTimeOffset.Parse("2026-02-04T00:00:00Z"), DisplayName = "Claude Opus 4.6", + MaxInputTokens = 0, + MaxTokens = 0, }, ], FirstID = "first_id", @@ -109,8 +233,39 @@ public void FieldRoundtripThroughSerialization_Works() new() { ID = "claude-opus-4-6", + Capabilities = new() + { + Batch = new(true), + Citations = new(true), + CodeExecution = new(true), + ContextManagement = new() + { + ClearThinking20251015 = new(true), + ClearToolUses20250919 = new(true), + Compact20260112 = new(true), + Supported = true, + }, + Effort = new() + { + High = new(true), + Low = new(true), + Max = new(true), + Medium = new(true), + Supported = true, + }, + ImageInput = new(true), + PdfInput = new(true), + StructuredOutputs = new(true), + Thinking = new() + { + Supported = true, + Types = new() { Adaptive = new(true), Enabled = new(true) }, + }, + }, CreatedAt = DateTimeOffset.Parse("2026-02-04T00:00:00Z"), DisplayName = "Claude Opus 4.6", + MaxInputTokens = 0, + MaxTokens = 0, }, ]; string expectedFirstID = "first_id"; @@ -137,8 +292,39 @@ public void Validation_Works() new() { ID = "claude-opus-4-6", + Capabilities = new() + { + Batch = new(true), + Citations = new(true), + CodeExecution = new(true), + ContextManagement = new() + { + ClearThinking20251015 = new(true), + ClearToolUses20250919 = new(true), + Compact20260112 = new(true), + Supported = true, + }, + Effort = new() + { + High = new(true), + Low = new(true), + Max = new(true), + Medium = new(true), + Supported = true, + }, + ImageInput = new(true), + PdfInput = new(true), + StructuredOutputs = new(true), + Thinking = new() + { + Supported = true, + Types = new() { Adaptive = new(true), Enabled = new(true) }, + }, + }, CreatedAt = DateTimeOffset.Parse("2026-02-04T00:00:00Z"), DisplayName = "Claude Opus 4.6", + MaxInputTokens = 0, + MaxTokens = 0, }, ], FirstID = "first_id", @@ -159,8 +345,39 @@ public void CopyConstructor_Works() new() { ID = "claude-opus-4-6", + Capabilities = new() + { + Batch = new(true), + Citations = new(true), + CodeExecution = new(true), + ContextManagement = new() + { + ClearThinking20251015 = new(true), + ClearToolUses20250919 = new(true), + Compact20260112 = new(true), + Supported = true, + }, + Effort = new() + { + High = new(true), + Low = new(true), + Max = new(true), + Medium = new(true), + Supported = true, + }, + ImageInput = new(true), + PdfInput = new(true), + StructuredOutputs = new(true), + Thinking = new() + { + Supported = true, + Types = new() { Adaptive = new(true), Enabled = new(true) }, + }, + }, CreatedAt = DateTimeOffset.Parse("2026-02-04T00:00:00Z"), DisplayName = "Claude Opus 4.6", + MaxInputTokens = 0, + MaxTokens = 0, }, ], FirstID = "first_id", diff --git a/src/Anthropic.Tests/Models/Models/ThinkingCapabilityTest.cs b/src/Anthropic.Tests/Models/Models/ThinkingCapabilityTest.cs new file mode 100644 index 000000000..5e03f94a5 --- /dev/null +++ b/src/Anthropic.Tests/Models/Models/ThinkingCapabilityTest.cs @@ -0,0 +1,91 @@ +using System.Text.Json; +using Anthropic.Core; +using Anthropic.Models.Models; + +namespace Anthropic.Tests.Models.Models; + +public class ThinkingCapabilityTest : TestBase +{ + [Fact] + public void FieldRoundtrip_Works() + { + var model = new ThinkingCapability + { + Supported = true, + Types = new() { Adaptive = new(true), Enabled = new(true) }, + }; + + bool expectedSupported = true; + ThinkingTypes expectedTypes = new() { Adaptive = new(true), Enabled = new(true) }; + + Assert.Equal(expectedSupported, model.Supported); + Assert.Equal(expectedTypes, model.Types); + } + + [Fact] + public void SerializationRoundtrip_Works() + { + var model = new ThinkingCapability + { + Supported = true, + Types = new() { Adaptive = new(true), Enabled = new(true) }, + }; + + string json = JsonSerializer.Serialize(model, ModelBase.SerializerOptions); + var deserialized = JsonSerializer.Deserialize( + json, + ModelBase.SerializerOptions + ); + + Assert.Equal(model, deserialized); + } + + [Fact] + public void FieldRoundtripThroughSerialization_Works() + { + var model = new ThinkingCapability + { + Supported = true, + Types = new() { Adaptive = new(true), Enabled = new(true) }, + }; + + string element = JsonSerializer.Serialize(model, ModelBase.SerializerOptions); + var deserialized = JsonSerializer.Deserialize( + element, + ModelBase.SerializerOptions + ); + Assert.NotNull(deserialized); + + bool expectedSupported = true; + ThinkingTypes expectedTypes = new() { Adaptive = new(true), Enabled = new(true) }; + + Assert.Equal(expectedSupported, deserialized.Supported); + Assert.Equal(expectedTypes, deserialized.Types); + } + + [Fact] + public void Validation_Works() + { + var model = new ThinkingCapability + { + Supported = true, + Types = new() { Adaptive = new(true), Enabled = new(true) }, + }; + + model.Validate(); + } + + [Fact] + public void CopyConstructor_Works() + { + var model = new ThinkingCapability + { + Supported = true, + Types = new() { Adaptive = new(true), Enabled = new(true) }, + }; + + ThinkingCapability copied = new(model); + + Assert.Equal(model, copied); + } +} diff --git a/src/Anthropic.Tests/Models/Models/ThinkingTypesTest.cs b/src/Anthropic.Tests/Models/Models/ThinkingTypesTest.cs new file mode 100644 index 000000000..04912a896 --- /dev/null +++ b/src/Anthropic.Tests/Models/Models/ThinkingTypesTest.cs @@ -0,0 +1,71 @@ +using System.Text.Json; +using Anthropic.Core; +using Anthropic.Models.Models; + +namespace Anthropic.Tests.Models.Models; + +public class ThinkingTypesTest : TestBase +{ + [Fact] + public void FieldRoundtrip_Works() + { + var model = new ThinkingTypes { Adaptive = new(true), Enabled = new(true) }; + + CapabilitySupport expectedAdaptive = new(true); + CapabilitySupport expectedEnabled = new(true); + + Assert.Equal(expectedAdaptive, model.Adaptive); + Assert.Equal(expectedEnabled, model.Enabled); + } + + [Fact] + public void SerializationRoundtrip_Works() + { + var model = new ThinkingTypes { Adaptive = new(true), Enabled = new(true) }; + + string json = JsonSerializer.Serialize(model, ModelBase.SerializerOptions); + var deserialized = JsonSerializer.Deserialize( + json, + ModelBase.SerializerOptions + ); + + Assert.Equal(model, deserialized); + } + + [Fact] + public void FieldRoundtripThroughSerialization_Works() + { + var model = new ThinkingTypes { Adaptive = new(true), Enabled = new(true) }; + + string element = JsonSerializer.Serialize(model, ModelBase.SerializerOptions); + var deserialized = JsonSerializer.Deserialize( + element, + ModelBase.SerializerOptions + ); + Assert.NotNull(deserialized); + + CapabilitySupport expectedAdaptive = new(true); + CapabilitySupport expectedEnabled = new(true); + + Assert.Equal(expectedAdaptive, deserialized.Adaptive); + Assert.Equal(expectedEnabled, deserialized.Enabled); + } + + [Fact] + public void Validation_Works() + { + var model = new ThinkingTypes { Adaptive = new(true), Enabled = new(true) }; + + model.Validate(); + } + + [Fact] + public void CopyConstructor_Works() + { + var model = new ThinkingTypes { Adaptive = new(true), Enabled = new(true) }; + + ThinkingTypes copied = new(model); + + Assert.Equal(model, copied); + } +} diff --git a/src/Anthropic.Tests/Services/Beta/Messages/BatchServiceTest.cs b/src/Anthropic.Tests/Services/Beta/Messages/BatchServiceTest.cs index 3b5ce96f8..7df6e8df7 100644 --- a/src/Anthropic.Tests/Services/Beta/Messages/BatchServiceTest.cs +++ b/src/Anthropic.Tests/Services/Beta/Messages/BatchServiceTest.cs @@ -32,9 +32,9 @@ public async Task Create_Works() [ new() { - SkillID = "x", + SkillID = "pdf", Type = BetaSkillParamsType.Anthropic, - Version = "x", + Version = "latest", }, ], }, @@ -111,7 +111,10 @@ public async Task Create_Works() ] ), Temperature = 1, - Thinking = new BetaThinkingConfigEnabled(1024), + Thinking = new BetaThinkingConfigAdaptive() + { + Display = Display.Summarized, + }, ToolChoice = new BetaToolChoiceAuto() { DisableParallelToolUse = true }, Tools = [ diff --git a/src/Anthropic.Tests/Services/Messages/BatchServiceTest.cs b/src/Anthropic.Tests/Services/Messages/BatchServiceTest.cs index c41515b45..38bc45084 100644 --- a/src/Anthropic.Tests/Services/Messages/BatchServiceTest.cs +++ b/src/Anthropic.Tests/Services/Messages/BatchServiceTest.cs @@ -62,7 +62,10 @@ public async Task Create_Works() ] ), Temperature = 1, - Thinking = new ThinkingConfigEnabled(1024), + Thinking = new ThinkingConfigAdaptive() + { + Display = Display.Summarized, + }, ToolChoice = new ToolChoiceAuto() { DisableParallelToolUse = true }, Tools = [ diff --git a/src/Anthropic.Tests/SseEventContentWrapperTest.cs b/src/Anthropic.Tests/SseEventContentWrapperTest.cs new file mode 100644 index 000000000..ea2c3c27e --- /dev/null +++ b/src/Anthropic.Tests/SseEventContentWrapperTest.cs @@ -0,0 +1,126 @@ +#pragma warning disable xUnit1051 // ReadAsStreamAsync CancellationToken overload not available on net472 +using System; +using System.Buffers.Binary; +using System.IO; +using System.Text; +using System.Text.Json; +using System.Threading; +using System.Threading.Tasks; +using Anthropic.Bedrock; + +namespace Anthropic.Tests; + +public class SseEventContentWrapperTest +{ + /// + /// Builds a binary AWS EventStream message containing a JSON payload + /// with a base64-encoded inner event, matching the Bedrock response format. + /// + private static byte[] BuildEventStreamMessage(string eventType, string eventJson) + { + // Inner payload: base64-encode the event JSON + var innerBytes = Encoding.UTF8.GetBytes(eventJson); + var base64 = Convert.ToBase64String(innerBytes); + var outerJson = $"{{\"bytes\":\"{base64}\"}}"; + var payloadBytes = Encoding.UTF8.GetBytes(outerJson); + + // Build a minimal header: ":event-type" -> eventType (string type = 7) + var headerName = ":event-type"u8; + var headerValue = Encoding.UTF8.GetBytes(eventType); + // Header format: name_len(1) + name + type(1) + value_len(2) + value + var headerLen = 1 + headerName.Length + 1 + 2 + headerValue.Length; + + var totalLen = 12 + headerLen + payloadBytes.Length + 4; // prelude(12) + headers + payload + message_crc(4) + var message = new byte[totalLen]; + + // Prelude: total_length(4) + header_length(4) + prelude_crc(4) + BinaryPrimitives.WriteInt32BigEndian(message.AsSpan(0), totalLen); + BinaryPrimitives.WriteInt32BigEndian(message.AsSpan(4), headerLen); + var preludeCrc = Crc32(message.AsSpan(0, 8)); + BinaryPrimitives.WriteUInt32BigEndian(message.AsSpan(8), preludeCrc); + + // Headers + var offset = 12; + message[offset++] = (byte)headerName.Length; + headerName.CopyTo(message.AsSpan(offset)); + offset += headerName.Length; + message[offset++] = 7; // string type + BinaryPrimitives.WriteUInt16BigEndian(message.AsSpan(offset), (ushort)headerValue.Length); + offset += 2; + headerValue.CopyTo(message, offset); + offset += headerValue.Length; + + // Payload + payloadBytes.CopyTo(message, offset); + offset += payloadBytes.Length; + + // Message CRC (over everything except the last 4 bytes) + var messageCrc = Crc32(message.AsSpan(0, offset)); + BinaryPrimitives.WriteUInt32BigEndian(message.AsSpan(offset), messageCrc); + + return message; + } + + private static uint Crc32(ReadOnlySpan data) => + AwsEventStreamHelpers.CRC32.ComputeChecksum(data); + + [Fact] + public async Task ReadAsync_HandlesEventLargerThanBuffer() + { + // Build an event with a large payload that will exceed a small read buffer + var largeContent = new string('x', 2000); + var eventJson = + $"{{\"type\":\"content_block_delta\",\"delta\":{{\"text\":\"{largeContent}\"}}}}"; + var messageBytes = BuildEventStreamMessage("chunk", eventJson); + + using var stream = new MemoryStream(messageBytes); + var wrapper = new SseEventContentWrapper(stream); + var contentStream = await wrapper.ReadAsStreamAsync(); + + // Read with a deliberately small buffer (256 bytes) + var allData = new MemoryStream(); + var buffer = new byte[256]; + int bytesRead; + while ( + ( + bytesRead = await contentStream.ReadAsync( + buffer, + TestContext.Current.CancellationToken + ) + ) > 0 + ) + { + allData.Write(buffer, 0, bytesRead); + } + + var result = Encoding.UTF8.GetString(allData.ToArray()); + + // The result should be a valid SSE event + Assert.StartsWith("event:", result); + Assert.Contains("content_block_delta", result); + Assert.Contains(largeContent, result); + Assert.EndsWith("\n\n", result); + } + + [Fact] + public async Task ReadAsync_HandlesSmallEvent() + { + var eventJson = "{\"type\":\"ping\"}"; + var messageBytes = BuildEventStreamMessage("chunk", eventJson); + + using var stream = new MemoryStream(messageBytes); + var wrapper = new SseEventContentWrapper(stream); + var contentStream = await wrapper.ReadAsStreamAsync(); + + // Use a large buffer — should fit in one read + var buffer = new byte[8192]; + var bytesRead = await contentStream.ReadAsync( + buffer, + TestContext.Current.CancellationToken + ); + + var result = Encoding.UTF8.GetString(buffer, 0, bytesRead); + Assert.StartsWith("event:", result); + Assert.Contains("ping", result); + } +} diff --git a/src/Anthropic.Tests/SseTest.cs b/src/Anthropic.Tests/SseTest.cs index 07a963397..7b1a4d745 100644 --- a/src/Anthropic.Tests/SseTest.cs +++ b/src/Anthropic.Tests/SseTest.cs @@ -1,10 +1,12 @@ using System.Collections.Generic; +using System.Net; using System.Net.Http; using System.Text.Json; using System.Threading; using System.Threading.Tasks; using Anthropic.Core; using Anthropic.Exceptions; +using Anthropic.Models; namespace Anthropic.Tests; @@ -114,4 +116,107 @@ var message in Sse.Enumerate( Assert.Equal("SSE error returned from server: 'unspecified error'", exception.Message); } + + [Fact] + public void CreateApiException_ExtractsErrorType() + { + var body = + """{"type":"error","error":{"type":"invalid_request_error","message":"Bad request"}}"""; + var ex = AnthropicExceptionFactory.CreateApiException(HttpStatusCode.BadRequest, body); + + Assert.IsType(ex); + Assert.Equal(ErrorType.InvalidRequestError, ex.ErrorType); + } + + [Fact] + public void CreateApiException_NullErrorType_WhenMissing() + { + var body = """{"message":"something went wrong"}"""; + var ex = AnthropicExceptionFactory.CreateApiException(HttpStatusCode.BadRequest, body); + + Assert.Null(ex.ErrorType); + } + + [Fact] + public void CreateApiException_NullErrorType_WhenNotJson() + { + var ex = AnthropicExceptionFactory.CreateApiException( + HttpStatusCode.BadGateway, + "Bad Gateway" + ); + + Assert.Null(ex.ErrorType); + } + + [Fact] + public void ExtractErrorType_NullForUnknownType() + { + var body = """{"type":"error","error":{"type":"some_future_error","message":"Unknown"}}"""; + var result = AnthropicExceptionFactory.ExtractErrorType(body); + + Assert.Null(result); + } + + [Fact] + public async Task StreamingError_SseException_WithErrorType() + { + var sseData = + """{"type":"error","error":{"type":"overloaded_error","message":"Overloaded"}}"""; + var resp = new HttpResponseMessage() + { + Content = new StringContent($"event: error\ndata: {sseData}\n\n"), + }; + + var exception = await Assert.ThrowsAsync(async () => + { + await foreach ( + var message in Sse.Enumerate( + resp, + TestContext.Current.CancellationToken + ) + ) { } + }); + + Assert.Equal(ErrorType.OverloadedError, exception.ErrorType); + } + + [Fact] + public async Task StreamingError_NonJsonData_NullErrorType() + { + var resp = new HttpResponseMessage() + { + Content = new StringContent("event: error\ndata: ThrottlingException\n\n"), + }; + + var exception = await Assert.ThrowsAsync(async () => + { + await foreach ( + var message in Sse.Enumerate( + resp, + TestContext.Current.CancellationToken + ) + ) { } + }); + + Assert.Null(exception.ErrorType); + } + + [Fact] + public void ServiceException_CatchesBothHttpAndSseErrors() + { + // HTTP error is catchable as AnthropicServiceException + var body = + """{"type":"error","error":{"type":"rate_limit_error","message":"Rate limited"}}"""; + var httpEx = AnthropicExceptionFactory.CreateApiException((HttpStatusCode)429, body); + Assert.IsType(httpEx, exactMatch: false); + Assert.Equal(ErrorType.RateLimitError, httpEx.ErrorType); + + // SSE error is catchable as AnthropicServiceException + var sseEx = new AnthropicSseException("SSE error") + { + ErrorType = ErrorType.OverloadedError, + }; + Assert.IsType(sseEx, exactMatch: false); + Assert.Equal(ErrorType.OverloadedError, sseEx.ErrorType); + } } diff --git a/src/Anthropic/Anthropic.csproj b/src/Anthropic/Anthropic.csproj index b16b33ab0..c7aea9b4e 100644 --- a/src/Anthropic/Anthropic.csproj +++ b/src/Anthropic/Anthropic.csproj @@ -4,7 +4,7 @@ Anthropic C# Anthropic Anthropic - 12.9.0 + 12.10.0 true The official .NET library for the Anthropic API. Library @@ -15,9 +15,9 @@ - + - + diff --git a/src/Anthropic/AnthropicClientExtensions.cs b/src/Anthropic/AnthropicClientExtensions.cs index 32846abf4..095253e5f 100644 --- a/src/Anthropic/AnthropicClientExtensions.cs +++ b/src/Anthropic/AnthropicClientExtensions.cs @@ -1,17 +1,22 @@ using System; using System.Collections.Frozen; using System.Collections.Generic; +using System.Globalization; +using System.IO; using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; using System.Text; +using System.Text.Encodings.Web; using System.Text.Json; +using System.Text.Json.Nodes; using System.Threading; using System.Threading.Tasks; using Anthropic; using Anthropic.Core; using Anthropic.Models.Messages; +#pragma warning disable MEAI001 // [Experimental] APIs in Microsoft.Extensions.AI #pragma warning disable IDE0130 // Namespace does not match folder structure namespace Microsoft.Extensions.AI; @@ -82,6 +87,372 @@ public static AITool AsAITool(this ToolUnion tool) return new ToolUnionAITool(tool); } + /// Serializer options using relaxed encoding for description augmentation. + /// + /// Matches the behavior of JavaScript's JSON.stringify() and Python's json.dumps(), + /// which do not escape characters like +, ', or &. The default + /// would escape these (e.g., + → \u002B), + /// producing less readable descriptions for the model. + /// + private static readonly JsonSerializerOptions s_relaxedJsonOptions = new() + { + Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping, + }; + + /// Supported string formats for Anthropic structured outputs. + /// + /// Matches the TypeScript and Python SDK transform behavior: + /// + /// + /// + /// + /// + private static readonly HashSet s_supportedStringFormats = new(StringComparer.Ordinal) + { + "date-time", + "time", + "date", + "duration", + "email", + "hostname", + "uri", + "ipv4", + "ipv6", + "uuid", + }; + + /// Properties supported across all JSON Schema types. + /// + /// enum and const are included here but are NOT in the TypeScript/Python SDK whitelists. + /// They are deliberately preserved because MEAI generates enum for .NET enum types and both + /// keywords are natively supported by the Anthropic API. + /// + private static readonly HashSet s_supportedBaseSchemaProperties = new( + StringComparer.Ordinal + ) + { + "type", + "description", + "title", + "$ref", + "$defs", + "anyOf", + "allOf", + "enum", + "const", + }; + + /// Properties supported for object schemas. + private static readonly HashSet s_supportedObjectSchemaProperties = new( + s_supportedBaseSchemaProperties, + StringComparer.Ordinal + ) + { + "properties", + "required", + "additionalProperties", + }; + + /// Properties supported for string schemas. + private static readonly HashSet s_supportedStringSchemaProperties = new( + s_supportedBaseSchemaProperties, + StringComparer.Ordinal + ) + { + "format", + }; + + /// Properties supported for array schemas. + private static readonly HashSet s_supportedArraySchemaProperties = new( + s_supportedBaseSchemaProperties, + StringComparer.Ordinal + ) + { + "items", + "minItems", + }; + + /// + /// Gets a shared cache for JSON schema transformations for Anthropic's structured output features. + /// + /// + /// Transforms schemas using the same whitelist approach as the TypeScript and Python SDKs: + /// unsupported constraints are removed and appended to the description so the model + /// might still follow them. See: + /// + /// + /// + /// + /// + internal static AIJsonSchemaTransformCache JsonSchemaTransformCache { get; } = + new( + new AIJsonSchemaTransformOptions + { + DisallowAdditionalProperties = true, + TransformSchemaNode = static (ctx, schemaNode) => + { + if (schemaNode is not JsonObject schemaObj) + { + return schemaNode; + } + + // Convert oneOf to anyOf, matching TS/Python SDK behavior. + // The Anthropic API documents anyOf but not oneOf for union types. + if ( + schemaObj.TryGetPropertyValue("oneOf", out JsonNode? oneOfNode) + && oneOfNode is not null + ) + { + schemaObj.Remove("oneOf"); + schemaObj["anyOf"] = oneOfNode; + } + + // Determine the schema type for type-specific handling. + string? type = + schemaObj.TryGetPropertyValue("type", out JsonNode? typeNode) + && typeNode is JsonValue + ? typeNode.GetValue() + : null; + + List>? removed = null; + + // String format: only supported formats are kept. + if ( + type == "string" + && schemaObj.TryGetPropertyValue("format", out JsonNode? formatNode) + && formatNode?.GetValue() is string format + && !s_supportedStringFormats.Contains(format) + ) + { + string serialized = formatNode!.ToJsonString(s_relaxedJsonOptions); + schemaObj.Remove("format"); + (removed ??= []).Add(new("format", serialized)); + } + + // Array minItems: only 0 and 1 are directly supported. + if ( + type == "array" + && schemaObj.TryGetPropertyValue("minItems", out JsonNode? minItemsNode) + && minItemsNode is JsonValue minItemsJsonValue + && minItemsJsonValue.TryGetValue(out int minItems) + && minItems is not (0 or 1) + ) + { + string serialized = minItemsNode.ToJsonString(s_relaxedJsonOptions); + schemaObj.Remove("minItems"); + (removed ??= []).Add(new("minItems", serialized)); + } + + // Remove all properties not in the supported set for this schema type. + HashSet supported = type switch + { + "object" => s_supportedObjectSchemaProperties, + "string" => s_supportedStringSchemaProperties, + "array" => s_supportedArraySchemaProperties, + _ => s_supportedBaseSchemaProperties, + }; + + foreach (KeyValuePair prop in schemaObj.ToArray()) + { + if (!supported.Contains(prop.Key)) + { + string serialized = + prop.Value?.ToJsonString(s_relaxedJsonOptions) ?? "null"; + schemaObj.Remove(prop.Key); + (removed ??= []).Add(new(prop.Key, serialized)); + } + } + + // Append removed constraints to description so the model might + // still follow them. + if (removed is { Count: > 0 }) + { + string? existing = schemaObj.TryGetPropertyValue( + "description", + out JsonNode? descNode + ) + ? descNode?.GetValue() + : null; + + string constraintInfo = + "{" + + string.Join(", ", removed.Select(c => $"{c.Key}: {c.Value}")) + + "}"; + + schemaObj["description"] = existing is not null + ? $"{existing}\n\n{constraintInfo}" + : constraintInfo; + } + + return schemaNode; + }, + } + ); + + /// + /// Gets a shared set of header data to include in requests from the implementation for Anthropic. + /// + internal static Dictionary MeaiHeaderData { get; } = + new() { ["User-Agent"] = JsonSerializer.SerializeToElement(CreateMeaiUserAgentValue()) }; + + private static string CreateMeaiUserAgentValue() + { + const string Name = "MEAI"; + + if ( + typeof(IChatClient) + .Assembly.GetCustomAttribute() + ?.InformationalVersion + is string version + ) + { + int pos = version.IndexOf('+'); + if (pos >= 0) + { + version = version.Substring(0, pos); + } + + if (version.Length > 0) + { + return $"{Name}/{version}"; + } + } + + return Name; + } + + // TODO: When targeting a version of .NET that exposes MediaTypeMap (Microsoft.Extensions.AI), + // these dictionaries can be replaced with MediaTypeMap.GetMediaType / MediaTypeMap.GetExtension. + + /// + /// Maps file extensions (with leading dot) to MIME types. + /// Grouped by category, sorted alphabetically by extension within each group. + /// + private static readonly Dictionary s_extensionToMediaType = new( + StringComparer.OrdinalIgnoreCase + ) + { + // Archives + [".7z"] = "application/x-7z-compressed", + [".gz"] = "application/gzip", + [".rar"] = "application/vnd.rar", + [".tar"] = "application/x-tar", + [".zip"] = "application/zip", + + // Audio + [".mp3"] = "audio/mpeg", + [".ogg"] = "audio/ogg", + [".wav"] = "audio/wav", + + // Code + [".c"] = "text/x-c", + [".cpp"] = "text/x-c++", + [".cs"] = "text/x-csharp", + [".css"] = "text/css", + [".go"] = "text/x-go", + [".java"] = "text/x-java-source", + [".js"] = "text/javascript", + [".jsx"] = "text/javascript", + [".py"] = "text/x-python", + [".r"] = "text/x-r", + [".rb"] = "text/x-ruby", + [".rs"] = "text/x-rust", + [".sh"] = "application/x-sh", + [".sql"] = "application/sql", + [".swift"] = "text/x-swift", + [".ts"] = "text/typescript", + [".tsx"] = "text/typescript", + [".wasm"] = "application/wasm", + + // Data + [".csv"] = "text/csv", + [".json"] = "application/json", + [".jsonl"] = "application/jsonl", + [".tsv"] = "text/tab-separated-values", + [".xml"] = "application/xml", + [".yaml"] = "application/yaml", + [".yml"] = "application/yaml", + + // Documents + [".doc"] = "application/msword", + [".docx"] = "application/vnd.openxmlformats-officedocument.wordprocessingml.document", + [".epub"] = "application/epub+zip", + [".odt"] = "application/vnd.oasis.opendocument.text", + [".pdf"] = "application/pdf", + [".pptx"] = "application/vnd.openxmlformats-officedocument.presentationml.presentation", + [".rtf"] = "application/rtf", + [".xls"] = "application/vnd.ms-excel", + [".xlsx"] = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", + + // Images + [".bmp"] = "image/bmp", + [".gif"] = "image/gif", + [".ico"] = "image/x-icon", + [".jpeg"] = "image/jpeg", + [".jpg"] = "image/jpeg", + [".png"] = "image/png", + [".svg"] = "image/svg+xml", + [".tif"] = "image/tiff", + [".tiff"] = "image/tiff", + [".webp"] = "image/webp", + + // Text/Markup + [".htm"] = "text/html", + [".html"] = "text/html", + [".md"] = "text/markdown", + [".txt"] = "text/plain", + + // Video + [".mp4"] = "video/mp4", + [".webm"] = "video/webm", + }; + + /// + /// Reverse mapping from MIME type to a preferred file extension (with leading dot). + /// Built from , preferring the last extension per media type + /// (which, given the alphabetical ordering, favors longer canonical forms like .html over .htm). + /// + private static readonly Dictionary s_mediaTypeToExtension = + BuildMediaTypeToExtension(); + + private static Dictionary BuildMediaTypeToExtension() + { + Dictionary result = new(StringComparer.OrdinalIgnoreCase); + foreach (KeyValuePair kvp in s_extensionToMediaType) + { + result[kvp.Value] = kvp.Key; + } + + // Override with preferred extensions where multiple map to the same media type. + result["image/jpeg"] = ".jpg"; + result["application/yaml"] = ".yaml"; + result["text/javascript"] = ".js"; + result["text/typescript"] = ".ts"; + + return result; + } + + /// Infers a media type from the file extension in a URL or path, defaulting to application/octet-stream. + internal static string InferMediaTypeFromExtension(string urlOrPath) + { + if ( + Path.GetExtension(urlOrPath) is { Length: > 0 } ext + && s_extensionToMediaType.TryGetValue(ext, out string? mediaType) + ) + { + return mediaType; + } + + return "application/octet-stream"; + } + + /// Infers a file extension (including the leading dot) from a media type, defaulting to empty string. + internal static string InferExtensionFromMediaType(string? mediaType) => + mediaType is not null + && s_mediaTypeToExtension.TryGetValue(mediaType, out string? extension) + ? extension + : ""; + private sealed class AnthropicChatClient( IAnthropicClient anthropicClient, string? defaultModelId, @@ -89,47 +460,12 @@ private sealed class AnthropicChatClient( ) : IChatClient { private const int DefaultMaxTokens = 1024; - private const string MeaiUserAgentHeaderKey = "User-Agent"; - - private static readonly FrozenDictionary s_meaiHeaderData = - new Dictionary - { - [MeaiUserAgentHeaderKey] = JsonSerializer.SerializeToElement( - CreateMeaiUserAgentValue() - ), - }.ToFrozenDictionary(); private readonly IAnthropicClient _anthropicClient = anthropicClient; private readonly string? _defaultModelId = defaultModelId; private readonly int _defaultMaxTokens = defaultMaxTokens ?? DefaultMaxTokens; private ChatClientMetadata? _metadata; - private static string CreateMeaiUserAgentValue() - { - const string Name = "MEAI"; - - if ( - typeof(IChatClient) - .Assembly.GetCustomAttribute() - ?.InformationalVersion - is string version - ) - { - int pos = version.IndexOf('+'); - if (pos >= 0) - { - version = version.Substring(0, pos); - } - - if (version.Length > 0) - { - return $"{Name}/{version}"; - } - } - - return Name; - } - /// void IDisposable.Dispose() { } @@ -190,12 +526,30 @@ out List? systemMessages options ); - var createResult = await _anthropicClient.Messages.Create( - createParams, - cancellationToken - ); + // When thinking is enabled, the auto-increased max_tokens may exceed the + // client-side non-streaming token limit. Use a streaming-level timeout to + // bypass that check while still providing appropriate timeout behavior. + var messageService = _anthropicClient.Messages; + if (createParams.Thinking is ThinkingConfigParam { Value: ThinkingConfigEnabled }) + { + messageService = messageService.WithOptions(opts => + opts with + { + Timeout = ClientOptions.TimeoutFromMaxTokens( + createParams.MaxTokens, + isStreaming: true, + createParams.Model + ), + } + ); + } + + var createResult = await messageService.Create(createParams, cancellationToken); - ChatMessage m = new(ChatRole.Assistant, [.. createResult.Content.Select(ToAIContent)]) + ChatMessage m = new( + ChatRole.Assistant, + [.. createResult.Content.Select(c => ContentBlockValueToAIContent(c.Value))] + ) { CreatedAt = DateTimeOffset.UtcNow, MessageId = createResult.ID, @@ -322,6 +676,21 @@ var createResult in _anthropicClient Name = toolUse.Name, }; break; + + case ServerToolUseBlock: + case WebSearchToolResultBlock: + case WebFetchToolResultBlock: + case CodeExecutionToolResultBlock: + case BashCodeExecutionToolResultBlock: + case TextEditorCodeExecutionToolResultBlock: + case ToolSearchToolResultBlock: + case ContainerUploadBlock: + contents.Add( + ContentBlockValueToAIContent( + contentBlockStart.ContentBlock.Value + ) + ); + break; } break; @@ -368,6 +737,19 @@ out StreamingFunctionData? functionData } ); break; + + case CitationsDelta citationsDelta: + if (ToAIAnnotation(citationsDelta.Citation) is { } streamAnnotation) + { + contents.Add( + new TextContent(string.Empty) + { + RawRepresentation = citationsDelta, + Annotations = [streamAnnotation], + } + ); + } + break; } break; @@ -747,11 +1129,6 @@ when string.Equals( ); } - if (messageParams.Count == 0) - { - messageParams.Add(new() { Role = Role.User, Content = new("\u200b") }); // zero-width space - } - return messageParams; } @@ -765,12 +1142,21 @@ private MessageCreateParams GetMessageCreateParams( // or with only the required properties set. MessageCreateParams? createParams = options?.RawRepresentationFactory?.Invoke(this) as MessageCreateParams; + + // Anthropic requires at least one message. If no messages were provided either directly + // or via the RawRepresentationFactory, add an empty message. + var createParamsOriginalMessages = createParams?.Messages; + if (createParamsOriginalMessages is not { Count: > 0 } && messages.Count == 0) + { + messages.Add(new MessageParam() { Role = Role.User, Content = new("\u200b") }); // zero-width space + } + if (createParams is not null) { // Merge any messages preconfigured on the params with the ones provided to the IChatClient. createParams = createParams with { - Messages = [.. createParams.Messages, .. messages], + Messages = [.. createParamsOriginalMessages ?? [], .. messages], }; } else @@ -796,6 +1182,47 @@ private MessageCreateParams GetMessageCreateParams( (systemMessages ??= []).Add(new TextBlockParam() { Text = instructions }); } + if ( + createParams.OutputConfig is null + && options.ResponseFormat is { } responseFormat + ) + { + switch (responseFormat) + { + case ChatResponseFormatJson formatJson when formatJson.Schema is not null: + JsonElement schema = JsonSchemaTransformCache + .GetOrCreateTransformedSchema(formatJson) + .GetValueOrDefault(); + if ( + schema.TryGetProperty("properties", out JsonElement properties) + && properties.ValueKind is JsonValueKind.Object + && schema.TryGetProperty("required", out JsonElement required) + && required.ValueKind is JsonValueKind.Array + ) + { + createParams = createParams with + { + OutputConfig = new OutputConfig() + { + Format = new JsonOutputFormat() + { + Schema = new Dictionary + { + ["type"] = JsonElement.Parse("\"object\""), + ["properties"] = properties, + ["required"] = required, + ["additionalProperties"] = JsonElement.Parse( + "false" + ), + }, + }, + }, + }; + } + break; + } + } + if (options.StopSequences is { Count: > 0 } stopSequences) { createParams = createParams.StopSequences is { } existingSequences @@ -836,44 +1263,14 @@ private MessageCreateParams GetMessageCreateParams( break; case AIFunctionDeclaration af: - Dictionary properties = []; - List required = []; - JsonElement inputSchema = af.JsonSchema; + JsonElement inputSchema = + JsonSchemaTransformCache.GetOrCreateTransformedSchema(af); + Dictionary schemaData = []; if (inputSchema.ValueKind is JsonValueKind.Object) { - if ( - inputSchema.TryGetProperty( - "properties", - out JsonElement propsElement - ) - && propsElement.ValueKind is JsonValueKind.Object - ) + foreach (JsonProperty p in inputSchema.EnumerateObject()) { - foreach (JsonProperty p in propsElement.EnumerateObject()) - { - properties[p.Name] = p.Value; - } - } - - if ( - inputSchema.TryGetProperty( - "required", - out JsonElement reqElement - ) - && reqElement.ValueKind is JsonValueKind.Array - ) - { - foreach (JsonElement r in reqElement.EnumerateArray()) - { - if ( - r.ValueKind is JsonValueKind.String - && r.GetString() is { } s - && !string.IsNullOrWhiteSpace(s) - ) - { - required.Add(s); - } - } + schemaData[p.Name] = p.Value; } } @@ -882,18 +1279,36 @@ r.ValueKind is JsonValueKind.String { Name = af.Name, Description = af.Description, - InputSchema = new InputSchema() - { - Properties = properties, - Required = required, - }, + InputSchema = new InputSchema(schemaData), + DeferLoading = GetValue( + af, + nameof(Tool.DeferLoading) + ), + Strict = GetValue(af, nameof(Tool.Strict)), + InputExamples = GetValue< + List> + >(af, nameof(Tool.InputExamples)), + AllowedCallers = GetValue< + List> + >(af, nameof(Tool.AllowedCallers)), } ); + + static T? GetValue(AIFunctionDeclaration af, string name) => + af.AdditionalProperties?.TryGetValue(name, out var value) + is true + && value is T tValue + ? tValue + : default; break; case HostedWebSearchTool: (createdTools ??= []).Add(new WebSearchTool20250305()); break; + + case HostedCodeInterpreterTool: + (createdTools ??= []).Add(new CodeExecutionTool20250825()); + break; } } @@ -923,6 +1338,79 @@ toolMode is AutoChatToolMode createParams = createParams with { ToolChoice = toolChoice }; } } + + if (createParams.Thinking is null && options.Reasoning is { } reasoning) + { + ThinkingConfigParam? thinkingConfig = null; + if (reasoning.Effort is ReasoningEffort.None) + { + thinkingConfig = new(new ThinkingConfigDisabled()); + } + else + { + long? budgetTokens = reasoning.Effort switch + { + ReasoningEffort.Low => 1024, + ReasoningEffort.Medium => 8192, + ReasoningEffort.High => 16384, + ReasoningEffort.ExtraHigh => 32768, + _ => null, + }; + + if (budgetTokens is { } budget) + { + // Anthropic requires thinking budget >= 1024 and < max tokens. + bool autoIncreaseMaxTokens = false; + if (createParams.MaxTokens <= budget) + { + if (options.MaxOutputTokens is not null) + { + // Caller explicitly set MaxOutputTokens. Clamp the budget to fit, + // and skip thinking if it can't meet the minimum. + budget = createParams.MaxTokens - 1; + } + else + { + autoIncreaseMaxTokens = true; + } + } + + if (budget >= 1024) + { + if (autoIncreaseMaxTokens) + { + // Caller didn't set MaxOutputTokens. Auto-increase max_tokens + // to accommodate the thinking budget plus room for output. + createParams = createParams with + { + MaxTokens = budget + _defaultMaxTokens, + }; + } + + thinkingConfig = new(new ThinkingConfigEnabled(budget)); + } + } + } + + if ( + thinkingConfig is not null + && reasoning.Output is ReasoningOutput.None + && thinkingConfig.Value is ThinkingConfigEnabled enabled + ) + { + thinkingConfig = new( + enabled with + { + Display = ThinkingConfigEnabledDisplay.Omitted, + } + ); + } + + if (thinkingConfig is not null) + { + createParams = createParams with { Thinking = thinkingConfig }; + } + } } if (systemMessages is not null) @@ -948,7 +1436,7 @@ toolMode is AutoChatToolMode private static MessageCreateParams AddMeaiHeaders(MessageCreateParams createParams) { - Dictionary mergedHeaders = new(s_meaiHeaderData); + Dictionary mergedHeaders = new(MeaiHeaderData); foreach (var header in createParams.RawHeaderData) { @@ -1014,6 +1502,12 @@ private static UsageDetails ToUsageDetails( cacheCreationInputTokens.Value; } + if (serverToolUsage?.WebFetchRequests is > 0) + { + (usageDetails.AdditionalCounts ??= [])[nameof(ServerToolUsage.WebFetchRequests)] = + serverToolUsage.WebFetchRequests; + } + if (serverToolUsage?.WebSearchRequests is > 0) { (usageDetails.AdditionalCounts ??= [])[nameof(ServerToolUsage.WebSearchRequests)] = @@ -1036,9 +1530,9 @@ private static UsageDetails ToUsageDetails( _ => ChatFinishReason.Stop, }; - private static AIContent ToAIContent(ContentBlock block) + private static AIContent ContentBlockValueToAIContent(object? blockValue) { - switch (block.Value) + switch (blockValue) { case TextBlock text: TextContent tc = new(text.Text) { RawRepresentation = text }; @@ -1085,12 +1579,376 @@ .. text.Citations.Select(ToAIAnnotation).OfType(), RawRepresentation = toolUse, }; + case CodeExecutionToolResultBlock ce: + { + CodeInterpreterToolResultContent c = new(ce.ToolUseID) + { + RawRepresentation = ce, + }; + + if (ce.Content.TryPickError(out var ceError)) + { + (c.Outputs ??= []).Add( + new ErrorContent(null) + { + ErrorCode = ceError.ErrorCode.Value().ToString(), + } + ); + } + + if (ce.Content.TryPickResultBlock(out var ceOutput)) + { + if (!string.IsNullOrWhiteSpace(ceOutput.Stdout)) + { + (c.Outputs ??= []).Add(new TextContent(ceOutput.Stdout)); + } + + if (!string.IsNullOrWhiteSpace(ceOutput.Stderr) || ceOutput.ReturnCode != 0) + { + (c.Outputs ??= []).Add( + new ErrorContent(ceOutput.Stderr) + { + ErrorCode = ceOutput.ReturnCode.ToString( + CultureInfo.InvariantCulture + ), + } + ); + } + + if (ceOutput.Content is { Count: > 0 }) + { + foreach (var ceOutputContent in ceOutput.Content) + { + (c.Outputs ??= []).Add( + new HostedFileContent(ceOutputContent.FileID) + ); + } + } + } + + if (ce.Content.TryPickEncryptedCodeExecutionResultBlock(out var ceEncrypted)) + { + // Unlike with the non-encrypted case above, we skip Stdout, as here it's encrypted. + + if ( + !string.IsNullOrWhiteSpace(ceEncrypted.Stderr) + || ceEncrypted.ReturnCode != 0 + ) + { + (c.Outputs ??= []).Add( + new ErrorContent(ceEncrypted.Stderr) + { + ErrorCode = ceEncrypted.ReturnCode.ToString( + CultureInfo.InvariantCulture + ), + } + ); + } + + if (ceEncrypted.Content is { Count: > 0 }) + { + foreach (var ceOutputContent in ceEncrypted.Content) + { + (c.Outputs ??= []).Add( + new HostedFileContent(ceOutputContent.FileID) + ); + } + } + } + + return c; + } + + case BashCodeExecutionToolResultBlock ce: + { + CodeInterpreterToolResultContent c = new(ce.ToolUseID) + { + RawRepresentation = ce, + }; + + if (ce.Content.TryPickBashCodeExecutionToolResultError(out var ceError)) + { + (c.Outputs ??= []).Add( + new ErrorContent(null) + { + ErrorCode = ceError.ErrorCode.Value().ToString(), + } + ); + } + + if (ce.Content.TryPickBashCodeExecutionResultBlock(out var ceOutput)) + { + if (!string.IsNullOrWhiteSpace(ceOutput.Stdout)) + { + (c.Outputs ??= []).Add(new TextContent(ceOutput.Stdout)); + } + + if (!string.IsNullOrWhiteSpace(ceOutput.Stderr) || ceOutput.ReturnCode != 0) + { + (c.Outputs ??= []).Add( + new ErrorContent(ceOutput.Stderr) + { + ErrorCode = ceOutput.ReturnCode.ToString( + CultureInfo.InvariantCulture + ), + } + ); + } + + if (ceOutput.Content is { Count: > 0 }) + { + foreach (var ceOutputContent in ceOutput.Content) + { + (c.Outputs ??= []).Add( + new HostedFileContent(ceOutputContent.FileID) + ); + } + } + } + + return c; + } + + case ServerToolUseBlock serverToolUse: + { + Name nameValue = serverToolUse.Name.Value(); + switch (nameValue) + { + case Name.WebSearch: + case Name.WebFetch: + WebSearchToolCallContent wsc = new(serverToolUse.ID) + { + RawRepresentation = serverToolUse, + }; + if ( + serverToolUse.Input?.TryGetValue( + "query", + out JsonElement queryElement + ) == true + && queryElement.ValueKind == JsonValueKind.String + ) + { + (wsc.Queries ??= []).Add(queryElement.GetString()!); + } + + return wsc; + + case Name.CodeExecution: + case Name.BashCodeExecution: + case Name.TextEditorCodeExecution: + CodeInterpreterToolCallContent cic = new(serverToolUse.ID) + { + RawRepresentation = serverToolUse, + }; + + // CodeExecution (legacy Python) uses "code"; Bash/TextEditor use "command". + if ( + ( + serverToolUse.Input?.TryGetValue( + "code", + out JsonElement codeElement + ) == true + || serverToolUse.Input?.TryGetValue("command", out codeElement) + == true + ) + && codeElement.ValueKind == JsonValueKind.String + ) + { + string code = codeElement.GetString()!; + string mediaType = + nameValue == Name.CodeExecution ? "text/x-python" + : nameValue == Name.BashCodeExecution ? "application/x-sh" + : "text/plain"; + (cic.Inputs ??= []).Add( + new DataContent(Encoding.UTF8.GetBytes(code), mediaType) + ); + } + + return cic; + + default: + return new ToolCallContent(serverToolUse.ID) + { + RawRepresentation = serverToolUse, + }; + } + } + + case WebSearchToolResultBlock wsResult: + { + WebSearchToolResultContent wsrc = new(wsResult.ToolUseID) + { + RawRepresentation = wsResult, + }; + + if (wsResult.Content.TryPickWebSearchResultBlocks(out var searchResults)) + { + foreach (var result in searchResults) + { + (wsrc.Results ??= []).Add( + new UriContent(result.Url, InferMediaTypeFromExtension(result.Url)) + { + RawRepresentation = result, + } + ); + } + } + else if (wsResult.Content.TryPickError(out var wsError)) + { + (wsrc.Results ??= []).Add( + new ErrorContent(null) + { + ErrorCode = wsError.ErrorCode.Value().ToString(), + RawRepresentation = wsError, + } + ); + } + + return wsrc; + } + + case WebFetchToolResultBlock wfResult: + { + WebSearchToolResultContent wfrc = new(wfResult.ToolUseID) + { + RawRepresentation = wfResult, + }; + + if (wfResult.Content.TryPickWebFetchBlock(out var fetchBlock)) + { + (wfrc.Results ??= []).Add( + new UriContent( + fetchBlock.Url, + InferMediaTypeFromExtension(fetchBlock.Url) + ) + { + RawRepresentation = fetchBlock, + } + ); + } + else if (wfResult.Content.TryPickWebFetchToolResultErrorBlock(out var wfError)) + { + (wfrc.Results ??= []).Add( + new ErrorContent(null) + { + ErrorCode = wfError.ErrorCode.Value().ToString(), + RawRepresentation = wfError, + } + ); + } + + return wfrc; + } + + case TextEditorCodeExecutionToolResultBlock te: + { + CodeInterpreterToolResultContent c = new(te.ToolUseID) + { + RawRepresentation = te, + }; + + if (te.Content.TryPickTextEditorCodeExecutionToolResultError(out var teError)) + { + (c.Outputs ??= []).Add( + new ErrorContent(teError.ErrorMessage) + { + ErrorCode = teError.ErrorCode.Value().ToString(), + RawRepresentation = teError, + } + ); + } + else if ( + te.Content.TryPickTextEditorCodeExecutionViewResultBlock(out var viewResult) + ) + { + (c.Outputs ??= []).Add( + new TextContent(viewResult.Content) { RawRepresentation = viewResult } + ); + } + else if ( + te.Content.TryPickTextEditorCodeExecutionCreateResultBlock( + out var createResult + ) + ) + { + (c.Outputs ??= []).Add( + new TextContent( + createResult.IsFileUpdate ? "File updated" : "File created" + ) + { + RawRepresentation = createResult, + } + ); + } + else if ( + te.Content.TryPickTextEditorCodeExecutionStrReplaceResultBlock( + out var replaceResult + ) + ) + { + (c.Outputs ??= []).Add( + new TextContent( + replaceResult.Lines is { Count: > 0 } + ? string.Join("\n", replaceResult.Lines) + : "String replacement applied" + ) + { + RawRepresentation = replaceResult, + } + ); + } + + return c; + } + + case ToolSearchToolResultBlock ts: + return new ToolResultContent(ts.ToolUseID) { RawRepresentation = ts }; + + case ContainerUploadBlock containerUpload: + return new HostedFileContent(containerUpload.FileID) + { + RawRepresentation = containerUpload, + }; + default: - return new AIContent() { RawRepresentation = block.Value }; + return new AIContent() { RawRepresentation = blockValue }; + } + } + + private static CitationAnnotation? ToAIAnnotation(TextCitation citation) + { + CitationAnnotation annotation = new() + { + Title = citation.Title ?? citation.DocumentTitle, + Snippet = citation.CitedText, + FileId = citation.FileID, + }; + + if (citation.TryPickCitationsWebSearchResultLocation(out var webSearchLocation)) + { + annotation.Url = Uri.TryCreate( + webSearchLocation.Url, + UriKind.Absolute, + out Uri? url + ) + ? url + : null; } + else if (citation.TryPickCitationsSearchResultLocation(out var searchLocation)) + { + annotation.Url = Uri.TryCreate( + searchLocation.Source, + UriKind.Absolute, + out Uri? url + ) + ? url + : null; + } + + return annotation; } - private static AIAnnotation? ToAIAnnotation(TextCitation citation) + private static CitationAnnotation? ToAIAnnotation(Citation citation) { CitationAnnotation annotation = new() { diff --git a/src/Anthropic/CHANGELOG.md b/src/Anthropic/CHANGELOG.md index a42db2e57..130e4ff55 100644 --- a/src/Anthropic/CHANGELOG.md +++ b/src/Anthropic/CHANGELOG.md @@ -1,5 +1,26 @@ # Changelog +## 12.10.0 (2026-03-31) + +Full Changelog: [Anthropic-v12.9.0...Anthropic-v12.10.0](https://github.com/anthropics/anthropic-sdk-csharp/compare/Anthropic-v12.9.0...Anthropic-v12.10.0) + +### Features + +* add ErrorType property to API error exceptions ([#760](https://github.com/anthropics/anthropic-sdk-csharp/issues/760)) ([8f63a96](https://github.com/anthropics/anthropic-sdk-csharp/commit/8f63a9699edb0407d6abca37ff1fa3b9cec9fadd)) +* **api:** GA thinking-display-setting ([b0f24aa](https://github.com/anthropics/anthropic-sdk-csharp/commit/b0f24aa6791c697d25b55ab7ca29b31e7aa121b2)) +* **api:** manual updates ([e093d70](https://github.com/anthropics/anthropic-sdk-csharp/commit/e093d702f21caa56a275e420b8226d77f7f944d5)) +* **api:** manual updates ([c9d41c1](https://github.com/anthropics/anthropic-sdk-csharp/commit/c9d41c1baf3a47acefc6972c6283d27b5a12d8ca)) +* **client:** update to M.E.AI.Abstractions 10.4.0 and update with latest MEAI/Anthropic features ([#118](https://github.com/anthropics/anthropic-sdk-csharp/issues/118)) ([81ab1b8](https://github.com/anthropics/anthropic-sdk-csharp/commit/81ab1b8b59e9fd38fba438e9987040fccc7fa08e)) + + +### Bug Fixes + +* **client:** allow cancelling when enumerating over an http response ([d3e2312](https://github.com/anthropics/anthropic-sdk-csharp/commit/d3e2312ddfc187f12c32fc4d85762f962954a59c)) +* **client:** don't overzealously validate union variants when deserializing ([1178915](https://github.com/anthropics/anthropic-sdk-csharp/commit/1178915fc59a6b2810bf89c0d894aadec97d60b8)) +* **client:** handle empty messages properly in IChatClient when raw representation has messages ([#144](https://github.com/anthropics/anthropic-sdk-csharp/issues/144)) ([5268a2c](https://github.com/anthropics/anthropic-sdk-csharp/commit/5268a2cd1106f1943e510d7f4632ba5d477b0d4c)) +* **client:** handle path params correctly in `FromRawUnchecked` ([9afe664](https://github.com/anthropics/anthropic-sdk-csharp/commit/9afe664c3874ba05618c48caf4e2dbb581c45c23)) +* **client:** handle root bodies in requests properly ([56ab27b](https://github.com/anthropics/anthropic-sdk-csharp/commit/56ab27b439fe34febf4e1be695c54fd239d90dae)) + ## 12.9.0 (2026-03-16) Full Changelog: [Anthropic-v12.8.0...Anthropic-v12.9.0](https://github.com/anthropics/anthropic-sdk-csharp/compare/Anthropic-v12.8.0...Anthropic-v12.9.0) diff --git a/src/Anthropic/Core/HttpResponse.cs b/src/Anthropic/Core/HttpResponse.cs index 921708ba2..fd61e6d75 100644 --- a/src/Anthropic/Core/HttpResponse.cs +++ b/src/Anthropic/Core/HttpResponse.cs @@ -5,6 +5,7 @@ using System.Linq; using System.Net; using System.Net.Http; +using System.Runtime.CompilerServices; using System.Text.Json; using System.Threading.Tasks; using Anthropic.Exceptions; @@ -176,12 +177,17 @@ internal StreamingHttpResponse( this.CancellationToken = response.CancellationToken; } - public IAsyncEnumerable Enumerate(Threading::CancellationToken cancellationToken = default) + public async IAsyncEnumerable Enumerate( + [EnumeratorCancellationAttribute] Threading::CancellationToken cancellationToken = default + ) { using var cts = Threading::CancellationTokenSource.CreateLinkedTokenSource( this.CancellationToken, cancellationToken ); - return this._enumerate(cts.Token); + await foreach (var item in this._enumerate(cts.Token)) + { + yield return item; + } } } diff --git a/src/Anthropic/Core/JsonDictionary.cs b/src/Anthropic/Core/JsonDictionary.cs index 4f218f5ee..a25827871 100644 --- a/src/Anthropic/Core/JsonDictionary.cs +++ b/src/Anthropic/Core/JsonDictionary.cs @@ -94,20 +94,7 @@ public T GetNotNullClass(string key) { throw new AnthropicInvalidDataException($"'{key}' cannot be absent"); } - T deserialized; - try - { - deserialized = - JsonSerializer.Deserialize(element, ModelBase.SerializerOptions) - ?? throw new AnthropicInvalidDataException($"'{key}' cannot be null"); - } - catch (JsonException e) - { - throw new AnthropicInvalidDataException( - $"'{key}' must be of type {typeof(T).FullName}", - e - ); - } + T deserialized = WrappedJsonSerializer.GetNotNullClass(element, key); _deserializedData[key] = deserialized; return deserialized; } @@ -123,20 +110,7 @@ public T GetNotNullStruct(string key) { throw new AnthropicInvalidDataException($"'{key}' cannot be absent"); } - T deserialized; - try - { - deserialized = - JsonSerializer.Deserialize(element, ModelBase.SerializerOptions) - ?? throw new AnthropicInvalidDataException($"'{key}' cannot be null"); - } - catch (JsonException e) - { - throw new AnthropicInvalidDataException( - $"'{key}' must be of type {typeof(T).FullName}", - e - ); - } + T deserialized = WrappedJsonSerializer.GetNotNullStruct(element, key); _deserializedData[key] = deserialized; return deserialized; } @@ -153,18 +127,7 @@ public T GetNotNullStruct(string key) _deserializedData[key] = null; return null; } - T? deserialized; - try - { - deserialized = JsonSerializer.Deserialize(element, ModelBase.SerializerOptions); - } - catch (JsonException e) - { - throw new AnthropicInvalidDataException( - $"'{key}' must be of type {typeof(T).FullName}", - e - ); - } + T? deserialized = WrappedJsonSerializer.GetNullableClass(element, key); _deserializedData[key] = deserialized; return deserialized; } @@ -181,18 +144,7 @@ public T GetNotNullStruct(string key) _deserializedData[key] = null; return null; } - T? deserialized; - try - { - deserialized = JsonSerializer.Deserialize(element, ModelBase.SerializerOptions); - } - catch (JsonException e) - { - throw new AnthropicInvalidDataException( - $"'{key}' must be of type {typeof(T).FullName}", - e - ); - } + T? deserialized = WrappedJsonSerializer.GetNullableStruct(element, key); _deserializedData[key] = deserialized; return deserialized; } diff --git a/src/Anthropic/Core/ModelBase.cs b/src/Anthropic/Core/ModelBase.cs index b72394a74..3d6171e83 100644 --- a/src/Anthropic/Core/ModelBase.cs +++ b/src/Anthropic/Core/ModelBase.cs @@ -1,5 +1,6 @@ using System.Text.Json; using Anthropic.Exceptions; +using Anthropic.Models; using Anthropic.Models.Beta; using Anthropic.Models.Messages; using Batches = Anthropic.Models.Messages.Batches; @@ -26,6 +27,7 @@ protected ModelBase(ModelBase modelBase) Converters = { new FrozenDictionaryConverterFactory(), + new ApiEnumConverter(), new ApiEnumConverter(), new ApiEnumConverter(), new ApiEnumConverter(), @@ -43,6 +45,8 @@ protected ModelBase(ModelBase modelBase) new ApiEnumConverter(), new ApiEnumConverter(), new ApiEnumConverter(), + new ApiEnumConverter(), + new ApiEnumConverter(), new ApiEnumConverter(), new ApiEnumConverter(), new ApiEnumConverter(), @@ -57,6 +61,7 @@ protected ModelBase(ModelBase modelBase) new ApiEnumConverter(), new ApiEnumConverter(), new ApiEnumConverter(), + new ApiEnumConverter(), new ApiEnumConverter(), new ApiEnumConverter(), new ApiEnumConverter(), @@ -97,6 +102,8 @@ protected ModelBase(ModelBase modelBase) string, Messages::BetaTextEditorCodeExecutionViewResultBlockParamFileType >(), + new ApiEnumConverter(), + new ApiEnumConverter(), new ApiEnumConverter(), new ApiEnumConverter(), new ApiEnumConverter(), @@ -118,6 +125,7 @@ protected ModelBase(ModelBase modelBase) new ApiEnumConverter(), new ApiEnumConverter(), new ApiEnumConverter(), + new ApiEnumConverter(), new ApiEnumConverter(), new ApiEnumConverter(), new ApiEnumConverter(), diff --git a/src/Anthropic/Core/MultipartJsonDictionary.cs b/src/Anthropic/Core/MultipartJsonDictionary.cs index 8aed77ec5..05e6934c6 100644 --- a/src/Anthropic/Core/MultipartJsonDictionary.cs +++ b/src/Anthropic/Core/MultipartJsonDictionary.cs @@ -97,20 +97,7 @@ public T GetNotNullClass(string key) { throw new AnthropicInvalidDataException($"'{key}' cannot be absent"); } - T deserialized; - try - { - deserialized = - MultipartJsonSerializer.Deserialize(element, ModelBase.SerializerOptions) - ?? throw new AnthropicInvalidDataException($"'{key}' cannot be null"); - } - catch (JsonException e) - { - throw new AnthropicInvalidDataException( - $"'{key}' must be of type {typeof(T).FullName}", - e - ); - } + T? deserialized = WrappedMultipartJsonSerializer.GetNotNullClass(element, key); _deserializedData[key] = deserialized; return deserialized; } @@ -126,20 +113,7 @@ public T GetNotNullStruct(string key) { throw new AnthropicInvalidDataException($"'{key}' cannot be absent"); } - T deserialized; - try - { - deserialized = - MultipartJsonSerializer.Deserialize(element, ModelBase.SerializerOptions) - ?? throw new AnthropicInvalidDataException($"'{key}' cannot be null"); - } - catch (JsonException e) - { - throw new AnthropicInvalidDataException( - $"'{key}' must be of type {typeof(T).FullName}", - e - ); - } + T deserialized = WrappedMultipartJsonSerializer.GetNotNullStruct(element, key); _deserializedData[key] = deserialized; return deserialized; } @@ -156,21 +130,7 @@ public T GetNotNullStruct(string key) _deserializedData[key] = null; return null; } - T? deserialized; - try - { - deserialized = MultipartJsonSerializer.Deserialize( - element, - ModelBase.SerializerOptions - ); - } - catch (JsonException e) - { - throw new AnthropicInvalidDataException( - $"'{key}' must be of type {typeof(T).FullName}", - e - ); - } + T? deserialized = WrappedMultipartJsonSerializer.GetNullableClass(element, key); _deserializedData[key] = deserialized; return deserialized; } @@ -187,21 +147,7 @@ public T GetNotNullStruct(string key) _deserializedData[key] = null; return null; } - T? deserialized; - try - { - deserialized = MultipartJsonSerializer.Deserialize( - element, - ModelBase.SerializerOptions - ); - } - catch (JsonException e) - { - throw new AnthropicInvalidDataException( - $"'{key}' must be of type {typeof(T).FullName}", - e - ); - } + T? deserialized = WrappedMultipartJsonSerializer.GetNullableStruct(element, key); _deserializedData[key] = deserialized; return deserialized; } diff --git a/src/Anthropic/Core/Sse.cs b/src/Anthropic/Core/Sse.cs index dcd06c2cf..485f06675 100644 --- a/src/Anthropic/Core/Sse.cs +++ b/src/Anthropic/Core/Sse.cs @@ -57,7 +57,10 @@ internal static async IAsyncEnumerable Enumerate( case "error": throw new AnthropicSseException( string.Format("SSE error returned from server: '{0}'", item.Data) - ); + ) + { + ErrorType = AnthropicExceptionFactory.ExtractErrorType(item.Data), + }; } } } diff --git a/src/Anthropic/Core/WrappedJsonSerializer.cs b/src/Anthropic/Core/WrappedJsonSerializer.cs new file mode 100644 index 000000000..1d1f7e41b --- /dev/null +++ b/src/Anthropic/Core/WrappedJsonSerializer.cs @@ -0,0 +1,87 @@ +using System.Text.Json; +using Anthropic.Exceptions; + +namespace Anthropic.Core; + +/// +/// Helper class for deserializing <c>JsonElement</c> objects. This handles +/// edge-cases around nullability and reference/value types. +/// +sealed class WrappedJsonSerializer +{ + public static T GetNotNullClass(JsonElement element, string name) + where T : class + { + T deserialized; + try + { + deserialized = + JsonSerializer.Deserialize(element, ModelBase.SerializerOptions) + ?? throw new AnthropicInvalidDataException($"'{name}' cannot be null"); + } + catch (JsonException e) + { + throw new AnthropicInvalidDataException( + $"'{name}' must be of type {typeof(T).FullName}", + e + ); + } + return deserialized; + } + + public static T GetNotNullStruct(JsonElement element, string name) + where T : struct + { + T deserialized; + try + { + deserialized = + JsonSerializer.Deserialize(element, ModelBase.SerializerOptions) + ?? throw new AnthropicInvalidDataException($"'{name}' cannot be null"); + } + catch (JsonException e) + { + throw new AnthropicInvalidDataException( + $"'{name}' must be of type {typeof(T).FullName}", + e + ); + } + return deserialized; + } + + public static T? GetNullableClass(JsonElement element, string name) + where T : class + { + T? deserialized; + try + { + deserialized = JsonSerializer.Deserialize(element, ModelBase.SerializerOptions); + } + catch (JsonException e) + { + throw new AnthropicInvalidDataException( + $"'{name}' must be of type {typeof(T).FullName}", + e + ); + } + return deserialized; + } + + public static T? GetNullableStruct(JsonElement element, string name) + where T : struct + { + T? deserialized; + try + { + deserialized = JsonSerializer.Deserialize(element, ModelBase.SerializerOptions); + } + catch (JsonException e) + { + throw new AnthropicInvalidDataException( + $"'{name}' must be of type {typeof(T).FullName}", + e + ); + } + return deserialized; + } +} diff --git a/src/Anthropic/Core/WrappedMultipartJsonSerializer.cs b/src/Anthropic/Core/WrappedMultipartJsonSerializer.cs new file mode 100644 index 000000000..f89f4a0a3 --- /dev/null +++ b/src/Anthropic/Core/WrappedMultipartJsonSerializer.cs @@ -0,0 +1,93 @@ +using System.Text.Json; +using Anthropic.Exceptions; + +namespace Anthropic.Core; + +/// +/// Helper class for deserializing <c>MultipartJsonElement</c> objects. +/// This handles edge-cases around nullability and reference/value types. +/// +sealed class WrappedMultipartJsonSerializer +{ + public static T GetNotNullClass(MultipartJsonElement element, string name) + where T : class + { + T deserialized; + try + { + deserialized = + MultipartJsonSerializer.Deserialize(element, ModelBase.SerializerOptions) + ?? throw new AnthropicInvalidDataException($"'{name}' cannot be null"); + } + catch (JsonException e) + { + throw new AnthropicInvalidDataException( + $"'{name}' must be of type {typeof(T).FullName}", + e + ); + } + return deserialized; + } + + public static T GetNotNullStruct(MultipartJsonElement element, string name) + where T : struct + { + T deserialized; + try + { + deserialized = + MultipartJsonSerializer.Deserialize(element, ModelBase.SerializerOptions) + ?? throw new AnthropicInvalidDataException($"'{name}' cannot be null"); + } + catch (JsonException e) + { + throw new AnthropicInvalidDataException( + $"'{name}' must be of type {typeof(T).FullName}", + e + ); + } + return deserialized; + } + + public static T? GetNullableClass(MultipartJsonElement element, string name) + where T : class + { + T? deserialized; + try + { + deserialized = MultipartJsonSerializer.Deserialize( + element, + ModelBase.SerializerOptions + ); + } + catch (JsonException e) + { + throw new AnthropicInvalidDataException( + $"'{name}' must be of type {typeof(T).FullName}", + e + ); + } + return deserialized; + } + + public static T? GetNullableStruct(MultipartJsonElement element, string name) + where T : struct + { + T? deserialized; + try + { + deserialized = MultipartJsonSerializer.Deserialize( + element, + ModelBase.SerializerOptions + ); + } + catch (JsonException e) + { + throw new AnthropicInvalidDataException( + $"'{name}' must be of type {typeof(T).FullName}", + e + ); + } + return deserialized; + } +} diff --git a/src/Anthropic/Exceptions/AnthropicApiException.cs b/src/Anthropic/Exceptions/AnthropicApiException.cs index 636379816..ac4f1f4a0 100644 --- a/src/Anthropic/Exceptions/AnthropicApiException.cs +++ b/src/Anthropic/Exceptions/AnthropicApiException.cs @@ -4,7 +4,7 @@ namespace Anthropic.Exceptions; -public class AnthropicApiException : AnthropicException +public class AnthropicApiException : AnthropicServiceException { public new HttpRequestException InnerException { diff --git a/src/Anthropic/Exceptions/AnthropicExceptionFactory.cs b/src/Anthropic/Exceptions/AnthropicExceptionFactory.cs index 08008d401..7945f96f7 100644 --- a/src/Anthropic/Exceptions/AnthropicExceptionFactory.cs +++ b/src/Anthropic/Exceptions/AnthropicExceptionFactory.cs @@ -1,4 +1,7 @@ +using System; using System.Net; +using System.Text.Json; +using Anthropic.Models; namespace Anthropic.Exceptions; @@ -9,53 +12,86 @@ public static AnthropicApiException CreateApiException( string responseBody ) { + var errorType = ExtractErrorType(responseBody); + return (int)statusCode switch { 400 => new AnthropicBadRequestException() { StatusCode = statusCode, ResponseBody = responseBody, + ErrorType = errorType, }, 401 => new AnthropicUnauthorizedException() { StatusCode = statusCode, ResponseBody = responseBody, + ErrorType = errorType, }, 403 => new AnthropicForbiddenException() { StatusCode = statusCode, ResponseBody = responseBody, + ErrorType = errorType, }, 404 => new AnthropicNotFoundException() { StatusCode = statusCode, ResponseBody = responseBody, + ErrorType = errorType, }, 422 => new AnthropicUnprocessableEntityException() { StatusCode = statusCode, ResponseBody = responseBody, + ErrorType = errorType, }, 429 => new AnthropicRateLimitException() { StatusCode = statusCode, ResponseBody = responseBody, + ErrorType = errorType, }, >= 400 and <= 499 => new Anthropic4xxException() { StatusCode = statusCode, ResponseBody = responseBody, + ErrorType = errorType, }, >= 500 and <= 599 => new Anthropic5xxException() { StatusCode = statusCode, ResponseBody = responseBody, + ErrorType = errorType, }, _ => new AnthropicUnexpectedStatusCodeException() { StatusCode = statusCode, ResponseBody = responseBody, + ErrorType = errorType, }, }; } + + internal static ErrorType? ExtractErrorType(string jsonBody) + { + try + { + using var doc = JsonDocument.Parse(jsonBody); + if ( + doc.RootElement.TryGetProperty("error", out var errorElement) + && errorElement.TryGetProperty("type", out var typeElement) + && typeElement.ValueKind == JsonValueKind.String + ) + { + var result = typeElement.Deserialize(); + return Enum.IsDefined(typeof(ErrorType), result) ? result : null; + } + } + catch (JsonException) + { + // Response body isn't valid JSON — leave ErrorType as null + } + return null; + } } diff --git a/src/Anthropic/Exceptions/AnthropicServiceException.cs b/src/Anthropic/Exceptions/AnthropicServiceException.cs new file mode 100644 index 000000000..41417b0bb --- /dev/null +++ b/src/Anthropic/Exceptions/AnthropicServiceException.cs @@ -0,0 +1,16 @@ +using System; +using System.Net.Http; +using Anthropic.Models; + +namespace Anthropic.Exceptions; + +public class AnthropicServiceException : AnthropicException +{ + public AnthropicServiceException(string message, Exception? innerException = null) + : base(message, innerException) { } + + protected AnthropicServiceException(HttpRequestException? innerException) + : base(innerException) { } + + public ErrorType? ErrorType { get; init; } +} diff --git a/src/Anthropic/Exceptions/AnthropicSseException.cs b/src/Anthropic/Exceptions/AnthropicSseException.cs index 47e47cdcc..a6985dd44 100644 --- a/src/Anthropic/Exceptions/AnthropicSseException.cs +++ b/src/Anthropic/Exceptions/AnthropicSseException.cs @@ -2,7 +2,7 @@ namespace Anthropic.Exceptions; -public class AnthropicSseException : AnthropicException +public class AnthropicSseException : AnthropicServiceException { public AnthropicSseException(string message, Exception? innerException = null) : base(message, innerException) { } diff --git a/src/Anthropic/Models/Beta/AnthropicBeta.cs b/src/Anthropic/Models/Beta/AnthropicBeta.cs index 6644b1a5c..e4e9938af 100644 --- a/src/Anthropic/Models/Beta/AnthropicBeta.cs +++ b/src/Anthropic/Models/Beta/AnthropicBeta.cs @@ -28,6 +28,7 @@ public enum AnthropicBeta ModelContextWindowExceeded2025_08_26, Skills2025_10_02, FastMode2026_02_01, + Output300k2026_03_24, } sealed class AnthropicBetaConverter : JsonConverter @@ -61,6 +62,7 @@ JsonSerializerOptions options AnthropicBeta.ModelContextWindowExceeded2025_08_26, "skills-2025-10-02" => AnthropicBeta.Skills2025_10_02, "fast-mode-2026-02-01" => AnthropicBeta.FastMode2026_02_01, + "output-300k-2026-03-24" => AnthropicBeta.Output300k2026_03_24, _ => (AnthropicBeta)(-1), }; } @@ -96,6 +98,7 @@ JsonSerializerOptions options "model-context-window-exceeded-2025-08-26", AnthropicBeta.Skills2025_10_02 => "skills-2025-10-02", AnthropicBeta.FastMode2026_02_01 => "fast-mode-2026-02-01", + AnthropicBeta.Output300k2026_03_24 => "output-300k-2026-03-24", _ => throw new AnthropicInvalidDataException( string.Format("Invalid value '{0}' in {1}", value, nameof(value)) ), diff --git a/src/Anthropic/Models/Beta/BetaError.cs b/src/Anthropic/Models/Beta/BetaError.cs index 93c35fde6..fd8685dfd 100644 --- a/src/Anthropic/Models/Beta/BetaError.cs +++ b/src/Anthropic/Models/Beta/BetaError.cs @@ -553,11 +553,10 @@ JsonSerializerOptions options ); if (deserialized != null) { - deserialized.Validate(); return new(deserialized, element); } } - catch (Exception e) when (e is JsonException || e is AnthropicInvalidDataException) + catch (JsonException) { // ignore } @@ -574,11 +573,10 @@ JsonSerializerOptions options ); if (deserialized != null) { - deserialized.Validate(); return new(deserialized, element); } } - catch (Exception e) when (e is JsonException || e is AnthropicInvalidDataException) + catch (JsonException) { // ignore } @@ -595,11 +593,10 @@ JsonSerializerOptions options ); if (deserialized != null) { - deserialized.Validate(); return new(deserialized, element); } } - catch (Exception e) when (e is JsonException || e is AnthropicInvalidDataException) + catch (JsonException) { // ignore } @@ -616,11 +613,10 @@ JsonSerializerOptions options ); if (deserialized != null) { - deserialized.Validate(); return new(deserialized, element); } } - catch (Exception e) when (e is JsonException || e is AnthropicInvalidDataException) + catch (JsonException) { // ignore } @@ -637,11 +633,10 @@ JsonSerializerOptions options ); if (deserialized != null) { - deserialized.Validate(); return new(deserialized, element); } } - catch (Exception e) when (e is JsonException || e is AnthropicInvalidDataException) + catch (JsonException) { // ignore } @@ -658,11 +653,10 @@ JsonSerializerOptions options ); if (deserialized != null) { - deserialized.Validate(); return new(deserialized, element); } } - catch (Exception e) when (e is JsonException || e is AnthropicInvalidDataException) + catch (JsonException) { // ignore } @@ -679,11 +673,10 @@ JsonSerializerOptions options ); if (deserialized != null) { - deserialized.Validate(); return new(deserialized, element); } } - catch (Exception e) when (e is JsonException || e is AnthropicInvalidDataException) + catch (JsonException) { // ignore } @@ -697,11 +690,10 @@ JsonSerializerOptions options var deserialized = JsonSerializer.Deserialize(element, options); if (deserialized != null) { - deserialized.Validate(); return new(deserialized, element); } } - catch (Exception e) when (e is JsonException || e is AnthropicInvalidDataException) + catch (JsonException) { // ignore } @@ -718,11 +710,10 @@ JsonSerializerOptions options ); if (deserialized != null) { - deserialized.Validate(); return new(deserialized, element); } } - catch (Exception e) when (e is JsonException || e is AnthropicInvalidDataException) + catch (JsonException) { // ignore } diff --git a/src/Anthropic/Models/Beta/Files/FileDeleteParams.cs b/src/Anthropic/Models/Beta/Files/FileDeleteParams.cs index 5e2d79ff8..94ba49e53 100644 --- a/src/Anthropic/Models/Beta/Files/FileDeleteParams.cs +++ b/src/Anthropic/Models/Beta/Files/FileDeleteParams.cs @@ -71,23 +71,27 @@ IReadOnlyDictionary rawQueryData [SetsRequiredMembers] FileDeleteParams( FrozenDictionary rawHeaderData, - FrozenDictionary rawQueryData + FrozenDictionary rawQueryData, + string fileID ) { this._rawHeaderData = new(rawHeaderData); this._rawQueryData = new(rawQueryData); + this.FileID = fileID; } #pragma warning restore CS8618 /// public static FileDeleteParams FromRawUnchecked( IReadOnlyDictionary rawHeaderData, - IReadOnlyDictionary rawQueryData + IReadOnlyDictionary rawQueryData, + string fileID ) { return new( FrozenDictionary.ToFrozenDictionary(rawHeaderData), - FrozenDictionary.ToFrozenDictionary(rawQueryData) + FrozenDictionary.ToFrozenDictionary(rawQueryData), + fileID ); } diff --git a/src/Anthropic/Models/Beta/Files/FileDownloadParams.cs b/src/Anthropic/Models/Beta/Files/FileDownloadParams.cs index 848aeb02a..1109b7aa6 100644 --- a/src/Anthropic/Models/Beta/Files/FileDownloadParams.cs +++ b/src/Anthropic/Models/Beta/Files/FileDownloadParams.cs @@ -71,23 +71,27 @@ IReadOnlyDictionary rawQueryData [SetsRequiredMembers] FileDownloadParams( FrozenDictionary rawHeaderData, - FrozenDictionary rawQueryData + FrozenDictionary rawQueryData, + string fileID ) { this._rawHeaderData = new(rawHeaderData); this._rawQueryData = new(rawQueryData); + this.FileID = fileID; } #pragma warning restore CS8618 /// public static FileDownloadParams FromRawUnchecked( IReadOnlyDictionary rawHeaderData, - IReadOnlyDictionary rawQueryData + IReadOnlyDictionary rawQueryData, + string fileID ) { return new( FrozenDictionary.ToFrozenDictionary(rawHeaderData), - FrozenDictionary.ToFrozenDictionary(rawQueryData) + FrozenDictionary.ToFrozenDictionary(rawQueryData), + fileID ); } diff --git a/src/Anthropic/Models/Beta/Files/FileRetrieveMetadataParams.cs b/src/Anthropic/Models/Beta/Files/FileRetrieveMetadataParams.cs index f311cab32..26d0ae3ae 100644 --- a/src/Anthropic/Models/Beta/Files/FileRetrieveMetadataParams.cs +++ b/src/Anthropic/Models/Beta/Files/FileRetrieveMetadataParams.cs @@ -71,23 +71,27 @@ IReadOnlyDictionary rawQueryData [SetsRequiredMembers] FileRetrieveMetadataParams( FrozenDictionary rawHeaderData, - FrozenDictionary rawQueryData + FrozenDictionary rawQueryData, + string fileID ) { this._rawHeaderData = new(rawHeaderData); this._rawQueryData = new(rawQueryData); + this.FileID = fileID; } #pragma warning restore CS8618 /// public static FileRetrieveMetadataParams FromRawUnchecked( IReadOnlyDictionary rawHeaderData, - IReadOnlyDictionary rawQueryData + IReadOnlyDictionary rawQueryData, + string fileID ) { return new( FrozenDictionary.ToFrozenDictionary(rawHeaderData), - FrozenDictionary.ToFrozenDictionary(rawQueryData) + FrozenDictionary.ToFrozenDictionary(rawQueryData), + fileID ); } diff --git a/src/Anthropic/Models/Beta/Messages/Batches/BatchCancelParams.cs b/src/Anthropic/Models/Beta/Messages/Batches/BatchCancelParams.cs index a6723ee91..5538ba3fc 100644 --- a/src/Anthropic/Models/Beta/Messages/Batches/BatchCancelParams.cs +++ b/src/Anthropic/Models/Beta/Messages/Batches/BatchCancelParams.cs @@ -79,23 +79,27 @@ IReadOnlyDictionary rawQueryData [SetsRequiredMembers] BatchCancelParams( FrozenDictionary rawHeaderData, - FrozenDictionary rawQueryData + FrozenDictionary rawQueryData, + string messageBatchID ) { this._rawHeaderData = new(rawHeaderData); this._rawQueryData = new(rawQueryData); + this.MessageBatchID = messageBatchID; } #pragma warning restore CS8618 /// public static BatchCancelParams FromRawUnchecked( IReadOnlyDictionary rawHeaderData, - IReadOnlyDictionary rawQueryData + IReadOnlyDictionary rawQueryData, + string messageBatchID ) { return new( FrozenDictionary.ToFrozenDictionary(rawHeaderData), - FrozenDictionary.ToFrozenDictionary(rawQueryData) + FrozenDictionary.ToFrozenDictionary(rawQueryData), + messageBatchID ); } diff --git a/src/Anthropic/Models/Beta/Messages/Batches/BatchDeleteParams.cs b/src/Anthropic/Models/Beta/Messages/Batches/BatchDeleteParams.cs index 47b45c53c..860b926a0 100644 --- a/src/Anthropic/Models/Beta/Messages/Batches/BatchDeleteParams.cs +++ b/src/Anthropic/Models/Beta/Messages/Batches/BatchDeleteParams.cs @@ -76,23 +76,27 @@ IReadOnlyDictionary rawQueryData [SetsRequiredMembers] BatchDeleteParams( FrozenDictionary rawHeaderData, - FrozenDictionary rawQueryData + FrozenDictionary rawQueryData, + string messageBatchID ) { this._rawHeaderData = new(rawHeaderData); this._rawQueryData = new(rawQueryData); + this.MessageBatchID = messageBatchID; } #pragma warning restore CS8618 /// public static BatchDeleteParams FromRawUnchecked( IReadOnlyDictionary rawHeaderData, - IReadOnlyDictionary rawQueryData + IReadOnlyDictionary rawQueryData, + string messageBatchID ) { return new( FrozenDictionary.ToFrozenDictionary(rawHeaderData), - FrozenDictionary.ToFrozenDictionary(rawQueryData) + FrozenDictionary.ToFrozenDictionary(rawQueryData), + messageBatchID ); } diff --git a/src/Anthropic/Models/Beta/Messages/Batches/BatchResultsParams.cs b/src/Anthropic/Models/Beta/Messages/Batches/BatchResultsParams.cs index df1b50985..c414d3e20 100644 --- a/src/Anthropic/Models/Beta/Messages/Batches/BatchResultsParams.cs +++ b/src/Anthropic/Models/Beta/Messages/Batches/BatchResultsParams.cs @@ -77,23 +77,27 @@ IReadOnlyDictionary rawQueryData [SetsRequiredMembers] BatchResultsParams( FrozenDictionary rawHeaderData, - FrozenDictionary rawQueryData + FrozenDictionary rawQueryData, + string messageBatchID ) { this._rawHeaderData = new(rawHeaderData); this._rawQueryData = new(rawQueryData); + this.MessageBatchID = messageBatchID; } #pragma warning restore CS8618 /// public static BatchResultsParams FromRawUnchecked( IReadOnlyDictionary rawHeaderData, - IReadOnlyDictionary rawQueryData + IReadOnlyDictionary rawQueryData, + string messageBatchID ) { return new( FrozenDictionary.ToFrozenDictionary(rawHeaderData), - FrozenDictionary.ToFrozenDictionary(rawQueryData) + FrozenDictionary.ToFrozenDictionary(rawQueryData), + messageBatchID ); } diff --git a/src/Anthropic/Models/Beta/Messages/Batches/BatchRetrieveParams.cs b/src/Anthropic/Models/Beta/Messages/Batches/BatchRetrieveParams.cs index 9d51e7d95..757568424 100644 --- a/src/Anthropic/Models/Beta/Messages/Batches/BatchRetrieveParams.cs +++ b/src/Anthropic/Models/Beta/Messages/Batches/BatchRetrieveParams.cs @@ -75,23 +75,27 @@ IReadOnlyDictionary rawQueryData [SetsRequiredMembers] BatchRetrieveParams( FrozenDictionary rawHeaderData, - FrozenDictionary rawQueryData + FrozenDictionary rawQueryData, + string messageBatchID ) { this._rawHeaderData = new(rawHeaderData); this._rawQueryData = new(rawQueryData); + this.MessageBatchID = messageBatchID; } #pragma warning restore CS8618 /// public static BatchRetrieveParams FromRawUnchecked( IReadOnlyDictionary rawHeaderData, - IReadOnlyDictionary rawQueryData + IReadOnlyDictionary rawQueryData, + string messageBatchID ) { return new( FrozenDictionary.ToFrozenDictionary(rawHeaderData), - FrozenDictionary.ToFrozenDictionary(rawQueryData) + FrozenDictionary.ToFrozenDictionary(rawQueryData), + messageBatchID ); } diff --git a/src/Anthropic/Models/Beta/Messages/Batches/BetaMessageBatchResult.cs b/src/Anthropic/Models/Beta/Messages/Batches/BetaMessageBatchResult.cs index 8fd8adead..63bcdcf12 100644 --- a/src/Anthropic/Models/Beta/Messages/Batches/BetaMessageBatchResult.cs +++ b/src/Anthropic/Models/Beta/Messages/Batches/BetaMessageBatchResult.cs @@ -351,12 +351,10 @@ JsonSerializerOptions options ); if (deserialized != null) { - deserialized.Validate(); return new(deserialized, element); } } - catch (System::Exception e) - when (e is JsonException || e is AnthropicInvalidDataException) + catch (JsonException) { // ignore } @@ -373,12 +371,10 @@ JsonSerializerOptions options ); if (deserialized != null) { - deserialized.Validate(); return new(deserialized, element); } } - catch (System::Exception e) - when (e is JsonException || e is AnthropicInvalidDataException) + catch (JsonException) { // ignore } @@ -395,12 +391,10 @@ JsonSerializerOptions options ); if (deserialized != null) { - deserialized.Validate(); return new(deserialized, element); } } - catch (System::Exception e) - when (e is JsonException || e is AnthropicInvalidDataException) + catch (JsonException) { // ignore } @@ -417,12 +411,10 @@ JsonSerializerOptions options ); if (deserialized != null) { - deserialized.Validate(); return new(deserialized, element); } } - catch (System::Exception e) - when (e is JsonException || e is AnthropicInvalidDataException) + catch (JsonException) { // ignore } diff --git a/src/Anthropic/Models/Beta/Messages/BetaCitationsDelta.cs b/src/Anthropic/Models/Beta/Messages/BetaCitationsDelta.cs index 65654e267..0c0df06d3 100644 --- a/src/Anthropic/Models/Beta/Messages/BetaCitationsDelta.cs +++ b/src/Anthropic/Models/Beta/Messages/BetaCitationsDelta.cs @@ -573,12 +573,10 @@ JsonSerializerOptions options ); if (deserialized != null) { - deserialized.Validate(); return new(deserialized, element); } } - catch (System::Exception e) - when (e is JsonException || e is AnthropicInvalidDataException) + catch (JsonException) { // ignore } @@ -595,12 +593,10 @@ JsonSerializerOptions options ); if (deserialized != null) { - deserialized.Validate(); return new(deserialized, element); } } - catch (System::Exception e) - when (e is JsonException || e is AnthropicInvalidDataException) + catch (JsonException) { // ignore } @@ -617,12 +613,10 @@ JsonSerializerOptions options ); if (deserialized != null) { - deserialized.Validate(); return new(deserialized, element); } } - catch (System::Exception e) - when (e is JsonException || e is AnthropicInvalidDataException) + catch (JsonException) { // ignore } @@ -640,12 +634,10 @@ JsonSerializerOptions options ); if (deserialized != null) { - deserialized.Validate(); return new(deserialized, element); } } - catch (System::Exception e) - when (e is JsonException || e is AnthropicInvalidDataException) + catch (JsonException) { // ignore } @@ -662,12 +654,10 @@ JsonSerializerOptions options ); if (deserialized != null) { - deserialized.Validate(); return new(deserialized, element); } } - catch (System::Exception e) - when (e is JsonException || e is AnthropicInvalidDataException) + catch (JsonException) { // ignore } diff --git a/src/Anthropic/Models/Beta/Messages/BetaClearToolUses20250919Edit.cs b/src/Anthropic/Models/Beta/Messages/BetaClearToolUses20250919Edit.cs index cc83dbb6a..d842c8077 100644 --- a/src/Anthropic/Models/Beta/Messages/BetaClearToolUses20250919Edit.cs +++ b/src/Anthropic/Models/Beta/Messages/BetaClearToolUses20250919Edit.cs @@ -673,12 +673,10 @@ JsonSerializerOptions options ); if (deserialized != null) { - deserialized.Validate(); return new(deserialized, element); } } - catch (System::Exception e) - when (e is JsonException || e is AnthropicInvalidDataException) + catch (JsonException) { // ignore } @@ -695,12 +693,10 @@ JsonSerializerOptions options ); if (deserialized != null) { - deserialized.Validate(); return new(deserialized, element); } } - catch (System::Exception e) - when (e is JsonException || e is AnthropicInvalidDataException) + catch (JsonException) { // ignore } diff --git a/src/Anthropic/Models/Beta/Messages/BetaCodeExecutionTool20250522.cs b/src/Anthropic/Models/Beta/Messages/BetaCodeExecutionTool20250522.cs index f8eb6780a..1bf57308b 100644 --- a/src/Anthropic/Models/Beta/Messages/BetaCodeExecutionTool20250522.cs +++ b/src/Anthropic/Models/Beta/Messages/BetaCodeExecutionTool20250522.cs @@ -191,6 +191,13 @@ IReadOnlyDictionary rawData ) => BetaCodeExecutionTool20250522.FromRawUnchecked(rawData); } +/// +/// Specifies who can invoke a tool. +/// +/// Values: direct: The model can call this tool directly. code_execution_20250825: +/// The tool can be called from the code execution environment (v1). code_execution_20260120: +/// The tool can be called from the code execution environment (v2 with persistence). +/// [JsonConverter(typeof(AllowedCallerConverter))] public enum AllowedCaller { diff --git a/src/Anthropic/Models/Beta/Messages/BetaCodeExecutionTool20250825.cs b/src/Anthropic/Models/Beta/Messages/BetaCodeExecutionTool20250825.cs index 9dd51fb51..683439749 100644 --- a/src/Anthropic/Models/Beta/Messages/BetaCodeExecutionTool20250825.cs +++ b/src/Anthropic/Models/Beta/Messages/BetaCodeExecutionTool20250825.cs @@ -192,6 +192,13 @@ IReadOnlyDictionary rawData ) => BetaCodeExecutionTool20250825.FromRawUnchecked(rawData); } +/// +/// Specifies who can invoke a tool. +/// +/// Values: direct: The model can call this tool directly. code_execution_20250825: +/// The tool can be called from the code execution environment (v1). code_execution_20260120: +/// The tool can be called from the code execution environment (v2 with persistence). +/// [JsonConverter(typeof(BetaCodeExecutionTool20250825AllowedCallerConverter))] public enum BetaCodeExecutionTool20250825AllowedCaller { diff --git a/src/Anthropic/Models/Beta/Messages/BetaCodeExecutionTool20260120.cs b/src/Anthropic/Models/Beta/Messages/BetaCodeExecutionTool20260120.cs index 15f5ef150..da6eecaa8 100644 --- a/src/Anthropic/Models/Beta/Messages/BetaCodeExecutionTool20260120.cs +++ b/src/Anthropic/Models/Beta/Messages/BetaCodeExecutionTool20260120.cs @@ -195,6 +195,13 @@ IReadOnlyDictionary rawData ) => BetaCodeExecutionTool20260120.FromRawUnchecked(rawData); } +/// +/// Specifies who can invoke a tool. +/// +/// Values: direct: The model can call this tool directly. code_execution_20250825: +/// The tool can be called from the code execution environment (v1). code_execution_20260120: +/// The tool can be called from the code execution environment (v2 with persistence). +/// [JsonConverter(typeof(BetaCodeExecutionTool20260120AllowedCallerConverter))] public enum BetaCodeExecutionTool20260120AllowedCaller { diff --git a/src/Anthropic/Models/Beta/Messages/BetaContentBlock.cs b/src/Anthropic/Models/Beta/Messages/BetaContentBlock.cs index 1a64d7ba5..452f8e94a 100644 --- a/src/Anthropic/Models/Beta/Messages/BetaContentBlock.cs +++ b/src/Anthropic/Models/Beta/Messages/BetaContentBlock.cs @@ -852,12 +852,10 @@ JsonSerializerOptions options var deserialized = JsonSerializer.Deserialize(element, options); if (deserialized != null) { - deserialized.Validate(); return new(deserialized, element); } } - catch (System::Exception e) - when (e is JsonException || e is AnthropicInvalidDataException) + catch (JsonException) { // ignore } @@ -874,12 +872,10 @@ JsonSerializerOptions options ); if (deserialized != null) { - deserialized.Validate(); return new(deserialized, element); } } - catch (System::Exception e) - when (e is JsonException || e is AnthropicInvalidDataException) + catch (JsonException) { // ignore } @@ -896,12 +892,10 @@ JsonSerializerOptions options ); if (deserialized != null) { - deserialized.Validate(); return new(deserialized, element); } } - catch (System::Exception e) - when (e is JsonException || e is AnthropicInvalidDataException) + catch (JsonException) { // ignore } @@ -918,12 +912,10 @@ JsonSerializerOptions options ); if (deserialized != null) { - deserialized.Validate(); return new(deserialized, element); } } - catch (System::Exception e) - when (e is JsonException || e is AnthropicInvalidDataException) + catch (JsonException) { // ignore } @@ -940,12 +932,10 @@ JsonSerializerOptions options ); if (deserialized != null) { - deserialized.Validate(); return new(deserialized, element); } } - catch (System::Exception e) - when (e is JsonException || e is AnthropicInvalidDataException) + catch (JsonException) { // ignore } @@ -962,12 +952,10 @@ JsonSerializerOptions options ); if (deserialized != null) { - deserialized.Validate(); return new(deserialized, element); } } - catch (System::Exception e) - when (e is JsonException || e is AnthropicInvalidDataException) + catch (JsonException) { // ignore } @@ -984,12 +972,10 @@ JsonSerializerOptions options ); if (deserialized != null) { - deserialized.Validate(); return new(deserialized, element); } } - catch (System::Exception e) - when (e is JsonException || e is AnthropicInvalidDataException) + catch (JsonException) { // ignore } @@ -1006,12 +992,10 @@ JsonSerializerOptions options ); if (deserialized != null) { - deserialized.Validate(); return new(deserialized, element); } } - catch (System::Exception e) - when (e is JsonException || e is AnthropicInvalidDataException) + catch (JsonException) { // ignore } @@ -1029,12 +1013,10 @@ JsonSerializerOptions options ); if (deserialized != null) { - deserialized.Validate(); return new(deserialized, element); } } - catch (System::Exception e) - when (e is JsonException || e is AnthropicInvalidDataException) + catch (JsonException) { // ignore } @@ -1052,12 +1034,10 @@ JsonSerializerOptions options ); if (deserialized != null) { - deserialized.Validate(); return new(deserialized, element); } } - catch (System::Exception e) - when (e is JsonException || e is AnthropicInvalidDataException) + catch (JsonException) { // ignore } @@ -1074,12 +1054,10 @@ JsonSerializerOptions options ); if (deserialized != null) { - deserialized.Validate(); return new(deserialized, element); } } - catch (System::Exception e) - when (e is JsonException || e is AnthropicInvalidDataException) + catch (JsonException) { // ignore } @@ -1096,12 +1074,10 @@ JsonSerializerOptions options ); if (deserialized != null) { - deserialized.Validate(); return new(deserialized, element); } } - catch (System::Exception e) - when (e is JsonException || e is AnthropicInvalidDataException) + catch (JsonException) { // ignore } @@ -1118,12 +1094,10 @@ JsonSerializerOptions options ); if (deserialized != null) { - deserialized.Validate(); return new(deserialized, element); } } - catch (System::Exception e) - when (e is JsonException || e is AnthropicInvalidDataException) + catch (JsonException) { // ignore } @@ -1140,12 +1114,10 @@ JsonSerializerOptions options ); if (deserialized != null) { - deserialized.Validate(); return new(deserialized, element); } } - catch (System::Exception e) - when (e is JsonException || e is AnthropicInvalidDataException) + catch (JsonException) { // ignore } @@ -1162,12 +1134,10 @@ JsonSerializerOptions options ); if (deserialized != null) { - deserialized.Validate(); return new(deserialized, element); } } - catch (System::Exception e) - when (e is JsonException || e is AnthropicInvalidDataException) + catch (JsonException) { // ignore } diff --git a/src/Anthropic/Models/Beta/Messages/BetaContentBlockParam.cs b/src/Anthropic/Models/Beta/Messages/BetaContentBlockParam.cs index 4cd76e9b5..c08bb82a2 100644 --- a/src/Anthropic/Models/Beta/Messages/BetaContentBlockParam.cs +++ b/src/Anthropic/Models/Beta/Messages/BetaContentBlockParam.cs @@ -1144,12 +1144,10 @@ JsonSerializerOptions options ); if (deserialized != null) { - deserialized.Validate(); return new(deserialized, element); } } - catch (System::Exception e) - when (e is JsonException || e is AnthropicInvalidDataException) + catch (JsonException) { // ignore } @@ -1166,12 +1164,10 @@ JsonSerializerOptions options ); if (deserialized != null) { - deserialized.Validate(); return new(deserialized, element); } } - catch (System::Exception e) - when (e is JsonException || e is AnthropicInvalidDataException) + catch (JsonException) { // ignore } @@ -1188,12 +1184,10 @@ JsonSerializerOptions options ); if (deserialized != null) { - deserialized.Validate(); return new(deserialized, element); } } - catch (System::Exception e) - when (e is JsonException || e is AnthropicInvalidDataException) + catch (JsonException) { // ignore } @@ -1210,12 +1204,10 @@ JsonSerializerOptions options ); if (deserialized != null) { - deserialized.Validate(); return new(deserialized, element); } } - catch (System::Exception e) - when (e is JsonException || e is AnthropicInvalidDataException) + catch (JsonException) { // ignore } @@ -1232,12 +1224,10 @@ JsonSerializerOptions options ); if (deserialized != null) { - deserialized.Validate(); return new(deserialized, element); } } - catch (System::Exception e) - when (e is JsonException || e is AnthropicInvalidDataException) + catch (JsonException) { // ignore } @@ -1254,12 +1244,10 @@ JsonSerializerOptions options ); if (deserialized != null) { - deserialized.Validate(); return new(deserialized, element); } } - catch (System::Exception e) - when (e is JsonException || e is AnthropicInvalidDataException) + catch (JsonException) { // ignore } @@ -1276,12 +1264,10 @@ JsonSerializerOptions options ); if (deserialized != null) { - deserialized.Validate(); return new(deserialized, element); } } - catch (System::Exception e) - when (e is JsonException || e is AnthropicInvalidDataException) + catch (JsonException) { // ignore } @@ -1298,12 +1284,10 @@ JsonSerializerOptions options ); if (deserialized != null) { - deserialized.Validate(); return new(deserialized, element); } } - catch (System::Exception e) - when (e is JsonException || e is AnthropicInvalidDataException) + catch (JsonException) { // ignore } @@ -1320,12 +1304,10 @@ JsonSerializerOptions options ); if (deserialized != null) { - deserialized.Validate(); return new(deserialized, element); } } - catch (System::Exception e) - when (e is JsonException || e is AnthropicInvalidDataException) + catch (JsonException) { // ignore } @@ -1343,12 +1325,10 @@ JsonSerializerOptions options ); if (deserialized != null) { - deserialized.Validate(); return new(deserialized, element); } } - catch (System::Exception e) - when (e is JsonException || e is AnthropicInvalidDataException) + catch (JsonException) { // ignore } @@ -1365,12 +1345,10 @@ JsonSerializerOptions options ); if (deserialized != null) { - deserialized.Validate(); return new(deserialized, element); } } - catch (System::Exception e) - when (e is JsonException || e is AnthropicInvalidDataException) + catch (JsonException) { // ignore } @@ -1388,12 +1366,10 @@ JsonSerializerOptions options ); if (deserialized != null) { - deserialized.Validate(); return new(deserialized, element); } } - catch (System::Exception e) - when (e is JsonException || e is AnthropicInvalidDataException) + catch (JsonException) { // ignore } @@ -1411,12 +1387,10 @@ JsonSerializerOptions options ); if (deserialized != null) { - deserialized.Validate(); return new(deserialized, element); } } - catch (System::Exception e) - when (e is JsonException || e is AnthropicInvalidDataException) + catch (JsonException) { // ignore } @@ -1434,12 +1408,10 @@ JsonSerializerOptions options ); if (deserialized != null) { - deserialized.Validate(); return new(deserialized, element); } } - catch (System::Exception e) - when (e is JsonException || e is AnthropicInvalidDataException) + catch (JsonException) { // ignore } @@ -1457,12 +1429,10 @@ JsonSerializerOptions options ); if (deserialized != null) { - deserialized.Validate(); return new(deserialized, element); } } - catch (System::Exception e) - when (e is JsonException || e is AnthropicInvalidDataException) + catch (JsonException) { // ignore } @@ -1479,12 +1449,10 @@ JsonSerializerOptions options ); if (deserialized != null) { - deserialized.Validate(); return new(deserialized, element); } } - catch (System::Exception e) - when (e is JsonException || e is AnthropicInvalidDataException) + catch (JsonException) { // ignore } @@ -1502,12 +1470,10 @@ JsonSerializerOptions options ); if (deserialized != null) { - deserialized.Validate(); return new(deserialized, element); } } - catch (System::Exception e) - when (e is JsonException || e is AnthropicInvalidDataException) + catch (JsonException) { // ignore } @@ -1524,12 +1490,10 @@ JsonSerializerOptions options ); if (deserialized != null) { - deserialized.Validate(); return new(deserialized, element); } } - catch (System::Exception e) - when (e is JsonException || e is AnthropicInvalidDataException) + catch (JsonException) { // ignore } @@ -1546,12 +1510,10 @@ JsonSerializerOptions options ); if (deserialized != null) { - deserialized.Validate(); return new(deserialized, element); } } - catch (System::Exception e) - when (e is JsonException || e is AnthropicInvalidDataException) + catch (JsonException) { // ignore } diff --git a/src/Anthropic/Models/Beta/Messages/BetaContextManagementConfig.cs b/src/Anthropic/Models/Beta/Messages/BetaContextManagementConfig.cs index 9ae7977e3..96d6c167f 100644 --- a/src/Anthropic/Models/Beta/Messages/BetaContextManagementConfig.cs +++ b/src/Anthropic/Models/Beta/Messages/BetaContextManagementConfig.cs @@ -377,12 +377,10 @@ JsonSerializerOptions options ); if (deserialized != null) { - deserialized.Validate(); return new(deserialized, element); } } - catch (System::Exception e) - when (e is JsonException || e is AnthropicInvalidDataException) + catch (JsonException) { // ignore } @@ -399,12 +397,10 @@ JsonSerializerOptions options ); if (deserialized != null) { - deserialized.Validate(); return new(deserialized, element); } } - catch (System::Exception e) - when (e is JsonException || e is AnthropicInvalidDataException) + catch (JsonException) { // ignore } @@ -421,12 +417,10 @@ JsonSerializerOptions options ); if (deserialized != null) { - deserialized.Validate(); return new(deserialized, element); } } - catch (System::Exception e) - when (e is JsonException || e is AnthropicInvalidDataException) + catch (JsonException) { // ignore } diff --git a/src/Anthropic/Models/Beta/Messages/BetaContextManagementResponse.cs b/src/Anthropic/Models/Beta/Messages/BetaContextManagementResponse.cs index 78257f66b..f92859963 100644 --- a/src/Anthropic/Models/Beta/Messages/BetaContextManagementResponse.cs +++ b/src/Anthropic/Models/Beta/Messages/BetaContextManagementResponse.cs @@ -367,12 +367,10 @@ JsonSerializerOptions options ); if (deserialized != null) { - deserialized.Validate(); return new(deserialized, element); } } - catch (System::Exception e) - when (e is JsonException || e is AnthropicInvalidDataException) + catch (JsonException) { // ignore } @@ -390,12 +388,10 @@ JsonSerializerOptions options ); if (deserialized != null) { - deserialized.Validate(); return new(deserialized, element); } } - catch (System::Exception e) - when (e is JsonException || e is AnthropicInvalidDataException) + catch (JsonException) { // ignore } diff --git a/src/Anthropic/Models/Beta/Messages/BetaDocumentBlock.cs b/src/Anthropic/Models/Beta/Messages/BetaDocumentBlock.cs index f1448855b..7cee27fc9 100644 --- a/src/Anthropic/Models/Beta/Messages/BetaDocumentBlock.cs +++ b/src/Anthropic/Models/Beta/Messages/BetaDocumentBlock.cs @@ -362,12 +362,10 @@ JsonSerializerOptions options ); if (deserialized != null) { - deserialized.Validate(); return new(deserialized, element); } } - catch (System::Exception e) - when (e is JsonException || e is AnthropicInvalidDataException) + catch (JsonException) { // ignore } @@ -384,12 +382,10 @@ JsonSerializerOptions options ); if (deserialized != null) { - deserialized.Validate(); return new(deserialized, element); } } - catch (System::Exception e) - when (e is JsonException || e is AnthropicInvalidDataException) + catch (JsonException) { // ignore } diff --git a/src/Anthropic/Models/Beta/Messages/BetaImageBlockParam.cs b/src/Anthropic/Models/Beta/Messages/BetaImageBlockParam.cs index 89b26873d..05ed14521 100644 --- a/src/Anthropic/Models/Beta/Messages/BetaImageBlockParam.cs +++ b/src/Anthropic/Models/Beta/Messages/BetaImageBlockParam.cs @@ -398,12 +398,10 @@ JsonSerializerOptions options ); if (deserialized != null) { - deserialized.Validate(); return new(deserialized, element); } } - catch (System::Exception e) - when (e is JsonException || e is AnthropicInvalidDataException) + catch (JsonException) { // ignore } @@ -420,12 +418,10 @@ JsonSerializerOptions options ); if (deserialized != null) { - deserialized.Validate(); return new(deserialized, element); } } - catch (System::Exception e) - when (e is JsonException || e is AnthropicInvalidDataException) + catch (JsonException) { // ignore } @@ -442,12 +438,10 @@ JsonSerializerOptions options ); if (deserialized != null) { - deserialized.Validate(); return new(deserialized, element); } } - catch (System::Exception e) - when (e is JsonException || e is AnthropicInvalidDataException) + catch (JsonException) { // ignore } diff --git a/src/Anthropic/Models/Beta/Messages/BetaIterationsUsageItems.cs b/src/Anthropic/Models/Beta/Messages/BetaIterationsUsageItems.cs index ffe8adf49..127779561 100644 --- a/src/Anthropic/Models/Beta/Messages/BetaIterationsUsageItems.cs +++ b/src/Anthropic/Models/Beta/Messages/BetaIterationsUsageItems.cs @@ -299,41 +299,63 @@ JsonSerializerOptions options ) { var element = JsonSerializer.Deserialize(ref reader, options); + string? type; try { - var deserialized = JsonSerializer.Deserialize( - element, - options - ); - if (deserialized != null) - { - deserialized.Validate(); - return new(deserialized, element); - } + type = element.GetProperty("type").GetString(); } - catch (System::Exception e) when (e is JsonException || e is AnthropicInvalidDataException) + catch { - // ignore + type = null; } - try + switch (type) { - var deserialized = JsonSerializer.Deserialize( - element, - options - ); - if (deserialized != null) + case "message": { - deserialized.Validate(); - return new(deserialized, element); + try + { + var deserialized = JsonSerializer.Deserialize( + element, + options + ); + if (deserialized != null) + { + return new(deserialized, element); + } + } + catch (JsonException) + { + // ignore + } + + return new(element); } - } - catch (System::Exception e) when (e is JsonException || e is AnthropicInvalidDataException) - { - // ignore - } + case "compaction": + { + try + { + var deserialized = JsonSerializer.Deserialize( + element, + options + ); + if (deserialized != null) + { + return new(deserialized, element); + } + } + catch (JsonException) + { + // ignore + } - return new(element); + return new(element); + } + default: + { + return new BetaIterationsUsageItems(element); + } + } } public override void Write( diff --git a/src/Anthropic/Models/Beta/Messages/BetaMemoryTool20250818.cs b/src/Anthropic/Models/Beta/Messages/BetaMemoryTool20250818.cs index 65c3fca0b..102647d77 100644 --- a/src/Anthropic/Models/Beta/Messages/BetaMemoryTool20250818.cs +++ b/src/Anthropic/Models/Beta/Messages/BetaMemoryTool20250818.cs @@ -215,6 +215,13 @@ IReadOnlyDictionary rawData ) => BetaMemoryTool20250818.FromRawUnchecked(rawData); } +/// +/// Specifies who can invoke a tool. +/// +/// Values: direct: The model can call this tool directly. code_execution_20250825: +/// The tool can be called from the code execution environment (v1). code_execution_20260120: +/// The tool can be called from the code execution environment (v2 with persistence). +/// [JsonConverter(typeof(BetaMemoryTool20250818AllowedCallerConverter))] public enum BetaMemoryTool20250818AllowedCaller { diff --git a/src/Anthropic/Models/Beta/Messages/BetaMemoryTool20250818Command.cs b/src/Anthropic/Models/Beta/Messages/BetaMemoryTool20250818Command.cs index 35751b68a..37fd876a5 100644 --- a/src/Anthropic/Models/Beta/Messages/BetaMemoryTool20250818Command.cs +++ b/src/Anthropic/Models/Beta/Messages/BetaMemoryTool20250818Command.cs @@ -475,12 +475,10 @@ JsonSerializerOptions options ); if (deserialized != null) { - deserialized.Validate(); return new(deserialized, element); } } - catch (System::Exception e) - when (e is JsonException || e is AnthropicInvalidDataException) + catch (JsonException) { // ignore } @@ -498,12 +496,10 @@ JsonSerializerOptions options ); if (deserialized != null) { - deserialized.Validate(); return new(deserialized, element); } } - catch (System::Exception e) - when (e is JsonException || e is AnthropicInvalidDataException) + catch (JsonException) { // ignore } @@ -521,12 +517,10 @@ JsonSerializerOptions options ); if (deserialized != null) { - deserialized.Validate(); return new(deserialized, element); } } - catch (System::Exception e) - when (e is JsonException || e is AnthropicInvalidDataException) + catch (JsonException) { // ignore } @@ -544,12 +538,10 @@ JsonSerializerOptions options ); if (deserialized != null) { - deserialized.Validate(); return new(deserialized, element); } } - catch (System::Exception e) - when (e is JsonException || e is AnthropicInvalidDataException) + catch (JsonException) { // ignore } @@ -567,12 +559,10 @@ JsonSerializerOptions options ); if (deserialized != null) { - deserialized.Validate(); return new(deserialized, element); } } - catch (System::Exception e) - when (e is JsonException || e is AnthropicInvalidDataException) + catch (JsonException) { // ignore } @@ -590,12 +580,10 @@ JsonSerializerOptions options ); if (deserialized != null) { - deserialized.Validate(); return new(deserialized, element); } } - catch (System::Exception e) - when (e is JsonException || e is AnthropicInvalidDataException) + catch (JsonException) { // ignore } diff --git a/src/Anthropic/Models/Beta/Messages/BetaRawContentBlockDelta.cs b/src/Anthropic/Models/Beta/Messages/BetaRawContentBlockDelta.cs index b1284ec87..bc9fcbe9c 100644 --- a/src/Anthropic/Models/Beta/Messages/BetaRawContentBlockDelta.cs +++ b/src/Anthropic/Models/Beta/Messages/BetaRawContentBlockDelta.cs @@ -422,12 +422,10 @@ JsonSerializerOptions options var deserialized = JsonSerializer.Deserialize(element, options); if (deserialized != null) { - deserialized.Validate(); return new(deserialized, element); } } - catch (System::Exception e) - when (e is JsonException || e is AnthropicInvalidDataException) + catch (JsonException) { // ignore } @@ -444,12 +442,10 @@ JsonSerializerOptions options ); if (deserialized != null) { - deserialized.Validate(); return new(deserialized, element); } } - catch (System::Exception e) - when (e is JsonException || e is AnthropicInvalidDataException) + catch (JsonException) { // ignore } @@ -466,12 +462,10 @@ JsonSerializerOptions options ); if (deserialized != null) { - deserialized.Validate(); return new(deserialized, element); } } - catch (System::Exception e) - when (e is JsonException || e is AnthropicInvalidDataException) + catch (JsonException) { // ignore } @@ -488,12 +482,10 @@ JsonSerializerOptions options ); if (deserialized != null) { - deserialized.Validate(); return new(deserialized, element); } } - catch (System::Exception e) - when (e is JsonException || e is AnthropicInvalidDataException) + catch (JsonException) { // ignore } @@ -510,12 +502,10 @@ JsonSerializerOptions options ); if (deserialized != null) { - deserialized.Validate(); return new(deserialized, element); } } - catch (System::Exception e) - when (e is JsonException || e is AnthropicInvalidDataException) + catch (JsonException) { // ignore } @@ -532,12 +522,10 @@ JsonSerializerOptions options ); if (deserialized != null) { - deserialized.Validate(); return new(deserialized, element); } } - catch (System::Exception e) - when (e is JsonException || e is AnthropicInvalidDataException) + catch (JsonException) { // ignore } diff --git a/src/Anthropic/Models/Beta/Messages/BetaRawContentBlockStartEvent.cs b/src/Anthropic/Models/Beta/Messages/BetaRawContentBlockStartEvent.cs index 87435fc2c..286a90867 100644 --- a/src/Anthropic/Models/Beta/Messages/BetaRawContentBlockStartEvent.cs +++ b/src/Anthropic/Models/Beta/Messages/BetaRawContentBlockStartEvent.cs @@ -952,12 +952,10 @@ JsonSerializerOptions options var deserialized = JsonSerializer.Deserialize(element, options); if (deserialized != null) { - deserialized.Validate(); return new(deserialized, element); } } - catch (System::Exception e) - when (e is JsonException || e is AnthropicInvalidDataException) + catch (JsonException) { // ignore } @@ -974,12 +972,10 @@ JsonSerializerOptions options ); if (deserialized != null) { - deserialized.Validate(); return new(deserialized, element); } } - catch (System::Exception e) - when (e is JsonException || e is AnthropicInvalidDataException) + catch (JsonException) { // ignore } @@ -996,12 +992,10 @@ JsonSerializerOptions options ); if (deserialized != null) { - deserialized.Validate(); return new(deserialized, element); } } - catch (System::Exception e) - when (e is JsonException || e is AnthropicInvalidDataException) + catch (JsonException) { // ignore } @@ -1018,12 +1012,10 @@ JsonSerializerOptions options ); if (deserialized != null) { - deserialized.Validate(); return new(deserialized, element); } } - catch (System::Exception e) - when (e is JsonException || e is AnthropicInvalidDataException) + catch (JsonException) { // ignore } @@ -1040,12 +1032,10 @@ JsonSerializerOptions options ); if (deserialized != null) { - deserialized.Validate(); return new(deserialized, element); } } - catch (System::Exception e) - when (e is JsonException || e is AnthropicInvalidDataException) + catch (JsonException) { // ignore } @@ -1062,12 +1052,10 @@ JsonSerializerOptions options ); if (deserialized != null) { - deserialized.Validate(); return new(deserialized, element); } } - catch (System::Exception e) - when (e is JsonException || e is AnthropicInvalidDataException) + catch (JsonException) { // ignore } @@ -1084,12 +1072,10 @@ JsonSerializerOptions options ); if (deserialized != null) { - deserialized.Validate(); return new(deserialized, element); } } - catch (System::Exception e) - when (e is JsonException || e is AnthropicInvalidDataException) + catch (JsonException) { // ignore } @@ -1106,12 +1092,10 @@ JsonSerializerOptions options ); if (deserialized != null) { - deserialized.Validate(); return new(deserialized, element); } } - catch (System::Exception e) - when (e is JsonException || e is AnthropicInvalidDataException) + catch (JsonException) { // ignore } @@ -1129,12 +1113,10 @@ JsonSerializerOptions options ); if (deserialized != null) { - deserialized.Validate(); return new(deserialized, element); } } - catch (System::Exception e) - when (e is JsonException || e is AnthropicInvalidDataException) + catch (JsonException) { // ignore } @@ -1152,12 +1134,10 @@ JsonSerializerOptions options ); if (deserialized != null) { - deserialized.Validate(); return new(deserialized, element); } } - catch (System::Exception e) - when (e is JsonException || e is AnthropicInvalidDataException) + catch (JsonException) { // ignore } @@ -1174,12 +1154,10 @@ JsonSerializerOptions options ); if (deserialized != null) { - deserialized.Validate(); return new(deserialized, element); } } - catch (System::Exception e) - when (e is JsonException || e is AnthropicInvalidDataException) + catch (JsonException) { // ignore } @@ -1196,12 +1174,10 @@ JsonSerializerOptions options ); if (deserialized != null) { - deserialized.Validate(); return new(deserialized, element); } } - catch (System::Exception e) - when (e is JsonException || e is AnthropicInvalidDataException) + catch (JsonException) { // ignore } @@ -1218,12 +1194,10 @@ JsonSerializerOptions options ); if (deserialized != null) { - deserialized.Validate(); return new(deserialized, element); } } - catch (System::Exception e) - when (e is JsonException || e is AnthropicInvalidDataException) + catch (JsonException) { // ignore } @@ -1240,12 +1214,10 @@ JsonSerializerOptions options ); if (deserialized != null) { - deserialized.Validate(); return new(deserialized, element); } } - catch (System::Exception e) - when (e is JsonException || e is AnthropicInvalidDataException) + catch (JsonException) { // ignore } @@ -1262,12 +1234,10 @@ JsonSerializerOptions options ); if (deserialized != null) { - deserialized.Validate(); return new(deserialized, element); } } - catch (System::Exception e) - when (e is JsonException || e is AnthropicInvalidDataException) + catch (JsonException) { // ignore } diff --git a/src/Anthropic/Models/Beta/Messages/BetaRawMessageStreamEvent.cs b/src/Anthropic/Models/Beta/Messages/BetaRawMessageStreamEvent.cs index 7b673a2a8..7fba5620a 100644 --- a/src/Anthropic/Models/Beta/Messages/BetaRawMessageStreamEvent.cs +++ b/src/Anthropic/Models/Beta/Messages/BetaRawMessageStreamEvent.cs @@ -453,12 +453,10 @@ JsonSerializerOptions options ); if (deserialized != null) { - deserialized.Validate(); return new(deserialized, element); } } - catch (System::Exception e) - when (e is JsonException || e is AnthropicInvalidDataException) + catch (JsonException) { // ignore } @@ -475,12 +473,10 @@ JsonSerializerOptions options ); if (deserialized != null) { - deserialized.Validate(); return new(deserialized, element); } } - catch (System::Exception e) - when (e is JsonException || e is AnthropicInvalidDataException) + catch (JsonException) { // ignore } @@ -497,12 +493,10 @@ JsonSerializerOptions options ); if (deserialized != null) { - deserialized.Validate(); return new(deserialized, element); } } - catch (System::Exception e) - when (e is JsonException || e is AnthropicInvalidDataException) + catch (JsonException) { // ignore } @@ -519,12 +513,10 @@ JsonSerializerOptions options ); if (deserialized != null) { - deserialized.Validate(); return new(deserialized, element); } } - catch (System::Exception e) - when (e is JsonException || e is AnthropicInvalidDataException) + catch (JsonException) { // ignore } @@ -541,12 +533,10 @@ JsonSerializerOptions options ); if (deserialized != null) { - deserialized.Validate(); return new(deserialized, element); } } - catch (System::Exception e) - when (e is JsonException || e is AnthropicInvalidDataException) + catch (JsonException) { // ignore } @@ -563,12 +553,10 @@ JsonSerializerOptions options ); if (deserialized != null) { - deserialized.Validate(); return new(deserialized, element); } } - catch (System::Exception e) - when (e is JsonException || e is AnthropicInvalidDataException) + catch (JsonException) { // ignore } diff --git a/src/Anthropic/Models/Beta/Messages/BetaRequestDocumentBlock.cs b/src/Anthropic/Models/Beta/Messages/BetaRequestDocumentBlock.cs index c828d3386..d8f8d30a9 100644 --- a/src/Anthropic/Models/Beta/Messages/BetaRequestDocumentBlock.cs +++ b/src/Anthropic/Models/Beta/Messages/BetaRequestDocumentBlock.cs @@ -544,12 +544,10 @@ JsonSerializerOptions options ); if (deserialized != null) { - deserialized.Validate(); return new(deserialized, element); } } - catch (System::Exception e) - when (e is JsonException || e is AnthropicInvalidDataException) + catch (JsonException) { // ignore } @@ -566,12 +564,10 @@ JsonSerializerOptions options ); if (deserialized != null) { - deserialized.Validate(); return new(deserialized, element); } } - catch (System::Exception e) - when (e is JsonException || e is AnthropicInvalidDataException) + catch (JsonException) { // ignore } @@ -588,12 +584,10 @@ JsonSerializerOptions options ); if (deserialized != null) { - deserialized.Validate(); return new(deserialized, element); } } - catch (System::Exception e) - when (e is JsonException || e is AnthropicInvalidDataException) + catch (JsonException) { // ignore } @@ -610,12 +604,10 @@ JsonSerializerOptions options ); if (deserialized != null) { - deserialized.Validate(); return new(deserialized, element); } } - catch (System::Exception e) - when (e is JsonException || e is AnthropicInvalidDataException) + catch (JsonException) { // ignore } @@ -632,12 +624,10 @@ JsonSerializerOptions options ); if (deserialized != null) { - deserialized.Validate(); return new(deserialized, element); } } - catch (System::Exception e) - when (e is JsonException || e is AnthropicInvalidDataException) + catch (JsonException) { // ignore } diff --git a/src/Anthropic/Models/Beta/Messages/BetaServerToolUseBlock.cs b/src/Anthropic/Models/Beta/Messages/BetaServerToolUseBlock.cs index 25251006f..47894e4fd 100644 --- a/src/Anthropic/Models/Beta/Messages/BetaServerToolUseBlock.cs +++ b/src/Anthropic/Models/Beta/Messages/BetaServerToolUseBlock.cs @@ -495,12 +495,10 @@ JsonSerializerOptions options ); if (deserialized != null) { - deserialized.Validate(); return new(deserialized, element); } } - catch (System::Exception e) - when (e is JsonException || e is AnthropicInvalidDataException) + catch (JsonException) { // ignore } @@ -517,12 +515,10 @@ JsonSerializerOptions options ); if (deserialized != null) { - deserialized.Validate(); return new(deserialized, element); } } - catch (System::Exception e) - when (e is JsonException || e is AnthropicInvalidDataException) + catch (JsonException) { // ignore } @@ -539,12 +535,10 @@ JsonSerializerOptions options ); if (deserialized != null) { - deserialized.Validate(); return new(deserialized, element); } } - catch (System::Exception e) - when (e is JsonException || e is AnthropicInvalidDataException) + catch (JsonException) { // ignore } diff --git a/src/Anthropic/Models/Beta/Messages/BetaServerToolUseBlockParam.cs b/src/Anthropic/Models/Beta/Messages/BetaServerToolUseBlockParam.cs index 23cec54ae..5059bd024 100644 --- a/src/Anthropic/Models/Beta/Messages/BetaServerToolUseBlockParam.cs +++ b/src/Anthropic/Models/Beta/Messages/BetaServerToolUseBlockParam.cs @@ -534,12 +534,10 @@ JsonSerializerOptions options ); if (deserialized != null) { - deserialized.Validate(); return new(deserialized, element); } } - catch (System::Exception e) - when (e is JsonException || e is AnthropicInvalidDataException) + catch (JsonException) { // ignore } @@ -556,12 +554,10 @@ JsonSerializerOptions options ); if (deserialized != null) { - deserialized.Validate(); return new(deserialized, element); } } - catch (System::Exception e) - when (e is JsonException || e is AnthropicInvalidDataException) + catch (JsonException) { // ignore } @@ -578,12 +574,10 @@ JsonSerializerOptions options ); if (deserialized != null) { - deserialized.Validate(); return new(deserialized, element); } } - catch (System::Exception e) - when (e is JsonException || e is AnthropicInvalidDataException) + catch (JsonException) { // ignore } diff --git a/src/Anthropic/Models/Beta/Messages/BetaTextCitation.cs b/src/Anthropic/Models/Beta/Messages/BetaTextCitation.cs index d29f5ba56..8c5fcc120 100644 --- a/src/Anthropic/Models/Beta/Messages/BetaTextCitation.cs +++ b/src/Anthropic/Models/Beta/Messages/BetaTextCitation.cs @@ -484,12 +484,10 @@ JsonSerializerOptions options ); if (deserialized != null) { - deserialized.Validate(); return new(deserialized, element); } } - catch (System::Exception e) - when (e is JsonException || e is AnthropicInvalidDataException) + catch (JsonException) { // ignore } @@ -506,12 +504,10 @@ JsonSerializerOptions options ); if (deserialized != null) { - deserialized.Validate(); return new(deserialized, element); } } - catch (System::Exception e) - when (e is JsonException || e is AnthropicInvalidDataException) + catch (JsonException) { // ignore } @@ -528,12 +524,10 @@ JsonSerializerOptions options ); if (deserialized != null) { - deserialized.Validate(); return new(deserialized, element); } } - catch (System::Exception e) - when (e is JsonException || e is AnthropicInvalidDataException) + catch (JsonException) { // ignore } @@ -551,12 +545,10 @@ JsonSerializerOptions options ); if (deserialized != null) { - deserialized.Validate(); return new(deserialized, element); } } - catch (System::Exception e) - when (e is JsonException || e is AnthropicInvalidDataException) + catch (JsonException) { // ignore } @@ -573,12 +565,10 @@ JsonSerializerOptions options ); if (deserialized != null) { - deserialized.Validate(); return new(deserialized, element); } } - catch (System::Exception e) - when (e is JsonException || e is AnthropicInvalidDataException) + catch (JsonException) { // ignore } diff --git a/src/Anthropic/Models/Beta/Messages/BetaTextCitationParam.cs b/src/Anthropic/Models/Beta/Messages/BetaTextCitationParam.cs index b2c0248ab..76215018b 100644 --- a/src/Anthropic/Models/Beta/Messages/BetaTextCitationParam.cs +++ b/src/Anthropic/Models/Beta/Messages/BetaTextCitationParam.cs @@ -490,12 +490,10 @@ JsonSerializerOptions options ); if (deserialized != null) { - deserialized.Validate(); return new(deserialized, element); } } - catch (System::Exception e) - when (e is JsonException || e is AnthropicInvalidDataException) + catch (JsonException) { // ignore } @@ -512,12 +510,10 @@ JsonSerializerOptions options ); if (deserialized != null) { - deserialized.Validate(); return new(deserialized, element); } } - catch (System::Exception e) - when (e is JsonException || e is AnthropicInvalidDataException) + catch (JsonException) { // ignore } @@ -535,12 +531,10 @@ JsonSerializerOptions options ); if (deserialized != null) { - deserialized.Validate(); return new(deserialized, element); } } - catch (System::Exception e) - when (e is JsonException || e is AnthropicInvalidDataException) + catch (JsonException) { // ignore } @@ -558,12 +552,10 @@ JsonSerializerOptions options ); if (deserialized != null) { - deserialized.Validate(); return new(deserialized, element); } } - catch (System::Exception e) - when (e is JsonException || e is AnthropicInvalidDataException) + catch (JsonException) { // ignore } @@ -581,12 +573,10 @@ JsonSerializerOptions options ); if (deserialized != null) { - deserialized.Validate(); return new(deserialized, element); } } - catch (System::Exception e) - when (e is JsonException || e is AnthropicInvalidDataException) + catch (JsonException) { // ignore } diff --git a/src/Anthropic/Models/Beta/Messages/BetaThinkingConfigAdaptive.cs b/src/Anthropic/Models/Beta/Messages/BetaThinkingConfigAdaptive.cs index caea663f4..1d7aea752 100644 --- a/src/Anthropic/Models/Beta/Messages/BetaThinkingConfigAdaptive.cs +++ b/src/Anthropic/Models/Beta/Messages/BetaThinkingConfigAdaptive.cs @@ -5,6 +5,7 @@ using System.Text.Json.Serialization; using Anthropic.Core; using Anthropic.Exceptions; +using System = System; namespace Anthropic.Models.Beta.Messages; @@ -23,6 +24,21 @@ public JsonElement Type init { this._rawData.Set("type", value); } } + /// + /// Controls how thinking content appears in the response. When set to `summarized`, + /// thinking is returned normally. When set to `omitted`, thinking content is + /// redacted but a signature is returned for multi-turn continuity. Defaults to `summarized`. + /// + public ApiEnum? Display + { + get + { + this._rawData.Freeze(); + return this._rawData.GetNullableClass>("display"); + } + init { this._rawData.Set("display", value); } + } + /// public override void Validate() { @@ -30,6 +46,7 @@ public override void Validate() { throw new AnthropicInvalidDataException("Invalid value given for constant"); } + this.Display?.Validate(); } public BetaThinkingConfigAdaptive() @@ -74,3 +91,48 @@ public BetaThinkingConfigAdaptive FromRawUnchecked( IReadOnlyDictionary rawData ) => BetaThinkingConfigAdaptive.FromRawUnchecked(rawData); } + +/// +/// Controls how thinking content appears in the response. When set to `summarized`, +/// thinking is returned normally. When set to `omitted`, thinking content is redacted +/// but a signature is returned for multi-turn continuity. Defaults to `summarized`. +/// +[JsonConverter(typeof(DisplayConverter))] +public enum Display +{ + Summarized, + Omitted, +} + +sealed class DisplayConverter : JsonConverter +{ + public override Display Read( + ref Utf8JsonReader reader, + System::Type typeToConvert, + JsonSerializerOptions options + ) + { + return JsonSerializer.Deserialize(ref reader, options) switch + { + "summarized" => Display.Summarized, + "omitted" => Display.Omitted, + _ => (Display)(-1), + }; + } + + public override void Write(Utf8JsonWriter writer, Display value, JsonSerializerOptions options) + { + JsonSerializer.Serialize( + writer, + value switch + { + Display.Summarized => "summarized", + Display.Omitted => "omitted", + _ => throw new AnthropicInvalidDataException( + string.Format("Invalid value '{0}' in {1}", value, nameof(value)) + ), + }, + options + ); + } +} diff --git a/src/Anthropic/Models/Beta/Messages/BetaThinkingConfigEnabled.cs b/src/Anthropic/Models/Beta/Messages/BetaThinkingConfigEnabled.cs index ba4cdf2fa..bee0f3baa 100644 --- a/src/Anthropic/Models/Beta/Messages/BetaThinkingConfigEnabled.cs +++ b/src/Anthropic/Models/Beta/Messages/BetaThinkingConfigEnabled.cs @@ -5,6 +5,7 @@ using System.Text.Json.Serialization; using Anthropic.Core; using Anthropic.Exceptions; +using System = System; namespace Anthropic.Models.Beta.Messages; @@ -43,6 +44,23 @@ public JsonElement Type init { this._rawData.Set("type", value); } } + /// + /// Controls how thinking content appears in the response. When set to `summarized`, + /// thinking is returned normally. When set to `omitted`, thinking content is + /// redacted but a signature is returned for multi-turn continuity. Defaults to `summarized`. + /// + public ApiEnum? Display + { + get + { + this._rawData.Freeze(); + return this._rawData.GetNullableClass< + ApiEnum + >("display"); + } + init { this._rawData.Set("display", value); } + } + /// public override void Validate() { @@ -51,6 +69,7 @@ public override void Validate() { throw new AnthropicInvalidDataException("Invalid value given for constant"); } + this.Display?.Validate(); } public BetaThinkingConfigEnabled() @@ -102,3 +121,53 @@ public BetaThinkingConfigEnabled FromRawUnchecked( IReadOnlyDictionary rawData ) => BetaThinkingConfigEnabled.FromRawUnchecked(rawData); } + +/// +/// Controls how thinking content appears in the response. When set to `summarized`, +/// thinking is returned normally. When set to `omitted`, thinking content is redacted +/// but a signature is returned for multi-turn continuity. Defaults to `summarized`. +/// +[JsonConverter(typeof(BetaThinkingConfigEnabledDisplayConverter))] +public enum BetaThinkingConfigEnabledDisplay +{ + Summarized, + Omitted, +} + +sealed class BetaThinkingConfigEnabledDisplayConverter + : JsonConverter +{ + public override BetaThinkingConfigEnabledDisplay Read( + ref Utf8JsonReader reader, + System::Type typeToConvert, + JsonSerializerOptions options + ) + { + return JsonSerializer.Deserialize(ref reader, options) switch + { + "summarized" => BetaThinkingConfigEnabledDisplay.Summarized, + "omitted" => BetaThinkingConfigEnabledDisplay.Omitted, + _ => (BetaThinkingConfigEnabledDisplay)(-1), + }; + } + + public override void Write( + Utf8JsonWriter writer, + BetaThinkingConfigEnabledDisplay value, + JsonSerializerOptions options + ) + { + JsonSerializer.Serialize( + writer, + value switch + { + BetaThinkingConfigEnabledDisplay.Summarized => "summarized", + BetaThinkingConfigEnabledDisplay.Omitted => "omitted", + _ => throw new AnthropicInvalidDataException( + string.Format("Invalid value '{0}' in {1}", value, nameof(value)) + ), + }, + options + ); + } +} diff --git a/src/Anthropic/Models/Beta/Messages/BetaThinkingConfigParam.cs b/src/Anthropic/Models/Beta/Messages/BetaThinkingConfigParam.cs index 139cf183b..4a6c61e6c 100644 --- a/src/Anthropic/Models/Beta/Messages/BetaThinkingConfigParam.cs +++ b/src/Anthropic/Models/Beta/Messages/BetaThinkingConfigParam.cs @@ -306,12 +306,10 @@ JsonSerializerOptions options ); if (deserialized != null) { - deserialized.Validate(); return new(deserialized, element); } } - catch (System::Exception e) - when (e is JsonException || e is AnthropicInvalidDataException) + catch (JsonException) { // ignore } @@ -328,12 +326,10 @@ JsonSerializerOptions options ); if (deserialized != null) { - deserialized.Validate(); return new(deserialized, element); } } - catch (System::Exception e) - when (e is JsonException || e is AnthropicInvalidDataException) + catch (JsonException) { // ignore } @@ -350,12 +346,10 @@ JsonSerializerOptions options ); if (deserialized != null) { - deserialized.Validate(); return new(deserialized, element); } } - catch (System::Exception e) - when (e is JsonException || e is AnthropicInvalidDataException) + catch (JsonException) { // ignore } diff --git a/src/Anthropic/Models/Beta/Messages/BetaTool.cs b/src/Anthropic/Models/Beta/Messages/BetaTool.cs index 66d4a9158..8582e1cbe 100644 --- a/src/Anthropic/Models/Beta/Messages/BetaTool.cs +++ b/src/Anthropic/Models/Beta/Messages/BetaTool.cs @@ -364,6 +364,13 @@ public InputSchema FromRawUnchecked(IReadOnlyDictionary raw InputSchema.FromRawUnchecked(rawData); } +/// +/// Specifies who can invoke a tool. +/// +/// Values: direct: The model can call this tool directly. code_execution_20250825: +/// The tool can be called from the code execution environment (v1). code_execution_20260120: +/// The tool can be called from the code execution environment (v2 with persistence). +/// [JsonConverter(typeof(BetaToolAllowedCallerConverter))] public enum BetaToolAllowedCaller { diff --git a/src/Anthropic/Models/Beta/Messages/BetaToolBash20241022.cs b/src/Anthropic/Models/Beta/Messages/BetaToolBash20241022.cs index e6a07d604..a53cb9bbc 100644 --- a/src/Anthropic/Models/Beta/Messages/BetaToolBash20241022.cs +++ b/src/Anthropic/Models/Beta/Messages/BetaToolBash20241022.cs @@ -214,6 +214,13 @@ IReadOnlyDictionary rawData ) => BetaToolBash20241022.FromRawUnchecked(rawData); } +/// +/// Specifies who can invoke a tool. +/// +/// Values: direct: The model can call this tool directly. code_execution_20250825: +/// The tool can be called from the code execution environment (v1). code_execution_20260120: +/// The tool can be called from the code execution environment (v2 with persistence). +/// [JsonConverter(typeof(BetaToolBash20241022AllowedCallerConverter))] public enum BetaToolBash20241022AllowedCaller { diff --git a/src/Anthropic/Models/Beta/Messages/BetaToolBash20250124.cs b/src/Anthropic/Models/Beta/Messages/BetaToolBash20250124.cs index 24a431497..976db88c5 100644 --- a/src/Anthropic/Models/Beta/Messages/BetaToolBash20250124.cs +++ b/src/Anthropic/Models/Beta/Messages/BetaToolBash20250124.cs @@ -214,6 +214,13 @@ IReadOnlyDictionary rawData ) => BetaToolBash20250124.FromRawUnchecked(rawData); } +/// +/// Specifies who can invoke a tool. +/// +/// Values: direct: The model can call this tool directly. code_execution_20250825: +/// The tool can be called from the code execution environment (v1). code_execution_20260120: +/// The tool can be called from the code execution environment (v2 with persistence). +/// [JsonConverter(typeof(BetaToolBash20250124AllowedCallerConverter))] public enum BetaToolBash20250124AllowedCaller { diff --git a/src/Anthropic/Models/Beta/Messages/BetaToolChoice.cs b/src/Anthropic/Models/Beta/Messages/BetaToolChoice.cs index 0b09546b1..6787b3a25 100644 --- a/src/Anthropic/Models/Beta/Messages/BetaToolChoice.cs +++ b/src/Anthropic/Models/Beta/Messages/BetaToolChoice.cs @@ -354,12 +354,10 @@ JsonSerializerOptions options ); if (deserialized != null) { - deserialized.Validate(); return new(deserialized, element); } } - catch (System::Exception e) - when (e is JsonException || e is AnthropicInvalidDataException) + catch (JsonException) { // ignore } @@ -376,12 +374,10 @@ JsonSerializerOptions options ); if (deserialized != null) { - deserialized.Validate(); return new(deserialized, element); } } - catch (System::Exception e) - when (e is JsonException || e is AnthropicInvalidDataException) + catch (JsonException) { // ignore } @@ -398,12 +394,10 @@ JsonSerializerOptions options ); if (deserialized != null) { - deserialized.Validate(); return new(deserialized, element); } } - catch (System::Exception e) - when (e is JsonException || e is AnthropicInvalidDataException) + catch (JsonException) { // ignore } @@ -420,12 +414,10 @@ JsonSerializerOptions options ); if (deserialized != null) { - deserialized.Validate(); return new(deserialized, element); } } - catch (System::Exception e) - when (e is JsonException || e is AnthropicInvalidDataException) + catch (JsonException) { // ignore } diff --git a/src/Anthropic/Models/Beta/Messages/BetaToolComputerUse20241022.cs b/src/Anthropic/Models/Beta/Messages/BetaToolComputerUse20241022.cs index 46c32d322..ec7783237 100644 --- a/src/Anthropic/Models/Beta/Messages/BetaToolComputerUse20241022.cs +++ b/src/Anthropic/Models/Beta/Messages/BetaToolComputerUse20241022.cs @@ -262,6 +262,13 @@ IReadOnlyDictionary rawData ) => BetaToolComputerUse20241022.FromRawUnchecked(rawData); } +/// +/// Specifies who can invoke a tool. +/// +/// Values: direct: The model can call this tool directly. code_execution_20250825: +/// The tool can be called from the code execution environment (v1). code_execution_20260120: +/// The tool can be called from the code execution environment (v2 with persistence). +/// [JsonConverter(typeof(BetaToolComputerUse20241022AllowedCallerConverter))] public enum BetaToolComputerUse20241022AllowedCaller { diff --git a/src/Anthropic/Models/Beta/Messages/BetaToolComputerUse20250124.cs b/src/Anthropic/Models/Beta/Messages/BetaToolComputerUse20250124.cs index cd17fbe95..34365b304 100644 --- a/src/Anthropic/Models/Beta/Messages/BetaToolComputerUse20250124.cs +++ b/src/Anthropic/Models/Beta/Messages/BetaToolComputerUse20250124.cs @@ -262,6 +262,13 @@ IReadOnlyDictionary rawData ) => BetaToolComputerUse20250124.FromRawUnchecked(rawData); } +/// +/// Specifies who can invoke a tool. +/// +/// Values: direct: The model can call this tool directly. code_execution_20250825: +/// The tool can be called from the code execution environment (v1). code_execution_20260120: +/// The tool can be called from the code execution environment (v2 with persistence). +/// [JsonConverter(typeof(BetaToolComputerUse20250124AllowedCallerConverter))] public enum BetaToolComputerUse20250124AllowedCaller { diff --git a/src/Anthropic/Models/Beta/Messages/BetaToolComputerUse20251124.cs b/src/Anthropic/Models/Beta/Messages/BetaToolComputerUse20251124.cs index 935d7ff7f..2bb95e1b1 100644 --- a/src/Anthropic/Models/Beta/Messages/BetaToolComputerUse20251124.cs +++ b/src/Anthropic/Models/Beta/Messages/BetaToolComputerUse20251124.cs @@ -284,6 +284,13 @@ IReadOnlyDictionary rawData ) => BetaToolComputerUse20251124.FromRawUnchecked(rawData); } +/// +/// Specifies who can invoke a tool. +/// +/// Values: direct: The model can call this tool directly. code_execution_20250825: +/// The tool can be called from the code execution environment (v1). code_execution_20260120: +/// The tool can be called from the code execution environment (v2 with persistence). +/// [JsonConverter(typeof(BetaToolComputerUse20251124AllowedCallerConverter))] public enum BetaToolComputerUse20251124AllowedCaller { diff --git a/src/Anthropic/Models/Beta/Messages/BetaToolResultBlockParam.cs b/src/Anthropic/Models/Beta/Messages/BetaToolResultBlockParam.cs index f0d5d05b1..e73010e68 100644 --- a/src/Anthropic/Models/Beta/Messages/BetaToolResultBlockParam.cs +++ b/src/Anthropic/Models/Beta/Messages/BetaToolResultBlockParam.cs @@ -808,12 +808,10 @@ JsonSerializerOptions options ); if (deserialized != null) { - deserialized.Validate(); return new(deserialized, element); } } - catch (System::Exception e) - when (e is JsonException || e is AnthropicInvalidDataException) + catch (JsonException) { // ignore } @@ -830,12 +828,10 @@ JsonSerializerOptions options ); if (deserialized != null) { - deserialized.Validate(); return new(deserialized, element); } } - catch (System::Exception e) - when (e is JsonException || e is AnthropicInvalidDataException) + catch (JsonException) { // ignore } @@ -852,12 +848,10 @@ JsonSerializerOptions options ); if (deserialized != null) { - deserialized.Validate(); return new(deserialized, element); } } - catch (System::Exception e) - when (e is JsonException || e is AnthropicInvalidDataException) + catch (JsonException) { // ignore } @@ -874,12 +868,10 @@ JsonSerializerOptions options ); if (deserialized != null) { - deserialized.Validate(); return new(deserialized, element); } } - catch (System::Exception e) - when (e is JsonException || e is AnthropicInvalidDataException) + catch (JsonException) { // ignore } @@ -896,12 +888,10 @@ JsonSerializerOptions options ); if (deserialized != null) { - deserialized.Validate(); return new(deserialized, element); } } - catch (System::Exception e) - when (e is JsonException || e is AnthropicInvalidDataException) + catch (JsonException) { // ignore } diff --git a/src/Anthropic/Models/Beta/Messages/BetaToolSearchToolBm25_20251119.cs b/src/Anthropic/Models/Beta/Messages/BetaToolSearchToolBm25_20251119.cs index 54f41844a..66c94edc3 100644 --- a/src/Anthropic/Models/Beta/Messages/BetaToolSearchToolBm25_20251119.cs +++ b/src/Anthropic/Models/Beta/Messages/BetaToolSearchToolBm25_20251119.cs @@ -248,6 +248,13 @@ JsonSerializerOptions options } } +/// +/// Specifies who can invoke a tool. +/// +/// Values: direct: The model can call this tool directly. code_execution_20250825: +/// The tool can be called from the code execution environment (v1). code_execution_20260120: +/// The tool can be called from the code execution environment (v2 with persistence). +/// [JsonConverter(typeof(BetaToolSearchToolBm25_20251119AllowedCallerConverter))] public enum BetaToolSearchToolBm25_20251119AllowedCaller { diff --git a/src/Anthropic/Models/Beta/Messages/BetaToolSearchToolRegex20251119.cs b/src/Anthropic/Models/Beta/Messages/BetaToolSearchToolRegex20251119.cs index 2c74b391c..0c2d50d1b 100644 --- a/src/Anthropic/Models/Beta/Messages/BetaToolSearchToolRegex20251119.cs +++ b/src/Anthropic/Models/Beta/Messages/BetaToolSearchToolRegex20251119.cs @@ -248,6 +248,13 @@ JsonSerializerOptions options } } +/// +/// Specifies who can invoke a tool. +/// +/// Values: direct: The model can call this tool directly. code_execution_20250825: +/// The tool can be called from the code execution environment (v1). code_execution_20260120: +/// The tool can be called from the code execution environment (v2 with persistence). +/// [JsonConverter(typeof(BetaToolSearchToolRegex20251119AllowedCallerConverter))] public enum BetaToolSearchToolRegex20251119AllowedCaller { diff --git a/src/Anthropic/Models/Beta/Messages/BetaToolTextEditor20241022.cs b/src/Anthropic/Models/Beta/Messages/BetaToolTextEditor20241022.cs index acc33dc2e..fd92809ac 100644 --- a/src/Anthropic/Models/Beta/Messages/BetaToolTextEditor20241022.cs +++ b/src/Anthropic/Models/Beta/Messages/BetaToolTextEditor20241022.cs @@ -225,6 +225,13 @@ IReadOnlyDictionary rawData ) => BetaToolTextEditor20241022.FromRawUnchecked(rawData); } +/// +/// Specifies who can invoke a tool. +/// +/// Values: direct: The model can call this tool directly. code_execution_20250825: +/// The tool can be called from the code execution environment (v1). code_execution_20260120: +/// The tool can be called from the code execution environment (v2 with persistence). +/// [JsonConverter(typeof(BetaToolTextEditor20241022AllowedCallerConverter))] public enum BetaToolTextEditor20241022AllowedCaller { diff --git a/src/Anthropic/Models/Beta/Messages/BetaToolTextEditor20250124.cs b/src/Anthropic/Models/Beta/Messages/BetaToolTextEditor20250124.cs index 21307b399..2a1fe21c3 100644 --- a/src/Anthropic/Models/Beta/Messages/BetaToolTextEditor20250124.cs +++ b/src/Anthropic/Models/Beta/Messages/BetaToolTextEditor20250124.cs @@ -225,6 +225,13 @@ IReadOnlyDictionary rawData ) => BetaToolTextEditor20250124.FromRawUnchecked(rawData); } +/// +/// Specifies who can invoke a tool. +/// +/// Values: direct: The model can call this tool directly. code_execution_20250825: +/// The tool can be called from the code execution environment (v1). code_execution_20260120: +/// The tool can be called from the code execution environment (v2 with persistence). +/// [JsonConverter(typeof(BetaToolTextEditor20250124AllowedCallerConverter))] public enum BetaToolTextEditor20250124AllowedCaller { diff --git a/src/Anthropic/Models/Beta/Messages/BetaToolTextEditor20250429.cs b/src/Anthropic/Models/Beta/Messages/BetaToolTextEditor20250429.cs index c17fc1088..e2d7a51d1 100644 --- a/src/Anthropic/Models/Beta/Messages/BetaToolTextEditor20250429.cs +++ b/src/Anthropic/Models/Beta/Messages/BetaToolTextEditor20250429.cs @@ -225,6 +225,13 @@ IReadOnlyDictionary rawData ) => BetaToolTextEditor20250429.FromRawUnchecked(rawData); } +/// +/// Specifies who can invoke a tool. +/// +/// Values: direct: The model can call this tool directly. code_execution_20250825: +/// The tool can be called from the code execution environment (v1). code_execution_20260120: +/// The tool can be called from the code execution environment (v2 with persistence). +/// [JsonConverter(typeof(BetaToolTextEditor20250429AllowedCallerConverter))] public enum BetaToolTextEditor20250429AllowedCaller { diff --git a/src/Anthropic/Models/Beta/Messages/BetaToolTextEditor20250728.cs b/src/Anthropic/Models/Beta/Messages/BetaToolTextEditor20250728.cs index 905259ea6..7f8317f40 100644 --- a/src/Anthropic/Models/Beta/Messages/BetaToolTextEditor20250728.cs +++ b/src/Anthropic/Models/Beta/Messages/BetaToolTextEditor20250728.cs @@ -240,6 +240,13 @@ IReadOnlyDictionary rawData ) => BetaToolTextEditor20250728.FromRawUnchecked(rawData); } +/// +/// Specifies who can invoke a tool. +/// +/// Values: direct: The model can call this tool directly. code_execution_20250825: +/// The tool can be called from the code execution environment (v1). code_execution_20260120: +/// The tool can be called from the code execution environment (v2 with persistence). +/// [JsonConverter(typeof(BetaToolTextEditor20250728AllowedCallerConverter))] public enum BetaToolTextEditor20250728AllowedCaller { diff --git a/src/Anthropic/Models/Beta/Messages/BetaToolUnion.cs b/src/Anthropic/Models/Beta/Messages/BetaToolUnion.cs index 18ca74f1b..32dde90c3 100644 --- a/src/Anthropic/Models/Beta/Messages/BetaToolUnion.cs +++ b/src/Anthropic/Models/Beta/Messages/BetaToolUnion.cs @@ -51,6 +51,7 @@ public BetaCacheControlEphemeral? CacheControl webFetchTool20250910: (x) => x.CacheControl, webSearchTool20260209: (x) => x.CacheControl, webFetchTool20260209: (x) => x.CacheControl, + webFetchTool20260309: (x) => x.CacheControl, searchToolBm25_20251119: (x) => x.CacheControl, searchToolRegex20251119: (x) => x.CacheControl, mcpToolset: (x) => x.CacheControl @@ -81,6 +82,7 @@ public bool? DeferLoading webFetchTool20250910: (x) => x.DeferLoading, webSearchTool20260209: (x) => x.DeferLoading, webFetchTool20260209: (x) => x.DeferLoading, + webFetchTool20260309: (x) => x.DeferLoading, searchToolBm25_20251119: (x) => x.DeferLoading, searchToolRegex20251119: (x) => x.DeferLoading, mcpToolset: (_) => null @@ -111,6 +113,7 @@ public bool? Strict webFetchTool20250910: (x) => x.Strict, webSearchTool20260209: (x) => x.Strict, webFetchTool20260209: (x) => x.Strict, + webFetchTool20260309: (x) => x.Strict, searchToolBm25_20251119: (x) => x.Strict, searchToolRegex20251119: (x) => x.Strict, mcpToolset: (_) => null @@ -141,6 +144,7 @@ public long? DisplayHeightPx webFetchTool20250910: (_) => null, webSearchTool20260209: (_) => null, webFetchTool20260209: (_) => null, + webFetchTool20260309: (_) => null, searchToolBm25_20251119: (_) => null, searchToolRegex20251119: (_) => null, mcpToolset: (_) => null @@ -171,6 +175,7 @@ public long? DisplayWidthPx webFetchTool20250910: (_) => null, webSearchTool20260209: (_) => null, webFetchTool20260209: (_) => null, + webFetchTool20260309: (_) => null, searchToolBm25_20251119: (_) => null, searchToolRegex20251119: (_) => null, mcpToolset: (_) => null @@ -201,6 +206,7 @@ public long? DisplayNumber webFetchTool20250910: (_) => null, webSearchTool20260209: (_) => null, webFetchTool20260209: (_) => null, + webFetchTool20260309: (_) => null, searchToolBm25_20251119: (_) => null, searchToolRegex20251119: (_) => null, mcpToolset: (_) => null @@ -231,6 +237,7 @@ public long? MaxUses webFetchTool20250910: (x) => x.MaxUses, webSearchTool20260209: (x) => x.MaxUses, webFetchTool20260209: (x) => x.MaxUses, + webFetchTool20260309: (x) => x.MaxUses, searchToolBm25_20251119: (_) => null, searchToolRegex20251119: (_) => null, mcpToolset: (_) => null @@ -261,6 +268,7 @@ public BetaUserLocation? UserLocation webFetchTool20250910: (_) => null, webSearchTool20260209: (x) => x.UserLocation, webFetchTool20260209: (_) => null, + webFetchTool20260309: (_) => null, searchToolBm25_20251119: (_) => null, searchToolRegex20251119: (_) => null, mcpToolset: (_) => null @@ -291,6 +299,7 @@ public BetaCitationsConfigParam? Citations webFetchTool20250910: (x) => x.Citations, webSearchTool20260209: (_) => null, webFetchTool20260209: (x) => x.Citations, + webFetchTool20260309: (x) => x.Citations, searchToolBm25_20251119: (_) => null, searchToolRegex20251119: (_) => null, mcpToolset: (_) => null @@ -321,6 +330,7 @@ public long? MaxContentTokens webFetchTool20250910: (x) => x.MaxContentTokens, webSearchTool20260209: (_) => null, webFetchTool20260209: (x) => x.MaxContentTokens, + webFetchTool20260309: (x) => x.MaxContentTokens, searchToolBm25_20251119: (_) => null, searchToolRegex20251119: (_) => null, mcpToolset: (_) => null @@ -436,6 +446,12 @@ public BetaToolUnion(BetaWebFetchTool20260209 value, JsonElement? element = null this._element = element; } + public BetaToolUnion(BetaWebFetchTool20260309 value, JsonElement? element = null) + { + this.Value = value; + this._element = element; + } + public BetaToolUnion(BetaToolSearchToolBm25_20251119 value, JsonElement? element = null) { this.Value = value; @@ -853,6 +869,27 @@ public bool TryPickWebFetchTool20260209([NotNullWhen(true)] out BetaWebFetchTool return value != null; } + /// + /// Returns true and sets the out parameter if the instance was constructed with a variant of + /// type . + /// + /// Consider using or if you need to handle every variant. + /// + /// + /// + /// if (instance.TryPickWebFetchTool20260309(out var value)) { + /// // `value` is of type `BetaWebFetchTool20260309` + /// Console.WriteLine(value); + /// } + /// + /// + /// + public bool TryPickWebFetchTool20260309([NotNullWhen(true)] out BetaWebFetchTool20260309? value) + { + value = this.Value as BetaWebFetchTool20260309; + return value != null; + } + /// /// Returns true and sets the out parameter if the instance was constructed with a variant of /// type . @@ -952,6 +989,7 @@ public bool TryPickMcpToolset([NotNullWhen(true)] out BetaMcpToolset? value) /// (BetaWebFetchTool20250910 value) => {...}, /// (BetaWebSearchTool20260209 value) => {...}, /// (BetaWebFetchTool20260209 value) => {...}, + /// (BetaWebFetchTool20260309 value) => {...}, /// (BetaToolSearchToolBm25_20251119 value) => {...}, /// (BetaToolSearchToolRegex20251119 value) => {...}, /// (BetaMcpToolset value) => {...} @@ -978,6 +1016,7 @@ public void Switch( System::Action webFetchTool20250910, System::Action webSearchTool20260209, System::Action webFetchTool20260209, + System::Action webFetchTool20260309, System::Action searchToolBm25_20251119, System::Action searchToolRegex20251119, System::Action mcpToolset @@ -1039,6 +1078,9 @@ public void Switch( case BetaWebFetchTool20260209 value: webFetchTool20260209(value); break; + case BetaWebFetchTool20260309 value: + webFetchTool20260309(value); + break; case BetaToolSearchToolBm25_20251119 value: searchToolBm25_20251119(value); break; @@ -1088,6 +1130,7 @@ public void Switch( /// (BetaWebFetchTool20250910 value) => {...}, /// (BetaWebSearchTool20260209 value) => {...}, /// (BetaWebFetchTool20260209 value) => {...}, + /// (BetaWebFetchTool20260309 value) => {...}, /// (BetaToolSearchToolBm25_20251119 value) => {...}, /// (BetaToolSearchToolRegex20251119 value) => {...}, /// (BetaMcpToolset value) => {...} @@ -1114,6 +1157,7 @@ public T Match( System::Func webFetchTool20250910, System::Func webSearchTool20260209, System::Func webFetchTool20260209, + System::Func webFetchTool20260309, System::Func searchToolBm25_20251119, System::Func searchToolRegex20251119, System::Func mcpToolset @@ -1139,6 +1183,7 @@ public T Match( BetaWebFetchTool20250910 value => webFetchTool20250910(value), BetaWebSearchTool20260209 value => webSearchTool20260209(value), BetaWebFetchTool20260209 value => webFetchTool20260209(value), + BetaWebFetchTool20260309 value => webFetchTool20260309(value), BetaToolSearchToolBm25_20251119 value => searchToolBm25_20251119(value), BetaToolSearchToolRegex20251119 value => searchToolRegex20251119(value), BetaMcpToolset value => mcpToolset(value), @@ -1187,6 +1232,8 @@ public static implicit operator BetaToolUnion(BetaCodeExecutionTool20260120 valu public static implicit operator BetaToolUnion(BetaWebFetchTool20260209 value) => new(value); + public static implicit operator BetaToolUnion(BetaWebFetchTool20260309 value) => new(value); + public static implicit operator BetaToolUnion(BetaToolSearchToolBm25_20251119 value) => new(value); @@ -1232,6 +1279,7 @@ public override void Validate() (webFetchTool20250910) => webFetchTool20250910.Validate(), (webSearchTool20260209) => webSearchTool20260209.Validate(), (webFetchTool20260209) => webFetchTool20260209.Validate(), + (webFetchTool20260309) => webFetchTool20260309.Validate(), (searchToolBm25_20251119) => searchToolBm25_20251119.Validate(), (searchToolRegex20251119) => searchToolRegex20251119.Validate(), (mcpToolset) => mcpToolset.Validate() @@ -1276,9 +1324,10 @@ int VariantIndex() BetaWebFetchTool20250910 _ => 15, BetaWebSearchTool20260209 _ => 16, BetaWebFetchTool20260209 _ => 17, - BetaToolSearchToolBm25_20251119 _ => 18, - BetaToolSearchToolRegex20251119 _ => 19, - BetaMcpToolset _ => 20, + BetaWebFetchTool20260309 _ => 18, + BetaToolSearchToolBm25_20251119 _ => 19, + BetaToolSearchToolRegex20251119 _ => 20, + BetaMcpToolset _ => 21, _ => -1, }; } @@ -1587,6 +1636,23 @@ JsonSerializerOptions options // ignore } + try + { + var deserialized = JsonSerializer.Deserialize( + element, + options + ); + if (deserialized != null) + { + deserialized.Validate(); + return new(deserialized, element); + } + } + catch (System::Exception e) when (e is JsonException || e is AnthropicInvalidDataException) + { + // ignore + } + try { var deserialized = JsonSerializer.Deserialize( diff --git a/src/Anthropic/Models/Beta/Messages/BetaToolUseBlock.cs b/src/Anthropic/Models/Beta/Messages/BetaToolUseBlock.cs index d17f7bd3c..58ced06ec 100644 --- a/src/Anthropic/Models/Beta/Messages/BetaToolUseBlock.cs +++ b/src/Anthropic/Models/Beta/Messages/BetaToolUseBlock.cs @@ -443,12 +443,10 @@ JsonSerializerOptions options ); if (deserialized != null) { - deserialized.Validate(); return new(deserialized, element); } } - catch (System::Exception e) - when (e is JsonException || e is AnthropicInvalidDataException) + catch (JsonException) { // ignore } @@ -465,12 +463,10 @@ JsonSerializerOptions options ); if (deserialized != null) { - deserialized.Validate(); return new(deserialized, element); } } - catch (System::Exception e) - when (e is JsonException || e is AnthropicInvalidDataException) + catch (JsonException) { // ignore } @@ -487,12 +483,10 @@ JsonSerializerOptions options ); if (deserialized != null) { - deserialized.Validate(); return new(deserialized, element); } } - catch (System::Exception e) - when (e is JsonException || e is AnthropicInvalidDataException) + catch (JsonException) { // ignore } diff --git a/src/Anthropic/Models/Beta/Messages/BetaToolUseBlockParam.cs b/src/Anthropic/Models/Beta/Messages/BetaToolUseBlockParam.cs index edbdeb35c..8e6ba908d 100644 --- a/src/Anthropic/Models/Beta/Messages/BetaToolUseBlockParam.cs +++ b/src/Anthropic/Models/Beta/Messages/BetaToolUseBlockParam.cs @@ -463,12 +463,10 @@ JsonSerializerOptions options ); if (deserialized != null) { - deserialized.Validate(); return new(deserialized, element); } } - catch (System::Exception e) - when (e is JsonException || e is AnthropicInvalidDataException) + catch (JsonException) { // ignore } @@ -485,12 +483,10 @@ JsonSerializerOptions options ); if (deserialized != null) { - deserialized.Validate(); return new(deserialized, element); } } - catch (System::Exception e) - when (e is JsonException || e is AnthropicInvalidDataException) + catch (JsonException) { // ignore } @@ -507,12 +503,10 @@ JsonSerializerOptions options ); if (deserialized != null) { - deserialized.Validate(); return new(deserialized, element); } } - catch (System::Exception e) - when (e is JsonException || e is AnthropicInvalidDataException) + catch (JsonException) { // ignore } diff --git a/src/Anthropic/Models/Beta/Messages/BetaWebFetchTool20250910.cs b/src/Anthropic/Models/Beta/Messages/BetaWebFetchTool20250910.cs index 0be72cde4..32d79c9e0 100644 --- a/src/Anthropic/Models/Beta/Messages/BetaWebFetchTool20250910.cs +++ b/src/Anthropic/Models/Beta/Messages/BetaWebFetchTool20250910.cs @@ -271,6 +271,13 @@ IReadOnlyDictionary rawData ) => BetaWebFetchTool20250910.FromRawUnchecked(rawData); } +/// +/// Specifies who can invoke a tool. +/// +/// Values: direct: The model can call this tool directly. code_execution_20250825: +/// The tool can be called from the code execution environment (v1). code_execution_20260120: +/// The tool can be called from the code execution environment (v2 with persistence). +/// [JsonConverter(typeof(BetaWebFetchTool20250910AllowedCallerConverter))] public enum BetaWebFetchTool20250910AllowedCaller { diff --git a/src/Anthropic/Models/Beta/Messages/BetaWebFetchTool20260209.cs b/src/Anthropic/Models/Beta/Messages/BetaWebFetchTool20260209.cs index 0f2593990..f9b1ee28e 100644 --- a/src/Anthropic/Models/Beta/Messages/BetaWebFetchTool20260209.cs +++ b/src/Anthropic/Models/Beta/Messages/BetaWebFetchTool20260209.cs @@ -271,6 +271,13 @@ IReadOnlyDictionary rawData ) => BetaWebFetchTool20260209.FromRawUnchecked(rawData); } +/// +/// Specifies who can invoke a tool. +/// +/// Values: direct: The model can call this tool directly. code_execution_20250825: +/// The tool can be called from the code execution environment (v1). code_execution_20260120: +/// The tool can be called from the code execution environment (v2 with persistence). +/// [JsonConverter(typeof(BetaWebFetchTool20260209AllowedCallerConverter))] public enum BetaWebFetchTool20260209AllowedCaller { diff --git a/src/Anthropic/Models/Beta/Messages/BetaWebFetchTool20260309.cs b/src/Anthropic/Models/Beta/Messages/BetaWebFetchTool20260309.cs new file mode 100644 index 000000000..ba0df1943 --- /dev/null +++ b/src/Anthropic/Models/Beta/Messages/BetaWebFetchTool20260309.cs @@ -0,0 +1,358 @@ +using System.Collections.Frozen; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Diagnostics.CodeAnalysis; +using System.Text.Json; +using System.Text.Json.Serialization; +using Anthropic.Core; +using Anthropic.Exceptions; +using System = System; + +namespace Anthropic.Models.Beta.Messages; + +/// +/// Web fetch tool with use_cache parameter for bypassing cached content. +/// +[JsonConverter( + typeof(JsonModelConverter) +)] +public sealed record class BetaWebFetchTool20260309 : JsonModel +{ + /// + /// Name of the tool. + /// + /// This is how the tool will be called by the model and in `tool_use` blocks. + /// + public JsonElement Name + { + get + { + this._rawData.Freeze(); + return this._rawData.GetNotNullStruct("name"); + } + init { this._rawData.Set("name", value); } + } + + public JsonElement Type + { + get + { + this._rawData.Freeze(); + return this._rawData.GetNotNullStruct("type"); + } + init { this._rawData.Set("type", value); } + } + + public IReadOnlyList>? AllowedCallers + { + get + { + this._rawData.Freeze(); + return this._rawData.GetNullableStruct< + ImmutableArray> + >("allowed_callers"); + } + init + { + if (value == null) + { + return; + } + + this._rawData.Set + >?>("allowed_callers", value == null ? null : ImmutableArray.ToImmutableArray(value)); + } + } + + /// + /// List of domains to allow fetching from + /// + public IReadOnlyList? AllowedDomains + { + get + { + this._rawData.Freeze(); + return this._rawData.GetNullableStruct>("allowed_domains"); + } + init + { + this._rawData.Set?>( + "allowed_domains", + value == null ? null : ImmutableArray.ToImmutableArray(value) + ); + } + } + + /// + /// List of domains to block fetching from + /// + public IReadOnlyList? BlockedDomains + { + get + { + this._rawData.Freeze(); + return this._rawData.GetNullableStruct>("blocked_domains"); + } + init + { + this._rawData.Set?>( + "blocked_domains", + value == null ? null : ImmutableArray.ToImmutableArray(value) + ); + } + } + + /// + /// Create a cache control breakpoint at this content block. + /// + public BetaCacheControlEphemeral? CacheControl + { + get + { + this._rawData.Freeze(); + return this._rawData.GetNullableClass("cache_control"); + } + init { this._rawData.Set("cache_control", value); } + } + + /// + /// Citations configuration for fetched documents. Citations are disabled by default. + /// + public BetaCitationsConfigParam? Citations + { + get + { + this._rawData.Freeze(); + return this._rawData.GetNullableClass("citations"); + } + init { this._rawData.Set("citations", value); } + } + + /// + /// If true, tool will not be included in initial system prompt. Only loaded when + /// returned via tool_reference from tool search. + /// + public bool? DeferLoading + { + get + { + this._rawData.Freeze(); + return this._rawData.GetNullableStruct("defer_loading"); + } + init + { + if (value == null) + { + return; + } + + this._rawData.Set("defer_loading", value); + } + } + + /// + /// Maximum number of tokens used by including web page text content in the context. + /// The limit is approximate and does not apply to binary content such as PDFs. + /// + public long? MaxContentTokens + { + get + { + this._rawData.Freeze(); + return this._rawData.GetNullableStruct("max_content_tokens"); + } + init { this._rawData.Set("max_content_tokens", value); } + } + + /// + /// Maximum number of times the tool can be used in the API request. + /// + public long? MaxUses + { + get + { + this._rawData.Freeze(); + return this._rawData.GetNullableStruct("max_uses"); + } + init { this._rawData.Set("max_uses", value); } + } + + /// + /// When true, guarantees schema validation on tool names and inputs + /// + public bool? Strict + { + get + { + this._rawData.Freeze(); + return this._rawData.GetNullableStruct("strict"); + } + init + { + if (value == null) + { + return; + } + + this._rawData.Set("strict", value); + } + } + + /// + /// Whether to use cached content. Set to false to bypass the cache and fetch + /// fresh content. Only set to false when the user explicitly requests fresh + /// content or when fetching rapidly-changing sources. + /// + public bool? UseCache + { + get + { + this._rawData.Freeze(); + return this._rawData.GetNullableStruct("use_cache"); + } + init + { + if (value == null) + { + return; + } + + this._rawData.Set("use_cache", value); + } + } + + /// + public override void Validate() + { + if (!JsonElement.DeepEquals(this.Name, JsonSerializer.SerializeToElement("web_fetch"))) + { + throw new AnthropicInvalidDataException("Invalid value given for constant"); + } + if ( + !JsonElement.DeepEquals( + this.Type, + JsonSerializer.SerializeToElement("web_fetch_20260309") + ) + ) + { + throw new AnthropicInvalidDataException("Invalid value given for constant"); + } + foreach (var item in this.AllowedCallers ?? []) + { + item.Validate(); + } + _ = this.AllowedDomains; + _ = this.BlockedDomains; + this.CacheControl?.Validate(); + this.Citations?.Validate(); + _ = this.DeferLoading; + _ = this.MaxContentTokens; + _ = this.MaxUses; + _ = this.Strict; + _ = this.UseCache; + } + + public BetaWebFetchTool20260309() + { + this.Name = JsonSerializer.SerializeToElement("web_fetch"); + this.Type = JsonSerializer.SerializeToElement("web_fetch_20260309"); + } + +#pragma warning disable CS8618 + [SetsRequiredMembers] + public BetaWebFetchTool20260309(BetaWebFetchTool20260309 betaWebFetchTool20260309) + : base(betaWebFetchTool20260309) { } +#pragma warning restore CS8618 + + public BetaWebFetchTool20260309(IReadOnlyDictionary rawData) + { + this._rawData = new(rawData); + + this.Name = JsonSerializer.SerializeToElement("web_fetch"); + this.Type = JsonSerializer.SerializeToElement("web_fetch_20260309"); + } + +#pragma warning disable CS8618 + [SetsRequiredMembers] + BetaWebFetchTool20260309(FrozenDictionary rawData) + { + this._rawData = new(rawData); + } +#pragma warning restore CS8618 + + /// + public static BetaWebFetchTool20260309 FromRawUnchecked( + IReadOnlyDictionary rawData + ) + { + return new(FrozenDictionary.ToFrozenDictionary(rawData)); + } +} + +class BetaWebFetchTool20260309FromRaw : IFromRawJson +{ + /// + public BetaWebFetchTool20260309 FromRawUnchecked( + IReadOnlyDictionary rawData + ) => BetaWebFetchTool20260309.FromRawUnchecked(rawData); +} + +/// +/// Specifies who can invoke a tool. +/// +/// Values: direct: The model can call this tool directly. code_execution_20250825: +/// The tool can be called from the code execution environment (v1). code_execution_20260120: +/// The tool can be called from the code execution environment (v2 with persistence). +/// +[JsonConverter(typeof(BetaWebFetchTool20260309AllowedCallerConverter))] +public enum BetaWebFetchTool20260309AllowedCaller +{ + Direct, + CodeExecution20250825, + CodeExecution20260120, +} + +sealed class BetaWebFetchTool20260309AllowedCallerConverter + : JsonConverter +{ + public override BetaWebFetchTool20260309AllowedCaller Read( + ref Utf8JsonReader reader, + System::Type typeToConvert, + JsonSerializerOptions options + ) + { + return JsonSerializer.Deserialize(ref reader, options) switch + { + "direct" => BetaWebFetchTool20260309AllowedCaller.Direct, + "code_execution_20250825" => + BetaWebFetchTool20260309AllowedCaller.CodeExecution20250825, + "code_execution_20260120" => + BetaWebFetchTool20260309AllowedCaller.CodeExecution20260120, + _ => (BetaWebFetchTool20260309AllowedCaller)(-1), + }; + } + + public override void Write( + Utf8JsonWriter writer, + BetaWebFetchTool20260309AllowedCaller value, + JsonSerializerOptions options + ) + { + JsonSerializer.Serialize( + writer, + value switch + { + BetaWebFetchTool20260309AllowedCaller.Direct => "direct", + BetaWebFetchTool20260309AllowedCaller.CodeExecution20250825 => + "code_execution_20250825", + BetaWebFetchTool20260309AllowedCaller.CodeExecution20260120 => + "code_execution_20260120", + _ => throw new AnthropicInvalidDataException( + string.Format("Invalid value '{0}' in {1}", value, nameof(value)) + ), + }, + options + ); + } +} diff --git a/src/Anthropic/Models/Beta/Messages/BetaWebFetchToolResultBlock.cs b/src/Anthropic/Models/Beta/Messages/BetaWebFetchToolResultBlock.cs index e470d8033..659ef441c 100644 --- a/src/Anthropic/Models/Beta/Messages/BetaWebFetchToolResultBlock.cs +++ b/src/Anthropic/Models/Beta/Messages/BetaWebFetchToolResultBlock.cs @@ -724,12 +724,10 @@ JsonSerializerOptions options ); if (deserialized != null) { - deserialized.Validate(); return new(deserialized, element); } } - catch (System::Exception e) - when (e is JsonException || e is AnthropicInvalidDataException) + catch (JsonException) { // ignore } @@ -746,12 +744,10 @@ JsonSerializerOptions options ); if (deserialized != null) { - deserialized.Validate(); return new(deserialized, element); } } - catch (System::Exception e) - when (e is JsonException || e is AnthropicInvalidDataException) + catch (JsonException) { // ignore } @@ -768,12 +764,10 @@ JsonSerializerOptions options ); if (deserialized != null) { - deserialized.Validate(); return new(deserialized, element); } } - catch (System::Exception e) - when (e is JsonException || e is AnthropicInvalidDataException) + catch (JsonException) { // ignore } diff --git a/src/Anthropic/Models/Beta/Messages/BetaWebFetchToolResultBlockParam.cs b/src/Anthropic/Models/Beta/Messages/BetaWebFetchToolResultBlockParam.cs index 045904bf5..a68f7dd66 100644 --- a/src/Anthropic/Models/Beta/Messages/BetaWebFetchToolResultBlockParam.cs +++ b/src/Anthropic/Models/Beta/Messages/BetaWebFetchToolResultBlockParam.cs @@ -760,12 +760,10 @@ JsonSerializerOptions options ); if (deserialized != null) { - deserialized.Validate(); return new(deserialized, element); } } - catch (System::Exception e) - when (e is JsonException || e is AnthropicInvalidDataException) + catch (JsonException) { // ignore } @@ -782,12 +780,10 @@ JsonSerializerOptions options ); if (deserialized != null) { - deserialized.Validate(); return new(deserialized, element); } } - catch (System::Exception e) - when (e is JsonException || e is AnthropicInvalidDataException) + catch (JsonException) { // ignore } @@ -804,12 +800,10 @@ JsonSerializerOptions options ); if (deserialized != null) { - deserialized.Validate(); return new(deserialized, element); } } - catch (System::Exception e) - when (e is JsonException || e is AnthropicInvalidDataException) + catch (JsonException) { // ignore } diff --git a/src/Anthropic/Models/Beta/Messages/BetaWebSearchTool20250305.cs b/src/Anthropic/Models/Beta/Messages/BetaWebSearchTool20250305.cs index df415e299..ba118dead 100644 --- a/src/Anthropic/Models/Beta/Messages/BetaWebSearchTool20250305.cs +++ b/src/Anthropic/Models/Beta/Messages/BetaWebSearchTool20250305.cs @@ -257,6 +257,13 @@ IReadOnlyDictionary rawData ) => BetaWebSearchTool20250305.FromRawUnchecked(rawData); } +/// +/// Specifies who can invoke a tool. +/// +/// Values: direct: The model can call this tool directly. code_execution_20250825: +/// The tool can be called from the code execution environment (v1). code_execution_20260120: +/// The tool can be called from the code execution environment (v2 with persistence). +/// [JsonConverter(typeof(BetaWebSearchTool20250305AllowedCallerConverter))] public enum BetaWebSearchTool20250305AllowedCaller { diff --git a/src/Anthropic/Models/Beta/Messages/BetaWebSearchTool20260209.cs b/src/Anthropic/Models/Beta/Messages/BetaWebSearchTool20260209.cs index 8eac676c7..19041e26e 100644 --- a/src/Anthropic/Models/Beta/Messages/BetaWebSearchTool20260209.cs +++ b/src/Anthropic/Models/Beta/Messages/BetaWebSearchTool20260209.cs @@ -257,6 +257,13 @@ IReadOnlyDictionary rawData ) => BetaWebSearchTool20260209.FromRawUnchecked(rawData); } +/// +/// Specifies who can invoke a tool. +/// +/// Values: direct: The model can call this tool directly. code_execution_20250825: +/// The tool can be called from the code execution environment (v1). code_execution_20260120: +/// The tool can be called from the code execution environment (v2 with persistence). +/// [JsonConverter(typeof(BetaWebSearchTool20260209AllowedCallerConverter))] public enum BetaWebSearchTool20260209AllowedCaller { diff --git a/src/Anthropic/Models/Beta/Messages/BetaWebSearchToolResultBlock.cs b/src/Anthropic/Models/Beta/Messages/BetaWebSearchToolResultBlock.cs index 737c22f94..2399686b1 100644 --- a/src/Anthropic/Models/Beta/Messages/BetaWebSearchToolResultBlock.cs +++ b/src/Anthropic/Models/Beta/Messages/BetaWebSearchToolResultBlock.cs @@ -444,12 +444,10 @@ JsonSerializerOptions options ); if (deserialized != null) { - deserialized.Validate(); return new(deserialized, element); } } - catch (System::Exception e) - when (e is JsonException || e is AnthropicInvalidDataException) + catch (JsonException) { // ignore } @@ -466,12 +464,10 @@ JsonSerializerOptions options ); if (deserialized != null) { - deserialized.Validate(); return new(deserialized, element); } } - catch (System::Exception e) - when (e is JsonException || e is AnthropicInvalidDataException) + catch (JsonException) { // ignore } @@ -488,12 +484,10 @@ JsonSerializerOptions options ); if (deserialized != null) { - deserialized.Validate(); return new(deserialized, element); } } - catch (System::Exception e) - when (e is JsonException || e is AnthropicInvalidDataException) + catch (JsonException) { // ignore } diff --git a/src/Anthropic/Models/Beta/Messages/BetaWebSearchToolResultBlockParam.cs b/src/Anthropic/Models/Beta/Messages/BetaWebSearchToolResultBlockParam.cs index 6aa99ca07..0a9f9a5fc 100644 --- a/src/Anthropic/Models/Beta/Messages/BetaWebSearchToolResultBlockParam.cs +++ b/src/Anthropic/Models/Beta/Messages/BetaWebSearchToolResultBlockParam.cs @@ -471,12 +471,10 @@ JsonSerializerOptions options ); if (deserialized != null) { - deserialized.Validate(); return new(deserialized, element); } } - catch (System::Exception e) - when (e is JsonException || e is AnthropicInvalidDataException) + catch (JsonException) { // ignore } @@ -493,12 +491,10 @@ JsonSerializerOptions options ); if (deserialized != null) { - deserialized.Validate(); return new(deserialized, element); } } - catch (System::Exception e) - when (e is JsonException || e is AnthropicInvalidDataException) + catch (JsonException) { // ignore } @@ -515,12 +511,10 @@ JsonSerializerOptions options ); if (deserialized != null) { - deserialized.Validate(); return new(deserialized, element); } } - catch (System::Exception e) - when (e is JsonException || e is AnthropicInvalidDataException) + catch (JsonException) { // ignore } diff --git a/src/Anthropic/Models/Beta/Messages/MessageBetaContentBlockSourceContent.cs b/src/Anthropic/Models/Beta/Messages/MessageBetaContentBlockSourceContent.cs index 9d058021a..f6425abe6 100644 --- a/src/Anthropic/Models/Beta/Messages/MessageBetaContentBlockSourceContent.cs +++ b/src/Anthropic/Models/Beta/Messages/MessageBetaContentBlockSourceContent.cs @@ -273,12 +273,10 @@ JsonSerializerOptions options ); if (deserialized != null) { - deserialized.Validate(); return new(deserialized, element); } } - catch (System::Exception e) - when (e is JsonException || e is AnthropicInvalidDataException) + catch (JsonException) { // ignore } @@ -295,12 +293,10 @@ JsonSerializerOptions options ); if (deserialized != null) { - deserialized.Validate(); return new(deserialized, element); } } - catch (System::Exception e) - when (e is JsonException || e is AnthropicInvalidDataException) + catch (JsonException) { // ignore } diff --git a/src/Anthropic/Models/Beta/Messages/MessageCountTokensParams.cs b/src/Anthropic/Models/Beta/Messages/MessageCountTokensParams.cs index c1e634aab..e45fb6e0e 100644 --- a/src/Anthropic/Models/Beta/Messages/MessageCountTokensParams.cs +++ b/src/Anthropic/Models/Beta/Messages/MessageCountTokensParams.cs @@ -879,6 +879,7 @@ public BetaCacheControlEphemeral? CacheControl betaWebFetchTool20250910: (x) => x.CacheControl, betaWebSearchTool20260209: (x) => x.CacheControl, betaWebFetchTool20260209: (x) => x.CacheControl, + betaWebFetchTool20260309: (x) => x.CacheControl, betaToolSearchToolBm25_20251119: (x) => x.CacheControl, betaToolSearchToolRegex20251119: (x) => x.CacheControl, betaMcpToolset: (x) => x.CacheControl @@ -909,6 +910,7 @@ public bool? DeferLoading betaWebFetchTool20250910: (x) => x.DeferLoading, betaWebSearchTool20260209: (x) => x.DeferLoading, betaWebFetchTool20260209: (x) => x.DeferLoading, + betaWebFetchTool20260309: (x) => x.DeferLoading, betaToolSearchToolBm25_20251119: (x) => x.DeferLoading, betaToolSearchToolRegex20251119: (x) => x.DeferLoading, betaMcpToolset: (_) => null @@ -939,6 +941,7 @@ public bool? Strict betaWebFetchTool20250910: (x) => x.Strict, betaWebSearchTool20260209: (x) => x.Strict, betaWebFetchTool20260209: (x) => x.Strict, + betaWebFetchTool20260309: (x) => x.Strict, betaToolSearchToolBm25_20251119: (x) => x.Strict, betaToolSearchToolRegex20251119: (x) => x.Strict, betaMcpToolset: (_) => null @@ -969,6 +972,7 @@ public long? DisplayHeightPx betaWebFetchTool20250910: (_) => null, betaWebSearchTool20260209: (_) => null, betaWebFetchTool20260209: (_) => null, + betaWebFetchTool20260309: (_) => null, betaToolSearchToolBm25_20251119: (_) => null, betaToolSearchToolRegex20251119: (_) => null, betaMcpToolset: (_) => null @@ -999,6 +1003,7 @@ public long? DisplayWidthPx betaWebFetchTool20250910: (_) => null, betaWebSearchTool20260209: (_) => null, betaWebFetchTool20260209: (_) => null, + betaWebFetchTool20260309: (_) => null, betaToolSearchToolBm25_20251119: (_) => null, betaToolSearchToolRegex20251119: (_) => null, betaMcpToolset: (_) => null @@ -1029,6 +1034,7 @@ public long? DisplayNumber betaWebFetchTool20250910: (_) => null, betaWebSearchTool20260209: (_) => null, betaWebFetchTool20260209: (_) => null, + betaWebFetchTool20260309: (_) => null, betaToolSearchToolBm25_20251119: (_) => null, betaToolSearchToolRegex20251119: (_) => null, betaMcpToolset: (_) => null @@ -1059,6 +1065,7 @@ public long? MaxUses betaWebFetchTool20250910: (x) => x.MaxUses, betaWebSearchTool20260209: (x) => x.MaxUses, betaWebFetchTool20260209: (x) => x.MaxUses, + betaWebFetchTool20260309: (x) => x.MaxUses, betaToolSearchToolBm25_20251119: (_) => null, betaToolSearchToolRegex20251119: (_) => null, betaMcpToolset: (_) => null @@ -1089,6 +1096,7 @@ public BetaUserLocation? UserLocation betaWebFetchTool20250910: (_) => null, betaWebSearchTool20260209: (x) => x.UserLocation, betaWebFetchTool20260209: (_) => null, + betaWebFetchTool20260309: (_) => null, betaToolSearchToolBm25_20251119: (_) => null, betaToolSearchToolRegex20251119: (_) => null, betaMcpToolset: (_) => null @@ -1119,6 +1127,7 @@ public BetaCitationsConfigParam? Citations betaWebFetchTool20250910: (x) => x.Citations, betaWebSearchTool20260209: (_) => null, betaWebFetchTool20260209: (x) => x.Citations, + betaWebFetchTool20260309: (x) => x.Citations, betaToolSearchToolBm25_20251119: (_) => null, betaToolSearchToolRegex20251119: (_) => null, betaMcpToolset: (_) => null @@ -1149,6 +1158,7 @@ public long? MaxContentTokens betaWebFetchTool20250910: (x) => x.MaxContentTokens, betaWebSearchTool20260209: (_) => null, betaWebFetchTool20260209: (x) => x.MaxContentTokens, + betaWebFetchTool20260309: (x) => x.MaxContentTokens, betaToolSearchToolBm25_20251119: (_) => null, betaToolSearchToolRegex20251119: (_) => null, betaMcpToolset: (_) => null @@ -1264,6 +1274,12 @@ public Tool(BetaWebFetchTool20260209 value, JsonElement? element = null) this._element = element; } + public Tool(BetaWebFetchTool20260309 value, JsonElement? element = null) + { + this.Value = value; + this._element = element; + } + public Tool(BetaToolSearchToolBm25_20251119 value, JsonElement? element = null) { this.Value = value; @@ -1693,6 +1709,29 @@ public bool TryPickBetaWebFetchTool20260209( return value != null; } + /// + /// Returns true and sets the out parameter if the instance was constructed with a variant of + /// type . + /// + /// Consider using or if you need to handle every variant. + /// + /// + /// + /// if (instance.TryPickBetaWebFetchTool20260309(out var value)) { + /// // `value` is of type `BetaWebFetchTool20260309` + /// Console.WriteLine(value); + /// } + /// + /// + /// + public bool TryPickBetaWebFetchTool20260309( + [NotNullWhen(true)] out BetaWebFetchTool20260309? value + ) + { + value = this.Value as BetaWebFetchTool20260309; + return value != null; + } + /// /// Returns true and sets the out parameter if the instance was constructed with a variant of /// type . @@ -1792,6 +1831,7 @@ public bool TryPickBetaMcpToolset([NotNullWhen(true)] out BetaMcpToolset? value) /// (BetaWebFetchTool20250910 value) => {...}, /// (BetaWebSearchTool20260209 value) => {...}, /// (BetaWebFetchTool20260209 value) => {...}, + /// (BetaWebFetchTool20260309 value) => {...}, /// (BetaToolSearchToolBm25_20251119 value) => {...}, /// (BetaToolSearchToolRegex20251119 value) => {...}, /// (BetaMcpToolset value) => {...} @@ -1818,6 +1858,7 @@ public void Switch( System::Action betaWebFetchTool20250910, System::Action betaWebSearchTool20260209, System::Action betaWebFetchTool20260209, + System::Action betaWebFetchTool20260309, System::Action betaToolSearchToolBm25_20251119, System::Action betaToolSearchToolRegex20251119, System::Action betaMcpToolset @@ -1879,6 +1920,9 @@ public void Switch( case BetaWebFetchTool20260209 value: betaWebFetchTool20260209(value); break; + case BetaWebFetchTool20260309 value: + betaWebFetchTool20260309(value); + break; case BetaToolSearchToolBm25_20251119 value: betaToolSearchToolBm25_20251119(value); break; @@ -1926,6 +1970,7 @@ public void Switch( /// (BetaWebFetchTool20250910 value) => {...}, /// (BetaWebSearchTool20260209 value) => {...}, /// (BetaWebFetchTool20260209 value) => {...}, + /// (BetaWebFetchTool20260309 value) => {...}, /// (BetaToolSearchToolBm25_20251119 value) => {...}, /// (BetaToolSearchToolRegex20251119 value) => {...}, /// (BetaMcpToolset value) => {...} @@ -1952,6 +1997,7 @@ public T Match( System::Func betaWebFetchTool20250910, System::Func betaWebSearchTool20260209, System::Func betaWebFetchTool20260209, + System::Func betaWebFetchTool20260309, System::Func betaToolSearchToolBm25_20251119, System::Func betaToolSearchToolRegex20251119, System::Func betaMcpToolset @@ -1977,6 +2023,7 @@ public T Match( BetaWebFetchTool20250910 value => betaWebFetchTool20250910(value), BetaWebSearchTool20260209 value => betaWebSearchTool20260209(value), BetaWebFetchTool20260209 value => betaWebFetchTool20260209(value), + BetaWebFetchTool20260309 value => betaWebFetchTool20260309(value), BetaToolSearchToolBm25_20251119 value => betaToolSearchToolBm25_20251119(value), BetaToolSearchToolRegex20251119 value => betaToolSearchToolRegex20251119(value), BetaMcpToolset value => betaMcpToolset(value), @@ -2020,6 +2067,8 @@ public T Match( public static implicit operator Tool(BetaWebFetchTool20260209 value) => new(value); + public static implicit operator Tool(BetaWebFetchTool20260309 value) => new(value); + public static implicit operator Tool(BetaToolSearchToolBm25_20251119 value) => new(value); public static implicit operator Tool(BetaToolSearchToolRegex20251119 value) => new(value); @@ -2061,6 +2110,7 @@ public override void Validate() (betaWebFetchTool20250910) => betaWebFetchTool20250910.Validate(), (betaWebSearchTool20260209) => betaWebSearchTool20260209.Validate(), (betaWebFetchTool20260209) => betaWebFetchTool20260209.Validate(), + (betaWebFetchTool20260309) => betaWebFetchTool20260309.Validate(), (betaToolSearchToolBm25_20251119) => betaToolSearchToolBm25_20251119.Validate(), (betaToolSearchToolRegex20251119) => betaToolSearchToolRegex20251119.Validate(), (betaMcpToolset) => betaMcpToolset.Validate() @@ -2105,9 +2155,10 @@ int VariantIndex() BetaWebFetchTool20250910 _ => 15, BetaWebSearchTool20260209 _ => 16, BetaWebFetchTool20260209 _ => 17, - BetaToolSearchToolBm25_20251119 _ => 18, - BetaToolSearchToolRegex20251119 _ => 19, - BetaMcpToolset _ => 20, + BetaWebFetchTool20260309 _ => 18, + BetaToolSearchToolBm25_20251119 _ => 19, + BetaToolSearchToolRegex20251119 _ => 20, + BetaMcpToolset _ => 21, _ => -1, }; } @@ -2416,6 +2467,23 @@ JsonSerializerOptions options // ignore } + try + { + var deserialized = JsonSerializer.Deserialize( + element, + options + ); + if (deserialized != null) + { + deserialized.Validate(); + return new(deserialized, element); + } + } + catch (System::Exception e) when (e is JsonException || e is AnthropicInvalidDataException) + { + // ignore + } + try { var deserialized = JsonSerializer.Deserialize( diff --git a/src/Anthropic/Models/Beta/Models/BetaCapabilitySupport.cs b/src/Anthropic/Models/Beta/Models/BetaCapabilitySupport.cs new file mode 100644 index 000000000..c58a92863 --- /dev/null +++ b/src/Anthropic/Models/Beta/Models/BetaCapabilitySupport.cs @@ -0,0 +1,78 @@ +using System.Collections.Frozen; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Text.Json; +using System.Text.Json.Serialization; +using Anthropic.Core; + +namespace Anthropic.Models.Beta.Models; + +/// +/// Indicates whether a capability is supported. +/// +[JsonConverter(typeof(JsonModelConverter))] +public sealed record class BetaCapabilitySupport : JsonModel +{ + /// + /// Whether this capability is supported by the model. + /// + public required bool Supported + { + get + { + this._rawData.Freeze(); + return this._rawData.GetNotNullStruct("supported"); + } + init { this._rawData.Set("supported", value); } + } + + /// + public override void Validate() + { + _ = this.Supported; + } + + public BetaCapabilitySupport() { } + +#pragma warning disable CS8618 + [SetsRequiredMembers] + public BetaCapabilitySupport(BetaCapabilitySupport betaCapabilitySupport) + : base(betaCapabilitySupport) { } +#pragma warning restore CS8618 + + public BetaCapabilitySupport(IReadOnlyDictionary rawData) + { + this._rawData = new(rawData); + } + +#pragma warning disable CS8618 + [SetsRequiredMembers] + BetaCapabilitySupport(FrozenDictionary rawData) + { + this._rawData = new(rawData); + } +#pragma warning restore CS8618 + + /// + public static BetaCapabilitySupport FromRawUnchecked( + IReadOnlyDictionary rawData + ) + { + return new(FrozenDictionary.ToFrozenDictionary(rawData)); + } + + [SetsRequiredMembers] + public BetaCapabilitySupport(bool supported) + : this() + { + this.Supported = supported; + } +} + +class BetaCapabilitySupportFromRaw : IFromRawJson +{ + /// + public BetaCapabilitySupport FromRawUnchecked( + IReadOnlyDictionary rawData + ) => BetaCapabilitySupport.FromRawUnchecked(rawData); +} diff --git a/src/Anthropic/Models/Beta/Models/BetaContextManagementCapability.cs b/src/Anthropic/Models/Beta/Models/BetaContextManagementCapability.cs new file mode 100644 index 000000000..c636f8f95 --- /dev/null +++ b/src/Anthropic/Models/Beta/Models/BetaContextManagementCapability.cs @@ -0,0 +1,122 @@ +using System.Collections.Frozen; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Text.Json; +using System.Text.Json.Serialization; +using Anthropic.Core; + +namespace Anthropic.Models.Beta.Models; + +/// +/// Context management capability details. +/// +[JsonConverter( + typeof(JsonModelConverter< + BetaContextManagementCapability, + BetaContextManagementCapabilityFromRaw + >) +)] +public sealed record class BetaContextManagementCapability : JsonModel +{ + /// + /// Indicates whether a capability is supported. + /// + public required BetaCapabilitySupport? ClearThinking20251015 + { + get + { + this._rawData.Freeze(); + return this._rawData.GetNullableClass("clear_thinking_20251015"); + } + init { this._rawData.Set("clear_thinking_20251015", value); } + } + + /// + /// Indicates whether a capability is supported. + /// + public required BetaCapabilitySupport? ClearToolUses20250919 + { + get + { + this._rawData.Freeze(); + return this._rawData.GetNullableClass( + "clear_tool_uses_20250919" + ); + } + init { this._rawData.Set("clear_tool_uses_20250919", value); } + } + + /// + /// Indicates whether a capability is supported. + /// + public required BetaCapabilitySupport? Compact20260112 + { + get + { + this._rawData.Freeze(); + return this._rawData.GetNullableClass("compact_20260112"); + } + init { this._rawData.Set("compact_20260112", value); } + } + + /// + /// Whether this capability is supported by the model. + /// + public required bool Supported + { + get + { + this._rawData.Freeze(); + return this._rawData.GetNotNullStruct("supported"); + } + init { this._rawData.Set("supported", value); } + } + + /// + public override void Validate() + { + this.ClearThinking20251015?.Validate(); + this.ClearToolUses20250919?.Validate(); + this.Compact20260112?.Validate(); + _ = this.Supported; + } + + public BetaContextManagementCapability() { } + +#pragma warning disable CS8618 + [SetsRequiredMembers] + public BetaContextManagementCapability( + BetaContextManagementCapability betaContextManagementCapability + ) + : base(betaContextManagementCapability) { } +#pragma warning restore CS8618 + + public BetaContextManagementCapability(IReadOnlyDictionary rawData) + { + this._rawData = new(rawData); + } + +#pragma warning disable CS8618 + [SetsRequiredMembers] + BetaContextManagementCapability(FrozenDictionary rawData) + { + this._rawData = new(rawData); + } +#pragma warning restore CS8618 + + /// + public static BetaContextManagementCapability FromRawUnchecked( + IReadOnlyDictionary rawData + ) + { + return new(FrozenDictionary.ToFrozenDictionary(rawData)); + } +} + +class BetaContextManagementCapabilityFromRaw : IFromRawJson +{ + /// + public BetaContextManagementCapability FromRawUnchecked( + IReadOnlyDictionary rawData + ) => BetaContextManagementCapability.FromRawUnchecked(rawData); +} diff --git a/src/Anthropic/Models/Beta/Models/BetaEffortCapability.cs b/src/Anthropic/Models/Beta/Models/BetaEffortCapability.cs new file mode 100644 index 000000000..d272b5905 --- /dev/null +++ b/src/Anthropic/Models/Beta/Models/BetaEffortCapability.cs @@ -0,0 +1,127 @@ +using System.Collections.Frozen; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Text.Json; +using System.Text.Json.Serialization; +using Anthropic.Core; + +namespace Anthropic.Models.Beta.Models; + +/// +/// Effort (reasoning_effort) capability details. +/// +[JsonConverter(typeof(JsonModelConverter))] +public sealed record class BetaEffortCapability : JsonModel +{ + /// + /// Whether the model supports high effort level. + /// + public required BetaCapabilitySupport High + { + get + { + this._rawData.Freeze(); + return this._rawData.GetNotNullClass("high"); + } + init { this._rawData.Set("high", value); } + } + + /// + /// Whether the model supports low effort level. + /// + public required BetaCapabilitySupport Low + { + get + { + this._rawData.Freeze(); + return this._rawData.GetNotNullClass("low"); + } + init { this._rawData.Set("low", value); } + } + + /// + /// Whether the model supports max effort level. + /// + public required BetaCapabilitySupport Max + { + get + { + this._rawData.Freeze(); + return this._rawData.GetNotNullClass("max"); + } + init { this._rawData.Set("max", value); } + } + + /// + /// Whether the model supports medium effort level. + /// + public required BetaCapabilitySupport Medium + { + get + { + this._rawData.Freeze(); + return this._rawData.GetNotNullClass("medium"); + } + init { this._rawData.Set("medium", value); } + } + + /// + /// Whether this capability is supported by the model. + /// + public required bool Supported + { + get + { + this._rawData.Freeze(); + return this._rawData.GetNotNullStruct("supported"); + } + init { this._rawData.Set("supported", value); } + } + + /// + public override void Validate() + { + this.High.Validate(); + this.Low.Validate(); + this.Max.Validate(); + this.Medium.Validate(); + _ = this.Supported; + } + + public BetaEffortCapability() { } + +#pragma warning disable CS8618 + [SetsRequiredMembers] + public BetaEffortCapability(BetaEffortCapability betaEffortCapability) + : base(betaEffortCapability) { } +#pragma warning restore CS8618 + + public BetaEffortCapability(IReadOnlyDictionary rawData) + { + this._rawData = new(rawData); + } + +#pragma warning disable CS8618 + [SetsRequiredMembers] + BetaEffortCapability(FrozenDictionary rawData) + { + this._rawData = new(rawData); + } +#pragma warning restore CS8618 + + /// + public static BetaEffortCapability FromRawUnchecked( + IReadOnlyDictionary rawData + ) + { + return new(FrozenDictionary.ToFrozenDictionary(rawData)); + } +} + +class BetaEffortCapabilityFromRaw : IFromRawJson +{ + /// + public BetaEffortCapability FromRawUnchecked( + IReadOnlyDictionary rawData + ) => BetaEffortCapability.FromRawUnchecked(rawData); +} diff --git a/src/Anthropic/Models/Beta/Models/BetaModelCapabilities.cs b/src/Anthropic/Models/Beta/Models/BetaModelCapabilities.cs new file mode 100644 index 000000000..c57a17fc9 --- /dev/null +++ b/src/Anthropic/Models/Beta/Models/BetaModelCapabilities.cs @@ -0,0 +1,185 @@ +using System.Collections.Frozen; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Text.Json; +using System.Text.Json.Serialization; +using Anthropic.Core; + +namespace Anthropic.Models.Beta.Models; + +/// +/// Model capability information. +/// +[JsonConverter(typeof(JsonModelConverter))] +public sealed record class BetaModelCapabilities : JsonModel +{ + /// + /// Whether the model supports the Batch API. + /// + public required BetaCapabilitySupport Batch + { + get + { + this._rawData.Freeze(); + return this._rawData.GetNotNullClass("batch"); + } + init { this._rawData.Set("batch", value); } + } + + /// + /// Whether the model supports citation generation. + /// + public required BetaCapabilitySupport Citations + { + get + { + this._rawData.Freeze(); + return this._rawData.GetNotNullClass("citations"); + } + init { this._rawData.Set("citations", value); } + } + + /// + /// Whether the model supports code execution tools. + /// + public required BetaCapabilitySupport CodeExecution + { + get + { + this._rawData.Freeze(); + return this._rawData.GetNotNullClass("code_execution"); + } + init { this._rawData.Set("code_execution", value); } + } + + /// + /// Context management support and available strategies. + /// + public required BetaContextManagementCapability ContextManagement + { + get + { + this._rawData.Freeze(); + return this._rawData.GetNotNullClass( + "context_management" + ); + } + init { this._rawData.Set("context_management", value); } + } + + /// + /// Effort (reasoning_effort) support and available levels. + /// + public required BetaEffortCapability Effort + { + get + { + this._rawData.Freeze(); + return this._rawData.GetNotNullClass("effort"); + } + init { this._rawData.Set("effort", value); } + } + + /// + /// Whether the model accepts image content blocks. + /// + public required BetaCapabilitySupport ImageInput + { + get + { + this._rawData.Freeze(); + return this._rawData.GetNotNullClass("image_input"); + } + init { this._rawData.Set("image_input", value); } + } + + /// + /// Whether the model accepts PDF content blocks. + /// + public required BetaCapabilitySupport PdfInput + { + get + { + this._rawData.Freeze(); + return this._rawData.GetNotNullClass("pdf_input"); + } + init { this._rawData.Set("pdf_input", value); } + } + + /// + /// Whether the model supports structured output / JSON mode / strict tool schemas. + /// + public required BetaCapabilitySupport StructuredOutputs + { + get + { + this._rawData.Freeze(); + return this._rawData.GetNotNullClass("structured_outputs"); + } + init { this._rawData.Set("structured_outputs", value); } + } + + /// + /// Thinking capability and supported type configurations. + /// + public required BetaThinkingCapability Thinking + { + get + { + this._rawData.Freeze(); + return this._rawData.GetNotNullClass("thinking"); + } + init { this._rawData.Set("thinking", value); } + } + + /// + public override void Validate() + { + this.Batch.Validate(); + this.Citations.Validate(); + this.CodeExecution.Validate(); + this.ContextManagement.Validate(); + this.Effort.Validate(); + this.ImageInput.Validate(); + this.PdfInput.Validate(); + this.StructuredOutputs.Validate(); + this.Thinking.Validate(); + } + + public BetaModelCapabilities() { } + +#pragma warning disable CS8618 + [SetsRequiredMembers] + public BetaModelCapabilities(BetaModelCapabilities betaModelCapabilities) + : base(betaModelCapabilities) { } +#pragma warning restore CS8618 + + public BetaModelCapabilities(IReadOnlyDictionary rawData) + { + this._rawData = new(rawData); + } + +#pragma warning disable CS8618 + [SetsRequiredMembers] + BetaModelCapabilities(FrozenDictionary rawData) + { + this._rawData = new(rawData); + } +#pragma warning restore CS8618 + + /// + public static BetaModelCapabilities FromRawUnchecked( + IReadOnlyDictionary rawData + ) + { + return new(FrozenDictionary.ToFrozenDictionary(rawData)); + } +} + +class BetaModelCapabilitiesFromRaw : IFromRawJson +{ + /// + public BetaModelCapabilities FromRawUnchecked( + IReadOnlyDictionary rawData + ) => BetaModelCapabilities.FromRawUnchecked(rawData); +} diff --git a/src/Anthropic/Models/Beta/Models/BetaModelInfo.cs b/src/Anthropic/Models/Beta/Models/BetaModelInfo.cs index cf411ab02..fee8af026 100644 --- a/src/Anthropic/Models/Beta/Models/BetaModelInfo.cs +++ b/src/Anthropic/Models/Beta/Models/BetaModelInfo.cs @@ -25,6 +25,19 @@ public required string ID init { this._rawData.Set("id", value); } } + /// + /// Model capability information. + /// + public required BetaModelCapabilities? Capabilities + { + get + { + this._rawData.Freeze(); + return this._rawData.GetNullableClass("capabilities"); + } + init { this._rawData.Set("capabilities", value); } + } + /// /// RFC 3339 datetime string representing the time at which the model was released. /// May be set to an epoch value if the release date is unknown. @@ -52,6 +65,32 @@ public required string DisplayName init { this._rawData.Set("display_name", value); } } + /// + /// Maximum input context window size in tokens for this model. + /// + public required long? MaxInputTokens + { + get + { + this._rawData.Freeze(); + return this._rawData.GetNullableStruct("max_input_tokens"); + } + init { this._rawData.Set("max_input_tokens", value); } + } + + /// + /// Maximum value for the `max_tokens` parameter when using this model. + /// + public required long? MaxTokens + { + get + { + this._rawData.Freeze(); + return this._rawData.GetNullableStruct("max_tokens"); + } + init { this._rawData.Set("max_tokens", value); } + } + /// /// Object type. /// @@ -71,8 +110,11 @@ public JsonElement Type public override void Validate() { _ = this.ID; + this.Capabilities?.Validate(); _ = this.CreatedAt; _ = this.DisplayName; + _ = this.MaxInputTokens; + _ = this.MaxTokens; if (!JsonElement.DeepEquals(this.Type, JsonSerializer.SerializeToElement("model"))) { throw new AnthropicInvalidDataException("Invalid value given for constant"); diff --git a/src/Anthropic/Models/Beta/Models/BetaThinkingCapability.cs b/src/Anthropic/Models/Beta/Models/BetaThinkingCapability.cs new file mode 100644 index 000000000..1b7368cd4 --- /dev/null +++ b/src/Anthropic/Models/Beta/Models/BetaThinkingCapability.cs @@ -0,0 +1,85 @@ +using System.Collections.Frozen; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Text.Json; +using System.Text.Json.Serialization; +using Anthropic.Core; + +namespace Anthropic.Models.Beta.Models; + +/// +/// Thinking capability details. +/// +[JsonConverter(typeof(JsonModelConverter))] +public sealed record class BetaThinkingCapability : JsonModel +{ + /// + /// Whether this capability is supported by the model. + /// + public required bool Supported + { + get + { + this._rawData.Freeze(); + return this._rawData.GetNotNullStruct("supported"); + } + init { this._rawData.Set("supported", value); } + } + + /// + /// Supported thinking type configurations. + /// + public required BetaThinkingTypes Types + { + get + { + this._rawData.Freeze(); + return this._rawData.GetNotNullClass("types"); + } + init { this._rawData.Set("types", value); } + } + + /// + public override void Validate() + { + _ = this.Supported; + this.Types.Validate(); + } + + public BetaThinkingCapability() { } + +#pragma warning disable CS8618 + [SetsRequiredMembers] + public BetaThinkingCapability(BetaThinkingCapability betaThinkingCapability) + : base(betaThinkingCapability) { } +#pragma warning restore CS8618 + + public BetaThinkingCapability(IReadOnlyDictionary rawData) + { + this._rawData = new(rawData); + } + +#pragma warning disable CS8618 + [SetsRequiredMembers] + BetaThinkingCapability(FrozenDictionary rawData) + { + this._rawData = new(rawData); + } +#pragma warning restore CS8618 + + /// + public static BetaThinkingCapability FromRawUnchecked( + IReadOnlyDictionary rawData + ) + { + return new(FrozenDictionary.ToFrozenDictionary(rawData)); + } +} + +class BetaThinkingCapabilityFromRaw : IFromRawJson +{ + /// + public BetaThinkingCapability FromRawUnchecked( + IReadOnlyDictionary rawData + ) => BetaThinkingCapability.FromRawUnchecked(rawData); +} diff --git a/src/Anthropic/Models/Beta/Models/BetaThinkingTypes.cs b/src/Anthropic/Models/Beta/Models/BetaThinkingTypes.cs new file mode 100644 index 000000000..a04635330 --- /dev/null +++ b/src/Anthropic/Models/Beta/Models/BetaThinkingTypes.cs @@ -0,0 +1,84 @@ +using System.Collections.Frozen; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Text.Json; +using System.Text.Json.Serialization; +using Anthropic.Core; + +namespace Anthropic.Models.Beta.Models; + +/// +/// Supported thinking type configurations. +/// +[JsonConverter(typeof(JsonModelConverter))] +public sealed record class BetaThinkingTypes : JsonModel +{ + /// + /// Whether the model supports thinking with type 'adaptive' (auto). + /// + public required BetaCapabilitySupport Adaptive + { + get + { + this._rawData.Freeze(); + return this._rawData.GetNotNullClass("adaptive"); + } + init { this._rawData.Set("adaptive", value); } + } + + /// + /// Whether the model supports thinking with type 'enabled'. + /// + public required BetaCapabilitySupport Enabled + { + get + { + this._rawData.Freeze(); + return this._rawData.GetNotNullClass("enabled"); + } + init { this._rawData.Set("enabled", value); } + } + + /// + public override void Validate() + { + this.Adaptive.Validate(); + this.Enabled.Validate(); + } + + public BetaThinkingTypes() { } + +#pragma warning disable CS8618 + [SetsRequiredMembers] + public BetaThinkingTypes(BetaThinkingTypes betaThinkingTypes) + : base(betaThinkingTypes) { } +#pragma warning restore CS8618 + + public BetaThinkingTypes(IReadOnlyDictionary rawData) + { + this._rawData = new(rawData); + } + +#pragma warning disable CS8618 + [SetsRequiredMembers] + BetaThinkingTypes(FrozenDictionary rawData) + { + this._rawData = new(rawData); + } +#pragma warning restore CS8618 + + /// + public static BetaThinkingTypes FromRawUnchecked( + IReadOnlyDictionary rawData + ) + { + return new(FrozenDictionary.ToFrozenDictionary(rawData)); + } +} + +class BetaThinkingTypesFromRaw : IFromRawJson +{ + /// + public BetaThinkingTypes FromRawUnchecked(IReadOnlyDictionary rawData) => + BetaThinkingTypes.FromRawUnchecked(rawData); +} diff --git a/src/Anthropic/Models/Beta/Models/ModelRetrieveParams.cs b/src/Anthropic/Models/Beta/Models/ModelRetrieveParams.cs index b1ba89a39..d4fc771b5 100644 --- a/src/Anthropic/Models/Beta/Models/ModelRetrieveParams.cs +++ b/src/Anthropic/Models/Beta/Models/ModelRetrieveParams.cs @@ -73,23 +73,27 @@ IReadOnlyDictionary rawQueryData [SetsRequiredMembers] ModelRetrieveParams( FrozenDictionary rawHeaderData, - FrozenDictionary rawQueryData + FrozenDictionary rawQueryData, + string modelID ) { this._rawHeaderData = new(rawHeaderData); this._rawQueryData = new(rawQueryData); + this.ModelID = modelID; } #pragma warning restore CS8618 /// public static ModelRetrieveParams FromRawUnchecked( IReadOnlyDictionary rawHeaderData, - IReadOnlyDictionary rawQueryData + IReadOnlyDictionary rawQueryData, + string modelID ) { return new( FrozenDictionary.ToFrozenDictionary(rawHeaderData), - FrozenDictionary.ToFrozenDictionary(rawQueryData) + FrozenDictionary.ToFrozenDictionary(rawQueryData), + modelID ); } diff --git a/src/Anthropic/Models/Beta/Skills/SkillDeleteParams.cs b/src/Anthropic/Models/Beta/Skills/SkillDeleteParams.cs index f5353959b..4050c43fb 100644 --- a/src/Anthropic/Models/Beta/Skills/SkillDeleteParams.cs +++ b/src/Anthropic/Models/Beta/Skills/SkillDeleteParams.cs @@ -71,23 +71,27 @@ IReadOnlyDictionary rawQueryData [SetsRequiredMembers] SkillDeleteParams( FrozenDictionary rawHeaderData, - FrozenDictionary rawQueryData + FrozenDictionary rawQueryData, + string skillID ) { this._rawHeaderData = new(rawHeaderData); this._rawQueryData = new(rawQueryData); + this.SkillID = skillID; } #pragma warning restore CS8618 /// public static SkillDeleteParams FromRawUnchecked( IReadOnlyDictionary rawHeaderData, - IReadOnlyDictionary rawQueryData + IReadOnlyDictionary rawQueryData, + string skillID ) { return new( FrozenDictionary.ToFrozenDictionary(rawHeaderData), - FrozenDictionary.ToFrozenDictionary(rawQueryData) + FrozenDictionary.ToFrozenDictionary(rawQueryData), + skillID ); } diff --git a/src/Anthropic/Models/Beta/Skills/SkillRetrieveParams.cs b/src/Anthropic/Models/Beta/Skills/SkillRetrieveParams.cs index f9e05bbbb..d5d326a3a 100644 --- a/src/Anthropic/Models/Beta/Skills/SkillRetrieveParams.cs +++ b/src/Anthropic/Models/Beta/Skills/SkillRetrieveParams.cs @@ -71,23 +71,27 @@ IReadOnlyDictionary rawQueryData [SetsRequiredMembers] SkillRetrieveParams( FrozenDictionary rawHeaderData, - FrozenDictionary rawQueryData + FrozenDictionary rawQueryData, + string skillID ) { this._rawHeaderData = new(rawHeaderData); this._rawQueryData = new(rawQueryData); + this.SkillID = skillID; } #pragma warning restore CS8618 /// public static SkillRetrieveParams FromRawUnchecked( IReadOnlyDictionary rawHeaderData, - IReadOnlyDictionary rawQueryData + IReadOnlyDictionary rawQueryData, + string skillID ) { return new( FrozenDictionary.ToFrozenDictionary(rawHeaderData), - FrozenDictionary.ToFrozenDictionary(rawQueryData) + FrozenDictionary.ToFrozenDictionary(rawQueryData), + skillID ); } diff --git a/src/Anthropic/Models/Beta/Skills/Versions/VersionCreateParams.cs b/src/Anthropic/Models/Beta/Skills/Versions/VersionCreateParams.cs index 302970ded..eb42a9e04 100644 --- a/src/Anthropic/Models/Beta/Skills/Versions/VersionCreateParams.cs +++ b/src/Anthropic/Models/Beta/Skills/Versions/VersionCreateParams.cs @@ -104,12 +104,14 @@ IReadOnlyDictionary rawBodyData VersionCreateParams( FrozenDictionary rawHeaderData, FrozenDictionary rawQueryData, - FrozenDictionary rawBodyData + FrozenDictionary rawBodyData, + string skillID ) { this._rawHeaderData = new(rawHeaderData); this._rawQueryData = new(rawQueryData); this._rawBodyData = new(rawBodyData); + this.SkillID = skillID; } #pragma warning restore CS8618 @@ -117,13 +119,15 @@ FrozenDictionary rawBodyData public static VersionCreateParams FromRawUnchecked( IReadOnlyDictionary rawHeaderData, IReadOnlyDictionary rawQueryData, - IReadOnlyDictionary rawBodyData + IReadOnlyDictionary rawBodyData, + string skillID ) { return new( FrozenDictionary.ToFrozenDictionary(rawHeaderData), FrozenDictionary.ToFrozenDictionary(rawQueryData), - FrozenDictionary.ToFrozenDictionary(rawBodyData) + FrozenDictionary.ToFrozenDictionary(rawBodyData), + skillID ); } diff --git a/src/Anthropic/Models/Beta/Skills/Versions/VersionDeleteParams.cs b/src/Anthropic/Models/Beta/Skills/Versions/VersionDeleteParams.cs index e13657f19..432a2ee7c 100644 --- a/src/Anthropic/Models/Beta/Skills/Versions/VersionDeleteParams.cs +++ b/src/Anthropic/Models/Beta/Skills/Versions/VersionDeleteParams.cs @@ -74,23 +74,31 @@ IReadOnlyDictionary rawQueryData [SetsRequiredMembers] VersionDeleteParams( FrozenDictionary rawHeaderData, - FrozenDictionary rawQueryData + FrozenDictionary rawQueryData, + string skillID, + string version ) { this._rawHeaderData = new(rawHeaderData); this._rawQueryData = new(rawQueryData); + this.SkillID = skillID; + this.Version = version; } #pragma warning restore CS8618 /// public static VersionDeleteParams FromRawUnchecked( IReadOnlyDictionary rawHeaderData, - IReadOnlyDictionary rawQueryData + IReadOnlyDictionary rawQueryData, + string skillID, + string version ) { return new( FrozenDictionary.ToFrozenDictionary(rawHeaderData), - FrozenDictionary.ToFrozenDictionary(rawQueryData) + FrozenDictionary.ToFrozenDictionary(rawQueryData), + skillID, + version ); } diff --git a/src/Anthropic/Models/Beta/Skills/Versions/VersionListParams.cs b/src/Anthropic/Models/Beta/Skills/Versions/VersionListParams.cs index de33f622d..e13ff388a 100644 --- a/src/Anthropic/Models/Beta/Skills/Versions/VersionListParams.cs +++ b/src/Anthropic/Models/Beta/Skills/Versions/VersionListParams.cs @@ -99,23 +99,27 @@ IReadOnlyDictionary rawQueryData [SetsRequiredMembers] VersionListParams( FrozenDictionary rawHeaderData, - FrozenDictionary rawQueryData + FrozenDictionary rawQueryData, + string skillID ) { this._rawHeaderData = new(rawHeaderData); this._rawQueryData = new(rawQueryData); + this.SkillID = skillID; } #pragma warning restore CS8618 /// public static VersionListParams FromRawUnchecked( IReadOnlyDictionary rawHeaderData, - IReadOnlyDictionary rawQueryData + IReadOnlyDictionary rawQueryData, + string skillID ) { return new( FrozenDictionary.ToFrozenDictionary(rawHeaderData), - FrozenDictionary.ToFrozenDictionary(rawQueryData) + FrozenDictionary.ToFrozenDictionary(rawQueryData), + skillID ); } diff --git a/src/Anthropic/Models/Beta/Skills/Versions/VersionRetrieveParams.cs b/src/Anthropic/Models/Beta/Skills/Versions/VersionRetrieveParams.cs index 0d5085d4e..a4af989ab 100644 --- a/src/Anthropic/Models/Beta/Skills/Versions/VersionRetrieveParams.cs +++ b/src/Anthropic/Models/Beta/Skills/Versions/VersionRetrieveParams.cs @@ -74,23 +74,31 @@ IReadOnlyDictionary rawQueryData [SetsRequiredMembers] VersionRetrieveParams( FrozenDictionary rawHeaderData, - FrozenDictionary rawQueryData + FrozenDictionary rawQueryData, + string skillID, + string version ) { this._rawHeaderData = new(rawHeaderData); this._rawQueryData = new(rawQueryData); + this.SkillID = skillID; + this.Version = version; } #pragma warning restore CS8618 /// public static VersionRetrieveParams FromRawUnchecked( IReadOnlyDictionary rawHeaderData, - IReadOnlyDictionary rawQueryData + IReadOnlyDictionary rawQueryData, + string skillID, + string version ) { return new( FrozenDictionary.ToFrozenDictionary(rawHeaderData), - FrozenDictionary.ToFrozenDictionary(rawQueryData) + FrozenDictionary.ToFrozenDictionary(rawQueryData), + skillID, + version ); } diff --git a/src/Anthropic/Models/ErrorObject.cs b/src/Anthropic/Models/ErrorObject.cs index 5233207b2..dcaadac7c 100644 --- a/src/Anthropic/Models/ErrorObject.cs +++ b/src/Anthropic/Models/ErrorObject.cs @@ -555,11 +555,10 @@ JsonSerializerOptions options ); if (deserialized != null) { - deserialized.Validate(); return new(deserialized, element); } } - catch (Exception e) when (e is JsonException || e is AnthropicInvalidDataException) + catch (JsonException) { // ignore } @@ -576,11 +575,10 @@ JsonSerializerOptions options ); if (deserialized != null) { - deserialized.Validate(); return new(deserialized, element); } } - catch (Exception e) when (e is JsonException || e is AnthropicInvalidDataException) + catch (JsonException) { // ignore } @@ -594,11 +592,10 @@ JsonSerializerOptions options var deserialized = JsonSerializer.Deserialize(element, options); if (deserialized != null) { - deserialized.Validate(); return new(deserialized, element); } } - catch (Exception e) when (e is JsonException || e is AnthropicInvalidDataException) + catch (JsonException) { // ignore } @@ -615,11 +612,10 @@ JsonSerializerOptions options ); if (deserialized != null) { - deserialized.Validate(); return new(deserialized, element); } } - catch (Exception e) when (e is JsonException || e is AnthropicInvalidDataException) + catch (JsonException) { // ignore } @@ -633,11 +629,10 @@ JsonSerializerOptions options var deserialized = JsonSerializer.Deserialize(element, options); if (deserialized != null) { - deserialized.Validate(); return new(deserialized, element); } } - catch (Exception e) when (e is JsonException || e is AnthropicInvalidDataException) + catch (JsonException) { // ignore } @@ -651,11 +646,10 @@ JsonSerializerOptions options var deserialized = JsonSerializer.Deserialize(element, options); if (deserialized != null) { - deserialized.Validate(); return new(deserialized, element); } } - catch (Exception e) when (e is JsonException || e is AnthropicInvalidDataException) + catch (JsonException) { // ignore } @@ -672,11 +666,10 @@ JsonSerializerOptions options ); if (deserialized != null) { - deserialized.Validate(); return new(deserialized, element); } } - catch (Exception e) when (e is JsonException || e is AnthropicInvalidDataException) + catch (JsonException) { // ignore } @@ -690,11 +683,10 @@ JsonSerializerOptions options var deserialized = JsonSerializer.Deserialize(element, options); if (deserialized != null) { - deserialized.Validate(); return new(deserialized, element); } } - catch (Exception e) when (e is JsonException || e is AnthropicInvalidDataException) + catch (JsonException) { // ignore } @@ -711,11 +703,10 @@ JsonSerializerOptions options ); if (deserialized != null) { - deserialized.Validate(); return new(deserialized, element); } } - catch (Exception e) when (e is JsonException || e is AnthropicInvalidDataException) + catch (JsonException) { // ignore } diff --git a/src/Anthropic/Models/ErrorType.cs b/src/Anthropic/Models/ErrorType.cs new file mode 100644 index 000000000..cc3b5d322 --- /dev/null +++ b/src/Anthropic/Models/ErrorType.cs @@ -0,0 +1,71 @@ +using System; +using System.Text.Json; +using System.Text.Json.Serialization; +using Anthropic.Exceptions; + +namespace Anthropic.Models; + +[JsonConverter(typeof(ErrorTypeConverter))] +public enum ErrorType +{ + InvalidRequestError, + AuthenticationError, + PermissionError, + NotFoundError, + RateLimitError, + TimeoutError, + OverloadedError, + ApiError, + BillingError, +} + +sealed class ErrorTypeConverter : JsonConverter +{ + public override ErrorType Read( + ref Utf8JsonReader reader, + Type typeToConvert, + JsonSerializerOptions options + ) + { + return JsonSerializer.Deserialize(ref reader, options) switch + { + "invalid_request_error" => ErrorType.InvalidRequestError, + "authentication_error" => ErrorType.AuthenticationError, + "permission_error" => ErrorType.PermissionError, + "not_found_error" => ErrorType.NotFoundError, + "rate_limit_error" => ErrorType.RateLimitError, + "timeout_error" => ErrorType.TimeoutError, + "overloaded_error" => ErrorType.OverloadedError, + "api_error" => ErrorType.ApiError, + "billing_error" => ErrorType.BillingError, + _ => (ErrorType)(-1), + }; + } + + public override void Write( + Utf8JsonWriter writer, + ErrorType value, + JsonSerializerOptions options + ) + { + JsonSerializer.Serialize( + writer, + value switch + { + ErrorType.InvalidRequestError => "invalid_request_error", + ErrorType.AuthenticationError => "authentication_error", + ErrorType.PermissionError => "permission_error", + ErrorType.NotFoundError => "not_found_error", + ErrorType.RateLimitError => "rate_limit_error", + ErrorType.TimeoutError => "timeout_error", + ErrorType.OverloadedError => "overloaded_error", + ErrorType.ApiError => "api_error", + ErrorType.BillingError => "billing_error", + _ => throw new AnthropicInvalidDataException( + string.Format("Invalid value '{0}' in {1}", value, nameof(value)) + ), + }, + options + ); + } +} diff --git a/src/Anthropic/Models/Messages/Batches/BatchCancelParams.cs b/src/Anthropic/Models/Messages/Batches/BatchCancelParams.cs index 9c9adf3de..602a93eef 100644 --- a/src/Anthropic/Models/Messages/Batches/BatchCancelParams.cs +++ b/src/Anthropic/Models/Messages/Batches/BatchCancelParams.cs @@ -51,23 +51,27 @@ IReadOnlyDictionary rawQueryData [SetsRequiredMembers] BatchCancelParams( FrozenDictionary rawHeaderData, - FrozenDictionary rawQueryData + FrozenDictionary rawQueryData, + string messageBatchID ) { this._rawHeaderData = new(rawHeaderData); this._rawQueryData = new(rawQueryData); + this.MessageBatchID = messageBatchID; } #pragma warning restore CS8618 /// public static BatchCancelParams FromRawUnchecked( IReadOnlyDictionary rawHeaderData, - IReadOnlyDictionary rawQueryData + IReadOnlyDictionary rawQueryData, + string messageBatchID ) { return new( FrozenDictionary.ToFrozenDictionary(rawHeaderData), - FrozenDictionary.ToFrozenDictionary(rawQueryData) + FrozenDictionary.ToFrozenDictionary(rawQueryData), + messageBatchID ); } diff --git a/src/Anthropic/Models/Messages/Batches/BatchDeleteParams.cs b/src/Anthropic/Models/Messages/Batches/BatchDeleteParams.cs index 0b4996de6..d6023834b 100644 --- a/src/Anthropic/Models/Messages/Batches/BatchDeleteParams.cs +++ b/src/Anthropic/Models/Messages/Batches/BatchDeleteParams.cs @@ -48,23 +48,27 @@ IReadOnlyDictionary rawQueryData [SetsRequiredMembers] BatchDeleteParams( FrozenDictionary rawHeaderData, - FrozenDictionary rawQueryData + FrozenDictionary rawQueryData, + string messageBatchID ) { this._rawHeaderData = new(rawHeaderData); this._rawQueryData = new(rawQueryData); + this.MessageBatchID = messageBatchID; } #pragma warning restore CS8618 /// public static BatchDeleteParams FromRawUnchecked( IReadOnlyDictionary rawHeaderData, - IReadOnlyDictionary rawQueryData + IReadOnlyDictionary rawQueryData, + string messageBatchID ) { return new( FrozenDictionary.ToFrozenDictionary(rawHeaderData), - FrozenDictionary.ToFrozenDictionary(rawQueryData) + FrozenDictionary.ToFrozenDictionary(rawQueryData), + messageBatchID ); } diff --git a/src/Anthropic/Models/Messages/Batches/BatchResultsParams.cs b/src/Anthropic/Models/Messages/Batches/BatchResultsParams.cs index c6faf79bb..d577e4c72 100644 --- a/src/Anthropic/Models/Messages/Batches/BatchResultsParams.cs +++ b/src/Anthropic/Models/Messages/Batches/BatchResultsParams.cs @@ -49,23 +49,27 @@ IReadOnlyDictionary rawQueryData [SetsRequiredMembers] BatchResultsParams( FrozenDictionary rawHeaderData, - FrozenDictionary rawQueryData + FrozenDictionary rawQueryData, + string messageBatchID ) { this._rawHeaderData = new(rawHeaderData); this._rawQueryData = new(rawQueryData); + this.MessageBatchID = messageBatchID; } #pragma warning restore CS8618 /// public static BatchResultsParams FromRawUnchecked( IReadOnlyDictionary rawHeaderData, - IReadOnlyDictionary rawQueryData + IReadOnlyDictionary rawQueryData, + string messageBatchID ) { return new( FrozenDictionary.ToFrozenDictionary(rawHeaderData), - FrozenDictionary.ToFrozenDictionary(rawQueryData) + FrozenDictionary.ToFrozenDictionary(rawQueryData), + messageBatchID ); } diff --git a/src/Anthropic/Models/Messages/Batches/BatchRetrieveParams.cs b/src/Anthropic/Models/Messages/Batches/BatchRetrieveParams.cs index 865cfa83a..10193601a 100644 --- a/src/Anthropic/Models/Messages/Batches/BatchRetrieveParams.cs +++ b/src/Anthropic/Models/Messages/Batches/BatchRetrieveParams.cs @@ -47,23 +47,27 @@ IReadOnlyDictionary rawQueryData [SetsRequiredMembers] BatchRetrieveParams( FrozenDictionary rawHeaderData, - FrozenDictionary rawQueryData + FrozenDictionary rawQueryData, + string messageBatchID ) { this._rawHeaderData = new(rawHeaderData); this._rawQueryData = new(rawQueryData); + this.MessageBatchID = messageBatchID; } #pragma warning restore CS8618 /// public static BatchRetrieveParams FromRawUnchecked( IReadOnlyDictionary rawHeaderData, - IReadOnlyDictionary rawQueryData + IReadOnlyDictionary rawQueryData, + string messageBatchID ) { return new( FrozenDictionary.ToFrozenDictionary(rawHeaderData), - FrozenDictionary.ToFrozenDictionary(rawQueryData) + FrozenDictionary.ToFrozenDictionary(rawQueryData), + messageBatchID ); } diff --git a/src/Anthropic/Models/Messages/Batches/MessageBatchResult.cs b/src/Anthropic/Models/Messages/Batches/MessageBatchResult.cs index f16d7a3c9..efe78c879 100644 --- a/src/Anthropic/Models/Messages/Batches/MessageBatchResult.cs +++ b/src/Anthropic/Models/Messages/Batches/MessageBatchResult.cs @@ -348,12 +348,10 @@ JsonSerializerOptions options ); if (deserialized != null) { - deserialized.Validate(); return new(deserialized, element); } } - catch (System::Exception e) - when (e is JsonException || e is AnthropicInvalidDataException) + catch (JsonException) { // ignore } @@ -370,12 +368,10 @@ JsonSerializerOptions options ); if (deserialized != null) { - deserialized.Validate(); return new(deserialized, element); } } - catch (System::Exception e) - when (e is JsonException || e is AnthropicInvalidDataException) + catch (JsonException) { // ignore } @@ -392,12 +388,10 @@ JsonSerializerOptions options ); if (deserialized != null) { - deserialized.Validate(); return new(deserialized, element); } } - catch (System::Exception e) - when (e is JsonException || e is AnthropicInvalidDataException) + catch (JsonException) { // ignore } @@ -414,12 +408,10 @@ JsonSerializerOptions options ); if (deserialized != null) { - deserialized.Validate(); return new(deserialized, element); } } - catch (System::Exception e) - when (e is JsonException || e is AnthropicInvalidDataException) + catch (JsonException) { // ignore } diff --git a/src/Anthropic/Models/Messages/CitationsDelta.cs b/src/Anthropic/Models/Messages/CitationsDelta.cs index 92fcb2e53..a85035ef0 100644 --- a/src/Anthropic/Models/Messages/CitationsDelta.cs +++ b/src/Anthropic/Models/Messages/CitationsDelta.cs @@ -563,12 +563,10 @@ JsonSerializerOptions options ); if (deserialized != null) { - deserialized.Validate(); return new(deserialized, element); } } - catch (System::Exception e) - when (e is JsonException || e is AnthropicInvalidDataException) + catch (JsonException) { // ignore } @@ -585,12 +583,10 @@ JsonSerializerOptions options ); if (deserialized != null) { - deserialized.Validate(); return new(deserialized, element); } } - catch (System::Exception e) - when (e is JsonException || e is AnthropicInvalidDataException) + catch (JsonException) { // ignore } @@ -607,12 +603,10 @@ JsonSerializerOptions options ); if (deserialized != null) { - deserialized.Validate(); return new(deserialized, element); } } - catch (System::Exception e) - when (e is JsonException || e is AnthropicInvalidDataException) + catch (JsonException) { // ignore } @@ -629,12 +623,10 @@ JsonSerializerOptions options ); if (deserialized != null) { - deserialized.Validate(); return new(deserialized, element); } } - catch (System::Exception e) - when (e is JsonException || e is AnthropicInvalidDataException) + catch (JsonException) { // ignore } @@ -651,12 +643,10 @@ JsonSerializerOptions options ); if (deserialized != null) { - deserialized.Validate(); return new(deserialized, element); } } - catch (System::Exception e) - when (e is JsonException || e is AnthropicInvalidDataException) + catch (JsonException) { // ignore } diff --git a/src/Anthropic/Models/Messages/CodeExecutionTool20250522.cs b/src/Anthropic/Models/Messages/CodeExecutionTool20250522.cs index 4a73c0c86..470bc121c 100644 --- a/src/Anthropic/Models/Messages/CodeExecutionTool20250522.cs +++ b/src/Anthropic/Models/Messages/CodeExecutionTool20250522.cs @@ -189,6 +189,13 @@ IReadOnlyDictionary rawData ) => CodeExecutionTool20250522.FromRawUnchecked(rawData); } +/// +/// Specifies who can invoke a tool. +/// +/// Values: direct: The model can call this tool directly. code_execution_20250825: +/// The tool can be called from the code execution environment (v1). code_execution_20260120: +/// The tool can be called from the code execution environment (v2 with persistence). +/// [JsonConverter(typeof(AllowedCallerConverter))] public enum AllowedCaller { diff --git a/src/Anthropic/Models/Messages/CodeExecutionTool20250825.cs b/src/Anthropic/Models/Messages/CodeExecutionTool20250825.cs index 8159fca8d..969c4664a 100644 --- a/src/Anthropic/Models/Messages/CodeExecutionTool20250825.cs +++ b/src/Anthropic/Models/Messages/CodeExecutionTool20250825.cs @@ -188,6 +188,13 @@ IReadOnlyDictionary rawData ) => CodeExecutionTool20250825.FromRawUnchecked(rawData); } +/// +/// Specifies who can invoke a tool. +/// +/// Values: direct: The model can call this tool directly. code_execution_20250825: +/// The tool can be called from the code execution environment (v1). code_execution_20260120: +/// The tool can be called from the code execution environment (v2 with persistence). +/// [JsonConverter(typeof(CodeExecutionTool20250825AllowedCallerConverter))] public enum CodeExecutionTool20250825AllowedCaller { diff --git a/src/Anthropic/Models/Messages/CodeExecutionTool20260120.cs b/src/Anthropic/Models/Messages/CodeExecutionTool20260120.cs index 2c9d138d4..76cdb6a8a 100644 --- a/src/Anthropic/Models/Messages/CodeExecutionTool20260120.cs +++ b/src/Anthropic/Models/Messages/CodeExecutionTool20260120.cs @@ -191,6 +191,13 @@ IReadOnlyDictionary rawData ) => CodeExecutionTool20260120.FromRawUnchecked(rawData); } +/// +/// Specifies who can invoke a tool. +/// +/// Values: direct: The model can call this tool directly. code_execution_20250825: +/// The tool can be called from the code execution environment (v1). code_execution_20260120: +/// The tool can be called from the code execution environment (v2 with persistence). +/// [JsonConverter(typeof(CodeExecutionTool20260120AllowedCallerConverter))] public enum CodeExecutionTool20260120AllowedCaller { diff --git a/src/Anthropic/Models/Messages/ContentBlock.cs b/src/Anthropic/Models/Messages/ContentBlock.cs index ce291efec..5a0b6b1f7 100644 --- a/src/Anthropic/Models/Messages/ContentBlock.cs +++ b/src/Anthropic/Models/Messages/ContentBlock.cs @@ -711,12 +711,10 @@ JsonSerializerOptions options var deserialized = JsonSerializer.Deserialize(element, options); if (deserialized != null) { - deserialized.Validate(); return new(deserialized, element); } } - catch (System::Exception e) - when (e is JsonException || e is AnthropicInvalidDataException) + catch (JsonException) { // ignore } @@ -730,12 +728,10 @@ JsonSerializerOptions options var deserialized = JsonSerializer.Deserialize(element, options); if (deserialized != null) { - deserialized.Validate(); return new(deserialized, element); } } - catch (System::Exception e) - when (e is JsonException || e is AnthropicInvalidDataException) + catch (JsonException) { // ignore } @@ -752,12 +748,10 @@ JsonSerializerOptions options ); if (deserialized != null) { - deserialized.Validate(); return new(deserialized, element); } } - catch (System::Exception e) - when (e is JsonException || e is AnthropicInvalidDataException) + catch (JsonException) { // ignore } @@ -771,12 +765,10 @@ JsonSerializerOptions options var deserialized = JsonSerializer.Deserialize(element, options); if (deserialized != null) { - deserialized.Validate(); return new(deserialized, element); } } - catch (System::Exception e) - when (e is JsonException || e is AnthropicInvalidDataException) + catch (JsonException) { // ignore } @@ -793,12 +785,10 @@ JsonSerializerOptions options ); if (deserialized != null) { - deserialized.Validate(); return new(deserialized, element); } } - catch (System::Exception e) - when (e is JsonException || e is AnthropicInvalidDataException) + catch (JsonException) { // ignore } @@ -815,12 +805,10 @@ JsonSerializerOptions options ); if (deserialized != null) { - deserialized.Validate(); return new(deserialized, element); } } - catch (System::Exception e) - when (e is JsonException || e is AnthropicInvalidDataException) + catch (JsonException) { // ignore } @@ -837,12 +825,10 @@ JsonSerializerOptions options ); if (deserialized != null) { - deserialized.Validate(); return new(deserialized, element); } } - catch (System::Exception e) - when (e is JsonException || e is AnthropicInvalidDataException) + catch (JsonException) { // ignore } @@ -859,12 +845,10 @@ JsonSerializerOptions options ); if (deserialized != null) { - deserialized.Validate(); return new(deserialized, element); } } - catch (System::Exception e) - when (e is JsonException || e is AnthropicInvalidDataException) + catch (JsonException) { // ignore } @@ -881,12 +865,10 @@ JsonSerializerOptions options ); if (deserialized != null) { - deserialized.Validate(); return new(deserialized, element); } } - catch (System::Exception e) - when (e is JsonException || e is AnthropicInvalidDataException) + catch (JsonException) { // ignore } @@ -904,12 +886,10 @@ JsonSerializerOptions options ); if (deserialized != null) { - deserialized.Validate(); return new(deserialized, element); } } - catch (System::Exception e) - when (e is JsonException || e is AnthropicInvalidDataException) + catch (JsonException) { // ignore } @@ -926,12 +906,10 @@ JsonSerializerOptions options ); if (deserialized != null) { - deserialized.Validate(); return new(deserialized, element); } } - catch (System::Exception e) - when (e is JsonException || e is AnthropicInvalidDataException) + catch (JsonException) { // ignore } @@ -948,12 +926,10 @@ JsonSerializerOptions options ); if (deserialized != null) { - deserialized.Validate(); return new(deserialized, element); } } - catch (System::Exception e) - when (e is JsonException || e is AnthropicInvalidDataException) + catch (JsonException) { // ignore } diff --git a/src/Anthropic/Models/Messages/ContentBlockParam.cs b/src/Anthropic/Models/Messages/ContentBlockParam.cs index a4abde586..e5281b6ad 100644 --- a/src/Anthropic/Models/Messages/ContentBlockParam.cs +++ b/src/Anthropic/Models/Messages/ContentBlockParam.cs @@ -950,12 +950,10 @@ JsonSerializerOptions options var deserialized = JsonSerializer.Deserialize(element, options); if (deserialized != null) { - deserialized.Validate(); return new(deserialized, element); } } - catch (System::Exception e) - when (e is JsonException || e is AnthropicInvalidDataException) + catch (JsonException) { // ignore } @@ -972,12 +970,10 @@ JsonSerializerOptions options ); if (deserialized != null) { - deserialized.Validate(); return new(deserialized, element); } } - catch (System::Exception e) - when (e is JsonException || e is AnthropicInvalidDataException) + catch (JsonException) { // ignore } @@ -994,12 +990,10 @@ JsonSerializerOptions options ); if (deserialized != null) { - deserialized.Validate(); return new(deserialized, element); } } - catch (System::Exception e) - when (e is JsonException || e is AnthropicInvalidDataException) + catch (JsonException) { // ignore } @@ -1016,12 +1010,10 @@ JsonSerializerOptions options ); if (deserialized != null) { - deserialized.Validate(); return new(deserialized, element); } } - catch (System::Exception e) - when (e is JsonException || e is AnthropicInvalidDataException) + catch (JsonException) { // ignore } @@ -1038,12 +1030,10 @@ JsonSerializerOptions options ); if (deserialized != null) { - deserialized.Validate(); return new(deserialized, element); } } - catch (System::Exception e) - when (e is JsonException || e is AnthropicInvalidDataException) + catch (JsonException) { // ignore } @@ -1060,12 +1050,10 @@ JsonSerializerOptions options ); if (deserialized != null) { - deserialized.Validate(); return new(deserialized, element); } } - catch (System::Exception e) - when (e is JsonException || e is AnthropicInvalidDataException) + catch (JsonException) { // ignore } @@ -1082,12 +1070,10 @@ JsonSerializerOptions options ); if (deserialized != null) { - deserialized.Validate(); return new(deserialized, element); } } - catch (System::Exception e) - when (e is JsonException || e is AnthropicInvalidDataException) + catch (JsonException) { // ignore } @@ -1104,12 +1090,10 @@ JsonSerializerOptions options ); if (deserialized != null) { - deserialized.Validate(); return new(deserialized, element); } } - catch (System::Exception e) - when (e is JsonException || e is AnthropicInvalidDataException) + catch (JsonException) { // ignore } @@ -1126,12 +1110,10 @@ JsonSerializerOptions options ); if (deserialized != null) { - deserialized.Validate(); return new(deserialized, element); } } - catch (System::Exception e) - when (e is JsonException || e is AnthropicInvalidDataException) + catch (JsonException) { // ignore } @@ -1148,12 +1130,10 @@ JsonSerializerOptions options ); if (deserialized != null) { - deserialized.Validate(); return new(deserialized, element); } } - catch (System::Exception e) - when (e is JsonException || e is AnthropicInvalidDataException) + catch (JsonException) { // ignore } @@ -1170,12 +1150,10 @@ JsonSerializerOptions options ); if (deserialized != null) { - deserialized.Validate(); return new(deserialized, element); } } - catch (System::Exception e) - when (e is JsonException || e is AnthropicInvalidDataException) + catch (JsonException) { // ignore } @@ -1193,12 +1171,10 @@ JsonSerializerOptions options ); if (deserialized != null) { - deserialized.Validate(); return new(deserialized, element); } } - catch (System::Exception e) - when (e is JsonException || e is AnthropicInvalidDataException) + catch (JsonException) { // ignore } @@ -1216,12 +1192,10 @@ JsonSerializerOptions options ); if (deserialized != null) { - deserialized.Validate(); return new(deserialized, element); } } - catch (System::Exception e) - when (e is JsonException || e is AnthropicInvalidDataException) + catch (JsonException) { // ignore } @@ -1239,12 +1213,10 @@ JsonSerializerOptions options ); if (deserialized != null) { - deserialized.Validate(); return new(deserialized, element); } } - catch (System::Exception e) - when (e is JsonException || e is AnthropicInvalidDataException) + catch (JsonException) { // ignore } @@ -1261,12 +1233,10 @@ JsonSerializerOptions options ); if (deserialized != null) { - deserialized.Validate(); return new(deserialized, element); } } - catch (System::Exception e) - when (e is JsonException || e is AnthropicInvalidDataException) + catch (JsonException) { // ignore } @@ -1283,12 +1253,10 @@ JsonSerializerOptions options ); if (deserialized != null) { - deserialized.Validate(); return new(deserialized, element); } } - catch (System::Exception e) - when (e is JsonException || e is AnthropicInvalidDataException) + catch (JsonException) { // ignore } diff --git a/src/Anthropic/Models/Messages/DocumentBlock.cs b/src/Anthropic/Models/Messages/DocumentBlock.cs index fb54346c6..55e32d0bb 100644 --- a/src/Anthropic/Models/Messages/DocumentBlock.cs +++ b/src/Anthropic/Models/Messages/DocumentBlock.cs @@ -357,12 +357,10 @@ JsonSerializerOptions options ); if (deserialized != null) { - deserialized.Validate(); return new(deserialized, element); } } - catch (System::Exception e) - when (e is JsonException || e is AnthropicInvalidDataException) + catch (JsonException) { // ignore } @@ -379,12 +377,10 @@ JsonSerializerOptions options ); if (deserialized != null) { - deserialized.Validate(); return new(deserialized, element); } } - catch (System::Exception e) - when (e is JsonException || e is AnthropicInvalidDataException) + catch (JsonException) { // ignore } diff --git a/src/Anthropic/Models/Messages/DocumentBlockParam.cs b/src/Anthropic/Models/Messages/DocumentBlockParam.cs index 4cf6073bb..3553a14d9 100644 --- a/src/Anthropic/Models/Messages/DocumentBlockParam.cs +++ b/src/Anthropic/Models/Messages/DocumentBlockParam.cs @@ -495,12 +495,10 @@ JsonSerializerOptions options ); if (deserialized != null) { - deserialized.Validate(); return new(deserialized, element); } } - catch (System::Exception e) - when (e is JsonException || e is AnthropicInvalidDataException) + catch (JsonException) { // ignore } @@ -517,12 +515,10 @@ JsonSerializerOptions options ); if (deserialized != null) { - deserialized.Validate(); return new(deserialized, element); } } - catch (System::Exception e) - when (e is JsonException || e is AnthropicInvalidDataException) + catch (JsonException) { // ignore } @@ -539,12 +535,10 @@ JsonSerializerOptions options ); if (deserialized != null) { - deserialized.Validate(); return new(deserialized, element); } } - catch (System::Exception e) - when (e is JsonException || e is AnthropicInvalidDataException) + catch (JsonException) { // ignore } @@ -558,12 +552,10 @@ JsonSerializerOptions options var deserialized = JsonSerializer.Deserialize(element, options); if (deserialized != null) { - deserialized.Validate(); return new(deserialized, element); } } - catch (System::Exception e) - when (e is JsonException || e is AnthropicInvalidDataException) + catch (JsonException) { // ignore } diff --git a/src/Anthropic/Models/Messages/ImageBlockParam.cs b/src/Anthropic/Models/Messages/ImageBlockParam.cs index 102c7ffb2..f23562bdb 100644 --- a/src/Anthropic/Models/Messages/ImageBlockParam.cs +++ b/src/Anthropic/Models/Messages/ImageBlockParam.cs @@ -344,12 +344,10 @@ JsonSerializerOptions options ); if (deserialized != null) { - deserialized.Validate(); return new(deserialized, element); } } - catch (System::Exception e) - when (e is JsonException || e is AnthropicInvalidDataException) + catch (JsonException) { // ignore } @@ -363,12 +361,10 @@ JsonSerializerOptions options var deserialized = JsonSerializer.Deserialize(element, options); if (deserialized != null) { - deserialized.Validate(); return new(deserialized, element); } } - catch (System::Exception e) - when (e is JsonException || e is AnthropicInvalidDataException) + catch (JsonException) { // ignore } diff --git a/src/Anthropic/Models/Messages/MemoryTool20250818.cs b/src/Anthropic/Models/Messages/MemoryTool20250818.cs index dd3fa0fb6..bf8d9aa7c 100644 --- a/src/Anthropic/Models/Messages/MemoryTool20250818.cs +++ b/src/Anthropic/Models/Messages/MemoryTool20250818.cs @@ -215,6 +215,13 @@ public MemoryTool20250818 FromRawUnchecked(IReadOnlyDictionary +/// Specifies who can invoke a tool. +/// +/// Values: direct: The model can call this tool directly. code_execution_20250825: +/// The tool can be called from the code execution environment (v1). code_execution_20260120: +/// The tool can be called from the code execution environment (v2 with persistence). +/// [JsonConverter(typeof(MemoryTool20250818AllowedCallerConverter))] public enum MemoryTool20250818AllowedCaller { diff --git a/src/Anthropic/Models/Messages/MessageContentBlockSourceContent.cs b/src/Anthropic/Models/Messages/MessageContentBlockSourceContent.cs index 7ddd2f29e..47b40364e 100644 --- a/src/Anthropic/Models/Messages/MessageContentBlockSourceContent.cs +++ b/src/Anthropic/Models/Messages/MessageContentBlockSourceContent.cs @@ -262,12 +262,10 @@ JsonSerializerOptions options var deserialized = JsonSerializer.Deserialize(element, options); if (deserialized != null) { - deserialized.Validate(); return new(deserialized, element); } } - catch (System::Exception e) - when (e is JsonException || e is AnthropicInvalidDataException) + catch (JsonException) { // ignore } @@ -284,12 +282,10 @@ JsonSerializerOptions options ); if (deserialized != null) { - deserialized.Validate(); return new(deserialized, element); } } - catch (System::Exception e) - when (e is JsonException || e is AnthropicInvalidDataException) + catch (JsonException) { // ignore } diff --git a/src/Anthropic/Models/Messages/MessageCountTokensTool.cs b/src/Anthropic/Models/Messages/MessageCountTokensTool.cs index d12c9ec85..0cf60669a 100644 --- a/src/Anthropic/Models/Messages/MessageCountTokensTool.cs +++ b/src/Anthropic/Models/Messages/MessageCountTokensTool.cs @@ -46,6 +46,7 @@ public CacheControlEphemeral? CacheControl webFetchTool20250910: (x) => x.CacheControl, webSearchTool20260209: (x) => x.CacheControl, webFetchTool20260209: (x) => x.CacheControl, + webFetchTool20260309: (x) => x.CacheControl, toolSearchToolBm25_20251119: (x) => x.CacheControl, toolSearchToolRegex20251119: (x) => x.CacheControl ); @@ -70,6 +71,7 @@ public bool? DeferLoading webFetchTool20250910: (x) => x.DeferLoading, webSearchTool20260209: (x) => x.DeferLoading, webFetchTool20260209: (x) => x.DeferLoading, + webFetchTool20260309: (x) => x.DeferLoading, toolSearchToolBm25_20251119: (x) => x.DeferLoading, toolSearchToolRegex20251119: (x) => x.DeferLoading ); @@ -94,6 +96,7 @@ public bool? Strict webFetchTool20250910: (x) => x.Strict, webSearchTool20260209: (x) => x.Strict, webFetchTool20260209: (x) => x.Strict, + webFetchTool20260309: (x) => x.Strict, toolSearchToolBm25_20251119: (x) => x.Strict, toolSearchToolRegex20251119: (x) => x.Strict ); @@ -118,6 +121,7 @@ public long? MaxUses webFetchTool20250910: (x) => x.MaxUses, webSearchTool20260209: (x) => x.MaxUses, webFetchTool20260209: (x) => x.MaxUses, + webFetchTool20260309: (x) => x.MaxUses, toolSearchToolBm25_20251119: (_) => null, toolSearchToolRegex20251119: (_) => null ); @@ -142,6 +146,7 @@ public UserLocation? UserLocation webFetchTool20250910: (_) => null, webSearchTool20260209: (x) => x.UserLocation, webFetchTool20260209: (_) => null, + webFetchTool20260309: (_) => null, toolSearchToolBm25_20251119: (_) => null, toolSearchToolRegex20251119: (_) => null ); @@ -166,6 +171,7 @@ public CitationsConfigParam? Citations webFetchTool20250910: (x) => x.Citations, webSearchTool20260209: (_) => null, webFetchTool20260209: (x) => x.Citations, + webFetchTool20260309: (x) => x.Citations, toolSearchToolBm25_20251119: (_) => null, toolSearchToolRegex20251119: (_) => null ); @@ -190,6 +196,7 @@ public long? MaxContentTokens webFetchTool20250910: (x) => x.MaxContentTokens, webSearchTool20260209: (_) => null, webFetchTool20260209: (x) => x.MaxContentTokens, + webFetchTool20260309: (x) => x.MaxContentTokens, toolSearchToolBm25_20251119: (_) => null, toolSearchToolRegex20251119: (_) => null ); @@ -274,6 +281,12 @@ public MessageCountTokensTool(WebFetchTool20260209 value, JsonElement? element = this._element = element; } + public MessageCountTokensTool(WebFetchTool20260309 value, JsonElement? element = null) + { + this.Value = value; + this._element = element; + } + public MessageCountTokensTool(ToolSearchToolBm25_20251119 value, JsonElement? element = null) { this.Value = value; @@ -570,6 +583,27 @@ public bool TryPickWebFetchTool20260209([NotNullWhen(true)] out WebFetchTool2026 return value != null; } + /// + /// Returns true and sets the out parameter if the instance was constructed with a variant of + /// type . + /// + /// Consider using or if you need to handle every variant. + /// + /// + /// + /// if (instance.TryPickWebFetchTool20260309(out var value)) { + /// // `value` is of type `WebFetchTool20260309` + /// Console.WriteLine(value); + /// } + /// + /// + /// + public bool TryPickWebFetchTool20260309([NotNullWhen(true)] out WebFetchTool20260309? value) + { + value = this.Value as WebFetchTool20260309; + return value != null; + } + /// /// Returns true and sets the out parameter if the instance was constructed with a variant of /// type . @@ -643,6 +677,7 @@ public bool TryPickToolSearchToolRegex20251119( /// (WebFetchTool20250910 value) => {...}, /// (WebSearchTool20260209 value) => {...}, /// (WebFetchTool20260209 value) => {...}, + /// (WebFetchTool20260309 value) => {...}, /// (ToolSearchToolBm25_20251119 value) => {...}, /// (ToolSearchToolRegex20251119 value) => {...} /// ); @@ -663,6 +698,7 @@ public void Switch( System::Action webFetchTool20250910, System::Action webSearchTool20260209, System::Action webFetchTool20260209, + System::Action webFetchTool20260309, System::Action toolSearchToolBm25_20251119, System::Action toolSearchToolRegex20251119 ) @@ -708,6 +744,9 @@ public void Switch( case WebFetchTool20260209 value: webFetchTool20260209(value); break; + case WebFetchTool20260309 value: + webFetchTool20260309(value); + break; case ToolSearchToolBm25_20251119 value: toolSearchToolBm25_20251119(value); break; @@ -749,6 +788,7 @@ public void Switch( /// (WebFetchTool20250910 value) => {...}, /// (WebSearchTool20260209 value) => {...}, /// (WebFetchTool20260209 value) => {...}, + /// (WebFetchTool20260309 value) => {...}, /// (ToolSearchToolBm25_20251119 value) => {...}, /// (ToolSearchToolRegex20251119 value) => {...} /// ); @@ -769,6 +809,7 @@ public T Match( System::Func webFetchTool20250910, System::Func webSearchTool20260209, System::Func webFetchTool20260209, + System::Func webFetchTool20260309, System::Func toolSearchToolBm25_20251119, System::Func toolSearchToolRegex20251119 ) @@ -788,6 +829,7 @@ public T Match( WebFetchTool20250910 value => webFetchTool20250910(value), WebSearchTool20260209 value => webSearchTool20260209(value), WebFetchTool20260209 value => webFetchTool20260209(value), + WebFetchTool20260309 value => webFetchTool20260309(value), ToolSearchToolBm25_20251119 value => toolSearchToolBm25_20251119(value), ToolSearchToolRegex20251119 value => toolSearchToolRegex20251119(value), _ => throw new AnthropicInvalidDataException( @@ -832,6 +874,9 @@ public static implicit operator MessageCountTokensTool(WebSearchTool20260209 val public static implicit operator MessageCountTokensTool(WebFetchTool20260209 value) => new(value); + public static implicit operator MessageCountTokensTool(WebFetchTool20260309 value) => + new(value); + public static implicit operator MessageCountTokensTool(ToolSearchToolBm25_20251119 value) => new(value); @@ -870,6 +915,7 @@ public override void Validate() (webFetchTool20250910) => webFetchTool20250910.Validate(), (webSearchTool20260209) => webSearchTool20260209.Validate(), (webFetchTool20260209) => webFetchTool20260209.Validate(), + (webFetchTool20260309) => webFetchTool20260309.Validate(), (toolSearchToolBm25_20251119) => toolSearchToolBm25_20251119.Validate(), (toolSearchToolRegex20251119) => toolSearchToolRegex20251119.Validate() ); @@ -908,8 +954,9 @@ int VariantIndex() WebFetchTool20250910 _ => 10, WebSearchTool20260209 _ => 11, WebFetchTool20260209 _ => 12, - ToolSearchToolBm25_20251119 _ => 13, - ToolSearchToolRegex20251119 _ => 14, + WebFetchTool20260309 _ => 13, + ToolSearchToolBm25_20251119 _ => 14, + ToolSearchToolRegex20251119 _ => 15, _ => -1, }; } @@ -1115,6 +1162,20 @@ JsonSerializerOptions options // ignore } + try + { + var deserialized = JsonSerializer.Deserialize(element, options); + if (deserialized != null) + { + deserialized.Validate(); + return new(deserialized, element); + } + } + catch (System::Exception e) when (e is JsonException || e is AnthropicInvalidDataException) + { + // ignore + } + try { var deserialized = JsonSerializer.Deserialize( diff --git a/src/Anthropic/Models/Messages/RawContentBlockDelta.cs b/src/Anthropic/Models/Messages/RawContentBlockDelta.cs index c76fedc61..1efef0d04 100644 --- a/src/Anthropic/Models/Messages/RawContentBlockDelta.cs +++ b/src/Anthropic/Models/Messages/RawContentBlockDelta.cs @@ -374,12 +374,10 @@ JsonSerializerOptions options var deserialized = JsonSerializer.Deserialize(element, options); if (deserialized != null) { - deserialized.Validate(); return new(deserialized, element); } } - catch (System::Exception e) - when (e is JsonException || e is AnthropicInvalidDataException) + catch (JsonException) { // ignore } @@ -393,12 +391,10 @@ JsonSerializerOptions options var deserialized = JsonSerializer.Deserialize(element, options); if (deserialized != null) { - deserialized.Validate(); return new(deserialized, element); } } - catch (System::Exception e) - when (e is JsonException || e is AnthropicInvalidDataException) + catch (JsonException) { // ignore } @@ -412,12 +408,10 @@ JsonSerializerOptions options var deserialized = JsonSerializer.Deserialize(element, options); if (deserialized != null) { - deserialized.Validate(); return new(deserialized, element); } } - catch (System::Exception e) - when (e is JsonException || e is AnthropicInvalidDataException) + catch (JsonException) { // ignore } @@ -431,12 +425,10 @@ JsonSerializerOptions options var deserialized = JsonSerializer.Deserialize(element, options); if (deserialized != null) { - deserialized.Validate(); return new(deserialized, element); } } - catch (System::Exception e) - when (e is JsonException || e is AnthropicInvalidDataException) + catch (JsonException) { // ignore } @@ -450,12 +442,10 @@ JsonSerializerOptions options var deserialized = JsonSerializer.Deserialize(element, options); if (deserialized != null) { - deserialized.Validate(); return new(deserialized, element); } } - catch (System::Exception e) - when (e is JsonException || e is AnthropicInvalidDataException) + catch (JsonException) { // ignore } diff --git a/src/Anthropic/Models/Messages/RawContentBlockStartEvent.cs b/src/Anthropic/Models/Messages/RawContentBlockStartEvent.cs index 6cfd41666..98174d29d 100644 --- a/src/Anthropic/Models/Messages/RawContentBlockStartEvent.cs +++ b/src/Anthropic/Models/Messages/RawContentBlockStartEvent.cs @@ -859,12 +859,10 @@ JsonSerializerOptions options var deserialized = JsonSerializer.Deserialize(element, options); if (deserialized != null) { - deserialized.Validate(); return new(deserialized, element); } } - catch (System::Exception e) - when (e is JsonException || e is AnthropicInvalidDataException) + catch (JsonException) { // ignore } @@ -878,12 +876,10 @@ JsonSerializerOptions options var deserialized = JsonSerializer.Deserialize(element, options); if (deserialized != null) { - deserialized.Validate(); return new(deserialized, element); } } - catch (System::Exception e) - when (e is JsonException || e is AnthropicInvalidDataException) + catch (JsonException) { // ignore } @@ -900,12 +896,10 @@ JsonSerializerOptions options ); if (deserialized != null) { - deserialized.Validate(); return new(deserialized, element); } } - catch (System::Exception e) - when (e is JsonException || e is AnthropicInvalidDataException) + catch (JsonException) { // ignore } @@ -919,12 +913,10 @@ JsonSerializerOptions options var deserialized = JsonSerializer.Deserialize(element, options); if (deserialized != null) { - deserialized.Validate(); return new(deserialized, element); } } - catch (System::Exception e) - when (e is JsonException || e is AnthropicInvalidDataException) + catch (JsonException) { // ignore } @@ -941,12 +933,10 @@ JsonSerializerOptions options ); if (deserialized != null) { - deserialized.Validate(); return new(deserialized, element); } } - catch (System::Exception e) - when (e is JsonException || e is AnthropicInvalidDataException) + catch (JsonException) { // ignore } @@ -963,12 +953,10 @@ JsonSerializerOptions options ); if (deserialized != null) { - deserialized.Validate(); return new(deserialized, element); } } - catch (System::Exception e) - when (e is JsonException || e is AnthropicInvalidDataException) + catch (JsonException) { // ignore } @@ -985,12 +973,10 @@ JsonSerializerOptions options ); if (deserialized != null) { - deserialized.Validate(); return new(deserialized, element); } } - catch (System::Exception e) - when (e is JsonException || e is AnthropicInvalidDataException) + catch (JsonException) { // ignore } @@ -1007,12 +993,10 @@ JsonSerializerOptions options ); if (deserialized != null) { - deserialized.Validate(); return new(deserialized, element); } } - catch (System::Exception e) - when (e is JsonException || e is AnthropicInvalidDataException) + catch (JsonException) { // ignore } @@ -1029,12 +1013,10 @@ JsonSerializerOptions options ); if (deserialized != null) { - deserialized.Validate(); return new(deserialized, element); } } - catch (System::Exception e) - when (e is JsonException || e is AnthropicInvalidDataException) + catch (JsonException) { // ignore } @@ -1052,12 +1034,10 @@ JsonSerializerOptions options ); if (deserialized != null) { - deserialized.Validate(); return new(deserialized, element); } } - catch (System::Exception e) - when (e is JsonException || e is AnthropicInvalidDataException) + catch (JsonException) { // ignore } @@ -1074,12 +1054,10 @@ JsonSerializerOptions options ); if (deserialized != null) { - deserialized.Validate(); return new(deserialized, element); } } - catch (System::Exception e) - when (e is JsonException || e is AnthropicInvalidDataException) + catch (JsonException) { // ignore } @@ -1096,12 +1074,10 @@ JsonSerializerOptions options ); if (deserialized != null) { - deserialized.Validate(); return new(deserialized, element); } } - catch (System::Exception e) - when (e is JsonException || e is AnthropicInvalidDataException) + catch (JsonException) { // ignore } diff --git a/src/Anthropic/Models/Messages/RawMessageStreamEvent.cs b/src/Anthropic/Models/Messages/RawMessageStreamEvent.cs index 0a7283050..39e9ccad5 100644 --- a/src/Anthropic/Models/Messages/RawMessageStreamEvent.cs +++ b/src/Anthropic/Models/Messages/RawMessageStreamEvent.cs @@ -435,12 +435,10 @@ JsonSerializerOptions options ); if (deserialized != null) { - deserialized.Validate(); return new(deserialized, element); } } - catch (System::Exception e) - when (e is JsonException || e is AnthropicInvalidDataException) + catch (JsonException) { // ignore } @@ -457,12 +455,10 @@ JsonSerializerOptions options ); if (deserialized != null) { - deserialized.Validate(); return new(deserialized, element); } } - catch (System::Exception e) - when (e is JsonException || e is AnthropicInvalidDataException) + catch (JsonException) { // ignore } @@ -479,12 +475,10 @@ JsonSerializerOptions options ); if (deserialized != null) { - deserialized.Validate(); return new(deserialized, element); } } - catch (System::Exception e) - when (e is JsonException || e is AnthropicInvalidDataException) + catch (JsonException) { // ignore } @@ -501,12 +495,10 @@ JsonSerializerOptions options ); if (deserialized != null) { - deserialized.Validate(); return new(deserialized, element); } } - catch (System::Exception e) - when (e is JsonException || e is AnthropicInvalidDataException) + catch (JsonException) { // ignore } @@ -523,12 +515,10 @@ JsonSerializerOptions options ); if (deserialized != null) { - deserialized.Validate(); return new(deserialized, element); } } - catch (System::Exception e) - when (e is JsonException || e is AnthropicInvalidDataException) + catch (JsonException) { // ignore } @@ -545,12 +535,10 @@ JsonSerializerOptions options ); if (deserialized != null) { - deserialized.Validate(); return new(deserialized, element); } } - catch (System::Exception e) - when (e is JsonException || e is AnthropicInvalidDataException) + catch (JsonException) { // ignore } diff --git a/src/Anthropic/Models/Messages/ServerToolUseBlock.cs b/src/Anthropic/Models/Messages/ServerToolUseBlock.cs index ab8c27b06..3bbd3abcc 100644 --- a/src/Anthropic/Models/Messages/ServerToolUseBlock.cs +++ b/src/Anthropic/Models/Messages/ServerToolUseBlock.cs @@ -428,12 +428,10 @@ JsonSerializerOptions options var deserialized = JsonSerializer.Deserialize(element, options); if (deserialized != null) { - deserialized.Validate(); return new(deserialized, element); } } - catch (System::Exception e) - when (e is JsonException || e is AnthropicInvalidDataException) + catch (JsonException) { // ignore } @@ -450,12 +448,10 @@ JsonSerializerOptions options ); if (deserialized != null) { - deserialized.Validate(); return new(deserialized, element); } } - catch (System::Exception e) - when (e is JsonException || e is AnthropicInvalidDataException) + catch (JsonException) { // ignore } @@ -472,12 +468,10 @@ JsonSerializerOptions options ); if (deserialized != null) { - deserialized.Validate(); return new(deserialized, element); } } - catch (System::Exception e) - when (e is JsonException || e is AnthropicInvalidDataException) + catch (JsonException) { // ignore } diff --git a/src/Anthropic/Models/Messages/ServerToolUseBlockParam.cs b/src/Anthropic/Models/Messages/ServerToolUseBlockParam.cs index b0067993e..e472d3671 100644 --- a/src/Anthropic/Models/Messages/ServerToolUseBlockParam.cs +++ b/src/Anthropic/Models/Messages/ServerToolUseBlockParam.cs @@ -521,12 +521,10 @@ JsonSerializerOptions options var deserialized = JsonSerializer.Deserialize(element, options); if (deserialized != null) { - deserialized.Validate(); return new(deserialized, element); } } - catch (System::Exception e) - when (e is JsonException || e is AnthropicInvalidDataException) + catch (JsonException) { // ignore } @@ -543,12 +541,10 @@ JsonSerializerOptions options ); if (deserialized != null) { - deserialized.Validate(); return new(deserialized, element); } } - catch (System::Exception e) - when (e is JsonException || e is AnthropicInvalidDataException) + catch (JsonException) { // ignore } @@ -565,12 +561,10 @@ JsonSerializerOptions options ); if (deserialized != null) { - deserialized.Validate(); return new(deserialized, element); } } - catch (System::Exception e) - when (e is JsonException || e is AnthropicInvalidDataException) + catch (JsonException) { // ignore } diff --git a/src/Anthropic/Models/Messages/TextCitation.cs b/src/Anthropic/Models/Messages/TextCitation.cs index 7a884a511..cf8fbea78 100644 --- a/src/Anthropic/Models/Messages/TextCitation.cs +++ b/src/Anthropic/Models/Messages/TextCitation.cs @@ -482,12 +482,10 @@ JsonSerializerOptions options ); if (deserialized != null) { - deserialized.Validate(); return new(deserialized, element); } } - catch (System::Exception e) - when (e is JsonException || e is AnthropicInvalidDataException) + catch (JsonException) { // ignore } @@ -504,12 +502,10 @@ JsonSerializerOptions options ); if (deserialized != null) { - deserialized.Validate(); return new(deserialized, element); } } - catch (System::Exception e) - when (e is JsonException || e is AnthropicInvalidDataException) + catch (JsonException) { // ignore } @@ -526,12 +522,10 @@ JsonSerializerOptions options ); if (deserialized != null) { - deserialized.Validate(); return new(deserialized, element); } } - catch (System::Exception e) - when (e is JsonException || e is AnthropicInvalidDataException) + catch (JsonException) { // ignore } @@ -548,12 +542,10 @@ JsonSerializerOptions options ); if (deserialized != null) { - deserialized.Validate(); return new(deserialized, element); } } - catch (System::Exception e) - when (e is JsonException || e is AnthropicInvalidDataException) + catch (JsonException) { // ignore } @@ -570,12 +562,10 @@ JsonSerializerOptions options ); if (deserialized != null) { - deserialized.Validate(); return new(deserialized, element); } } - catch (System::Exception e) - when (e is JsonException || e is AnthropicInvalidDataException) + catch (JsonException) { // ignore } diff --git a/src/Anthropic/Models/Messages/TextCitationParam.cs b/src/Anthropic/Models/Messages/TextCitationParam.cs index d6c62b33e..898719f96 100644 --- a/src/Anthropic/Models/Messages/TextCitationParam.cs +++ b/src/Anthropic/Models/Messages/TextCitationParam.cs @@ -479,12 +479,10 @@ JsonSerializerOptions options ); if (deserialized != null) { - deserialized.Validate(); return new(deserialized, element); } } - catch (System::Exception e) - when (e is JsonException || e is AnthropicInvalidDataException) + catch (JsonException) { // ignore } @@ -501,12 +499,10 @@ JsonSerializerOptions options ); if (deserialized != null) { - deserialized.Validate(); return new(deserialized, element); } } - catch (System::Exception e) - when (e is JsonException || e is AnthropicInvalidDataException) + catch (JsonException) { // ignore } @@ -524,12 +520,10 @@ JsonSerializerOptions options ); if (deserialized != null) { - deserialized.Validate(); return new(deserialized, element); } } - catch (System::Exception e) - when (e is JsonException || e is AnthropicInvalidDataException) + catch (JsonException) { // ignore } @@ -547,12 +541,10 @@ JsonSerializerOptions options ); if (deserialized != null) { - deserialized.Validate(); return new(deserialized, element); } } - catch (System::Exception e) - when (e is JsonException || e is AnthropicInvalidDataException) + catch (JsonException) { // ignore } @@ -570,12 +562,10 @@ JsonSerializerOptions options ); if (deserialized != null) { - deserialized.Validate(); return new(deserialized, element); } } - catch (System::Exception e) - when (e is JsonException || e is AnthropicInvalidDataException) + catch (JsonException) { // ignore } diff --git a/src/Anthropic/Models/Messages/ThinkingConfigAdaptive.cs b/src/Anthropic/Models/Messages/ThinkingConfigAdaptive.cs index 56fe6f47a..954602eb4 100644 --- a/src/Anthropic/Models/Messages/ThinkingConfigAdaptive.cs +++ b/src/Anthropic/Models/Messages/ThinkingConfigAdaptive.cs @@ -5,6 +5,7 @@ using System.Text.Json.Serialization; using Anthropic.Core; using Anthropic.Exceptions; +using System = System; namespace Anthropic.Models.Messages; @@ -21,6 +22,21 @@ public JsonElement Type init { this._rawData.Set("type", value); } } + /// + /// Controls how thinking content appears in the response. When set to `summarized`, + /// thinking is returned normally. When set to `omitted`, thinking content is + /// redacted but a signature is returned for multi-turn continuity. Defaults to `summarized`. + /// + public ApiEnum? Display + { + get + { + this._rawData.Freeze(); + return this._rawData.GetNullableClass>("display"); + } + init { this._rawData.Set("display", value); } + } + /// public override void Validate() { @@ -28,6 +44,7 @@ public override void Validate() { throw new AnthropicInvalidDataException("Invalid value given for constant"); } + this.Display?.Validate(); } public ThinkingConfigAdaptive() @@ -72,3 +89,48 @@ public ThinkingConfigAdaptive FromRawUnchecked( IReadOnlyDictionary rawData ) => ThinkingConfigAdaptive.FromRawUnchecked(rawData); } + +/// +/// Controls how thinking content appears in the response. When set to `summarized`, +/// thinking is returned normally. When set to `omitted`, thinking content is redacted +/// but a signature is returned for multi-turn continuity. Defaults to `summarized`. +/// +[JsonConverter(typeof(DisplayConverter))] +public enum Display +{ + Summarized, + Omitted, +} + +sealed class DisplayConverter : JsonConverter +{ + public override Display Read( + ref Utf8JsonReader reader, + System::Type typeToConvert, + JsonSerializerOptions options + ) + { + return JsonSerializer.Deserialize(ref reader, options) switch + { + "summarized" => Display.Summarized, + "omitted" => Display.Omitted, + _ => (Display)(-1), + }; + } + + public override void Write(Utf8JsonWriter writer, Display value, JsonSerializerOptions options) + { + JsonSerializer.Serialize( + writer, + value switch + { + Display.Summarized => "summarized", + Display.Omitted => "omitted", + _ => throw new AnthropicInvalidDataException( + string.Format("Invalid value '{0}' in {1}", value, nameof(value)) + ), + }, + options + ); + } +} diff --git a/src/Anthropic/Models/Messages/ThinkingConfigEnabled.cs b/src/Anthropic/Models/Messages/ThinkingConfigEnabled.cs index 1f752eae3..180eed851 100644 --- a/src/Anthropic/Models/Messages/ThinkingConfigEnabled.cs +++ b/src/Anthropic/Models/Messages/ThinkingConfigEnabled.cs @@ -5,6 +5,7 @@ using System.Text.Json.Serialization; using Anthropic.Core; using Anthropic.Exceptions; +using System = System; namespace Anthropic.Models.Messages; @@ -41,6 +42,23 @@ public JsonElement Type init { this._rawData.Set("type", value); } } + /// + /// Controls how thinking content appears in the response. When set to `summarized`, + /// thinking is returned normally. When set to `omitted`, thinking content is + /// redacted but a signature is returned for multi-turn continuity. Defaults to `summarized`. + /// + public ApiEnum? Display + { + get + { + this._rawData.Freeze(); + return this._rawData.GetNullableClass>( + "display" + ); + } + init { this._rawData.Set("display", value); } + } + /// public override void Validate() { @@ -49,6 +67,7 @@ public override void Validate() { throw new AnthropicInvalidDataException("Invalid value given for constant"); } + this.Display?.Validate(); } public ThinkingConfigEnabled() @@ -100,3 +119,52 @@ public ThinkingConfigEnabled FromRawUnchecked( IReadOnlyDictionary rawData ) => ThinkingConfigEnabled.FromRawUnchecked(rawData); } + +/// +/// Controls how thinking content appears in the response. When set to `summarized`, +/// thinking is returned normally. When set to `omitted`, thinking content is redacted +/// but a signature is returned for multi-turn continuity. Defaults to `summarized`. +/// +[JsonConverter(typeof(ThinkingConfigEnabledDisplayConverter))] +public enum ThinkingConfigEnabledDisplay +{ + Summarized, + Omitted, +} + +sealed class ThinkingConfigEnabledDisplayConverter : JsonConverter +{ + public override ThinkingConfigEnabledDisplay Read( + ref Utf8JsonReader reader, + System::Type typeToConvert, + JsonSerializerOptions options + ) + { + return JsonSerializer.Deserialize(ref reader, options) switch + { + "summarized" => ThinkingConfigEnabledDisplay.Summarized, + "omitted" => ThinkingConfigEnabledDisplay.Omitted, + _ => (ThinkingConfigEnabledDisplay)(-1), + }; + } + + public override void Write( + Utf8JsonWriter writer, + ThinkingConfigEnabledDisplay value, + JsonSerializerOptions options + ) + { + JsonSerializer.Serialize( + writer, + value switch + { + ThinkingConfigEnabledDisplay.Summarized => "summarized", + ThinkingConfigEnabledDisplay.Omitted => "omitted", + _ => throw new AnthropicInvalidDataException( + string.Format("Invalid value '{0}' in {1}", value, nameof(value)) + ), + }, + options + ); + } +} diff --git a/src/Anthropic/Models/Messages/ThinkingConfigParam.cs b/src/Anthropic/Models/Messages/ThinkingConfigParam.cs index 59d0e9ba7..28ed897ea 100644 --- a/src/Anthropic/Models/Messages/ThinkingConfigParam.cs +++ b/src/Anthropic/Models/Messages/ThinkingConfigParam.cs @@ -303,12 +303,10 @@ JsonSerializerOptions options ); if (deserialized != null) { - deserialized.Validate(); return new(deserialized, element); } } - catch (System::Exception e) - when (e is JsonException || e is AnthropicInvalidDataException) + catch (JsonException) { // ignore } @@ -325,12 +323,10 @@ JsonSerializerOptions options ); if (deserialized != null) { - deserialized.Validate(); return new(deserialized, element); } } - catch (System::Exception e) - when (e is JsonException || e is AnthropicInvalidDataException) + catch (JsonException) { // ignore } @@ -347,12 +343,10 @@ JsonSerializerOptions options ); if (deserialized != null) { - deserialized.Validate(); return new(deserialized, element); } } - catch (System::Exception e) - when (e is JsonException || e is AnthropicInvalidDataException) + catch (JsonException) { // ignore } diff --git a/src/Anthropic/Models/Messages/Tool.cs b/src/Anthropic/Models/Messages/Tool.cs index 3b01c9926..2643ead38 100644 --- a/src/Anthropic/Models/Messages/Tool.cs +++ b/src/Anthropic/Models/Messages/Tool.cs @@ -366,6 +366,13 @@ public InputSchema FromRawUnchecked(IReadOnlyDictionary raw InputSchema.FromRawUnchecked(rawData); } +/// +/// Specifies who can invoke a tool. +/// +/// Values: direct: The model can call this tool directly. code_execution_20250825: +/// The tool can be called from the code execution environment (v1). code_execution_20260120: +/// The tool can be called from the code execution environment (v2 with persistence). +/// [JsonConverter(typeof(ToolAllowedCallerConverter))] public enum ToolAllowedCaller { diff --git a/src/Anthropic/Models/Messages/ToolBash20250124.cs b/src/Anthropic/Models/Messages/ToolBash20250124.cs index d0d3644a4..2912b9a50 100644 --- a/src/Anthropic/Models/Messages/ToolBash20250124.cs +++ b/src/Anthropic/Models/Messages/ToolBash20250124.cs @@ -213,6 +213,13 @@ public ToolBash20250124 FromRawUnchecked(IReadOnlyDictionary +/// Specifies who can invoke a tool. +/// +/// Values: direct: The model can call this tool directly. code_execution_20250825: +/// The tool can be called from the code execution environment (v1). code_execution_20260120: +/// The tool can be called from the code execution environment (v2 with persistence). +/// [JsonConverter(typeof(ToolBash20250124AllowedCallerConverter))] public enum ToolBash20250124AllowedCaller { diff --git a/src/Anthropic/Models/Messages/ToolChoice.cs b/src/Anthropic/Models/Messages/ToolChoice.cs index facd7ccf0..656ae4806 100644 --- a/src/Anthropic/Models/Messages/ToolChoice.cs +++ b/src/Anthropic/Models/Messages/ToolChoice.cs @@ -349,12 +349,10 @@ JsonSerializerOptions options var deserialized = JsonSerializer.Deserialize(element, options); if (deserialized != null) { - deserialized.Validate(); return new(deserialized, element); } } - catch (System::Exception e) - when (e is JsonException || e is AnthropicInvalidDataException) + catch (JsonException) { // ignore } @@ -368,12 +366,10 @@ JsonSerializerOptions options var deserialized = JsonSerializer.Deserialize(element, options); if (deserialized != null) { - deserialized.Validate(); return new(deserialized, element); } } - catch (System::Exception e) - when (e is JsonException || e is AnthropicInvalidDataException) + catch (JsonException) { // ignore } @@ -387,12 +383,10 @@ JsonSerializerOptions options var deserialized = JsonSerializer.Deserialize(element, options); if (deserialized != null) { - deserialized.Validate(); return new(deserialized, element); } } - catch (System::Exception e) - when (e is JsonException || e is AnthropicInvalidDataException) + catch (JsonException) { // ignore } @@ -406,12 +400,10 @@ JsonSerializerOptions options var deserialized = JsonSerializer.Deserialize(element, options); if (deserialized != null) { - deserialized.Validate(); return new(deserialized, element); } } - catch (System::Exception e) - when (e is JsonException || e is AnthropicInvalidDataException) + catch (JsonException) { // ignore } diff --git a/src/Anthropic/Models/Messages/ToolResultBlockParam.cs b/src/Anthropic/Models/Messages/ToolResultBlockParam.cs index 13be59b62..019a0be88 100644 --- a/src/Anthropic/Models/Messages/ToolResultBlockParam.cs +++ b/src/Anthropic/Models/Messages/ToolResultBlockParam.cs @@ -800,12 +800,10 @@ JsonSerializerOptions options var deserialized = JsonSerializer.Deserialize(element, options); if (deserialized != null) { - deserialized.Validate(); return new(deserialized, element); } } - catch (System::Exception e) - when (e is JsonException || e is AnthropicInvalidDataException) + catch (JsonException) { // ignore } @@ -822,12 +820,10 @@ JsonSerializerOptions options ); if (deserialized != null) { - deserialized.Validate(); return new(deserialized, element); } } - catch (System::Exception e) - when (e is JsonException || e is AnthropicInvalidDataException) + catch (JsonException) { // ignore } @@ -844,12 +840,10 @@ JsonSerializerOptions options ); if (deserialized != null) { - deserialized.Validate(); return new(deserialized, element); } } - catch (System::Exception e) - when (e is JsonException || e is AnthropicInvalidDataException) + catch (JsonException) { // ignore } @@ -866,12 +860,10 @@ JsonSerializerOptions options ); if (deserialized != null) { - deserialized.Validate(); return new(deserialized, element); } } - catch (System::Exception e) - when (e is JsonException || e is AnthropicInvalidDataException) + catch (JsonException) { // ignore } @@ -888,12 +880,10 @@ JsonSerializerOptions options ); if (deserialized != null) { - deserialized.Validate(); return new(deserialized, element); } } - catch (System::Exception e) - when (e is JsonException || e is AnthropicInvalidDataException) + catch (JsonException) { // ignore } diff --git a/src/Anthropic/Models/Messages/ToolSearchToolBm25_20251119.cs b/src/Anthropic/Models/Messages/ToolSearchToolBm25_20251119.cs index 4c0c1769b..11ecc2534 100644 --- a/src/Anthropic/Models/Messages/ToolSearchToolBm25_20251119.cs +++ b/src/Anthropic/Models/Messages/ToolSearchToolBm25_20251119.cs @@ -239,6 +239,13 @@ JsonSerializerOptions options } } +/// +/// Specifies who can invoke a tool. +/// +/// Values: direct: The model can call this tool directly. code_execution_20250825: +/// The tool can be called from the code execution environment (v1). code_execution_20260120: +/// The tool can be called from the code execution environment (v2 with persistence). +/// [JsonConverter(typeof(ToolSearchToolBm25_20251119AllowedCallerConverter))] public enum ToolSearchToolBm25_20251119AllowedCaller { diff --git a/src/Anthropic/Models/Messages/ToolSearchToolRegex20251119.cs b/src/Anthropic/Models/Messages/ToolSearchToolRegex20251119.cs index 490ac0f49..0c22e042d 100644 --- a/src/Anthropic/Models/Messages/ToolSearchToolRegex20251119.cs +++ b/src/Anthropic/Models/Messages/ToolSearchToolRegex20251119.cs @@ -239,6 +239,13 @@ JsonSerializerOptions options } } +/// +/// Specifies who can invoke a tool. +/// +/// Values: direct: The model can call this tool directly. code_execution_20250825: +/// The tool can be called from the code execution environment (v1). code_execution_20260120: +/// The tool can be called from the code execution environment (v2 with persistence). +/// [JsonConverter(typeof(ToolSearchToolRegex20251119AllowedCallerConverter))] public enum ToolSearchToolRegex20251119AllowedCaller { diff --git a/src/Anthropic/Models/Messages/ToolTextEditor20250124.cs b/src/Anthropic/Models/Messages/ToolTextEditor20250124.cs index af022c7a3..e9327ffe0 100644 --- a/src/Anthropic/Models/Messages/ToolTextEditor20250124.cs +++ b/src/Anthropic/Models/Messages/ToolTextEditor20250124.cs @@ -223,6 +223,13 @@ IReadOnlyDictionary rawData ) => ToolTextEditor20250124.FromRawUnchecked(rawData); } +/// +/// Specifies who can invoke a tool. +/// +/// Values: direct: The model can call this tool directly. code_execution_20250825: +/// The tool can be called from the code execution environment (v1). code_execution_20260120: +/// The tool can be called from the code execution environment (v2 with persistence). +/// [JsonConverter(typeof(ToolTextEditor20250124AllowedCallerConverter))] public enum ToolTextEditor20250124AllowedCaller { diff --git a/src/Anthropic/Models/Messages/ToolTextEditor20250429.cs b/src/Anthropic/Models/Messages/ToolTextEditor20250429.cs index ba85278a7..de0eaaa66 100644 --- a/src/Anthropic/Models/Messages/ToolTextEditor20250429.cs +++ b/src/Anthropic/Models/Messages/ToolTextEditor20250429.cs @@ -223,6 +223,13 @@ IReadOnlyDictionary rawData ) => ToolTextEditor20250429.FromRawUnchecked(rawData); } +/// +/// Specifies who can invoke a tool. +/// +/// Values: direct: The model can call this tool directly. code_execution_20250825: +/// The tool can be called from the code execution environment (v1). code_execution_20260120: +/// The tool can be called from the code execution environment (v2 with persistence). +/// [JsonConverter(typeof(ToolTextEditor20250429AllowedCallerConverter))] public enum ToolTextEditor20250429AllowedCaller { diff --git a/src/Anthropic/Models/Messages/ToolTextEditor20250728.cs b/src/Anthropic/Models/Messages/ToolTextEditor20250728.cs index ed91a7443..1bb3ff5a8 100644 --- a/src/Anthropic/Models/Messages/ToolTextEditor20250728.cs +++ b/src/Anthropic/Models/Messages/ToolTextEditor20250728.cs @@ -238,6 +238,13 @@ IReadOnlyDictionary rawData ) => ToolTextEditor20250728.FromRawUnchecked(rawData); } +/// +/// Specifies who can invoke a tool. +/// +/// Values: direct: The model can call this tool directly. code_execution_20250825: +/// The tool can be called from the code execution environment (v1). code_execution_20260120: +/// The tool can be called from the code execution environment (v2 with persistence). +/// [JsonConverter(typeof(ToolTextEditor20250728AllowedCallerConverter))] public enum ToolTextEditor20250728AllowedCaller { diff --git a/src/Anthropic/Models/Messages/ToolUnion.cs b/src/Anthropic/Models/Messages/ToolUnion.cs index e04714b72..cc578e14c 100644 --- a/src/Anthropic/Models/Messages/ToolUnion.cs +++ b/src/Anthropic/Models/Messages/ToolUnion.cs @@ -46,6 +46,7 @@ public CacheControlEphemeral? CacheControl webFetchTool20250910: (x) => x.CacheControl, webSearchTool20260209: (x) => x.CacheControl, webFetchTool20260209: (x) => x.CacheControl, + webFetchTool20260309: (x) => x.CacheControl, searchToolBm25_20251119: (x) => x.CacheControl, searchToolRegex20251119: (x) => x.CacheControl ); @@ -70,6 +71,7 @@ public bool? DeferLoading webFetchTool20250910: (x) => x.DeferLoading, webSearchTool20260209: (x) => x.DeferLoading, webFetchTool20260209: (x) => x.DeferLoading, + webFetchTool20260309: (x) => x.DeferLoading, searchToolBm25_20251119: (x) => x.DeferLoading, searchToolRegex20251119: (x) => x.DeferLoading ); @@ -94,6 +96,7 @@ public bool? Strict webFetchTool20250910: (x) => x.Strict, webSearchTool20260209: (x) => x.Strict, webFetchTool20260209: (x) => x.Strict, + webFetchTool20260309: (x) => x.Strict, searchToolBm25_20251119: (x) => x.Strict, searchToolRegex20251119: (x) => x.Strict ); @@ -118,6 +121,7 @@ public long? MaxUses webFetchTool20250910: (x) => x.MaxUses, webSearchTool20260209: (x) => x.MaxUses, webFetchTool20260209: (x) => x.MaxUses, + webFetchTool20260309: (x) => x.MaxUses, searchToolBm25_20251119: (_) => null, searchToolRegex20251119: (_) => null ); @@ -142,6 +146,7 @@ public UserLocation? UserLocation webFetchTool20250910: (_) => null, webSearchTool20260209: (x) => x.UserLocation, webFetchTool20260209: (_) => null, + webFetchTool20260309: (_) => null, searchToolBm25_20251119: (_) => null, searchToolRegex20251119: (_) => null ); @@ -166,6 +171,7 @@ public CitationsConfigParam? Citations webFetchTool20250910: (x) => x.Citations, webSearchTool20260209: (_) => null, webFetchTool20260209: (x) => x.Citations, + webFetchTool20260309: (x) => x.Citations, searchToolBm25_20251119: (_) => null, searchToolRegex20251119: (_) => null ); @@ -190,6 +196,7 @@ public long? MaxContentTokens webFetchTool20250910: (x) => x.MaxContentTokens, webSearchTool20260209: (_) => null, webFetchTool20260209: (x) => x.MaxContentTokens, + webFetchTool20260309: (x) => x.MaxContentTokens, searchToolBm25_20251119: (_) => null, searchToolRegex20251119: (_) => null ); @@ -274,6 +281,12 @@ public ToolUnion(WebFetchTool20260209 value, JsonElement? element = null) this._element = element; } + public ToolUnion(WebFetchTool20260309 value, JsonElement? element = null) + { + this.Value = value; + this._element = element; + } + public ToolUnion(ToolSearchToolBm25_20251119 value, JsonElement? element = null) { this.Value = value; @@ -570,6 +583,27 @@ public bool TryPickWebFetchTool20260209([NotNullWhen(true)] out WebFetchTool2026 return value != null; } + /// + /// Returns true and sets the out parameter if the instance was constructed with a variant of + /// type . + /// + /// Consider using or if you need to handle every variant. + /// + /// + /// + /// if (instance.TryPickWebFetchTool20260309(out var value)) { + /// // `value` is of type `WebFetchTool20260309` + /// Console.WriteLine(value); + /// } + /// + /// + /// + public bool TryPickWebFetchTool20260309([NotNullWhen(true)] out WebFetchTool20260309? value) + { + value = this.Value as WebFetchTool20260309; + return value != null; + } + /// /// Returns true and sets the out parameter if the instance was constructed with a variant of /// type . @@ -643,6 +677,7 @@ public bool TryPickSearchToolRegex20251119( /// (WebFetchTool20250910 value) => {...}, /// (WebSearchTool20260209 value) => {...}, /// (WebFetchTool20260209 value) => {...}, + /// (WebFetchTool20260309 value) => {...}, /// (ToolSearchToolBm25_20251119 value) => {...}, /// (ToolSearchToolRegex20251119 value) => {...} /// ); @@ -663,6 +698,7 @@ public void Switch( System::Action webFetchTool20250910, System::Action webSearchTool20260209, System::Action webFetchTool20260209, + System::Action webFetchTool20260309, System::Action searchToolBm25_20251119, System::Action searchToolRegex20251119 ) @@ -708,6 +744,9 @@ public void Switch( case WebFetchTool20260209 value: webFetchTool20260209(value); break; + case WebFetchTool20260309 value: + webFetchTool20260309(value); + break; case ToolSearchToolBm25_20251119 value: searchToolBm25_20251119(value); break; @@ -749,6 +788,7 @@ public void Switch( /// (WebFetchTool20250910 value) => {...}, /// (WebSearchTool20260209 value) => {...}, /// (WebFetchTool20260209 value) => {...}, + /// (WebFetchTool20260309 value) => {...}, /// (ToolSearchToolBm25_20251119 value) => {...}, /// (ToolSearchToolRegex20251119 value) => {...} /// ); @@ -769,6 +809,7 @@ public T Match( System::Func webFetchTool20250910, System::Func webSearchTool20260209, System::Func webFetchTool20260209, + System::Func webFetchTool20260309, System::Func searchToolBm25_20251119, System::Func searchToolRegex20251119 ) @@ -788,6 +829,7 @@ public T Match( WebFetchTool20250910 value => webFetchTool20250910(value), WebSearchTool20260209 value => webSearchTool20260209(value), WebFetchTool20260209 value => webFetchTool20260209(value), + WebFetchTool20260309 value => webFetchTool20260309(value), ToolSearchToolBm25_20251119 value => searchToolBm25_20251119(value), ToolSearchToolRegex20251119 value => searchToolRegex20251119(value), _ => throw new AnthropicInvalidDataException( @@ -822,6 +864,8 @@ public T Match( public static implicit operator ToolUnion(WebFetchTool20260209 value) => new(value); + public static implicit operator ToolUnion(WebFetchTool20260309 value) => new(value); + public static implicit operator ToolUnion(ToolSearchToolBm25_20251119 value) => new(value); public static implicit operator ToolUnion(ToolSearchToolRegex20251119 value) => new(value); @@ -856,6 +900,7 @@ public override void Validate() (webFetchTool20250910) => webFetchTool20250910.Validate(), (webSearchTool20260209) => webSearchTool20260209.Validate(), (webFetchTool20260209) => webFetchTool20260209.Validate(), + (webFetchTool20260309) => webFetchTool20260309.Validate(), (searchToolBm25_20251119) => searchToolBm25_20251119.Validate(), (searchToolRegex20251119) => searchToolRegex20251119.Validate() ); @@ -894,8 +939,9 @@ int VariantIndex() WebFetchTool20250910 _ => 10, WebSearchTool20260209 _ => 11, WebFetchTool20260209 _ => 12, - ToolSearchToolBm25_20251119 _ => 13, - ToolSearchToolRegex20251119 _ => 14, + WebFetchTool20260309 _ => 13, + ToolSearchToolBm25_20251119 _ => 14, + ToolSearchToolRegex20251119 _ => 15, _ => -1, }; } @@ -1101,6 +1147,20 @@ JsonSerializerOptions options // ignore } + try + { + var deserialized = JsonSerializer.Deserialize(element, options); + if (deserialized != null) + { + deserialized.Validate(); + return new(deserialized, element); + } + } + catch (System::Exception e) when (e is JsonException || e is AnthropicInvalidDataException) + { + // ignore + } + try { var deserialized = JsonSerializer.Deserialize( diff --git a/src/Anthropic/Models/Messages/ToolUseBlock.cs b/src/Anthropic/Models/Messages/ToolUseBlock.cs index f292a61f7..785322b6f 100644 --- a/src/Anthropic/Models/Messages/ToolUseBlock.cs +++ b/src/Anthropic/Models/Messages/ToolUseBlock.cs @@ -429,12 +429,10 @@ JsonSerializerOptions options var deserialized = JsonSerializer.Deserialize(element, options); if (deserialized != null) { - deserialized.Validate(); return new(deserialized, element); } } - catch (System::Exception e) - when (e is JsonException || e is AnthropicInvalidDataException) + catch (JsonException) { // ignore } @@ -451,12 +449,10 @@ JsonSerializerOptions options ); if (deserialized != null) { - deserialized.Validate(); return new(deserialized, element); } } - catch (System::Exception e) - when (e is JsonException || e is AnthropicInvalidDataException) + catch (JsonException) { // ignore } @@ -473,12 +469,10 @@ JsonSerializerOptions options ); if (deserialized != null) { - deserialized.Validate(); return new(deserialized, element); } } - catch (System::Exception e) - when (e is JsonException || e is AnthropicInvalidDataException) + catch (JsonException) { // ignore } diff --git a/src/Anthropic/Models/Messages/ToolUseBlockParam.cs b/src/Anthropic/Models/Messages/ToolUseBlockParam.cs index 493ceea9e..48334d380 100644 --- a/src/Anthropic/Models/Messages/ToolUseBlockParam.cs +++ b/src/Anthropic/Models/Messages/ToolUseBlockParam.cs @@ -453,12 +453,10 @@ JsonSerializerOptions options var deserialized = JsonSerializer.Deserialize(element, options); if (deserialized != null) { - deserialized.Validate(); return new(deserialized, element); } } - catch (System::Exception e) - when (e is JsonException || e is AnthropicInvalidDataException) + catch (JsonException) { // ignore } @@ -475,12 +473,10 @@ JsonSerializerOptions options ); if (deserialized != null) { - deserialized.Validate(); return new(deserialized, element); } } - catch (System::Exception e) - when (e is JsonException || e is AnthropicInvalidDataException) + catch (JsonException) { // ignore } @@ -497,12 +493,10 @@ JsonSerializerOptions options ); if (deserialized != null) { - deserialized.Validate(); return new(deserialized, element); } } - catch (System::Exception e) - when (e is JsonException || e is AnthropicInvalidDataException) + catch (JsonException) { // ignore } diff --git a/src/Anthropic/Models/Messages/WebFetchTool20250910.cs b/src/Anthropic/Models/Messages/WebFetchTool20250910.cs index 1152016c2..0f1071502 100644 --- a/src/Anthropic/Models/Messages/WebFetchTool20250910.cs +++ b/src/Anthropic/Models/Messages/WebFetchTool20250910.cs @@ -270,6 +270,13 @@ IReadOnlyDictionary rawData ) => WebFetchTool20250910.FromRawUnchecked(rawData); } +/// +/// Specifies who can invoke a tool. +/// +/// Values: direct: The model can call this tool directly. code_execution_20250825: +/// The tool can be called from the code execution environment (v1). code_execution_20260120: +/// The tool can be called from the code execution environment (v2 with persistence). +/// [JsonConverter(typeof(WebFetchTool20250910AllowedCallerConverter))] public enum WebFetchTool20250910AllowedCaller { diff --git a/src/Anthropic/Models/Messages/WebFetchTool20260209.cs b/src/Anthropic/Models/Messages/WebFetchTool20260209.cs index a88f5d31e..a0289bdbf 100644 --- a/src/Anthropic/Models/Messages/WebFetchTool20260209.cs +++ b/src/Anthropic/Models/Messages/WebFetchTool20260209.cs @@ -270,6 +270,13 @@ IReadOnlyDictionary rawData ) => WebFetchTool20260209.FromRawUnchecked(rawData); } +/// +/// Specifies who can invoke a tool. +/// +/// Values: direct: The model can call this tool directly. code_execution_20250825: +/// The tool can be called from the code execution environment (v1). code_execution_20260120: +/// The tool can be called from the code execution environment (v2 with persistence). +/// [JsonConverter(typeof(WebFetchTool20260209AllowedCallerConverter))] public enum WebFetchTool20260209AllowedCaller { diff --git a/src/Anthropic/Models/Messages/WebFetchTool20260309.cs b/src/Anthropic/Models/Messages/WebFetchTool20260309.cs new file mode 100644 index 000000000..b7fe2705e --- /dev/null +++ b/src/Anthropic/Models/Messages/WebFetchTool20260309.cs @@ -0,0 +1,355 @@ +using System.Collections.Frozen; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Diagnostics.CodeAnalysis; +using System.Text.Json; +using System.Text.Json.Serialization; +using Anthropic.Core; +using Anthropic.Exceptions; +using System = System; + +namespace Anthropic.Models.Messages; + +/// +/// Web fetch tool with use_cache parameter for bypassing cached content. +/// +[JsonConverter(typeof(JsonModelConverter))] +public sealed record class WebFetchTool20260309 : JsonModel +{ + /// + /// Name of the tool. + /// + /// This is how the tool will be called by the model and in `tool_use` blocks. + /// + public JsonElement Name + { + get + { + this._rawData.Freeze(); + return this._rawData.GetNotNullStruct("name"); + } + init { this._rawData.Set("name", value); } + } + + public JsonElement Type + { + get + { + this._rawData.Freeze(); + return this._rawData.GetNotNullStruct("type"); + } + init { this._rawData.Set("type", value); } + } + + public IReadOnlyList>? AllowedCallers + { + get + { + this._rawData.Freeze(); + return this._rawData.GetNullableStruct< + ImmutableArray> + >("allowed_callers"); + } + init + { + if (value == null) + { + return; + } + + this._rawData.Set>?>( + "allowed_callers", + value == null ? null : ImmutableArray.ToImmutableArray(value) + ); + } + } + + /// + /// List of domains to allow fetching from + /// + public IReadOnlyList? AllowedDomains + { + get + { + this._rawData.Freeze(); + return this._rawData.GetNullableStruct>("allowed_domains"); + } + init + { + this._rawData.Set?>( + "allowed_domains", + value == null ? null : ImmutableArray.ToImmutableArray(value) + ); + } + } + + /// + /// List of domains to block fetching from + /// + public IReadOnlyList? BlockedDomains + { + get + { + this._rawData.Freeze(); + return this._rawData.GetNullableStruct>("blocked_domains"); + } + init + { + this._rawData.Set?>( + "blocked_domains", + value == null ? null : ImmutableArray.ToImmutableArray(value) + ); + } + } + + /// + /// Create a cache control breakpoint at this content block. + /// + public CacheControlEphemeral? CacheControl + { + get + { + this._rawData.Freeze(); + return this._rawData.GetNullableClass("cache_control"); + } + init { this._rawData.Set("cache_control", value); } + } + + /// + /// Citations configuration for fetched documents. Citations are disabled by default. + /// + public CitationsConfigParam? Citations + { + get + { + this._rawData.Freeze(); + return this._rawData.GetNullableClass("citations"); + } + init { this._rawData.Set("citations", value); } + } + + /// + /// If true, tool will not be included in initial system prompt. Only loaded when + /// returned via tool_reference from tool search. + /// + public bool? DeferLoading + { + get + { + this._rawData.Freeze(); + return this._rawData.GetNullableStruct("defer_loading"); + } + init + { + if (value == null) + { + return; + } + + this._rawData.Set("defer_loading", value); + } + } + + /// + /// Maximum number of tokens used by including web page text content in the context. + /// The limit is approximate and does not apply to binary content such as PDFs. + /// + public long? MaxContentTokens + { + get + { + this._rawData.Freeze(); + return this._rawData.GetNullableStruct("max_content_tokens"); + } + init { this._rawData.Set("max_content_tokens", value); } + } + + /// + /// Maximum number of times the tool can be used in the API request. + /// + public long? MaxUses + { + get + { + this._rawData.Freeze(); + return this._rawData.GetNullableStruct("max_uses"); + } + init { this._rawData.Set("max_uses", value); } + } + + /// + /// When true, guarantees schema validation on tool names and inputs + /// + public bool? Strict + { + get + { + this._rawData.Freeze(); + return this._rawData.GetNullableStruct("strict"); + } + init + { + if (value == null) + { + return; + } + + this._rawData.Set("strict", value); + } + } + + /// + /// Whether to use cached content. Set to false to bypass the cache and fetch + /// fresh content. Only set to false when the user explicitly requests fresh + /// content or when fetching rapidly-changing sources. + /// + public bool? UseCache + { + get + { + this._rawData.Freeze(); + return this._rawData.GetNullableStruct("use_cache"); + } + init + { + if (value == null) + { + return; + } + + this._rawData.Set("use_cache", value); + } + } + + /// + public override void Validate() + { + if (!JsonElement.DeepEquals(this.Name, JsonSerializer.SerializeToElement("web_fetch"))) + { + throw new AnthropicInvalidDataException("Invalid value given for constant"); + } + if ( + !JsonElement.DeepEquals( + this.Type, + JsonSerializer.SerializeToElement("web_fetch_20260309") + ) + ) + { + throw new AnthropicInvalidDataException("Invalid value given for constant"); + } + foreach (var item in this.AllowedCallers ?? []) + { + item.Validate(); + } + _ = this.AllowedDomains; + _ = this.BlockedDomains; + this.CacheControl?.Validate(); + this.Citations?.Validate(); + _ = this.DeferLoading; + _ = this.MaxContentTokens; + _ = this.MaxUses; + _ = this.Strict; + _ = this.UseCache; + } + + public WebFetchTool20260309() + { + this.Name = JsonSerializer.SerializeToElement("web_fetch"); + this.Type = JsonSerializer.SerializeToElement("web_fetch_20260309"); + } + +#pragma warning disable CS8618 + [SetsRequiredMembers] + public WebFetchTool20260309(WebFetchTool20260309 webFetchTool20260309) + : base(webFetchTool20260309) { } +#pragma warning restore CS8618 + + public WebFetchTool20260309(IReadOnlyDictionary rawData) + { + this._rawData = new(rawData); + + this.Name = JsonSerializer.SerializeToElement("web_fetch"); + this.Type = JsonSerializer.SerializeToElement("web_fetch_20260309"); + } + +#pragma warning disable CS8618 + [SetsRequiredMembers] + WebFetchTool20260309(FrozenDictionary rawData) + { + this._rawData = new(rawData); + } +#pragma warning restore CS8618 + + /// + public static WebFetchTool20260309 FromRawUnchecked( + IReadOnlyDictionary rawData + ) + { + return new(FrozenDictionary.ToFrozenDictionary(rawData)); + } +} + +class WebFetchTool20260309FromRaw : IFromRawJson +{ + /// + public WebFetchTool20260309 FromRawUnchecked( + IReadOnlyDictionary rawData + ) => WebFetchTool20260309.FromRawUnchecked(rawData); +} + +/// +/// Specifies who can invoke a tool. +/// +/// Values: direct: The model can call this tool directly. code_execution_20250825: +/// The tool can be called from the code execution environment (v1). code_execution_20260120: +/// The tool can be called from the code execution environment (v2 with persistence). +/// +[JsonConverter(typeof(WebFetchTool20260309AllowedCallerConverter))] +public enum WebFetchTool20260309AllowedCaller +{ + Direct, + CodeExecution20250825, + CodeExecution20260120, +} + +sealed class WebFetchTool20260309AllowedCallerConverter + : JsonConverter +{ + public override WebFetchTool20260309AllowedCaller Read( + ref Utf8JsonReader reader, + System::Type typeToConvert, + JsonSerializerOptions options + ) + { + return JsonSerializer.Deserialize(ref reader, options) switch + { + "direct" => WebFetchTool20260309AllowedCaller.Direct, + "code_execution_20250825" => WebFetchTool20260309AllowedCaller.CodeExecution20250825, + "code_execution_20260120" => WebFetchTool20260309AllowedCaller.CodeExecution20260120, + _ => (WebFetchTool20260309AllowedCaller)(-1), + }; + } + + public override void Write( + Utf8JsonWriter writer, + WebFetchTool20260309AllowedCaller value, + JsonSerializerOptions options + ) + { + JsonSerializer.Serialize( + writer, + value switch + { + WebFetchTool20260309AllowedCaller.Direct => "direct", + WebFetchTool20260309AllowedCaller.CodeExecution20250825 => + "code_execution_20250825", + WebFetchTool20260309AllowedCaller.CodeExecution20260120 => + "code_execution_20260120", + _ => throw new AnthropicInvalidDataException( + string.Format("Invalid value '{0}' in {1}", value, nameof(value)) + ), + }, + options + ); + } +} diff --git a/src/Anthropic/Models/Messages/WebFetchToolResultBlock.cs b/src/Anthropic/Models/Messages/WebFetchToolResultBlock.cs index 495d00152..aa0464767 100644 --- a/src/Anthropic/Models/Messages/WebFetchToolResultBlock.cs +++ b/src/Anthropic/Models/Messages/WebFetchToolResultBlock.cs @@ -424,12 +424,10 @@ JsonSerializerOptions options var deserialized = JsonSerializer.Deserialize(element, options); if (deserialized != null) { - deserialized.Validate(); return new(deserialized, element); } } - catch (System::Exception e) - when (e is JsonException || e is AnthropicInvalidDataException) + catch (JsonException) { // ignore } @@ -446,12 +444,10 @@ JsonSerializerOptions options ); if (deserialized != null) { - deserialized.Validate(); return new(deserialized, element); } } - catch (System::Exception e) - when (e is JsonException || e is AnthropicInvalidDataException) + catch (JsonException) { // ignore } @@ -468,12 +464,10 @@ JsonSerializerOptions options ); if (deserialized != null) { - deserialized.Validate(); return new(deserialized, element); } } - catch (System::Exception e) - when (e is JsonException || e is AnthropicInvalidDataException) + catch (JsonException) { // ignore } diff --git a/src/Anthropic/Models/Messages/WebFetchToolResultBlockParam.cs b/src/Anthropic/Models/Messages/WebFetchToolResultBlockParam.cs index 508f7ccae..19d043809 100644 --- a/src/Anthropic/Models/Messages/WebFetchToolResultBlockParam.cs +++ b/src/Anthropic/Models/Messages/WebFetchToolResultBlockParam.cs @@ -735,12 +735,10 @@ JsonSerializerOptions options var deserialized = JsonSerializer.Deserialize(element, options); if (deserialized != null) { - deserialized.Validate(); return new(deserialized, element); } } - catch (System::Exception e) - when (e is JsonException || e is AnthropicInvalidDataException) + catch (JsonException) { // ignore } @@ -757,12 +755,10 @@ JsonSerializerOptions options ); if (deserialized != null) { - deserialized.Validate(); return new(deserialized, element); } } - catch (System::Exception e) - when (e is JsonException || e is AnthropicInvalidDataException) + catch (JsonException) { // ignore } @@ -779,12 +775,10 @@ JsonSerializerOptions options ); if (deserialized != null) { - deserialized.Validate(); return new(deserialized, element); } } - catch (System::Exception e) - when (e is JsonException || e is AnthropicInvalidDataException) + catch (JsonException) { // ignore } diff --git a/src/Anthropic/Models/Messages/WebSearchTool20250305.cs b/src/Anthropic/Models/Messages/WebSearchTool20250305.cs index 72978d5df..f25e5cbde 100644 --- a/src/Anthropic/Models/Messages/WebSearchTool20250305.cs +++ b/src/Anthropic/Models/Messages/WebSearchTool20250305.cs @@ -256,6 +256,13 @@ IReadOnlyDictionary rawData ) => WebSearchTool20250305.FromRawUnchecked(rawData); } +/// +/// Specifies who can invoke a tool. +/// +/// Values: direct: The model can call this tool directly. code_execution_20250825: +/// The tool can be called from the code execution environment (v1). code_execution_20260120: +/// The tool can be called from the code execution environment (v2 with persistence). +/// [JsonConverter(typeof(WebSearchTool20250305AllowedCallerConverter))] public enum WebSearchTool20250305AllowedCaller { diff --git a/src/Anthropic/Models/Messages/WebSearchTool20260209.cs b/src/Anthropic/Models/Messages/WebSearchTool20260209.cs index 95e96a886..1f91c6951 100644 --- a/src/Anthropic/Models/Messages/WebSearchTool20260209.cs +++ b/src/Anthropic/Models/Messages/WebSearchTool20260209.cs @@ -256,6 +256,13 @@ IReadOnlyDictionary rawData ) => WebSearchTool20260209.FromRawUnchecked(rawData); } +/// +/// Specifies who can invoke a tool. +/// +/// Values: direct: The model can call this tool directly. code_execution_20250825: +/// The tool can be called from the code execution environment (v1). code_execution_20260120: +/// The tool can be called from the code execution environment (v2 with persistence). +/// [JsonConverter(typeof(WebSearchTool20260209AllowedCallerConverter))] public enum WebSearchTool20260209AllowedCaller { diff --git a/src/Anthropic/Models/Messages/WebSearchToolResultBlock.cs b/src/Anthropic/Models/Messages/WebSearchToolResultBlock.cs index 4e557d157..4c042c377 100644 --- a/src/Anthropic/Models/Messages/WebSearchToolResultBlock.cs +++ b/src/Anthropic/Models/Messages/WebSearchToolResultBlock.cs @@ -428,12 +428,10 @@ JsonSerializerOptions options var deserialized = JsonSerializer.Deserialize(element, options); if (deserialized != null) { - deserialized.Validate(); return new(deserialized, element); } } - catch (System::Exception e) - when (e is JsonException || e is AnthropicInvalidDataException) + catch (JsonException) { // ignore } @@ -450,12 +448,10 @@ JsonSerializerOptions options ); if (deserialized != null) { - deserialized.Validate(); return new(deserialized, element); } } - catch (System::Exception e) - when (e is JsonException || e is AnthropicInvalidDataException) + catch (JsonException) { // ignore } @@ -472,12 +468,10 @@ JsonSerializerOptions options ); if (deserialized != null) { - deserialized.Validate(); return new(deserialized, element); } } - catch (System::Exception e) - when (e is JsonException || e is AnthropicInvalidDataException) + catch (JsonException) { // ignore } diff --git a/src/Anthropic/Models/Messages/WebSearchToolResultBlockParam.cs b/src/Anthropic/Models/Messages/WebSearchToolResultBlockParam.cs index 2cc2b504c..054e57ef4 100644 --- a/src/Anthropic/Models/Messages/WebSearchToolResultBlockParam.cs +++ b/src/Anthropic/Models/Messages/WebSearchToolResultBlockParam.cs @@ -453,12 +453,10 @@ JsonSerializerOptions options var deserialized = JsonSerializer.Deserialize(element, options); if (deserialized != null) { - deserialized.Validate(); return new(deserialized, element); } } - catch (System::Exception e) - when (e is JsonException || e is AnthropicInvalidDataException) + catch (JsonException) { // ignore } @@ -475,12 +473,10 @@ JsonSerializerOptions options ); if (deserialized != null) { - deserialized.Validate(); return new(deserialized, element); } } - catch (System::Exception e) - when (e is JsonException || e is AnthropicInvalidDataException) + catch (JsonException) { // ignore } @@ -497,12 +493,10 @@ JsonSerializerOptions options ); if (deserialized != null) { - deserialized.Validate(); return new(deserialized, element); } } - catch (System::Exception e) - when (e is JsonException || e is AnthropicInvalidDataException) + catch (JsonException) { // ignore } diff --git a/src/Anthropic/Models/Models/CapabilitySupport.cs b/src/Anthropic/Models/Models/CapabilitySupport.cs new file mode 100644 index 000000000..50637d0d2 --- /dev/null +++ b/src/Anthropic/Models/Models/CapabilitySupport.cs @@ -0,0 +1,77 @@ +using System.Collections.Frozen; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Text.Json; +using System.Text.Json.Serialization; +using Anthropic.Core; + +namespace Anthropic.Models.Models; + +/// +/// Indicates whether a capability is supported. +/// +[JsonConverter(typeof(JsonModelConverter))] +public sealed record class CapabilitySupport : JsonModel +{ + /// + /// Whether this capability is supported by the model. + /// + public required bool Supported + { + get + { + this._rawData.Freeze(); + return this._rawData.GetNotNullStruct("supported"); + } + init { this._rawData.Set("supported", value); } + } + + /// + public override void Validate() + { + _ = this.Supported; + } + + public CapabilitySupport() { } + +#pragma warning disable CS8618 + [SetsRequiredMembers] + public CapabilitySupport(CapabilitySupport capabilitySupport) + : base(capabilitySupport) { } +#pragma warning restore CS8618 + + public CapabilitySupport(IReadOnlyDictionary rawData) + { + this._rawData = new(rawData); + } + +#pragma warning disable CS8618 + [SetsRequiredMembers] + CapabilitySupport(FrozenDictionary rawData) + { + this._rawData = new(rawData); + } +#pragma warning restore CS8618 + + /// + public static CapabilitySupport FromRawUnchecked( + IReadOnlyDictionary rawData + ) + { + return new(FrozenDictionary.ToFrozenDictionary(rawData)); + } + + [SetsRequiredMembers] + public CapabilitySupport(bool supported) + : this() + { + this.Supported = supported; + } +} + +class CapabilitySupportFromRaw : IFromRawJson +{ + /// + public CapabilitySupport FromRawUnchecked(IReadOnlyDictionary rawData) => + CapabilitySupport.FromRawUnchecked(rawData); +} diff --git a/src/Anthropic/Models/Models/ContextManagementCapability.cs b/src/Anthropic/Models/Models/ContextManagementCapability.cs new file mode 100644 index 000000000..afc4ecbd0 --- /dev/null +++ b/src/Anthropic/Models/Models/ContextManagementCapability.cs @@ -0,0 +1,115 @@ +using System.Collections.Frozen; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Text.Json; +using System.Text.Json.Serialization; +using Anthropic.Core; + +namespace Anthropic.Models.Models; + +/// +/// Context management capability details. +/// +[JsonConverter( + typeof(JsonModelConverter) +)] +public sealed record class ContextManagementCapability : JsonModel +{ + /// + /// Indicates whether a capability is supported. + /// + public required CapabilitySupport? ClearThinking20251015 + { + get + { + this._rawData.Freeze(); + return this._rawData.GetNullableClass("clear_thinking_20251015"); + } + init { this._rawData.Set("clear_thinking_20251015", value); } + } + + /// + /// Indicates whether a capability is supported. + /// + public required CapabilitySupport? ClearToolUses20250919 + { + get + { + this._rawData.Freeze(); + return this._rawData.GetNullableClass("clear_tool_uses_20250919"); + } + init { this._rawData.Set("clear_tool_uses_20250919", value); } + } + + /// + /// Indicates whether a capability is supported. + /// + public required CapabilitySupport? Compact20260112 + { + get + { + this._rawData.Freeze(); + return this._rawData.GetNullableClass("compact_20260112"); + } + init { this._rawData.Set("compact_20260112", value); } + } + + /// + /// Whether this capability is supported by the model. + /// + public required bool Supported + { + get + { + this._rawData.Freeze(); + return this._rawData.GetNotNullStruct("supported"); + } + init { this._rawData.Set("supported", value); } + } + + /// + public override void Validate() + { + this.ClearThinking20251015?.Validate(); + this.ClearToolUses20250919?.Validate(); + this.Compact20260112?.Validate(); + _ = this.Supported; + } + + public ContextManagementCapability() { } + +#pragma warning disable CS8618 + [SetsRequiredMembers] + public ContextManagementCapability(ContextManagementCapability contextManagementCapability) + : base(contextManagementCapability) { } +#pragma warning restore CS8618 + + public ContextManagementCapability(IReadOnlyDictionary rawData) + { + this._rawData = new(rawData); + } + +#pragma warning disable CS8618 + [SetsRequiredMembers] + ContextManagementCapability(FrozenDictionary rawData) + { + this._rawData = new(rawData); + } +#pragma warning restore CS8618 + + /// + public static ContextManagementCapability FromRawUnchecked( + IReadOnlyDictionary rawData + ) + { + return new(FrozenDictionary.ToFrozenDictionary(rawData)); + } +} + +class ContextManagementCapabilityFromRaw : IFromRawJson +{ + /// + public ContextManagementCapability FromRawUnchecked( + IReadOnlyDictionary rawData + ) => ContextManagementCapability.FromRawUnchecked(rawData); +} diff --git a/src/Anthropic/Models/Models/EffortCapability.cs b/src/Anthropic/Models/Models/EffortCapability.cs new file mode 100644 index 000000000..28e35ec4c --- /dev/null +++ b/src/Anthropic/Models/Models/EffortCapability.cs @@ -0,0 +1,126 @@ +using System.Collections.Frozen; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Text.Json; +using System.Text.Json.Serialization; +using Anthropic.Core; + +namespace Anthropic.Models.Models; + +/// +/// Effort (reasoning_effort) capability details. +/// +[JsonConverter(typeof(JsonModelConverter))] +public sealed record class EffortCapability : JsonModel +{ + /// + /// Whether the model supports high effort level. + /// + public required CapabilitySupport High + { + get + { + this._rawData.Freeze(); + return this._rawData.GetNotNullClass("high"); + } + init { this._rawData.Set("high", value); } + } + + /// + /// Whether the model supports low effort level. + /// + public required CapabilitySupport Low + { + get + { + this._rawData.Freeze(); + return this._rawData.GetNotNullClass("low"); + } + init { this._rawData.Set("low", value); } + } + + /// + /// Whether the model supports max effort level. + /// + public required CapabilitySupport Max + { + get + { + this._rawData.Freeze(); + return this._rawData.GetNotNullClass("max"); + } + init { this._rawData.Set("max", value); } + } + + /// + /// Whether the model supports medium effort level. + /// + public required CapabilitySupport Medium + { + get + { + this._rawData.Freeze(); + return this._rawData.GetNotNullClass("medium"); + } + init { this._rawData.Set("medium", value); } + } + + /// + /// Whether this capability is supported by the model. + /// + public required bool Supported + { + get + { + this._rawData.Freeze(); + return this._rawData.GetNotNullStruct("supported"); + } + init { this._rawData.Set("supported", value); } + } + + /// + public override void Validate() + { + this.High.Validate(); + this.Low.Validate(); + this.Max.Validate(); + this.Medium.Validate(); + _ = this.Supported; + } + + public EffortCapability() { } + +#pragma warning disable CS8618 + [SetsRequiredMembers] + public EffortCapability(EffortCapability effortCapability) + : base(effortCapability) { } +#pragma warning restore CS8618 + + public EffortCapability(IReadOnlyDictionary rawData) + { + this._rawData = new(rawData); + } + +#pragma warning disable CS8618 + [SetsRequiredMembers] + EffortCapability(FrozenDictionary rawData) + { + this._rawData = new(rawData); + } +#pragma warning restore CS8618 + + /// + public static EffortCapability FromRawUnchecked( + IReadOnlyDictionary rawData + ) + { + return new(FrozenDictionary.ToFrozenDictionary(rawData)); + } +} + +class EffortCapabilityFromRaw : IFromRawJson +{ + /// + public EffortCapability FromRawUnchecked(IReadOnlyDictionary rawData) => + EffortCapability.FromRawUnchecked(rawData); +} diff --git a/src/Anthropic/Models/Models/ModelCapabilities.cs b/src/Anthropic/Models/Models/ModelCapabilities.cs new file mode 100644 index 000000000..b4ebcba53 --- /dev/null +++ b/src/Anthropic/Models/Models/ModelCapabilities.cs @@ -0,0 +1,182 @@ +using System.Collections.Frozen; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Text.Json; +using System.Text.Json.Serialization; +using Anthropic.Core; + +namespace Anthropic.Models.Models; + +/// +/// Model capability information. +/// +[JsonConverter(typeof(JsonModelConverter))] +public sealed record class ModelCapabilities : JsonModel +{ + /// + /// Whether the model supports the Batch API. + /// + public required CapabilitySupport Batch + { + get + { + this._rawData.Freeze(); + return this._rawData.GetNotNullClass("batch"); + } + init { this._rawData.Set("batch", value); } + } + + /// + /// Whether the model supports citation generation. + /// + public required CapabilitySupport Citations + { + get + { + this._rawData.Freeze(); + return this._rawData.GetNotNullClass("citations"); + } + init { this._rawData.Set("citations", value); } + } + + /// + /// Whether the model supports code execution tools. + /// + public required CapabilitySupport CodeExecution + { + get + { + this._rawData.Freeze(); + return this._rawData.GetNotNullClass("code_execution"); + } + init { this._rawData.Set("code_execution", value); } + } + + /// + /// Context management support and available strategies. + /// + public required ContextManagementCapability ContextManagement + { + get + { + this._rawData.Freeze(); + return this._rawData.GetNotNullClass("context_management"); + } + init { this._rawData.Set("context_management", value); } + } + + /// + /// Effort (reasoning_effort) support and available levels. + /// + public required EffortCapability Effort + { + get + { + this._rawData.Freeze(); + return this._rawData.GetNotNullClass("effort"); + } + init { this._rawData.Set("effort", value); } + } + + /// + /// Whether the model accepts image content blocks. + /// + public required CapabilitySupport ImageInput + { + get + { + this._rawData.Freeze(); + return this._rawData.GetNotNullClass("image_input"); + } + init { this._rawData.Set("image_input", value); } + } + + /// + /// Whether the model accepts PDF content blocks. + /// + public required CapabilitySupport PdfInput + { + get + { + this._rawData.Freeze(); + return this._rawData.GetNotNullClass("pdf_input"); + } + init { this._rawData.Set("pdf_input", value); } + } + + /// + /// Whether the model supports structured output / JSON mode / strict tool schemas. + /// + public required CapabilitySupport StructuredOutputs + { + get + { + this._rawData.Freeze(); + return this._rawData.GetNotNullClass("structured_outputs"); + } + init { this._rawData.Set("structured_outputs", value); } + } + + /// + /// Thinking capability and supported type configurations. + /// + public required ThinkingCapability Thinking + { + get + { + this._rawData.Freeze(); + return this._rawData.GetNotNullClass("thinking"); + } + init { this._rawData.Set("thinking", value); } + } + + /// + public override void Validate() + { + this.Batch.Validate(); + this.Citations.Validate(); + this.CodeExecution.Validate(); + this.ContextManagement.Validate(); + this.Effort.Validate(); + this.ImageInput.Validate(); + this.PdfInput.Validate(); + this.StructuredOutputs.Validate(); + this.Thinking.Validate(); + } + + public ModelCapabilities() { } + +#pragma warning disable CS8618 + [SetsRequiredMembers] + public ModelCapabilities(ModelCapabilities modelCapabilities) + : base(modelCapabilities) { } +#pragma warning restore CS8618 + + public ModelCapabilities(IReadOnlyDictionary rawData) + { + this._rawData = new(rawData); + } + +#pragma warning disable CS8618 + [SetsRequiredMembers] + ModelCapabilities(FrozenDictionary rawData) + { + this._rawData = new(rawData); + } +#pragma warning restore CS8618 + + /// + public static ModelCapabilities FromRawUnchecked( + IReadOnlyDictionary rawData + ) + { + return new(FrozenDictionary.ToFrozenDictionary(rawData)); + } +} + +class ModelCapabilitiesFromRaw : IFromRawJson +{ + /// + public ModelCapabilities FromRawUnchecked(IReadOnlyDictionary rawData) => + ModelCapabilities.FromRawUnchecked(rawData); +} diff --git a/src/Anthropic/Models/Models/ModelInfo.cs b/src/Anthropic/Models/Models/ModelInfo.cs index 3503540e7..3349ae7a4 100644 --- a/src/Anthropic/Models/Models/ModelInfo.cs +++ b/src/Anthropic/Models/Models/ModelInfo.cs @@ -25,6 +25,19 @@ public required string ID init { this._rawData.Set("id", value); } } + /// + /// Model capability information. + /// + public required ModelCapabilities? Capabilities + { + get + { + this._rawData.Freeze(); + return this._rawData.GetNullableClass("capabilities"); + } + init { this._rawData.Set("capabilities", value); } + } + /// /// RFC 3339 datetime string representing the time at which the model was released. /// May be set to an epoch value if the release date is unknown. @@ -52,6 +65,32 @@ public required string DisplayName init { this._rawData.Set("display_name", value); } } + /// + /// Maximum input context window size in tokens for this model. + /// + public required long? MaxInputTokens + { + get + { + this._rawData.Freeze(); + return this._rawData.GetNullableStruct("max_input_tokens"); + } + init { this._rawData.Set("max_input_tokens", value); } + } + + /// + /// Maximum value for the `max_tokens` parameter when using this model. + /// + public required long? MaxTokens + { + get + { + this._rawData.Freeze(); + return this._rawData.GetNullableStruct("max_tokens"); + } + init { this._rawData.Set("max_tokens", value); } + } + /// /// Object type. /// @@ -71,8 +110,11 @@ public JsonElement Type public override void Validate() { _ = this.ID; + this.Capabilities?.Validate(); _ = this.CreatedAt; _ = this.DisplayName; + _ = this.MaxInputTokens; + _ = this.MaxTokens; if (!JsonElement.DeepEquals(this.Type, JsonSerializer.SerializeToElement("model"))) { throw new AnthropicInvalidDataException("Invalid value given for constant"); diff --git a/src/Anthropic/Models/Models/ModelRetrieveParams.cs b/src/Anthropic/Models/Models/ModelRetrieveParams.cs index 401007b57..fcc660b9a 100644 --- a/src/Anthropic/Models/Models/ModelRetrieveParams.cs +++ b/src/Anthropic/Models/Models/ModelRetrieveParams.cs @@ -74,23 +74,27 @@ IReadOnlyDictionary rawQueryData [SetsRequiredMembers] ModelRetrieveParams( FrozenDictionary rawHeaderData, - FrozenDictionary rawQueryData + FrozenDictionary rawQueryData, + string modelID ) { this._rawHeaderData = new(rawHeaderData); this._rawQueryData = new(rawQueryData); + this.ModelID = modelID; } #pragma warning restore CS8618 /// public static ModelRetrieveParams FromRawUnchecked( IReadOnlyDictionary rawHeaderData, - IReadOnlyDictionary rawQueryData + IReadOnlyDictionary rawQueryData, + string modelID ) { return new( FrozenDictionary.ToFrozenDictionary(rawHeaderData), - FrozenDictionary.ToFrozenDictionary(rawQueryData) + FrozenDictionary.ToFrozenDictionary(rawQueryData), + modelID ); } diff --git a/src/Anthropic/Models/Models/ThinkingCapability.cs b/src/Anthropic/Models/Models/ThinkingCapability.cs new file mode 100644 index 000000000..c9f446356 --- /dev/null +++ b/src/Anthropic/Models/Models/ThinkingCapability.cs @@ -0,0 +1,84 @@ +using System.Collections.Frozen; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Text.Json; +using System.Text.Json.Serialization; +using Anthropic.Core; + +namespace Anthropic.Models.Models; + +/// +/// Thinking capability details. +/// +[JsonConverter(typeof(JsonModelConverter))] +public sealed record class ThinkingCapability : JsonModel +{ + /// + /// Whether this capability is supported by the model. + /// + public required bool Supported + { + get + { + this._rawData.Freeze(); + return this._rawData.GetNotNullStruct("supported"); + } + init { this._rawData.Set("supported", value); } + } + + /// + /// Supported thinking type configurations. + /// + public required ThinkingTypes Types + { + get + { + this._rawData.Freeze(); + return this._rawData.GetNotNullClass("types"); + } + init { this._rawData.Set("types", value); } + } + + /// + public override void Validate() + { + _ = this.Supported; + this.Types.Validate(); + } + + public ThinkingCapability() { } + +#pragma warning disable CS8618 + [SetsRequiredMembers] + public ThinkingCapability(ThinkingCapability thinkingCapability) + : base(thinkingCapability) { } +#pragma warning restore CS8618 + + public ThinkingCapability(IReadOnlyDictionary rawData) + { + this._rawData = new(rawData); + } + +#pragma warning disable CS8618 + [SetsRequiredMembers] + ThinkingCapability(FrozenDictionary rawData) + { + this._rawData = new(rawData); + } +#pragma warning restore CS8618 + + /// + public static ThinkingCapability FromRawUnchecked( + IReadOnlyDictionary rawData + ) + { + return new(FrozenDictionary.ToFrozenDictionary(rawData)); + } +} + +class ThinkingCapabilityFromRaw : IFromRawJson +{ + /// + public ThinkingCapability FromRawUnchecked(IReadOnlyDictionary rawData) => + ThinkingCapability.FromRawUnchecked(rawData); +} diff --git a/src/Anthropic/Models/Models/ThinkingTypes.cs b/src/Anthropic/Models/Models/ThinkingTypes.cs new file mode 100644 index 000000000..7221fbd45 --- /dev/null +++ b/src/Anthropic/Models/Models/ThinkingTypes.cs @@ -0,0 +1,82 @@ +using System.Collections.Frozen; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Text.Json; +using System.Text.Json.Serialization; +using Anthropic.Core; + +namespace Anthropic.Models.Models; + +/// +/// Supported thinking type configurations. +/// +[JsonConverter(typeof(JsonModelConverter))] +public sealed record class ThinkingTypes : JsonModel +{ + /// + /// Whether the model supports thinking with type 'adaptive' (auto). + /// + public required CapabilitySupport Adaptive + { + get + { + this._rawData.Freeze(); + return this._rawData.GetNotNullClass("adaptive"); + } + init { this._rawData.Set("adaptive", value); } + } + + /// + /// Whether the model supports thinking with type 'enabled'. + /// + public required CapabilitySupport Enabled + { + get + { + this._rawData.Freeze(); + return this._rawData.GetNotNullClass("enabled"); + } + init { this._rawData.Set("enabled", value); } + } + + /// + public override void Validate() + { + this.Adaptive.Validate(); + this.Enabled.Validate(); + } + + public ThinkingTypes() { } + +#pragma warning disable CS8618 + [SetsRequiredMembers] + public ThinkingTypes(ThinkingTypes thinkingTypes) + : base(thinkingTypes) { } +#pragma warning restore CS8618 + + public ThinkingTypes(IReadOnlyDictionary rawData) + { + this._rawData = new(rawData); + } + +#pragma warning disable CS8618 + [SetsRequiredMembers] + ThinkingTypes(FrozenDictionary rawData) + { + this._rawData = new(rawData); + } +#pragma warning restore CS8618 + + /// + public static ThinkingTypes FromRawUnchecked(IReadOnlyDictionary rawData) + { + return new(FrozenDictionary.ToFrozenDictionary(rawData)); + } +} + +class ThinkingTypesFromRaw : IFromRawJson +{ + /// + public ThinkingTypes FromRawUnchecked(IReadOnlyDictionary rawData) => + ThinkingTypes.FromRawUnchecked(rawData); +} diff --git a/src/Anthropic/Services/Beta/FileService.cs b/src/Anthropic/Services/Beta/FileService.cs index de79c00ae..4b21f3310 100644 --- a/src/Anthropic/Services/Beta/FileService.cs +++ b/src/Anthropic/Services/Beta/FileService.cs @@ -24,7 +24,7 @@ public IFileServiceWithRawResponse WithRawResponse get { return _withRawResponse.Value; } } - readonly IAnthropicClient _client; + internal readonly IAnthropicClient _client; /// public IFileService WithOptions(Func modifier) diff --git a/src/Anthropic/Services/Beta/Messages/AnthropicBetaClientExtensions.cs b/src/Anthropic/Services/Beta/Messages/AnthropicBetaClientExtensions.cs index 5d4349dcf..ca15d55a2 100644 --- a/src/Anthropic/Services/Beta/Messages/AnthropicBetaClientExtensions.cs +++ b/src/Anthropic/Services/Beta/Messages/AnthropicBetaClientExtensions.cs @@ -1,17 +1,16 @@ using System; -using System.Collections.Frozen; using System.Collections.Generic; using System.Globalization; +using System.IO; using System.Linq; -using System.Reflection; +using System.Net.Http.Headers; using System.Runtime.CompilerServices; using System.Text; using System.Text.Json; -using System.Text.Json.Nodes; using System.Threading; using System.Threading.Tasks; using Anthropic.Core; -using Anthropic.Models.Beta; +using Anthropic.Models.Beta.Files; using Anthropic.Models.Beta.Messages; using Anthropic.Services.Beta; @@ -103,6 +102,40 @@ public static IChatClient AsIChatClient( return new AnthropicChatClient(betaService, defaultModelId, defaultMaxOutputTokens); } + /// + /// Creates an that can be used to manage files via the . + /// + /// The file service to use. + /// An that can be used to manage files via the . + /// is . + public static IHostedFileClient AsIHostedFileClient(this IFileService fileService) + { + if (fileService is null) + { + throw new ArgumentNullException(nameof(fileService)); + } + + return new AnthropicHostedFileClient(fileService); + } + + /// + /// Creates an that can be used to manage files via the . + /// + /// The beta service to use. + /// An that can be used to manage files via the . + /// is . + public static IHostedFileClient AsIHostedFileClient( + this Anthropic.Services.IBetaService betaService + ) + { + if (betaService is null) + { + throw new ArgumentNullException(nameof(betaService)); + } + + return betaService.Files.AsIHostedFileClient(); + } + /// Creates an to represent a raw . /// The tool to wrap as an . /// The wrapped as an . @@ -139,70 +172,6 @@ private sealed class AnthropicChatClient( ) : IChatClient { private const int DefaultMaxTokens = 1024; - private const string MeaiUserAgentHeaderKey = "User-Agent"; - - private static readonly FrozenDictionary s_meaiHeaderData = - new Dictionary - { - [MeaiUserAgentHeaderKey] = JsonSerializer.SerializeToElement( - CreateMeaiUserAgentValue() - ), - }.ToFrozenDictionary(); - - private static string CreateMeaiUserAgentValue() - { - const string Name = "MEAI"; - - if ( - typeof(IChatClient) - .Assembly.GetCustomAttribute() - ?.InformationalVersion - is string version - ) - { - int pos = version.IndexOf('+'); - if (pos >= 0) - { - version = version.Substring(0, pos); - } - - if (version.Length > 0) - { - return $"{Name}/{version}"; - } - } - - return Name; - } - - private static readonly AIJsonSchemaTransformCache s_transformCache = new( - new AIJsonSchemaTransformOptions - { - DisallowAdditionalProperties = true, - TransformSchemaNode = (ctx, schemaNode) => - { - if (schemaNode is JsonObject schemaObj) - { - // From https://platform.claude.com/docs/en/build-with-claude/structured-outputs - ReadOnlySpan unsupportedProperties = - [ - "minimum", - "maximum", - "multipleOf", - "minLength", - "maxLength", - ]; - - foreach (string propName in unsupportedProperties) - { - _ = schemaObj.Remove(propName); - } - } - - return schemaNode; - }, - } - ); private readonly Anthropic.Services.IBetaService _betaService = betaService; private readonly string? _defaultModelId = defaultModelId; @@ -265,15 +234,46 @@ public async Task GetResponseAsync( messages, out List? systemMessages ); + bool hasHostedFiles = messages + .SelectMany(m => m.Contents) + .OfType() + .Any(); MessageCreateParams createParams = GetMessageCreateParams( messageParams, systemMessages, - options + options, + hasHostedFiles ); - var createResult = await _betaService.Messages.Create(createParams, cancellationToken); + // When thinking is enabled, the auto-increased max_tokens may exceed the + // client-side non-streaming token limit. Use a streaming-level timeout to + // bypass that check while still providing appropriate timeout behavior. + var messageService = _betaService.Messages; + if ( + createParams.Thinking is BetaThinkingConfigParam + { + Value: BetaThinkingConfigEnabled + } + ) + { + messageService = messageService.WithOptions(opts => + opts with + { + Timeout = ClientOptions.TimeoutFromMaxTokens( + createParams.MaxTokens, + isStreaming: true, + createParams.Model + ), + } + ); + } + + var createResult = await messageService.Create(createParams, cancellationToken); - ChatMessage m = new(ChatRole.Assistant, [.. createResult.Content.Select(ToAIContent)]) + ChatMessage m = new( + ChatRole.Assistant, + [.. createResult.Content.Select(b => ContentBlockValueToAIContent(b.Value))] + ) { CreatedAt = DateTimeOffset.UtcNow, MessageId = createResult.ID, @@ -306,10 +306,15 @@ public async IAsyncEnumerable GetStreamingResponseAsync( messages, out List? systemMessages ); + bool hasHostedFiles = messages + .SelectMany(m => m.Contents) + .OfType() + .Any(); MessageCreateParams createParams = GetMessageCreateParams( messageParams, systemMessages, - options + options, + hasHostedFiles ); string? messageId = null; @@ -400,6 +405,23 @@ var createResult in _betaService Name = toolUse.Name, }; break; + + case BetaServerToolUseBlock: + case BetaWebSearchToolResultBlock: + case BetaWebFetchToolResultBlock: + case BetaCodeExecutionToolResultBlock: + case BetaBashCodeExecutionToolResultBlock: + case BetaTextEditorCodeExecutionToolResultBlock: + case BetaMcpToolUseBlock: + case BetaMcpToolResultBlock: + case BetaToolSearchToolResultBlock: + case BetaContainerUploadBlock: + contents.Add( + ContentBlockValueToAIContent( + contentBlockStart.ContentBlock.Value + ) + ); + break; } break; @@ -446,6 +468,19 @@ out var functionData } ); break; + + case BetaCitationsDelta citationsDelta: + if (ToAIAnnotation(citationsDelta.Citation) is { } streamAnnotation) + { + contents.Add( + new TextContent(string.Empty) + { + RawRepresentation = citationsDelta, + Annotations = [streamAnnotation], + } + ); + } + break; } break; @@ -855,30 +890,35 @@ when string.Equals( ); } - if (messageParams.Count == 0) - { - messageParams.Add(new() { Role = Role.User, Content = new("\u200b") }); // zero-width space - } - return messageParams; } private MessageCreateParams GetMessageCreateParams( List messages, List? systemMessages, - ChatOptions? options + ChatOptions? options, + bool hasHostedFiles ) { // Get the initial MessageCreateParams, either with a raw representation provided by the options // or with only the required properties set. MessageCreateParams? createParams = options?.RawRepresentationFactory?.Invoke(this) as MessageCreateParams; + + // Anthropic requires at least one message. If no messages were provided either directly + // or via the RawRepresentationFactory, add an empty message. + var createParamsOriginalMessages = createParams?.Messages; + if (createParamsOriginalMessages is not { Count: > 0 } && messages.Count == 0) + { + messages.Add(new BetaMessageParam() { Role = Role.User, Content = new("\u200b") }); // zero-width space + } + if (createParams is not null) { // Merge any messages preconfigured on the params with the ones provided to the IChatClient. createParams = createParams with { - Messages = [.. createParams.Messages, .. messages], + Messages = [.. createParamsOriginalMessages ?? [], .. messages], }; } else @@ -901,6 +941,11 @@ private MessageCreateParams GetMessageCreateParams( : null; int originalBetaHeadersCount = betaHeaders?.Count ?? 0; + if (hasHostedFiles) + { + (betaHeaders ??= []).Add("files-api-2025-04-14"); + } + if (options is not null) { if (options.Instructions is { } instructions) @@ -916,8 +961,8 @@ createParams.OutputFormat is null switch (responseFormat) { case ChatResponseFormatJson formatJson when formatJson.Schema is not null: - JsonElement schema = s_transformCache - .GetOrCreateTransformedSchema(formatJson) + JsonElement schema = AnthropicClientExtensions + .JsonSchemaTransformCache.GetOrCreateTransformedSchema(formatJson) .GetValueOrDefault(); if ( schema.TryGetProperty("properties", out JsonElement properties) @@ -999,44 +1044,16 @@ createParams.OutputFormat is null break; case AIFunctionDeclaration af: - Dictionary properties = []; - List required = []; - JsonElement inputSchema = af.JsonSchema; + JsonElement inputSchema = + AnthropicClientExtensions.JsonSchemaTransformCache.GetOrCreateTransformedSchema( + af + ); + Dictionary schemaData = []; if (inputSchema.ValueKind is JsonValueKind.Object) { - if ( - inputSchema.TryGetProperty( - "properties", - out JsonElement propsElement - ) - && propsElement.ValueKind is JsonValueKind.Object - ) - { - foreach (JsonProperty p in propsElement.EnumerateObject()) - { - properties[p.Name] = p.Value; - } - } - - if ( - inputSchema.TryGetProperty( - "required", - out JsonElement reqElement - ) - && reqElement.ValueKind is JsonValueKind.Array - ) + foreach (JsonProperty p in inputSchema.EnumerateObject()) { - foreach (JsonElement r in reqElement.EnumerateArray()) - { - if ( - r.ValueKind is JsonValueKind.String - && r.GetString() is { } s - && !string.IsNullOrWhiteSpace(s) - ) - { - required.Add(s); - } - } + schemaData[p.Name] = p.Value; } } @@ -1045,11 +1062,7 @@ r.ValueKind is JsonValueKind.String { Name = af.Name, Description = af.Description, - InputSchema = new InputSchema() - { - Properties = properties, - Required = required, - }, + InputSchema = new InputSchema(schemaData), DeferLoading = GetValue( af, nameof(BetaTool.DeferLoading) @@ -1162,6 +1175,79 @@ toolMode is AutoChatToolMode createParams = createParams with { ToolChoice = toolChoice }; } } + + if (createParams.Thinking is null && options.Reasoning is { } reasoning) + { + BetaThinkingConfigParam? thinkingConfig = null; + if (reasoning.Effort is ReasoningEffort.None) + { + thinkingConfig = new(new BetaThinkingConfigDisabled()); + } + else + { + long? budgetTokens = reasoning.Effort switch + { + ReasoningEffort.Low => 1024, + ReasoningEffort.Medium => 8192, + ReasoningEffort.High => 16384, + ReasoningEffort.ExtraHigh => 32768, + _ => null, + }; + + if (budgetTokens is { } budget) + { + // Anthropic requires thinking budget >= 1024 and < max tokens. + bool autoIncreaseMaxTokens = false; + if (createParams.MaxTokens <= budget) + { + if (options.MaxOutputTokens is not null) + { + // Caller explicitly set MaxOutputTokens. Clamp the budget to fit, + // and skip thinking if it can't meet the minimum. + budget = createParams.MaxTokens - 1; + } + else + { + autoIncreaseMaxTokens = true; + } + } + + if (budget >= 1024) + { + if (autoIncreaseMaxTokens) + { + // Caller didn't set MaxOutputTokens. Auto-increase max_tokens + // to accommodate the thinking budget plus room for output. + createParams = createParams with + { + MaxTokens = budget + _defaultMaxTokens, + }; + } + + thinkingConfig = new(new BetaThinkingConfigEnabled(budget)); + } + } + } + + if ( + thinkingConfig is not null + && reasoning.Output is ReasoningOutput.None + && thinkingConfig.Value is BetaThinkingConfigEnabled enabled + ) + { + thinkingConfig = new( + enabled with + { + Display = BetaThinkingConfigEnabledDisplay.Omitted, + } + ); + } + + if (thinkingConfig is not null) + { + createParams = createParams with { Thinking = thinkingConfig }; + } + } } if (systemMessages is not null) @@ -1197,7 +1283,9 @@ existingSystem.Value is IReadOnlyList existingMessages private static MessageCreateParams AddMeaiHeaders(MessageCreateParams createParams) { - Dictionary mergedHeaders = new(s_meaiHeaderData); + Dictionary mergedHeaders = new( + AnthropicClientExtensions.MeaiHeaderData + ); foreach (var header in createParams.RawHeaderData) { @@ -1295,7 +1383,7 @@ private static UsageDetails ToUsageDetails( _ => ChatFinishReason.Stop, }; - private static AIContent ToAIContent(BetaContentBlock block) + private static AIContent ContentBlockValueToAIContent(object? blockValue) { static AIContent FromBetaTextBlock(BetaTextBlock text) { @@ -1312,7 +1400,7 @@ .. text.Citations.Select(ToAIAnnotation).OfType(), return tc; } - switch (block.Value) + switch (blockValue) { case BetaTextBlock text: return FromBetaTextBlock(text); @@ -1366,7 +1454,7 @@ .. text.Citations.Select(ToAIAnnotation).OfType(), case BetaMcpToolResultBlock mcpToolResult: return new McpServerToolResultContent(mcpToolResult.ToolUseID) { - Output = mcpToolResult.IsError + Outputs = mcpToolResult.IsError ? [new ErrorContent(mcpToolResult.Content.Value?.ToString())] : mcpToolResult.Content.Value switch { @@ -1381,9 +1469,8 @@ .. text.Citations.Select(ToAIAnnotation).OfType(), case BetaCodeExecutionToolResultBlock ce: { - CodeInterpreterToolResultContent c = new() + CodeInterpreterToolResultContent c = new(ce.ToolUseID) { - CallId = ce.ToolUseID, RawRepresentation = ce, }; @@ -1427,6 +1514,36 @@ .. text.Citations.Select(ToAIAnnotation).OfType(), } } + if (ce.Content.TryPickEncryptedCodeExecutionResultBlock(out var ceEncrypted)) + { + // Unlike with the non-encrypted case above, we skip Stdout, as here it's encrypted. + + if ( + !string.IsNullOrWhiteSpace(ceEncrypted.Stderr) + || ceEncrypted.ReturnCode != 0 + ) + { + (c.Outputs ??= []).Add( + new ErrorContent(ceEncrypted.Stderr) + { + ErrorCode = ceEncrypted.ReturnCode.ToString( + CultureInfo.InvariantCulture + ), + } + ); + } + + if (ceEncrypted.Content is { Count: > 0 }) + { + foreach (var ceOutputContent in ceEncrypted.Content) + { + (c.Outputs ??= []).Add( + new HostedFileContent(ceOutputContent.FileID) + ); + } + } + } + return c; } @@ -1434,9 +1551,8 @@ .. text.Citations.Select(ToAIAnnotation).OfType(), // This is the same as BetaCodeExecutionToolResultBlock but with a different type names. // Keep both of them in sync. { - CodeInterpreterToolResultContent c = new() + CodeInterpreterToolResultContent c = new(ce.ToolUseID) { - CallId = ce.ToolUseID, RawRepresentation = ce, }; @@ -1483,8 +1599,224 @@ .. text.Citations.Select(ToAIAnnotation).OfType(), return c; } + case BetaServerToolUseBlock serverToolUse: + { + Name nameValue = serverToolUse.Name.Value(); + switch (nameValue) + { + case Name.WebSearch: + case Name.WebFetch: + WebSearchToolCallContent wsc = new(serverToolUse.ID) + { + RawRepresentation = serverToolUse, + }; + if ( + serverToolUse.Input?.TryGetValue( + "query", + out JsonElement queryElement + ) == true + && queryElement.ValueKind == JsonValueKind.String + ) + { + (wsc.Queries ??= []).Add(queryElement.GetString()!); + } + + return wsc; + + case Name.CodeExecution: + case Name.BashCodeExecution: + case Name.TextEditorCodeExecution: + CodeInterpreterToolCallContent cic = new(serverToolUse.ID) + { + RawRepresentation = serverToolUse, + }; + + // CodeExecution (legacy Python) uses "code"; Bash/TextEditor use "command". + if ( + ( + serverToolUse.Input?.TryGetValue( + "code", + out JsonElement codeElement + ) == true + || serverToolUse.Input?.TryGetValue("command", out codeElement) + == true + ) + && codeElement.ValueKind == JsonValueKind.String + ) + { + string code = codeElement.GetString()!; + string mediaType = + nameValue == Name.CodeExecution ? "text/x-python" + : nameValue == Name.BashCodeExecution ? "application/x-sh" + : "text/plain"; + (cic.Inputs ??= []).Add( + new DataContent(Encoding.UTF8.GetBytes(code), mediaType) + ); + } + + return cic; + + default: + return new ToolCallContent(serverToolUse.ID) + { + RawRepresentation = serverToolUse, + }; + } + } + + case BetaWebSearchToolResultBlock wsResult: + { + WebSearchToolResultContent wsrc = new(wsResult.ToolUseID) + { + RawRepresentation = wsResult, + }; + + if (wsResult.Content.TryPickBetaWebSearchResultBlocks(out var searchResults)) + { + foreach (var result in searchResults) + { + (wsrc.Results ??= []).Add( + new UriContent( + result.Url, + AnthropicClientExtensions.InferMediaTypeFromExtension( + result.Url + ) + ) + { + RawRepresentation = result, + } + ); + } + } + else if (wsResult.Content.TryPickError(out var wsError)) + { + (wsrc.Results ??= []).Add( + new ErrorContent(null) + { + ErrorCode = wsError.ErrorCode.Value().ToString(), + RawRepresentation = wsError, + } + ); + } + + return wsrc; + } + + case BetaWebFetchToolResultBlock wfResult: + { + WebSearchToolResultContent wfrc = new(wfResult.ToolUseID) + { + RawRepresentation = wfResult, + }; + + if (wfResult.Content.TryPickBetaWebFetchBlock(out var fetchBlock)) + { + (wfrc.Results ??= []).Add( + new UriContent( + fetchBlock.Url, + AnthropicClientExtensions.InferMediaTypeFromExtension( + fetchBlock.Url + ) + ) + { + RawRepresentation = fetchBlock, + } + ); + } + else if ( + wfResult.Content.TryPickBetaWebFetchToolResultErrorBlock(out var wfError) + ) + { + (wfrc.Results ??= []).Add( + new ErrorContent(null) + { + ErrorCode = wfError.ErrorCode.Value().ToString(), + RawRepresentation = wfError, + } + ); + } + + return wfrc; + } + + case BetaTextEditorCodeExecutionToolResultBlock te: + { + CodeInterpreterToolResultContent c = new(te.ToolUseID) + { + RawRepresentation = te, + }; + + if ( + te.Content.TryPickBetaTextEditorCodeExecutionToolResultError( + out var teError + ) + ) + { + (c.Outputs ??= []).Add( + new ErrorContent(teError.ErrorMessage) + { + ErrorCode = teError.ErrorCode.Value().ToString(), + RawRepresentation = teError, + } + ); + } + else if ( + te.Content.TryPickBetaTextEditorCodeExecutionViewResultBlock( + out var viewResult + ) + ) + { + (c.Outputs ??= []).Add( + new TextContent(viewResult.Content) { RawRepresentation = viewResult } + ); + } + else if ( + te.Content.TryPickBetaTextEditorCodeExecutionCreateResultBlock( + out var createResult + ) + ) + { + (c.Outputs ??= []).Add( + new TextContent( + createResult.IsFileUpdate ? "File updated" : "File created" + ) + { + RawRepresentation = createResult, + } + ); + } + else if ( + te.Content.TryPickBetaTextEditorCodeExecutionStrReplaceResultBlock( + out var replaceResult + ) + ) + { + (c.Outputs ??= []).Add( + new TextContent( + replaceResult.Lines is { Count: > 0 } + ? string.Join("\n", replaceResult.Lines) + : "String replacement applied" + ) + { + RawRepresentation = replaceResult, + } + ); + } + + return c; + } + + case BetaToolSearchToolResultBlock ts: + return new ToolResultContent(ts.ToolUseID) { RawRepresentation = ts }; + + case BetaContainerUploadBlock containerUpload: + return new HostedFileContent(containerUpload.FileID) + { + RawRepresentation = containerUpload, + }; + default: - return new AIContent() { RawRepresentation = block.Value }; + return new AIContent() { RawRepresentation = blockValue }; } } @@ -1521,6 +1853,39 @@ out Uri? url return annotation; } + private static CitationAnnotation? ToAIAnnotation(Citation citation) + { + CitationAnnotation annotation = new() + { + Title = citation.Title ?? citation.DocumentTitle, + Snippet = citation.CitedText, + FileId = citation.FileID, + }; + + if (citation.TryPickBetaCitationsWebSearchResultLocation(out var webSearchLocation)) + { + annotation.Url = Uri.TryCreate( + webSearchLocation.Url, + UriKind.Absolute, + out Uri? url + ) + ? url + : null; + } + else if (citation.TryPickBetaCitationSearchResultLocation(out var searchLocation)) + { + annotation.Url = Uri.TryCreate( + searchLocation.Source, + UriKind.Absolute, + out Uri? url + ) + ? url + : null; + } + + return annotation; + } + private sealed class StreamingFunctionData { public string CallId { get; set; } = ""; @@ -1529,6 +1894,277 @@ private sealed class StreamingFunctionData } } + private sealed class AnthropicHostedFileClient(IFileService fileService) : IHostedFileClient + { + private HostedFileClientMetadata? _metadata; + + /// + void IDisposable.Dispose() { } + + /// + public object? GetService(System.Type serviceType, object? serviceKey = null) + { + if (serviceType is null) + { + throw new ArgumentNullException(nameof(serviceType)); + } + + if (serviceKey is not null) + { + return null; + } + + if (serviceType == typeof(HostedFileClientMetadata)) + { + return _metadata ??= new( + "anthropic", + fileService is FileService { _client.BaseUrl: string baseUrl } + ? new Uri(baseUrl) + : null + ); + } + + if (serviceType.IsInstanceOfType(this)) + { + return this; + } + + return null; + } + + /// + public async Task UploadAsync( + Stream content, + string? mediaType, + string? fileName, + HostedFileClientOptions? options, + CancellationToken cancellationToken + ) + { + if (content is null) + { + throw new ArgumentNullException(nameof(content)); + } + + // Infer fileName/mediaType when not provided, matching the OpenAI provider's behavior: + // https://github.com/dotnet/extensions/blob/1ebbf3879591843e2f9ec943e17efc7e4163c854/src/Libraries/Microsoft.Extensions.AI.OpenAI/OpenAIHostedFileClient.cs#L105-L107 + fileName ??= content is FileStream fs ? System.IO.Path.GetFileName(fs.Name) : null; + mediaType ??= fileName is not null + ? AnthropicClientExtensions.InferMediaTypeFromExtension( + System.IO.Path.GetExtension(fileName) + ) + : null; + fileName ??= + $"{Guid.NewGuid():N}{AnthropicClientExtensions.InferExtensionFromMediaType(mediaType)}"; + + var binaryContent = new BinaryContent { Stream = content, FileName = fileName }; + + if (mediaType is not null) + { + binaryContent.ContentType = new MediaTypeHeaderValue(mediaType); + } + + FileMetadata result = await fileService.Upload( + new FileUploadParams { File = binaryContent }, + cancellationToken + ); + + return ToHostedFileContent(result); + } + + /// + public async Task DownloadAsync( + string fileId, + HostedFileClientOptions? options, + CancellationToken cancellationToken + ) + { + ThrowIfFileIdInvalid(fileId); + + HttpResponse response = await fileService.Download( + fileId, + cancellationToken: cancellationToken + ); + + Stream stream = await response.ReadAsStream(cancellationToken); + + string? contentType = response.RawMessage.Content.Headers.ContentType?.MediaType; + + return new AnthropicHostedFileDownloadStream(stream, response, contentType, null); + } + + /// + public async Task GetFileInfoAsync( + string fileId, + HostedFileClientOptions? options, + CancellationToken cancellationToken + ) + { + ThrowIfFileIdInvalid(fileId); + + FileMetadata result = await fileService.RetrieveMetadata( + fileId, + cancellationToken: cancellationToken + ); + + return ToHostedFileContent(result); + } + + /// + public async IAsyncEnumerable ListFilesAsync( + HostedFileClientOptions? options, + [EnumeratorCancellation] CancellationToken cancellationToken + ) + { + FileListPage page = await fileService.List(cancellationToken: cancellationToken); + + while (true) + { + foreach (FileMetadata file in page.Items) + { + yield return ToHostedFileContent(file); + } + + if (!page.HasNext()) + { + break; + } + + page = await page.Next(cancellationToken); + } + } + + /// + public async Task DeleteAsync( + string fileId, + HostedFileClientOptions? options, + CancellationToken cancellationToken + ) + { + ThrowIfFileIdInvalid(fileId); + + await fileService.Delete(fileId, cancellationToken: cancellationToken); + return true; + } + + private static void ThrowIfFileIdInvalid(string fileId) + { + if (fileId is null) + { + throw new ArgumentNullException(nameof(fileId)); + } + + if (fileId.Length == 0) + { + throw new ArgumentException("File ID cannot be empty.", nameof(fileId)); + } + } + + private static HostedFileContent ToHostedFileContent(FileMetadata metadata) => + new(metadata.ID) + { + MediaType = metadata.MimeType, + Name = metadata.Filename, + SizeInBytes = metadata.SizeBytes, + CreatedAt = metadata.CreatedAt, + RawRepresentation = metadata, + }; + + /// + /// A that wraps an Anthropic file download response. + /// + private sealed class AnthropicHostedFileDownloadStream( + Stream innerStream, + HttpResponse response, + string? mediaType, + string? fileName + ) : HostedFileDownloadStream + { + public override string? MediaType => mediaType; + + public override string? FileName => fileName; + + public override bool CanRead => innerStream.CanRead; + + public override bool CanSeek => innerStream.CanSeek; + + public override bool CanWrite => false; + + public override long Length => innerStream.Length; + + public override long Position + { + get => innerStream.Position; + set => innerStream.Position = value; + } + + public override int Read(byte[] buffer, int offset, int count) => + innerStream.Read(buffer, offset, count); + + public override Task ReadAsync( + byte[] buffer, + int offset, + int count, + CancellationToken cancellationToken + ) => innerStream.ReadAsync(buffer, offset, count, cancellationToken); + + public override Task FlushAsync(CancellationToken cancellationToken) => + innerStream.FlushAsync(cancellationToken); + + public override IAsyncResult BeginRead( + byte[] buffer, + int offset, + int count, + AsyncCallback? callback, + object? state + ) => innerStream.BeginRead(buffer, offset, count, callback, state); + + public override int EndRead(IAsyncResult asyncResult) => + innerStream.EndRead(asyncResult); + + public override Task CopyToAsync( + Stream destination, + int bufferSize, + CancellationToken cancellationToken + ) => innerStream.CopyToAsync(destination, bufferSize, cancellationToken); + + public override int ReadByte() => innerStream.ReadByte(); + + public override long Seek(long offset, SeekOrigin origin) => + innerStream.Seek(offset, origin); + + public override void SetLength(long value) => throw new NotSupportedException(); + + public override void Write(byte[] buffer, int offset, int count) => + throw new NotSupportedException(); + + public override void Flush() => innerStream.Flush(); + + protected override void Dispose(bool disposing) + { + if (disposing) + { + innerStream.Dispose(); + response.Dispose(); + } + + base.Dispose(disposing); + } + +#if NET + public override int Read(Span buffer) => innerStream.Read(buffer); + + public override ValueTask ReadAsync( + Memory buffer, + CancellationToken cancellationToken = default + ) => innerStream.ReadAsync(buffer, cancellationToken); + + public override void CopyTo(Stream destination, int bufferSize) => + innerStream.CopyTo(destination, bufferSize); +#endif + } + } + private sealed class BetaToolUnionAITool(BetaToolUnion tool) : AITool { public BetaToolUnion Tool => tool;