fix(registry): add Claude Sonnet 5 model metadata

This commit is contained in:
Wolfgang Schoenberger
2026-06-30 19:36:15 -07:00
parent 00114bec1b
commit 956ce7cf78
4 changed files with 90 additions and 22 deletions

View File

@@ -147,3 +147,31 @@ func TestLookupModelInfoReturnsCloneForStaticDefinitions(t *testing.T) {
t.Fatalf("expected static lookup clone, got %+v", second)
}
}
func TestLookupModelInfoIncludesClaudeSonnet5(t *testing.T) {
model := LookupModelInfo("claude-sonnet-5")
if model == nil {
t.Fatal("expected Claude Sonnet 5 static model")
}
if model.Type != "claude" {
t.Fatalf("Claude Sonnet 5 type = %q, want claude", model.Type)
}
if model.ContextLength != 1000000 {
t.Fatalf("Claude Sonnet 5 context length = %d, want 1000000", model.ContextLength)
}
if model.MaxCompletionTokens != 128000 {
t.Fatalf("Claude Sonnet 5 max completion tokens = %d, want 128000", model.MaxCompletionTokens)
}
if model.Thinking == nil || !model.Thinking.ZeroAllowed || !model.Thinking.DynamicAllowed || model.Thinking.Min != 0 || model.Thinking.Max != 0 {
t.Fatalf("expected Claude Sonnet 5 dynamic level-only thinking with zero allowed, got %+v", model.Thinking)
}
expectedLevels := []string{"low", "medium", "high", "xhigh", "max"}
if len(model.Thinking.Levels) != len(expectedLevels) {
t.Fatalf("Claude Sonnet 5 thinking levels = %+v, want %+v", model.Thinking.Levels, expectedLevels)
}
for i, level := range expectedLevels {
if model.Thinking.Levels[i] != level {
t.Fatalf("Claude Sonnet 5 thinking levels = %+v, want %+v", model.Thinking.Levels, expectedLevels)
}
}
}

View File

@@ -119,6 +119,28 @@
]
}
},
{
"id": "claude-sonnet-5",
"object": "model",
"created": 1782777600,
"owned_by": "anthropic",
"type": "claude",
"display_name": "Claude Sonnet 5",
"description": "Anthropic's agentic Sonnet model for coding, tool use, and enterprise workflows",
"context_length": 1000000,
"max_completion_tokens": 128000,
"thinking": {
"zero_allowed": true,
"dynamic_allowed": true,
"levels": [
"low",
"medium",
"high",
"xhigh",
"max"
]
}
},
{
"id": "claude-fable-5",
"object": "model",

View File

@@ -244,7 +244,7 @@ func (e *ClaudeExecutor) Execute(ctx context.Context, auth *cliproxyauth.Auth, r
// Disable thinking if tool_choice forces tool use (Anthropic API constraint)
body = disableThinkingIfToolChoiceForced(body)
body = normalizeClaudeTemperatureForThinking(body)
body = normalizeClaudeSamplingForThinking(body)
// Auto-inject cache_control if missing (optimization for ClawdBot/clients without caching support)
if countCacheControls(body) == 0 {
@@ -434,7 +434,7 @@ func (e *ClaudeExecutor) ExecuteStream(ctx context.Context, auth *cliproxyauth.A
// Disable thinking if tool_choice forces tool use (Anthropic API constraint)
body = disableThinkingIfToolChoiceForced(body)
body = normalizeClaudeTemperatureForThinking(body)
body = normalizeClaudeSamplingForThinking(body)
// Auto-inject cache_control if missing (optimization for ClawdBot/clients without caching support)
if countCacheControls(body) == 0 {
@@ -865,21 +865,18 @@ func disableThinkingIfToolChoiceForced(body []byte) []byte {
return body
}
// normalizeClaudeTemperatureForThinking keeps Anthropic message requests valid when
// thinking is enabled. Anthropic rejects temperatures other than 1 when
// thinking.type is enabled/adaptive/auto.
func normalizeClaudeTemperatureForThinking(body []byte) []byte {
if !gjson.GetBytes(body, "temperature").Exists() {
return body
}
// normalizeClaudeSamplingForThinking keeps Anthropic message requests valid when
// thinking is active. Anthropic rejects non-default sampling while thinking is
// enabled/adaptive/auto.
func normalizeClaudeSamplingForThinking(body []byte) []byte {
thinkingType := strings.ToLower(strings.TrimSpace(gjson.GetBytes(body, "thinking.type").String()))
switch thinkingType {
case "enabled", "adaptive", "auto":
if temp := gjson.GetBytes(body, "temperature"); temp.Exists() && temp.Type == gjson.Number && temp.Float() == 1 {
return body
if temp := gjson.GetBytes(body, "temperature"); temp.Exists() && (temp.Type != gjson.Number || temp.Float() != 1) {
body, _ = sjson.SetBytes(body, "temperature", 1)
}
body, _ = sjson.SetBytes(body, "temperature", 1)
body, _ = sjson.DeleteBytes(body, "top_p")
body, _ = sjson.DeleteBytes(body, "top_k")
}
return body
}

View File

@@ -2659,37 +2659,58 @@ func TestApplyCloaking_PreservesConfiguredStrictModeAndSensitiveWordsWhenModeOmi
}
}
func TestNormalizeClaudeTemperatureForThinking_AdaptiveCoercesToOne(t *testing.T) {
func TestNormalizeClaudeSamplingForThinking_AdaptiveCoercesTemperatureToOne(t *testing.T) {
payload := []byte(`{"temperature":0,"thinking":{"type":"adaptive"},"output_config":{"effort":"max"}}`)
out := normalizeClaudeTemperatureForThinking(payload)
out := normalizeClaudeSamplingForThinking(payload)
if got := gjson.GetBytes(out, "temperature").Float(); got != 1 {
t.Fatalf("temperature = %v, want 1", got)
}
}
func TestNormalizeClaudeTemperatureForThinking_EnabledCoercesToOne(t *testing.T) {
func TestNormalizeClaudeSamplingForThinking_EnabledCoercesTemperatureToOne(t *testing.T) {
payload := []byte(`{"temperature":0.2,"thinking":{"type":"enabled","budget_tokens":2048}}`)
out := normalizeClaudeTemperatureForThinking(payload)
out := normalizeClaudeSamplingForThinking(payload)
if got := gjson.GetBytes(out, "temperature").Float(); got != 1 {
t.Fatalf("temperature = %v, want 1", got)
}
}
func TestNormalizeClaudeTemperatureForThinking_NoThinkingLeavesTemperatureAlone(t *testing.T) {
payload := []byte(`{"temperature":0,"messages":[{"role":"user","content":"hi"}]}`)
out := normalizeClaudeTemperatureForThinking(payload)
func TestNormalizeClaudeSamplingForThinking_RemovesTopPAndTopK(t *testing.T) {
payload := []byte(`{"temperature":0.2,"top_p":0.9,"top_k":40,"thinking":{"type":"adaptive"}}`)
out := normalizeClaudeSamplingForThinking(payload)
if got := gjson.GetBytes(out, "temperature").Float(); got != 1 {
t.Fatalf("temperature = %v, want 1", got)
}
if gjson.GetBytes(out, "top_p").Exists() {
t.Fatalf("top_p should be removed when thinking is active")
}
if gjson.GetBytes(out, "top_k").Exists() {
t.Fatalf("top_k should be removed when thinking is active")
}
}
func TestNormalizeClaudeSamplingForThinking_NoThinkingLeavesTemperatureAlone(t *testing.T) {
payload := []byte(`{"temperature":0,"top_p":0.9,"top_k":40,"messages":[{"role":"user","content":"hi"}]}`)
out := normalizeClaudeSamplingForThinking(payload)
if got := gjson.GetBytes(out, "temperature").Float(); got != 0 {
t.Fatalf("temperature = %v, want 0", got)
}
if got := gjson.GetBytes(out, "top_p").Float(); got != 0.9 {
t.Fatalf("top_p = %v, want 0.9", got)
}
if got := gjson.GetBytes(out, "top_k").Int(); got != 40 {
t.Fatalf("top_k = %v, want 40", got)
}
}
func TestNormalizeClaudeTemperatureForThinking_AfterForcedToolChoiceKeepsOriginalTemperature(t *testing.T) {
func TestNormalizeClaudeSamplingForThinking_AfterForcedToolChoiceKeepsOriginalTemperature(t *testing.T) {
payload := []byte(`{"temperature":0,"thinking":{"type":"adaptive"},"output_config":{"effort":"max"},"tool_choice":{"type":"any"}}`)
out := disableThinkingIfToolChoiceForced(payload)
out = normalizeClaudeTemperatureForThinking(out)
out = normalizeClaudeSamplingForThinking(out)
if gjson.GetBytes(out, "thinking").Exists() {
t.Fatalf("thinking should be removed when tool_choice forces tool use")