mirror of
https://github.com/CJackHwang/ds2api.git
synced 2026-05-05 00:45:29 +08:00
123 lines
3.5 KiB
Go
123 lines
3.5 KiB
Go
package claude
|
|
|
|
import (
|
|
"fmt"
|
|
"strings"
|
|
|
|
"ds2api/internal/config"
|
|
"ds2api/internal/prompt"
|
|
"ds2api/internal/promptcompat"
|
|
"ds2api/internal/util"
|
|
)
|
|
|
|
type claudeNormalizedRequest struct {
|
|
Standard promptcompat.StandardRequest
|
|
NormalizedMessages []any
|
|
}
|
|
|
|
func normalizeClaudeRequest(store ConfigReader, req map[string]any) (claudeNormalizedRequest, error) {
|
|
model, _ := req["model"].(string)
|
|
messagesRaw, _ := req["messages"].([]any)
|
|
if strings.TrimSpace(model) == "" || len(messagesRaw) == 0 {
|
|
return claudeNormalizedRequest{}, fmt.Errorf("request must include 'model' and 'messages'")
|
|
}
|
|
if _, ok := req["max_tokens"]; !ok {
|
|
req["max_tokens"] = 8192
|
|
}
|
|
normalizedMessages := normalizeClaudeMessages(messagesRaw)
|
|
payload := cloneMap(req)
|
|
payload["messages"] = normalizedMessages
|
|
toolsRequested, _ := req["tools"].([]any)
|
|
payload["messages"] = injectClaudeToolPrompt(payload, normalizedMessages, toolsRequested)
|
|
|
|
dsPayload := convertClaudeToDeepSeek(payload, store)
|
|
dsModel, _ := dsPayload["model"].(string)
|
|
defaultThinkingEnabled, searchEnabled, ok := config.GetModelConfig(dsModel)
|
|
if !ok {
|
|
searchEnabled = false
|
|
}
|
|
thinkingEnabled := util.ResolveThinkingEnabled(req, defaultThinkingEnabled)
|
|
if config.IsNoThinkingModel(dsModel) {
|
|
thinkingEnabled = false
|
|
}
|
|
finalPrompt := prompt.MessagesPrepareWithThinking(toMessageMaps(dsPayload["messages"]), thinkingEnabled)
|
|
toolNames := extractClaudeToolNames(toolsRequested)
|
|
if len(toolNames) == 0 && len(toolsRequested) > 0 {
|
|
toolNames = []string{"__any_tool__"}
|
|
}
|
|
|
|
return claudeNormalizedRequest{
|
|
Standard: promptcompat.StandardRequest{
|
|
Surface: "anthropic_messages",
|
|
RequestedModel: strings.TrimSpace(model),
|
|
ResolvedModel: dsModel,
|
|
ResponseModel: strings.TrimSpace(model),
|
|
Messages: payload["messages"].([]any),
|
|
PromptTokenText: finalPrompt,
|
|
ToolsRaw: toolsRequested,
|
|
FinalPrompt: finalPrompt,
|
|
ToolNames: toolNames,
|
|
Stream: util.ToBool(req["stream"]),
|
|
Thinking: thinkingEnabled,
|
|
Search: searchEnabled,
|
|
},
|
|
NormalizedMessages: normalizedMessages,
|
|
}, nil
|
|
}
|
|
|
|
func injectClaudeToolPrompt(payload map[string]any, normalizedMessages []any, tools []any) []any {
|
|
if len(tools) == 0 {
|
|
return normalizedMessages
|
|
}
|
|
toolPrompt := strings.TrimSpace(buildClaudeToolPrompt(tools))
|
|
if toolPrompt == "" {
|
|
return normalizedMessages
|
|
}
|
|
|
|
// Prefer top-level Anthropic-style system prompt when available.
|
|
if systemText, ok := payload["system"].(string); ok && strings.TrimSpace(systemText) != "" {
|
|
payload["system"] = mergeSystemPrompt(systemText, toolPrompt)
|
|
return normalizedMessages
|
|
}
|
|
|
|
messages := cloneAnySlice(normalizedMessages)
|
|
for i := range messages {
|
|
msg, ok := messages[i].(map[string]any)
|
|
if !ok {
|
|
continue
|
|
}
|
|
role, _ := msg["role"].(string)
|
|
if !strings.EqualFold(strings.TrimSpace(role), "system") {
|
|
continue
|
|
}
|
|
copied := cloneMap(msg)
|
|
copied["content"] = mergeSystemPrompt(strings.TrimSpace(fmt.Sprintf("%v", copied["content"])), toolPrompt)
|
|
messages[i] = copied
|
|
return messages
|
|
}
|
|
|
|
return append([]any{map[string]any{"role": "system", "content": toolPrompt}}, messages...)
|
|
}
|
|
|
|
func mergeSystemPrompt(base, extra string) string {
|
|
base = strings.TrimSpace(base)
|
|
extra = strings.TrimSpace(extra)
|
|
switch {
|
|
case base == "":
|
|
return extra
|
|
case extra == "":
|
|
return base
|
|
default:
|
|
return base + "\n\n" + extra
|
|
}
|
|
}
|
|
|
|
func cloneAnySlice(in []any) []any {
|
|
if len(in) == 0 {
|
|
return nil
|
|
}
|
|
out := make([]any, len(in))
|
|
copy(out, in)
|
|
return out
|
|
}
|