mirror of
https://gitee.com/mateos/mateclaw.git
synced 2026-09-15 03:55:09 +08:00
fix(anthropic): bidirectional mcp_ tool-name prefix on OAuth requests
This commit is contained in:
parent
44548e3010
commit
ae6467a5dc
@ -1,50 +1,67 @@
|
|||||||
package vip.mate.agent.chatmodel;
|
package vip.mate.agent.chatmodel;
|
||||||
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
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.Message;
|
||||||
import org.springframework.ai.chat.messages.SystemMessage;
|
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.ChatModel;
|
||||||
import org.springframework.ai.chat.model.ChatResponse;
|
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.ChatOptions;
|
||||||
import org.springframework.ai.chat.prompt.Prompt;
|
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 reactor.core.publisher.Flux;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.LinkedHashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* RFC-062: Claude Code OAuth identity transform applied to every Anthropic
|
* RFC-062: Claude Code OAuth identity transform applied to every Anthropic
|
||||||
* request when the underlying auth is a Claude Code OAuth token.
|
* request when the underlying auth is a Claude Code OAuth token.
|
||||||
*
|
*
|
||||||
* <p>Anthropic's OAuth edge enforces an anti-abuse path that rate-limits
|
* <p>Anthropic's OAuth edge enforces an anti-abuse path that rate-limits
|
||||||
* (and intermittently 5xxs) requests whose system prompt does NOT claim
|
* (and intermittently 5xxs) requests claiming Claude Code identity but
|
||||||
* Claude Code identity. Symptoms include:
|
* shaped differently from real Claude Code traffic. Symptoms:
|
||||||
*
|
*
|
||||||
* <ul>
|
* <ul>
|
||||||
* <li>HTTP 429 with {@code rate_limit_error} on quiet accounts that haven't
|
* <li>HTTP 429 with {@code rate_limit_error} on quiet accounts that haven't
|
||||||
* come close to their token budget — the give-away is a body of just
|
* come close to their token budget — give-away is a body of just
|
||||||
* {@code {"type":"error","error":{"type":"rate_limit_error","message":"Error"}}}
|
* {@code {"type":"error","error":{"type":"rate_limit_error","message":"Error"}}}
|
||||||
* (genuine quota exhaustion includes a descriptive message).</li>
|
* (genuine quota exhaustion carries a descriptive message).</li>
|
||||||
* <li>Sporadic 500s on the first call after a long idle period.</li>
|
* <li>Sporadic 500s on the first call after a long idle period.</li>
|
||||||
* </ul>
|
* </ul>
|
||||||
*
|
*
|
||||||
* <p>Reference: hermes-agent {@code anthropic_adapter._build_anthropic_messages_request}
|
* <p>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.
|
* {@code is_oauth=True} requests.
|
||||||
*
|
*
|
||||||
* <h2>Transforms applied per call</h2>
|
* <h2>Transforms applied per call</h2>
|
||||||
* <ol>
|
* <ol>
|
||||||
* <li>Prepend {@code "You are Claude Code, Anthropic's official CLI for Claude."}
|
* <li><b>System prompt prefix</b>: prepend
|
||||||
* to the system prompt. If no system message is present we insert one.</li>
|
* {@code "You are Claude Code, Anthropic's official CLI for Claude."}.
|
||||||
* <li>Scrub MateClaw branding from system text — replace
|
* Insert a new SystemMessage if none exists.</li>
|
||||||
* {@code "MateClaw"} → {@code "Claude Code"} and a few common variants —
|
* <li><b>Brand scrub</b>: replace {@code "MateClaw"}/{@code "mateclaw"}
|
||||||
* so server-side content filters don't fire on the spoofed identity.</li>
|
* in system text with their Claude Code equivalents — Anthropic's
|
||||||
|
* content filter flags identity contradictions.</li>
|
||||||
|
* <li><b>Tool {@code mcp_} prefix (outgoing)</b>: every tool definition
|
||||||
|
* sent to Anthropic is renamed {@code mcp_<orig>} — Claude Code
|
||||||
|
* runs all tools through MCP servers, so real Claude Code traffic
|
||||||
|
* always has the prefix. Mismatch trips anti-abuse.</li>
|
||||||
|
* <li><b>History tool_use prefix</b>: previously-issued tool calls in
|
||||||
|
* AssistantMessage history get the prefix re-applied (we strip on
|
||||||
|
* response, so they're stored unprefixed).</li>
|
||||||
|
* <li><b>Tool {@code mcp_} prefix (incoming)</b>: ChatResponse tool_use
|
||||||
|
* names are stripped of the {@code mcp_} prefix so MateClaw's tool
|
||||||
|
* registry can resolve them.</li>
|
||||||
* </ol>
|
* </ol>
|
||||||
*
|
|
||||||
* <p><b>Tool-name {@code mcp_} prefix</b> (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
|
@Slf4j
|
||||||
public class ClaudeCodeIdentityChatModelDecorator implements ChatModel {
|
public class ClaudeCodeIdentityChatModelDecorator implements ChatModel {
|
||||||
@ -53,6 +70,9 @@ public class ClaudeCodeIdentityChatModelDecorator implements ChatModel {
|
|||||||
static final String CLAUDE_CODE_SYSTEM_PREFIX =
|
static final String CLAUDE_CODE_SYSTEM_PREFIX =
|
||||||
"You are Claude Code, Anthropic's official CLI for Claude.";
|
"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;
|
private final ChatModel delegate;
|
||||||
|
|
||||||
public ClaudeCodeIdentityChatModelDecorator(ChatModel delegate) {
|
public ClaudeCodeIdentityChatModelDecorator(ChatModel delegate) {
|
||||||
@ -61,12 +81,12 @@ public class ClaudeCodeIdentityChatModelDecorator implements ChatModel {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ChatResponse call(Prompt prompt) {
|
public ChatResponse call(Prompt prompt) {
|
||||||
return delegate.call(transform(prompt));
|
return stripToolPrefixes(delegate.call(transform(prompt)));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Flux<ChatResponse> stream(Prompt prompt) {
|
public Flux<ChatResponse> stream(Prompt prompt) {
|
||||||
return delegate.stream(transform(prompt));
|
return delegate.stream(transform(prompt)).map(this::stripToolPrefixes);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -74,6 +94,10 @@ public class ClaudeCodeIdentityChatModelDecorator implements ChatModel {
|
|||||||
return delegate.getDefaultOptions();
|
return delegate.getDefaultOptions();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ====================================================================== */
|
||||||
|
/* Outbound transform: Prompt → Prompt with identity + tool prefix */
|
||||||
|
/* ====================================================================== */
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Build a new {@link Prompt} with the OAuth identity transforms applied.
|
* Build a new {@link Prompt} with the OAuth identity transforms applied.
|
||||||
* Package-private for unit tests.
|
* Package-private for unit tests.
|
||||||
@ -88,40 +112,145 @@ public class ClaudeCodeIdentityChatModelDecorator implements ChatModel {
|
|||||||
boolean systemSeen = false;
|
boolean systemSeen = false;
|
||||||
for (Message msg : source) {
|
for (Message msg : source) {
|
||||||
if (msg instanceof SystemMessage sm && !systemSeen) {
|
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()))));
|
rewritten.add(new SystemMessage(prependIdentity(sanitizeBranding(sm.getText()))));
|
||||||
systemSeen = true;
|
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 {
|
} else {
|
||||||
rewritten.add(msg);
|
rewritten.add(msg);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!systemSeen) {
|
if (!systemSeen) {
|
||||||
// No system message at all → insert one with just the identity prefix.
|
|
||||||
rewritten.add(0, new SystemMessage(CLAUDE_CODE_SYSTEM_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_<orig>}. 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<ToolCallback> originalCallbacks = anthropicOpts.getToolCallbacks();
|
||||||
|
Set<String> 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<ToolCallback> 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<String> 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<Generation> origGens = response.getResults();
|
||||||
|
if (origGens == null || origGens.isEmpty()) {
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
List<Generation> 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<AssistantMessage.ToolCall> 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) {
|
private static String prependIdentity(String existingSystem) {
|
||||||
if (existingSystem == null || existingSystem.isBlank()) {
|
if (existingSystem == null || existingSystem.isBlank()) {
|
||||||
return CLAUDE_CODE_SYSTEM_PREFIX;
|
return CLAUDE_CODE_SYSTEM_PREFIX;
|
||||||
}
|
}
|
||||||
if (existingSystem.startsWith(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 existingSystem;
|
||||||
}
|
}
|
||||||
return CLAUDE_CODE_SYSTEM_PREFIX + "\n\n" + 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) {
|
static String sanitizeBranding(String text) {
|
||||||
if (text == null || text.isEmpty()) {
|
if (text == null || text.isEmpty()) {
|
||||||
return text;
|
return text;
|
||||||
@ -131,4 +260,57 @@ public class ClaudeCodeIdentityChatModelDecorator implements ChatModel {
|
|||||||
.replace("mateclaw", "claude-code")
|
.replace("mateclaw", "claude-code")
|
||||||
.replace("Mate Claw", "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_<orig>}, 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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.
|
-- Claude 4.7 series (direct Anthropic + OpenRouter). Sonnet/Opus.
|
||||||
-- Note: Claude 4.7 forbids temperature/top_p/top_k — handled in AgentAnthropicChatModelBuilder.
|
-- 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),
|
(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),
|
(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).
|
-- 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),
|
(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
|
-- Default system settings
|
||||||
MERGE INTO mate_system_setting (id, setting_key, setting_value, description, create_time, update_time)
|
MERGE INTO mate_system_setting (id, setting_key, setting_value, description, create_time, update_time)
|
||||||
|
|||||||
@ -302,12 +302,13 @@ VALUES
|
|||||||
-- Claude 4.7 series (direct Anthropic + OpenRouter).
|
-- Claude 4.7 series (direct Anthropic + OpenRouter).
|
||||||
-- Note: Claude 4.7 forbids temperature/top_p/top_k — handled in AgentAnthropicChatModelBuilder.
|
-- 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),
|
(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),
|
(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).
|
-- 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),
|
(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);
|
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
|
-- Default system settings
|
||||||
|
|||||||
@ -302,12 +302,13 @@ VALUES
|
|||||||
-- Claude 4.7 系列(直连 Anthropic + OpenRouter)
|
-- Claude 4.7 系列(直连 Anthropic + OpenRouter)
|
||||||
-- 注意:Claude 4.7 禁止 temperature / top_p / top_k 参数,已在 AgentAnthropicChatModelBuilder 中适配
|
-- 注意: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),
|
(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),
|
(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
|
-- 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),
|
(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);
|
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);
|
||||||
|
|
||||||
-- 默认系统设置
|
-- 默认系统设置
|
||||||
|
|||||||
@ -268,12 +268,13 @@ MERGE INTO mate_model_config (id, name, provider, model_name, description, tempe
|
|||||||
-- Claude 4.7 系列(直连 Anthropic + OpenRouter)
|
-- Claude 4.7 系列(直连 Anthropic + OpenRouter)
|
||||||
-- 注意:Claude 4.7 禁止 temperature / top_p / top_k 参数,已在 AgentAnthropicChatModelBuilder 中适配
|
-- 注意: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),
|
(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),
|
(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
|
-- 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),
|
(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)
|
MERGE INTO mate_system_setting (id, setting_key, setting_value, description, create_time, update_time)
|
||||||
|
|||||||
@ -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';
|
||||||
@ -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';
|
||||||
Loading…
Reference in New Issue
Block a user