package protocol import ( _ "embed" "encoding/json" "fmt" "strings" ) const ( DeepSeekHost = "chat.deepseek.com" DeepSeekLoginURL = "https://chat.deepseek.com/api/v0/users/login" DeepSeekCreateSessionURL = "https://chat.deepseek.com/api/v0/chat_session/create" DeepSeekCreatePowURL = "https://chat.deepseek.com/api/v0/chat/create_pow_challenge" DeepSeekCompletionURL = "https://chat.deepseek.com/api/v0/chat/completion" DeepSeekContinueURL = "https://chat.deepseek.com/api/v0/chat/continue" DeepSeekUploadFileURL = "https://chat.deepseek.com/api/v0/file/upload_file" DeepSeekFetchFilesURL = "https://chat.deepseek.com/api/v0/file/fetch_files" DeepSeekFetchSessionURL = "https://chat.deepseek.com/api/v0/chat_session/fetch_page" DeepSeekDeleteSessionURL = "https://chat.deepseek.com/api/v0/chat_session/delete" DeepSeekDeleteAllSessionsURL = "https://chat.deepseek.com/api/v0/chat_session/delete_all" DeepSeekCompletionTargetPath = "/api/v0/chat/completion" DeepSeekUploadTargetPath = "/api/v0/file/upload_file" ) var defaultStaticBaseHeaders = map[string]string{ "Host": "chat.deepseek.com", "Accept": "application/json", "Content-Type": "application/json", "accept-charset": "UTF-8", } var defaultSkipContainsPatterns = []string{ "quasi_status", "elapsed_secs", "token_usage", "pending_fragment", "conversation_mode", "fragments/-1/status", "fragments/-2/status", "fragments/-3/status", } var defaultSkipExactPaths = []string{ "response/search_status", } var ClientVersion string var currentPlatform = "android" var currentAndroidAPILevel = "35" // default from normalizeClientConstants var BaseHeaders = map[string]string{} var SkipContainsPatterns = cloneStringSlice(defaultSkipContainsPatterns) var SkipExactPathSet = toStringSet(defaultSkipExactPaths) type clientConstants struct { Name string `json:"name"` Platform string `json:"platform"` Version string `json:"version"` AndroidAPILevel string `json:"android_api_level"` Locale string `json:"locale"` } type sharedConstants struct { Client clientConstants `json:"client"` BaseHeaders map[string]string `json:"base_headers"` SkipContainsPattern []string `json:"skip_contains_patterns"` SkipExactPaths []string `json:"skip_exact_paths"` } //go:embed constants_shared.json var sharedConstantsJSON []byte //go:embed constants_web.json var webConstantsJSON []byte func init() { cfg := sharedConstants{} if err := json.Unmarshal(sharedConstantsJSON, &cfg); err != nil { panic(fmt.Errorf("load DeepSeek shared constants: %w", err)) } applySharedConstants(cfg) } func applySharedConstants(cfg sharedConstants) { client := normalizeClientConstants(cfg.Client) ClientVersion = client.Version if client.Platform == "android" { currentAndroidAPILevel = client.AndroidAPILevel } BaseHeaders = buildBaseHeaders(client, cfg.BaseHeaders) SkipContainsPatterns = cloneStringSlice(defaultSkipContainsPatterns) if len(cfg.SkipContainsPattern) > 0 { SkipContainsPatterns = cloneStringSlice(cfg.SkipContainsPattern) } SkipExactPathSet = toStringSet(defaultSkipExactPaths) if len(cfg.SkipExactPaths) > 0 { SkipExactPathSet = toStringSet(cfg.SkipExactPaths) } } func normalizeClientConstants(in clientConstants) clientConstants { if in.Name == "" { in.Name = "DeepSeek" } if in.Platform == "" { in.Platform = "android" } if in.AndroidAPILevel == "" { in.AndroidAPILevel = "35" } if in.Locale == "" { in.Locale = "zh_CN" } return in } func buildBaseHeaders(client clientConstants, overrides map[string]string) map[string]string { out := cloneStringMap(defaultStaticBaseHeaders) for k, v := range overrides { if k == "" || v == "" { continue } out[k] = v } out["User-Agent"] = userAgentForPlatform(client) if client.Platform != "" { out["x-client-platform"] = client.Platform } if client.Version != "" { out["x-client-version"] = client.Version } if client.Locale != "" { out["x-client-locale"] = client.Locale } return out } const webUserAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/137.0.0.0 Safari/537.36" func userAgentForPlatform(client clientConstants) string { if client.Platform == "web" { return webUserAgent } if client.Name != "" && client.Version != "" { ua := client.Name + "/" + client.Version if client.Platform == "android" && client.AndroidAPILevel != "" { ua += " Android/" + client.AndroidAPILevel } return ua } return "" } // ApplyPlatform reloads constants for the given platform ("android" or "web"). func ApplyPlatform(platform string) { p := strings.ToLower(platform) var data []byte switch p { case "web": data = webConstantsJSON default: data = sharedConstantsJSON p = "android" } cfg := sharedConstants{} if err := json.Unmarshal(data, &cfg); err != nil { panic(fmt.Errorf("load DeepSeek %s constants: %w", p, err)) } currentPlatform = p applySharedConstants(cfg) } // ApplyVersionOverrides patches ClientVersion and BaseHeaders with // user-specified version overrides from config. Empty strings mean // "keep the JSON default". func ApplyVersionOverrides(androidVersion, webVersion, androidAPILevel string) { if currentPlatform == "android" { ver := androidVersion if ver == "" { ver = ClientVersion } apiLevel := androidAPILevel if apiLevel == "" { apiLevel = currentAndroidAPILevel } ClientVersion = ver BaseHeaders["x-client-version"] = ver BaseHeaders["x-client-platform"] = "android" BaseHeaders["User-Agent"] = "DeepSeek/" + ver + " Android/" + apiLevel } else if currentPlatform == "web" { ver := webVersion if ver == "" { ver = ClientVersion } ClientVersion = ver BaseHeaders["x-client-version"] = ver BaseHeaders["x-app-version"] = ver } } // IsWebPlatform returns whether the current platform is web. func IsWebPlatform() bool { return currentPlatform == "web" } // UserAgent returns the appropriate User-Agent string based on the current platform. func UserAgent() string { if currentPlatform == "web" { return webUserAgent } return "DeepSeek/" + ClientVersion } // WebExtraHeaders returns browser-specific headers that are not in the JSON // config but need to be added dynamically for the web platform. func WebExtraHeaders() map[string]string { return map[string]string{ "sec-ch-ua": `"Chromium";v="137", "Not/A)Brand";v="24"`, "sec-ch-ua-mobile": "?0", "sec-ch-ua-platform": `"Windows"`, "origin": "https://chat.deepseek.com", "referer": "https://chat.deepseek.com/", "sec-fetch-dest": "empty", "sec-fetch-mode": "cors", "sec-fetch-site": "same-origin", } } func cloneStringMap(in map[string]string) map[string]string { out := make(map[string]string, len(in)) for k, v := range in { out[k] = v } return out } func cloneStringSlice(in []string) []string { out := make([]string, len(in)) copy(out, in) return out } func toStringSet(in []string) map[string]struct{} { out := make(map[string]struct{}, len(in)) for _, v := range in { if v == "" { continue } out[v] = struct{}{} } return out } const ( KeepAliveTimeout = 5 StreamIdleTimeout = 300 MaxKeepaliveCount = 40 )