Skip to content

Feature/support deepseek#3519

Open
berthojoris wants to merge 3 commits into
router-for-me:devfrom
berthojoris:feature/support-deepseek
Open

Feature/support deepseek#3519
berthojoris wants to merge 3 commits into
router-for-me:devfrom
berthojoris:feature/support-deepseek

Conversation

@berthojoris
Copy link
Copy Markdown

Summary

 Adds DeepSeek Web support to CLIProxyAPI, including DeepSeek token-based authentication, model definitions, request execution, streaming       
 response handling, proof-of-work helpers, and SDK/config integration.

 ## Changes

 - Added DeepSeek Web executor support
 - Added DeepSeek-specific helpers for prompt formatting, SSE parsing, transport, and proof-of-work
 - Added DeepSeek model definitions and registry integration
 - Added config parsing/synthesis support for DeepSeek credentials
 - Added SDK auth/service support for DeepSeek
 - Updated README with DeepSeek Web usage and model configuration examples
 - Added unit tests for config, model definitions, executor behavior, and proof-of-work

 ## Testing

 - Added/updated tests covering DeepSeek config handling, model definitions, executor behavior, and helper logic

…tions

- Added DeepSeekKey configuration structure for managing DeepSeek Web tokens with optional routing overrides.
- Implemented sanitization for DeepSeek keys in configuration loading.
- Registered DeepSeek models in the model registry and updated model handling logic.
- Enhanced synthesizer to create Auth entries for DeepSeek tokens.
- Updated relevant areas to support DeepSeek in OAuth model aliasing and API key resolution.

Closes: router-for-me#3486
…urations

- Updated README to reflect the addition of DeepSeek Web as a compatible provider.
- Added configuration examples for `deepseek-api-key` and listed built-in model IDs.
- Clarified existing support for OpenAI, Gemini, Claude, Codex, and Grok APIs.
@github-actions github-actions Bot changed the base branch from main to dev May 22, 2026 17:55
@github-actions
Copy link
Copy Markdown

This pull request targeted main.

The base branch has been automatically changed to dev.

Copy link
Copy Markdown
Contributor

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces support for the DeepSeek Web Provider, implementing a new executor, configuration handling, and model definitions. Key features include a custom Proof-of-Work (PoW) solver, SSE parsing for streaming responses, and specialized HTTP transport. The reviewer identified a critical bug in the Keccak round loop that would cause PoW verification to fail. Other feedback suggests performance improvements through HTTP client reuse and pre-compiled regular expressions, as well as better error handling for response body decompression.

a15, a16, a17, a18, a19 := s[15], s[16], s[17], s[18], s[19]
a20, a21, a22, a23, a24 := s[20], s[21], s[22], s[23], s[24]

for r := 1; r < 24; r++ {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

critical

The Keccak round loop starts at index 1 and performs only 23 rounds. Standard Keccak-f[1600] (which the keccakRoundConstants array and the permutation logic are based on) requires 24 rounds, typically indexed 0 to 23. Skipping the first round constant (0x0000000000000001) will result in incorrect hash values, which will cause Proof-of-Work verification to fail against the real DeepSeek API.

Suggested change
for r := 1; r < 24; r++ {
for r := 0; r < 24; r++ {

Comment on lines +32 to +46
func NewClients(cfg *config.Config, auth *cliproxyauth.Auth) Clients {
proxyURL := ""
if auth != nil {
proxyURL = strings.TrimSpace(auth.ProxyURL)
}
if proxyURL == "" && cfg != nil {
proxyURL = strings.TrimSpace(cfg.ProxyURL)
}
return Clients{
Regular: newFingerprintClient(60*time.Second, proxyURL),
Stream: newFingerprintClient(0, proxyURL),
Fallback: newFallbackClient(60*time.Second, proxyURL),
FallbackStream: newFallbackClient(0, proxyURL),
}
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

Creating new HTTP clients and transports for every request is inefficient as it prevents connection reuse and increases overhead due to repeated TLS handshakes. http.Client and http.Transport should be long-lived and reused across requests. Consider caching these clients within the DeepSeekExecutor, potentially keyed by the proxyURL if it varies per credential.

}()
helps.RecordAPIResponseMetadata(ctx, e.cfg, flow.response.StatusCode, flow.response.Header.Clone())
if flow.response.StatusCode < 200 || flow.response.StatusCode >= 300 {
body, _ := dshelp.ReadResponseBody(flow.response)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The error from dshelp.ReadResponseBody is ignored here and in the streaming execution path. If reading or decompressing the response body fails, the resulting error message or metadata recorded will be empty or incomplete, which can make troubleshooting upstream failures difficult.

t3 := binary.LittleEndian.Uint64(ta[24:])

prefix := []byte(salt + "_" + strconv.FormatInt(expireAt, 10) + "_")
const rate = 136
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The rate constant is defined twice in this file (line 141 and line 186). Consider using a single package-level constant to improve maintainability.

}

func findToolCallBlock(text string) (string, int, int) {
re := regexp.MustCompile(`(?is)<(?:\|DSML\|)?tool_calls\b[^>]*>.*?</(?:\|DSML\|)?tool_calls>`)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

regexp.MustCompile is called inside findToolCallBlock, which is executed during request processing. It is more efficient to compile the regular expression once and store it in a package-level variable to avoid the overhead of repeated compilation.

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 5dcdbc9515

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +79 to +82
token, _, _ := e.resolveCredentials(auth)
if token != "" {
return false
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Re-authenticate when a stored DeepSeek token expires

This guard skips PrepareRequestAuth whenever any token is present, so login-based DeepSeek auth (email/mobile + password) will keep using an expired bearer token and never refresh it during normal request preparation. In the common case where a token is cached in metadata and later expires, requests will repeatedly fail with 401 until the token is manually removed or external refresh metadata is configured.

Useful? React with 👍 / 👎.

Comment on lines +71 to +72
clients := dshelp.NewClients(e.cfg, auth)
return clients.Regular.Do(httpReq)
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Use a no-timeout client for DeepSeek HttpRequest

HttpRequest always dispatches through clients.Regular, which is constructed with a 60-second http.Client.Timeout. For any long-lived SSE/raw forwarding path that uses HttpRequest, the client will abort at ~60s with a context deadline error even though the stream is healthy, which can truncate DeepSeek responses in production.

Useful? React with 👍 / 👎.

…r handling

- Introduced a mutex and a cache for clients in DeepSeekExecutor to optimize client management.
- Refactored client creation logic to utilize cached clients based on proxy URL.
- Improved error handling for response body reading in Execute and ExecuteStream methods.
- Updated DeepSeek hashing functions to use a constant for the rate, enhancing code clarity and maintainability.
- Replaced regex instantiation with precompiled regex variables for performance optimization in prompt handling.
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: c9c418f6c5

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

body["usage"] = usage
}
raw, _ := json.Marshal(body)
return append([]byte("data: "), raw...)
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Emit plain chunk JSON from DeepSeek stream adapter

openAIStreamChunk currently prefixes each payload with data: , and forwardDeepSeekStream also emits data: [DONE]; this breaks OpenAI chat streaming because the OpenAI handlers already add SSE framing (data: ...\n\n) around every chunk. On /v1/chat/completions streams with the DeepSeek executor, clients receive malformed lines like data: data: {...} (and duplicated/invalid done markers), which causes standard SSE/OpenAI parsers to fail.

Useful? React with 👍 / 👎.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant