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 to deepseek-v4-flash when aliases are unavailable.
  • System Message Injection: The converter transforms Claude's top-level system field into a DeepSeek message object with role: "system", prepending it to the messages array.
  • Parameter Translation: Supported fields like temperature and stream pass through unchanged, while stop_sequences becomes stop to match DeepSeek's schema.
  • File Locations: Core logic resides in internal/claudeconv/convert.go, HTTP handling in internal/httpapi/claude/standard_request.go, and the public wrapper in internal/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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →