From ae6467a5dcabb4d09e09e9b99ef05586e8aa3a31 Mon Sep 17 00:00:00 2001 From: matevip Date: Sun, 26 Apr 2026 08:34:10 +0800 Subject: [PATCH] fix(anthropic): bidirectional mcp_ tool-name prefix on OAuth requests --- .../ClaudeCodeIdentityChatModelDecorator.java | 242 +++++++++++++++--- .../src/main/resources/db/data-en.sql | 7 +- .../src/main/resources/db/data-mysql-en.sql | 7 +- .../src/main/resources/db/data-mysql-zh.sql | 7 +- .../src/main/resources/db/data-zh.sql | 7 +- ...4__fix_claude_sonnet_47_does_not_exist.sql | 32 +++ ...4__fix_claude_sonnet_47_does_not_exist.sql | 31 +++ 7 files changed, 291 insertions(+), 42 deletions(-) create mode 100644 mateclaw-server/src/main/resources/db/migration/h2/V44__fix_claude_sonnet_47_does_not_exist.sql create mode 100644 mateclaw-server/src/main/resources/db/migration/mysql/V44__fix_claude_sonnet_47_does_not_exist.sql diff --git a/mateclaw-server/src/main/java/vip/mate/agent/chatmodel/ClaudeCodeIdentityChatModelDecorator.java b/mateclaw-server/src/main/java/vip/mate/agent/chatmodel/ClaudeCodeIdentityChatModelDecorator.java index 5c90f095..9d64d422 100644 --- a/mateclaw-server/src/main/java/vip/mate/agent/chatmodel/ClaudeCodeIdentityChatModelDecorator.java +++ b/mateclaw-server/src/main/java/vip/mate/agent/chatmodel/ClaudeCodeIdentityChatModelDecorator.java @@ -1,50 +1,67 @@ package vip.mate.agent.chatmodel; import lombok.extern.slf4j.Slf4j; +import org.springframework.ai.anthropic.AnthropicChatOptions; +import org.springframework.ai.chat.messages.AssistantMessage; import org.springframework.ai.chat.messages.Message; import org.springframework.ai.chat.messages.SystemMessage; +import org.springframework.ai.chat.metadata.ChatGenerationMetadata; import org.springframework.ai.chat.model.ChatModel; import org.springframework.ai.chat.model.ChatResponse; +import org.springframework.ai.chat.model.Generation; import org.springframework.ai.chat.prompt.ChatOptions; import org.springframework.ai.chat.prompt.Prompt; +import org.springframework.ai.chat.model.ToolContext; +import org.springframework.ai.tool.ToolCallback; +import org.springframework.ai.tool.definition.DefaultToolDefinition; +import org.springframework.ai.tool.definition.ToolDefinition; +import org.springframework.ai.tool.metadata.ToolMetadata; import reactor.core.publisher.Flux; import java.util.ArrayList; +import java.util.LinkedHashSet; import java.util.List; +import java.util.Set; /** * RFC-062: Claude Code OAuth identity transform applied to every Anthropic * request when the underlying auth is a Claude Code OAuth token. * *

Anthropic's OAuth edge enforces an anti-abuse path that rate-limits - * (and intermittently 5xxs) requests whose system prompt does NOT claim - * Claude Code identity. Symptoms include: + * (and intermittently 5xxs) requests claiming Claude Code identity but + * shaped differently from real Claude Code traffic. Symptoms: * *

* *

Reference: hermes-agent {@code anthropic_adapter._build_anthropic_messages_request} - * lines 1571-1607 — applies the same transforms unconditionally on + * lines 1571-1607 — same transforms applied unconditionally on * {@code is_oauth=True} requests. * *

Transforms applied per call

*
    - *
  1. Prepend {@code "You are Claude Code, Anthropic's official CLI for Claude."} - * to the system prompt. If no system message is present we insert one.
  2. - *
  3. Scrub MateClaw branding from system text — replace - * {@code "MateClaw"} → {@code "Claude Code"} and a few common variants — - * so server-side content filters don't fire on the spoofed identity.
  4. + *
  5. System prompt prefix: prepend + * {@code "You are Claude Code, Anthropic's official CLI for Claude."}. + * Insert a new SystemMessage if none exists.
  6. + *
  7. Brand scrub: replace {@code "MateClaw"}/{@code "mateclaw"} + * in system text with their Claude Code equivalents — Anthropic's + * content filter flags identity contradictions.
  8. + *
  9. Tool {@code mcp_} prefix (outgoing): every tool definition + * sent to Anthropic is renamed {@code mcp_} — Claude Code + * runs all tools through MCP servers, so real Claude Code traffic + * always has the prefix. Mismatch trips anti-abuse.
  10. + *
  11. History tool_use prefix: previously-issued tool calls in + * AssistantMessage history get the prefix re-applied (we strip on + * response, so they're stored unprefixed).
  12. + *
  13. Tool {@code mcp_} prefix (incoming): ChatResponse tool_use + * names are stripped of the {@code mcp_} prefix so MateClaw's tool + * registry can resolve them.
  14. *
- * - *

Tool-name {@code mcp_} prefix (hermes lines 1593-1607) is a - * separate concern: it requires bidirectional rewriting (out + back) and - * touches Spring AI's tool-callback layer. Deferred to a follow-up — current - * symptom is rate-limit on memory analysis (which uses no tools). */ @Slf4j public class ClaudeCodeIdentityChatModelDecorator implements ChatModel { @@ -53,6 +70,9 @@ public class ClaudeCodeIdentityChatModelDecorator implements ChatModel { static final String CLAUDE_CODE_SYSTEM_PREFIX = "You are Claude Code, Anthropic's official CLI for Claude."; + /** Tool-name prefix Claude Code uses for all MCP-routed tools. */ + static final String MCP_TOOL_PREFIX = "mcp_"; + private final ChatModel delegate; public ClaudeCodeIdentityChatModelDecorator(ChatModel delegate) { @@ -61,12 +81,12 @@ public class ClaudeCodeIdentityChatModelDecorator implements ChatModel { @Override public ChatResponse call(Prompt prompt) { - return delegate.call(transform(prompt)); + return stripToolPrefixes(delegate.call(transform(prompt))); } @Override public Flux stream(Prompt prompt) { - return delegate.stream(transform(prompt)); + return delegate.stream(transform(prompt)).map(this::stripToolPrefixes); } @Override @@ -74,6 +94,10 @@ public class ClaudeCodeIdentityChatModelDecorator implements ChatModel { return delegate.getDefaultOptions(); } + /* ====================================================================== */ + /* Outbound transform: Prompt → Prompt with identity + tool prefix */ + /* ====================================================================== */ + /** * Build a new {@link Prompt} with the OAuth identity transforms applied. * Package-private for unit tests. @@ -88,40 +112,145 @@ public class ClaudeCodeIdentityChatModelDecorator implements ChatModel { boolean systemSeen = false; for (Message msg : source) { if (msg instanceof SystemMessage sm && !systemSeen) { - // Only mutate the FIRST system message — multiple system messages - // are rare in practice but preserve the second-onward verbatim. rewritten.add(new SystemMessage(prependIdentity(sanitizeBranding(sm.getText())))); systemSeen = true; + } else if (msg instanceof AssistantMessage am && am.hasToolCalls()) { + // Re-prefix tool_use names in history. We strip on response, so + // by the time MateClaw stores the AssistantMessage the names + // are unprefixed — must put the prefix back when echoing the + // history to Anthropic for it to match its own prior turn. + rewritten.add(rebuildAssistantMessage(am, true)); } else { rewritten.add(msg); } } if (!systemSeen) { - // No system message at all → insert one with just the identity prefix. rewritten.add(0, new SystemMessage(CLAUDE_CODE_SYSTEM_PREFIX)); } - return new Prompt(rewritten, original.getOptions()); + + ChatOptions transformedOptions = transformOptions(original.getOptions()); + return new Prompt(rewritten, transformedOptions); } + /** + * Wrap each tool callback in the options so its {@code getToolDefinition().name()} + * returns {@code mcp_}. Spring AI sends those names verbatim to Anthropic. + * Other tool fields (description, schema) untouched. Returns {@code null} for + * non-Anthropic options so we don't accidentally drop them on a custom subclass. + */ + private ChatOptions transformOptions(ChatOptions options) { + if (!(options instanceof AnthropicChatOptions anthropicOpts)) { + return options; + } + List originalCallbacks = anthropicOpts.getToolCallbacks(); + Set originalToolNames = anthropicOpts.getToolNames(); + + boolean hasCallbacks = originalCallbacks != null && !originalCallbacks.isEmpty(); + boolean hasToolNames = originalToolNames != null && !originalToolNames.isEmpty(); + if (!hasCallbacks && !hasToolNames) { + return options; + } + + AnthropicChatOptions copy = AnthropicChatOptions.fromOptions(anthropicOpts); + if (hasCallbacks) { + List wrapped = new ArrayList<>(originalCallbacks.size()); + for (ToolCallback cb : originalCallbacks) { + wrapped.add(cb instanceof PrefixedToolCallback ? cb : new PrefixedToolCallback(cb)); + } + copy.setToolCallbacks(wrapped); + } + if (hasToolNames) { + // toolNames is a set used by Spring AI's tool resolver to filter from + // a wider registry. If MateClaw populates it (most paths use callbacks + // directly so this is rare), prefix the names so they line up with + // the wrapped callbacks above. + Set prefixed = new LinkedHashSet<>(originalToolNames.size()); + for (String n : originalToolNames) { + prefixed.add(n.startsWith(MCP_TOOL_PREFIX) ? n : MCP_TOOL_PREFIX + n); + } + copy.setToolNames(prefixed); + } + return copy; + } + + /* ====================================================================== */ + /* Inbound transform: ChatResponse → strip tool prefix */ + /* ====================================================================== */ + + ChatResponse stripToolPrefixes(ChatResponse response) { + if (response == null) { + return null; + } + List origGens = response.getResults(); + if (origGens == null || origGens.isEmpty()) { + return response; + } + List rewritten = null; + for (int i = 0; i < origGens.size(); i++) { + Generation g = origGens.get(i); + AssistantMessage am = g.getOutput(); + if (am == null || !am.hasToolCalls()) continue; + boolean changed = false; + for (AssistantMessage.ToolCall tc : am.getToolCalls()) { + if (tc.name() != null && tc.name().startsWith(MCP_TOOL_PREFIX)) { + changed = true; + break; + } + } + if (!changed) continue; + + if (rewritten == null) { + rewritten = new ArrayList<>(origGens); + } + AssistantMessage stripped = rebuildAssistantMessage(am, false); + ChatGenerationMetadata meta = g.getMetadata(); + rewritten.set(i, new Generation(stripped, meta)); + } + if (rewritten == null) { + return response; // no tool_use blocks needed rewriting + } + return new ChatResponse(rewritten, response.getMetadata()); + } + + /** + * Rebuild an AssistantMessage with tool_call names prefixed (when + * {@code prefix=true}) or stripped (when {@code prefix=false}). + */ + private AssistantMessage rebuildAssistantMessage(AssistantMessage original, boolean prefix) { + List rebuilt = new ArrayList<>(original.getToolCalls().size()); + for (AssistantMessage.ToolCall tc : original.getToolCalls()) { + String name = tc.name(); + String newName; + if (prefix) { + newName = (name == null || name.startsWith(MCP_TOOL_PREFIX)) ? name : MCP_TOOL_PREFIX + name; + } else { + newName = (name != null && name.startsWith(MCP_TOOL_PREFIX)) + ? name.substring(MCP_TOOL_PREFIX.length()) : name; + } + rebuilt.add(new AssistantMessage.ToolCall(tc.id(), tc.type(), newName, tc.arguments())); + } + return AssistantMessage.builder() + .content(original.getText()) + .properties(original.getMetadata()) + .toolCalls(rebuilt) + .media(original.getMedia()) + .build(); + } + + /* ====================================================================== */ + /* String helpers (system prompt + branding) */ + /* ====================================================================== */ + private static String prependIdentity(String existingSystem) { if (existingSystem == null || existingSystem.isBlank()) { return CLAUDE_CODE_SYSTEM_PREFIX; } if (existingSystem.startsWith(CLAUDE_CODE_SYSTEM_PREFIX)) { - // Already prefixed (defensive — protects against double-wrapping - // if a caller invokes the decorator twice). return existingSystem; } return CLAUDE_CODE_SYSTEM_PREFIX + "\n\n" + existingSystem; } - /** - * Replace MateClaw / agent-specific branding tokens with their Claude Code - * equivalents. Same idea as hermes-agent's - * {@code text.replace("Hermes Agent", "Claude Code")} chain — Anthropic's - * server-side filter flags requests where the spoofed identity contradicts - * itself ("You are Claude Code … built by MateClaw"). - */ static String sanitizeBranding(String text) { if (text == null || text.isEmpty()) { return text; @@ -131,4 +260,57 @@ public class ClaudeCodeIdentityChatModelDecorator implements ChatModel { .replace("mateclaw", "claude-code") .replace("Mate Claw", "Claude Code"); } + + /* ====================================================================== */ + /* PrefixedToolCallback — wraps a ToolCallback to expose the mcp_ name */ + /* ====================================================================== */ + + /** + * Wraps a {@link ToolCallback} so its {@code getToolDefinition().name()} + * returns {@code mcp_}, while {@code call(...)} forwards verbatim + * to the underlying tool. Anthropic sees the prefixed name on the wire; + * MateClaw's tool implementation never sees the prefix. + */ + static final class PrefixedToolCallback implements ToolCallback { + + private final ToolCallback delegate; + private final ToolDefinition prefixedDefinition; + + PrefixedToolCallback(ToolCallback delegate) { + this.delegate = delegate; + ToolDefinition orig = delegate.getToolDefinition(); + String origName = orig.name(); + String prefixed = (origName != null && origName.startsWith(MCP_TOOL_PREFIX)) + ? origName : MCP_TOOL_PREFIX + origName; + this.prefixedDefinition = DefaultToolDefinition.builder() + .name(prefixed) + .description(orig.description()) + .inputSchema(orig.inputSchema()) + .build(); + } + + @Override + public ToolDefinition getToolDefinition() { + return prefixedDefinition; + } + + @Override + public ToolMetadata getToolMetadata() { + return delegate.getToolMetadata(); + } + + @Override + public String call(String input) { + return delegate.call(input); + } + + @Override + public String call(String input, ToolContext context) { + return delegate.call(input, context); + } + + ToolCallback unwrap() { + return delegate; + } + } } diff --git a/mateclaw-server/src/main/resources/db/data-en.sql b/mateclaw-server/src/main/resources/db/data-en.sql index edc47c3a..7a672fa5 100644 --- a/mateclaw-server/src/main/resources/db/data-en.sql +++ b/mateclaw-server/src/main/resources/db/data-en.sql @@ -264,12 +264,13 @@ MERGE INTO mate_model_config (id, name, provider, model_name, description, tempe -- Claude 4.7 series (direct Anthropic + OpenRouter). Sonnet/Opus. -- Note: Claude 4.7 forbids temperature/top_p/top_k — handled in AgentAnthropicChatModelBuilder. (1000000270, 'Claude Opus 4.7', 'anthropic', 'claude-opus-4-7', 'Anthropic Claude Opus 4.7 (xhigh adaptive thinking)', NULL, 4096, NULL, TRUE, TRUE, FALSE, NOW(), NOW(), 0), -(1000000271, 'Claude Sonnet 4.7', 'anthropic', 'claude-sonnet-4-7', 'Anthropic Claude Sonnet 4.7', NULL, 4096, NULL, TRUE, TRUE, FALSE, NOW(), NOW(), 0), +-- Anthropic only released Opus 4.7 — Sonnet stays at 4.6 until further notice. +(1000000271, 'Claude Sonnet 4.6', 'anthropic', 'claude-sonnet-4-6', 'Anthropic Claude Sonnet 4.6 (latest Sonnet — 4.7 not yet released)', NULL, 4096, NULL, TRUE, TRUE, FALSE, NOW(), NOW(), 0), (1000000272, 'Claude Opus 4.7', 'openrouter', 'anthropic/claude-opus-4-7', 'Claude Opus 4.7 via OpenRouter', NULL, 4096, NULL, TRUE, TRUE, FALSE, NOW(), NOW(), 0), -(1000000273, 'Claude Sonnet 4.7', 'openrouter', 'anthropic/claude-sonnet-4-7', 'Claude Sonnet 4.7 via OpenRouter', NULL, 4096, NULL, TRUE, TRUE, FALSE, NOW(), NOW(), 0), +(1000000273, 'Claude Sonnet 4.6', 'openrouter', 'anthropic/claude-sonnet-4-6', 'Claude Sonnet 4.6 via OpenRouter', NULL, 4096, NULL, TRUE, TRUE, FALSE, NOW(), NOW(), 0), -- RFC-062: Claude 4.7 via Claude Code OAuth subscription (Pro/Max plan). (1000000280, 'Claude Opus 4.7', 'anthropic-claude-code', 'claude-opus-4-7', 'Claude Opus 4.7 via Claude Code Pro/Max subscription', NULL, 4096, NULL, TRUE, TRUE, FALSE, NOW(), NOW(), 0), -(1000000281, 'Claude Sonnet 4.7', 'anthropic-claude-code', 'claude-sonnet-4-7', 'Claude Sonnet 4.7 via Claude Code Pro/Max subscription', NULL, 4096, NULL, TRUE, TRUE, FALSE, NOW(), NOW(), 0); +(1000000281, 'Claude Sonnet 4.6', 'anthropic-claude-code', 'claude-sonnet-4-6', 'Claude Sonnet 4.6 via Claude Code Pro/Max subscription', NULL, 4096, NULL, TRUE, TRUE, FALSE, NOW(), NOW(), 0); -- Default system settings MERGE INTO mate_system_setting (id, setting_key, setting_value, description, create_time, update_time) diff --git a/mateclaw-server/src/main/resources/db/data-mysql-en.sql b/mateclaw-server/src/main/resources/db/data-mysql-en.sql index 29e1a1ef..8260ac4f 100644 --- a/mateclaw-server/src/main/resources/db/data-mysql-en.sql +++ b/mateclaw-server/src/main/resources/db/data-mysql-en.sql @@ -302,12 +302,13 @@ VALUES -- Claude 4.7 series (direct Anthropic + OpenRouter). -- Note: Claude 4.7 forbids temperature/top_p/top_k — handled in AgentAnthropicChatModelBuilder. (1000000270, 'Claude Opus 4.7', 'anthropic', 'claude-opus-4-7', 'Anthropic Claude Opus 4.7 (xhigh adaptive thinking)', NULL, 4096, NULL, TRUE, TRUE, FALSE, NOW(), NOW(), 0), -(1000000271, 'Claude Sonnet 4.7', 'anthropic', 'claude-sonnet-4-7', 'Anthropic Claude Sonnet 4.7', NULL, 4096, NULL, TRUE, TRUE, FALSE, NOW(), NOW(), 0), +-- Anthropic only released Opus 4.7 — Sonnet stays at 4.6 until further notice. +(1000000271, 'Claude Sonnet 4.6', 'anthropic', 'claude-sonnet-4-6', 'Anthropic Claude Sonnet 4.6 (latest Sonnet — 4.7 not yet released)', NULL, 4096, NULL, TRUE, TRUE, FALSE, NOW(), NOW(), 0), (1000000272, 'Claude Opus 4.7', 'openrouter', 'anthropic/claude-opus-4-7', 'Claude Opus 4.7 via OpenRouter', NULL, 4096, NULL, TRUE, TRUE, FALSE, NOW(), NOW(), 0), -(1000000273, 'Claude Sonnet 4.7', 'openrouter', 'anthropic/claude-sonnet-4-7', 'Claude Sonnet 4.7 via OpenRouter', NULL, 4096, NULL, TRUE, TRUE, FALSE, NOW(), NOW(), 0), +(1000000273, 'Claude Sonnet 4.6', 'openrouter', 'anthropic/claude-sonnet-4-6', 'Claude Sonnet 4.6 via OpenRouter', NULL, 4096, NULL, TRUE, TRUE, FALSE, NOW(), NOW(), 0), -- RFC-062: Claude 4.7 via Claude Code OAuth subscription (Pro/Max plan). (1000000280, 'Claude Opus 4.7', 'anthropic-claude-code', 'claude-opus-4-7', 'Claude Opus 4.7 via Claude Code Pro/Max subscription', NULL, 4096, NULL, TRUE, TRUE, FALSE, NOW(), NOW(), 0), -(1000000281, 'Claude Sonnet 4.7', 'anthropic-claude-code', 'claude-sonnet-4-7', 'Claude Sonnet 4.7 via Claude Code Pro/Max subscription', NULL, 4096, NULL, TRUE, TRUE, FALSE, NOW(), NOW(), 0) +(1000000281, 'Claude Sonnet 4.6', 'anthropic-claude-code', 'claude-sonnet-4-6', 'Claude Sonnet 4.6 via Claude Code Pro/Max subscription', NULL, 4096, NULL, TRUE, TRUE, FALSE, NOW(), NOW(), 0) ON DUPLICATE KEY UPDATE name=VALUES(name), provider=VALUES(provider), model_name=VALUES(model_name), description=VALUES(description), temperature=VALUES(temperature), max_tokens=VALUES(max_tokens), top_p=VALUES(top_p), builtin=VALUES(builtin), enabled=VALUES(enabled), is_default=VALUES(is_default), update_time=VALUES(update_time), deleted=VALUES(deleted); -- Default system settings diff --git a/mateclaw-server/src/main/resources/db/data-mysql-zh.sql b/mateclaw-server/src/main/resources/db/data-mysql-zh.sql index 0fa59483..686fdaa5 100644 --- a/mateclaw-server/src/main/resources/db/data-mysql-zh.sql +++ b/mateclaw-server/src/main/resources/db/data-mysql-zh.sql @@ -302,12 +302,13 @@ VALUES -- Claude 4.7 系列(直连 Anthropic + OpenRouter) -- 注意:Claude 4.7 禁止 temperature / top_p / top_k 参数,已在 AgentAnthropicChatModelBuilder 中适配 (1000000270, 'Claude Opus 4.7', 'anthropic', 'claude-opus-4-7', 'Anthropic Claude Opus 4.7(xhigh 自适应思考)', NULL, 4096, NULL, TRUE, TRUE, FALSE, NOW(), NOW(), 0), -(1000000271, 'Claude Sonnet 4.7', 'anthropic', 'claude-sonnet-4-7', 'Anthropic Claude Sonnet 4.7', NULL, 4096, NULL, TRUE, TRUE, FALSE, NOW(), NOW(), 0), +-- Anthropic 仅发布了 Opus 4.7,Sonnet 暂时仍是 4.6 +(1000000271, 'Claude Sonnet 4.6', 'anthropic', 'claude-sonnet-4-6', 'Anthropic 最新 Sonnet (Sonnet 4.7 暂未发布)', NULL, 4096, NULL, TRUE, TRUE, FALSE, NOW(), NOW(), 0), (1000000272, 'Claude Opus 4.7', 'openrouter', 'anthropic/claude-opus-4-7', 'OpenRouter 代理 Claude Opus 4.7', NULL, 4096, NULL, TRUE, TRUE, FALSE, NOW(), NOW(), 0), -(1000000273, 'Claude Sonnet 4.7', 'openrouter', 'anthropic/claude-sonnet-4-7', 'OpenRouter 代理 Claude Sonnet 4.7', NULL, 4096, NULL, TRUE, TRUE, FALSE, NOW(), NOW(), 0), +(1000000273, 'Claude Sonnet 4.6', 'openrouter', 'anthropic/claude-sonnet-4-6', 'OpenRouter 代理 Claude Sonnet 4.6', NULL, 4096, NULL, TRUE, TRUE, FALSE, NOW(), NOW(), 0), -- RFC-062:通过 Claude Code Pro/Max 订阅调用 Claude 4.7 (1000000280, 'Claude Opus 4.7', 'anthropic-claude-code', 'claude-opus-4-7', '通过 Claude Code Pro/Max 订阅调用 Claude Opus 4.7', NULL, 4096, NULL, TRUE, TRUE, FALSE, NOW(), NOW(), 0), -(1000000281, 'Claude Sonnet 4.7', 'anthropic-claude-code', 'claude-sonnet-4-7', '通过 Claude Code Pro/Max 订阅调用 Claude Sonnet 4.7', NULL, 4096, NULL, TRUE, TRUE, FALSE, NOW(), NOW(), 0) +(1000000281, 'Claude Sonnet 4.6', 'anthropic-claude-code', 'claude-sonnet-4-6', '通过 Claude Code Pro/Max 订阅调用 Claude Sonnet 4.6', NULL, 4096, NULL, TRUE, TRUE, FALSE, NOW(), NOW(), 0) ON DUPLICATE KEY UPDATE name=VALUES(name), provider=VALUES(provider), model_name=VALUES(model_name), description=VALUES(description), temperature=VALUES(temperature), max_tokens=VALUES(max_tokens), top_p=VALUES(top_p), builtin=VALUES(builtin), enabled=VALUES(enabled), is_default=VALUES(is_default), update_time=VALUES(update_time), deleted=VALUES(deleted); -- 默认系统设置 diff --git a/mateclaw-server/src/main/resources/db/data-zh.sql b/mateclaw-server/src/main/resources/db/data-zh.sql index 791ece92..3ffda686 100644 --- a/mateclaw-server/src/main/resources/db/data-zh.sql +++ b/mateclaw-server/src/main/resources/db/data-zh.sql @@ -268,12 +268,13 @@ MERGE INTO mate_model_config (id, name, provider, model_name, description, tempe -- Claude 4.7 系列(直连 Anthropic + OpenRouter) -- 注意:Claude 4.7 禁止 temperature / top_p / top_k 参数,已在 AgentAnthropicChatModelBuilder 中适配 (1000000270, 'Claude Opus 4.7', 'anthropic', 'claude-opus-4-7', 'Anthropic Claude Opus 4.7(xhigh 自适应思考)', NULL, 4096, NULL, TRUE, TRUE, FALSE, NOW(), NOW(), 0), -(1000000271, 'Claude Sonnet 4.7', 'anthropic', 'claude-sonnet-4-7', 'Anthropic Claude Sonnet 4.7', NULL, 4096, NULL, TRUE, TRUE, FALSE, NOW(), NOW(), 0), +-- Anthropic 仅发布了 Opus 4.7,Sonnet 暂时仍是 4.6 +(1000000271, 'Claude Sonnet 4.6', 'anthropic', 'claude-sonnet-4-6', 'Anthropic 最新 Sonnet (Sonnet 4.7 暂未发布)', NULL, 4096, NULL, TRUE, TRUE, FALSE, NOW(), NOW(), 0), (1000000272, 'Claude Opus 4.7', 'openrouter', 'anthropic/claude-opus-4-7', 'OpenRouter 代理 Claude Opus 4.7', NULL, 4096, NULL, TRUE, TRUE, FALSE, NOW(), NOW(), 0), -(1000000273, 'Claude Sonnet 4.7', 'openrouter', 'anthropic/claude-sonnet-4-7', 'OpenRouter 代理 Claude Sonnet 4.7', NULL, 4096, NULL, TRUE, TRUE, FALSE, NOW(), NOW(), 0), +(1000000273, 'Claude Sonnet 4.6', 'openrouter', 'anthropic/claude-sonnet-4-6', 'OpenRouter 代理 Claude Sonnet 4.6', NULL, 4096, NULL, TRUE, TRUE, FALSE, NOW(), NOW(), 0), -- RFC-062:通过 Claude Code Pro/Max 订阅调用 Claude 4.7 (1000000280, 'Claude Opus 4.7', 'anthropic-claude-code', 'claude-opus-4-7', '通过 Claude Code Pro/Max 订阅调用 Claude Opus 4.7', NULL, 4096, NULL, TRUE, TRUE, FALSE, NOW(), NOW(), 0), -(1000000281, 'Claude Sonnet 4.7', 'anthropic-claude-code', 'claude-sonnet-4-7', '通过 Claude Code Pro/Max 订阅调用 Claude Sonnet 4.7', NULL, 4096, NULL, TRUE, TRUE, FALSE, NOW(), NOW(), 0); +(1000000281, 'Claude Sonnet 4.6', 'anthropic-claude-code', 'claude-sonnet-4-6', '通过 Claude Code Pro/Max 订阅调用 Claude Sonnet 4.6', NULL, 4096, NULL, TRUE, TRUE, FALSE, NOW(), NOW(), 0); -- 默认系统设置 MERGE INTO mate_system_setting (id, setting_key, setting_value, description, create_time, update_time) diff --git a/mateclaw-server/src/main/resources/db/migration/h2/V44__fix_claude_sonnet_47_does_not_exist.sql b/mateclaw-server/src/main/resources/db/migration/h2/V44__fix_claude_sonnet_47_does_not_exist.sql new file mode 100644 index 00000000..4cf55e42 --- /dev/null +++ b/mateclaw-server/src/main/resources/db/migration/h2/V44__fix_claude_sonnet_47_does_not_exist.sql @@ -0,0 +1,32 @@ +-- Repair migration for V42/V43: Anthropic only released Opus 4.7 — there +-- is no claude-sonnet-4-7 model. Calls return HTTP 404 with body +-- {"type":"not_found_error","message":"model: claude-sonnet-4-7"}. +-- +-- Reference: hermes-agent anthropic_adapter.py _ANTHROPIC_OUTPUT_LIMITS +-- (lines 65-93) lists claude-opus-4-7 but no claude-sonnet-4-7. The latest +-- released Sonnet remains claude-sonnet-4-6 (released alongside Opus 4.6). +-- +-- Strategy: rename in place — preserve ids 1000000271, 1000000273, 1000000281 +-- so user-customised settings (default flag, enabled flag) survive. +-- When/if Anthropic ships Sonnet 4.7, a future migration can switch back. + +UPDATE mate_model_config +SET name = 'Claude Sonnet 4.6', + model_name = 'claude-sonnet-4-6', + description = 'Anthropic Claude Sonnet 4.6 (latest Sonnet — 4.7 not yet released)', + update_time = NOW() +WHERE id = 1000000271 AND model_name = 'claude-sonnet-4-7'; + +UPDATE mate_model_config +SET name = 'Claude Sonnet 4.6', + model_name = 'anthropic/claude-sonnet-4-6', + description = 'Claude Sonnet 4.6 via OpenRouter', + update_time = NOW() +WHERE id = 1000000273 AND model_name = 'anthropic/claude-sonnet-4-7'; + +UPDATE mate_model_config +SET name = 'Claude Sonnet 4.6', + model_name = 'claude-sonnet-4-6', + description = 'Claude Sonnet 4.6 via Claude Code Pro/Max subscription', + update_time = NOW() +WHERE id = 1000000281 AND model_name = 'claude-sonnet-4-7'; diff --git a/mateclaw-server/src/main/resources/db/migration/mysql/V44__fix_claude_sonnet_47_does_not_exist.sql b/mateclaw-server/src/main/resources/db/migration/mysql/V44__fix_claude_sonnet_47_does_not_exist.sql new file mode 100644 index 00000000..27be8b1a --- /dev/null +++ b/mateclaw-server/src/main/resources/db/migration/mysql/V44__fix_claude_sonnet_47_does_not_exist.sql @@ -0,0 +1,31 @@ +-- Repair migration for V42/V43: Anthropic only released Opus 4.7 — there +-- is no claude-sonnet-4-7 model. Calls return HTTP 404 with body +-- {"type":"not_found_error","message":"model: claude-sonnet-4-7"}. +-- +-- Reference: hermes-agent anthropic_adapter.py _ANTHROPIC_OUTPUT_LIMITS +-- (lines 65-93) lists claude-opus-4-7 but no claude-sonnet-4-7. The latest +-- released Sonnet remains claude-sonnet-4-6 (released alongside Opus 4.6). +-- +-- Strategy: rename in place — preserve ids 1000000271, 1000000273, 1000000281 +-- so user-customised settings (default flag, enabled flag) survive. + +UPDATE mate_model_config +SET name = 'Claude Sonnet 4.6', + model_name = 'claude-sonnet-4-6', + description = 'Anthropic Claude Sonnet 4.6 (latest Sonnet — 4.7 not yet released)', + update_time = NOW() +WHERE id = 1000000271 AND model_name = 'claude-sonnet-4-7'; + +UPDATE mate_model_config +SET name = 'Claude Sonnet 4.6', + model_name = 'anthropic/claude-sonnet-4-6', + description = 'Claude Sonnet 4.6 via OpenRouter', + update_time = NOW() +WHERE id = 1000000273 AND model_name = 'anthropic/claude-sonnet-4-7'; + +UPDATE mate_model_config +SET name = 'Claude Sonnet 4.6', + model_name = 'claude-sonnet-4-6', + description = 'Claude Sonnet 4.6 via Claude Code Pro/Max subscription', + update_time = NOW() +WHERE id = 1000000281 AND model_name = 'claude-sonnet-4-7';