diff --git a/mateclaw-server/src/main/java/vip/mate/agent/chatmodel/AgentClaudeCodeChatModelBuilder.java b/mateclaw-server/src/main/java/vip/mate/agent/chatmodel/AgentClaudeCodeChatModelBuilder.java index 0acc89b8..e9e184a9 100644 --- a/mateclaw-server/src/main/java/vip/mate/agent/chatmodel/AgentClaudeCodeChatModelBuilder.java +++ b/mateclaw-server/src/main/java/vip/mate/agent/chatmodel/AgentClaudeCodeChatModelBuilder.java @@ -97,12 +97,18 @@ public class AgentClaudeCodeChatModelBuilder implements ChatModelBuilder { // sampling-params handling, thinking-budget mapping, prompt cache. AnthropicChatOptions options = anthropicBuilder.buildAnthropicOptions(model); - return AnthropicChatModel.builder() + AnthropicChatModel raw = AnthropicChatModel.builder() .anthropicApi(api) .defaultOptions(options) .retryTemplate(retry) .observationRegistry(observationRegistryProvider.getIfAvailable(() -> ObservationRegistry.NOOP)) .build(); + + // 4) Wrap with the OAuth identity decorator. Anthropic's edge rate-limits / + // 5xxs requests that don't claim Claude Code identity in the system + // prompt — symptom: 429 rate_limit_error with body "Error" on quiet + // accounts. See ClaudeCodeIdentityChatModelDecorator javadoc. + return new ClaudeCodeIdentityChatModelDecorator(raw); } /** 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 new file mode 100644 index 00000000..5c90f095 --- /dev/null +++ b/mateclaw-server/src/main/java/vip/mate/agent/chatmodel/ClaudeCodeIdentityChatModelDecorator.java @@ -0,0 +1,134 @@ +package vip.mate.agent.chatmodel; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.ai.chat.messages.Message; +import org.springframework.ai.chat.messages.SystemMessage; +import org.springframework.ai.chat.model.ChatModel; +import org.springframework.ai.chat.model.ChatResponse; +import org.springframework.ai.chat.prompt.ChatOptions; +import org.springframework.ai.chat.prompt.Prompt; +import reactor.core.publisher.Flux; + +import java.util.ArrayList; +import java.util.List; + +/** + * 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: + * + *
Reference: hermes-agent {@code anthropic_adapter._build_anthropic_messages_request} + * lines 1571-1607 — applies the same transforms unconditionally on + * {@code is_oauth=True} requests. + * + *
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 {
+
+ /** Magic identity prefix Anthropic's OAuth edge requires in the system prompt. */
+ static final String CLAUDE_CODE_SYSTEM_PREFIX =
+ "You are Claude Code, Anthropic's official CLI for Claude.";
+
+ private final ChatModel delegate;
+
+ public ClaudeCodeIdentityChatModelDecorator(ChatModel delegate) {
+ this.delegate = delegate;
+ }
+
+ @Override
+ public ChatResponse call(Prompt prompt) {
+ return delegate.call(transform(prompt));
+ }
+
+ @Override
+ public Flux