How DS2API Converts Claude Messages Format to DeepSeek Web Context
DS2API converts Claude messages to DeepSeek web context by resolving model aliases, reshaping message arrays to inject system prompts, and forwarding compatible parameters like temperature and stop sequences.
The open-source project CJackHwang/ds2api acts as a compatibility shim that translates Anthropic Claude-style API requests into DeepSeek-compatible web payloads. This conversion process bridges the structural differences between the two AI providers, allowing clients to send standard Claude requests while the proxy handles the protocol translation. Understanding this transformation is essential for developers integrating Claude-based tools with DeepSeek's backend infrastructure.
The Three-Stage Conversion Pipeline
The conversion logic in internal/claudeconv/convert.go executes through three tightly coupled stages that map the incoming Claude payload to DeepSeek's expected schema.
Model Resolution and Alias Mapping
First, DS2API resolves the Claude model name to its DeepSeek equivalent using the global alias configuration. In internal/claudeconv/convert.go (lines 16-19), the ConvertClaudeToDeepSeek function extracts the model field and attempts resolution via config.ResolveModel. If the alias lookup fails or returns an empty string, the system falls back to deepseek-v4-flash as a hard-coded default.
// internal/claudeconv/convert.go
dsModel, ok := config.ResolveModel(aliasProvider, model)
if !ok || strings.TrimSpace(dsModel) == "" {
dsModel = "deepseek-v4-flash" // hard-coded fallback
}
Message Reshaping and System Injection
The second stage handles the structural differences in message formatting. Claude permits a top-level "system" string separate from the messages array, while DeepSeek expects system instructions as a standard message object with role: "system". The converter checks for this field at lines 21-26 and prepends a system message to the array before appending the original Claude messages unchanged.
// internal/claudeconv/convert.go
convertedMessages := make([]any, 0, len(messages)+1)
if system, ok := claudeReq["system"].(string); ok && system != "" {
convertedMessages = append(
convertedMessages,
map[string]any{"role": "system", "content": system},
)
}
convertedMessages = append(convertedMessages, messages...)
Parameter Forwarding and Field Translation
Finally, the converter maps supported top-level parameters from the Claude request to DeepSeek-compatible fields. According to lines 27-35 in convert.go, DS2API forwards temperature, top_p, and stream verbatim. It specifically translates Claude's stop_sequences parameter to DeepSeek's stop field to satisfy the downstream API schema.
// internal/claudeconv/convert.go
for _, k := range []string{"temperature", "top_p", "stream"} {
if v, ok := claudeReq[k]; ok {
out[k] = v
}
}
if stopSeq, ok := claudeReq["stop_sequences"]; ok {
out["stop"] = stopSeq
}
Entry Points and API Integration
The HTTP API layer initiates conversion through normalizeClaudeRequest in internal/httpapi/claude/standard_request.go (line 33). This handler receives the incoming Claude payload and invokes the conversion chain.
// internal/httpapi/claude/standard_request.go
dsPayload := convertClaudeToDeepSeek(payload, store) // line 33
For broader codebase accessibility, a thin wrapper in internal/util/messages.go exposes the conversion with a built-in default model constant. The ConvertClaudeToDeepSeek wrapper injects ClaudeDefaultModel (defined as "claude-sonnet-4-6") as the fallback when the incoming request lacks a model specification.
// internal/util/messages.go
const ClaudeDefaultModel = "claude-sonnet-4-6"
func ConvertClaudeToDeepSeek(claudeReq map[string]any, store *config.Store) map[string]any {
return claudeconv.ConvertClaudeToDeepSeek(claudeReq, store, ClaudeDefaultModel)
}
Practical Conversion Example
When processing a typical Claude request containing a system prompt and user message, DS2API transforms the payload as follows:
// Simulated Claude request (as received by DS2API)
claudeReq := map[string]any{
"model": "claude-sonnet-4-6",
"system": "You are a helpful assistant.",
"messages": []any{
map[string]any{"role": "user", "content": "Tell me a joke."},
},
"temperature": 0.7,
"stream": true,
}
// Conversion (the same call chain used inside the server)
deepseekPayload := ConvertClaudeToDeepSeek(claudeReq, store)
// Resulting DeepSeek-compatible payload
/*
{
"model": "deepseek-v4-flash", // resolved alias or fallback
"messages": [
{"role":"system","content":"You are a helpful assistant."},
{"role":"user","content":"Tell me a joke."}
],
"temperature": 0.7,
"stream": true
}
*/
Summary
- Model Resolution: DS2API maps Claude model names to DeepSeek equivalents via
config.ResolveModel, defaulting todeepseek-v4-flashwhen aliases are unavailable. - System Message Injection: The converter transforms Claude's top-level
systemfield into a DeepSeek message object withrole: "system", prepending it to the messages array. - Parameter Translation: Supported fields like
temperatureandstreampass through unchanged, whilestop_sequencesbecomesstopto match DeepSeek's schema. - File Locations: Core logic resides in
internal/claudeconv/convert.go, HTTP handling ininternal/httpapi/claude/standard_request.go, and the public wrapper ininternal/util/messages.go.
Frequently Asked Questions
What happens if the Claude request doesn't specify a model?
DS2API applies the built-in default "claude-sonnet-4-6" defined in internal/util/messages.go, then attempts to resolve this to a DeepSeek model alias. If resolution fails, it falls back to deepseek-v4-flash according to the logic in internal/claudeconv/convert.go.
Does DS2API modify the original Claude messages array?
No, the converter preserves the original message objects unchanged. It only prepends a system message when the Claude request contains a top-level system string, creating a new array that includes both the injected system message and the original messages.
Which parameters does DS2API forward to DeepSeek?
The converter explicitly forwards temperature, top_p, stream, and stop_sequences (renamed to stop). Other Claude-specific parameters are not included in the DeepSeek payload, as implemented in the parameter forwarding loop at lines 27-35 of internal/claudeconv/convert.go.
Where does the actual conversion get triggered in the HTTP API?
The entry point is normalizeClaudeRequest in internal/httpapi/claude/standard_request.go at line 33, which calls convertClaudeToDeepSeek to transform the incoming request before forwarding it to DeepSeek's web endpoint.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →