diff --git a/Directory.Packages.props b/Directory.Packages.props
index 1fcda75..b9ec2ce 100644
--- a/Directory.Packages.props
+++ b/Directory.Packages.props
@@ -19,7 +19,7 @@
-
+
diff --git a/docs/providers.md b/docs/providers.md
index b7362e5..425e17c 100644
--- a/docs/providers.md
+++ b/docs/providers.md
@@ -103,3 +103,7 @@ Hard failures use **`ProviderExecutionException`** with **`ProviderFailureKind`*
4. Extend **`ProviderCatalogOptions`** (defaults / aliases) via **`IConfiguration`** or **`Configure`** / **`PostConfigure`**.
**Test pattern:** **`SharpClaw.Code.MockProvider`** registers **`DeterministicMockModelProvider`** with **`PostConfigure`** so **`default`** maps to provider name **`mock`** (`MockProviderServiceCollectionExtensions`).
+
+Anthropic requests omit the deprecated `temperature` field, including when the shared
+provider request specifies a value. This avoids rejection by newer Claude models;
+the shared request contract and other providers retain temperature support.
diff --git a/src/SharpClaw.Code.Providers/AnthropicProvider.cs b/src/SharpClaw.Code.Providers/AnthropicProvider.cs
index 511a354..abee896 100644
--- a/src/SharpClaw.Code.Providers/AnthropicProvider.cs
+++ b/src/SharpClaw.Code.Providers/AnthropicProvider.cs
@@ -50,8 +50,40 @@ public async Task StartStreamAsync(ProviderRequest request
var client = CreateClient(resolved.ApiKey);
var modelId = Internal.ProviderHttpHelpers.ResolveModelOrDefault(request.Model, _options.DefaultModel);
+ var parameters = CreateMessageParameters(request, modelId);
+
+ logger.LogInformation("Starting Anthropic SDK stream for request {RequestId}.", request.Id);
+
+ IAsyncEnumerable stream;
+ try
+ {
+ stream = client.Messages.CreateStreaming(parameters, cancellationToken);
+ }
+ catch (OperationCanceledException)
+ {
+ throw;
+ }
+ catch (Exception exception) when (ProviderStreamFailureClassifier.IsAuthenticationFailure(exception))
+ {
+ throw new ProviderExecutionException(
+ ProviderName,
+ modelId,
+ ProviderFailureKind.AuthenticationUnavailable,
+ $"Provider '{ProviderName}' authentication failed while starting the stream.",
+ exception);
+ }
+
+ logger.LogInformation("Started Anthropic SDK stream for request {RequestId}.", request.Id);
+
+ return new ProviderStreamHandle(request, AnthropicSdkStreamAdapter.AdaptAsync(stream, request.Id, systemClock, cancellationToken));
+ }
+
+ ///
+ /// Builds SDK parameters without the obsolete temperature field, which newer models reject.
+ ///
+ internal static MessageCreateParams CreateMessageParameters(ProviderRequest request, string modelId)
+ {
var systemPrompt = string.IsNullOrWhiteSpace(request.SystemPrompt) ? null : request.SystemPrompt;
- float? temperature = request.Temperature.HasValue ? (float)request.Temperature.Value : null;
MessageCreateParams parameters;
@@ -63,7 +95,6 @@ public async Task StartStreamAsync(ProviderRequest request
MaxTokens = request.MaxTokens ?? 1024,
Model = modelId,
Messages = messages,
- Temperature = temperature,
};
if (request.Tools is { Count: > 0 } tools)
@@ -86,7 +117,6 @@ public async Task StartStreamAsync(ProviderRequest request
Content = request.Prompt,
},
],
- Temperature = temperature,
};
}
@@ -95,30 +125,7 @@ public async Task StartStreamAsync(ProviderRequest request
parameters = parameters with { System = systemPrompt };
}
- logger.LogInformation("Starting Anthropic SDK stream for request {RequestId}.", request.Id);
-
- IAsyncEnumerable stream;
- try
- {
- stream = client.Messages.CreateStreaming(parameters, cancellationToken);
- }
- catch (OperationCanceledException)
- {
- throw;
- }
- catch (Exception exception) when (ProviderStreamFailureClassifier.IsAuthenticationFailure(exception))
- {
- throw new ProviderExecutionException(
- ProviderName,
- modelId,
- ProviderFailureKind.AuthenticationUnavailable,
- $"Provider '{ProviderName}' authentication failed while starting the stream.",
- exception);
- }
-
- logger.LogInformation("Started Anthropic SDK stream for request {RequestId}.", request.Id);
-
- return new ProviderStreamHandle(request, AnthropicSdkStreamAdapter.AdaptAsync(stream, request.Id, systemClock, cancellationToken));
+ return parameters;
}
private AnthropicClient CreateClient(string? resolvedApiKey)
diff --git a/tests/SharpClaw.Code.UnitTests/Providers/AnthropicRequestCompatibilityTests.cs b/tests/SharpClaw.Code.UnitTests/Providers/AnthropicRequestCompatibilityTests.cs
new file mode 100644
index 0000000..2402cad
--- /dev/null
+++ b/tests/SharpClaw.Code.UnitTests/Providers/AnthropicRequestCompatibilityTests.cs
@@ -0,0 +1,37 @@
+using System.Text.Json;
+using SharpClaw.Code.Protocol.Enums;
+using SharpClaw.Code.Protocol.Models;
+using SharpClaw.Code.Providers;
+
+namespace SharpClaw.Code.UnitTests.Providers;
+
+/// Verifies wire compatibility with Anthropic models that reject temperature.
+public sealed class AnthropicRequestCompatibilityTests
+{
+ [Theory]
+ [InlineData(false)]
+ [InlineData(true)]
+ public void Requests_omit_temperature_and_preserve_message_options(bool useHistory)
+ {
+ var request = new ProviderRequest(
+ "request", "session", "turn", "anthropic", "claude-model", "Hello", "Be concise.",
+ OutputFormat.Text, 0.2m, null,
+ Messages: useHistory ? [new ChatMessage("user", [new ContentBlock(ContentBlockKind.Text, "Hello", null, null, null, null)])] : null,
+ Tools: useHistory ? [new ProviderToolDefinition("read_file", "Read a file", "{\"type\":\"object\"}")] : null,
+ MaxTokens: 2048);
+
+ var parameters = AnthropicProvider.CreateMessageParameters(request, request.Model);
+ using var json = JsonDocument.Parse(JsonSerializer.Serialize(parameters.RawBodyData));
+ var root = json.RootElement;
+
+ Assert.False(root.TryGetProperty("temperature", out _));
+ Assert.Equal("claude-model", root.GetProperty("model").GetString());
+ Assert.Equal(2048, root.GetProperty("max_tokens").GetInt32());
+ Assert.Equal("Be concise.", root.GetProperty("system").GetString());
+ Assert.Single(root.GetProperty("messages").EnumerateArray());
+ if (useHistory)
+ {
+ Assert.Equal("read_file", root.GetProperty("tools")[0].GetProperty("name").GetString());
+ }
+ }
+}