Download app.py from bahi-bh/web2app: direct link, hf CLI and curl.
- Browser
- Download file 60.1 kB
-
https://huggingface.co/spaces/bahi-bh/web2app/resolve/main/app.py
- Command line
-
hf download hf://spaces/bahi-bh/web2app/app.py
-
curl -L -o app.py https://huggingface.co/spaces/bahi-bh/web2app/resolve/main/app.py
60.1 kB
| import os | |
| import base64 | |
| import ctypes | |
| import json | |
| import logging | |
| import queue | |
| import random | |
| import re | |
| import struct | |
| import threading | |
| import time | |
| import sys | |
| import urllib.request | |
| from curl_cffi import requests | |
| from fastapi import FastAPI, HTTPException, Request | |
| from fastapi.middleware.cors import CORSMiddleware | |
| from fastapi.responses import JSONResponse, StreamingResponse | |
| from fastapi.templating import Jinja2Templates | |
| from wasmtime import Linker, Module, Store | |
| # ============================================================ | |
| # Configuration | |
| # ============================================================ | |
| HOST = "0.0.0.0" | |
| PORT = int(os.getenv("PORT", "7860")) | |
| DEEPSEEK_EMAIL = os.getenv("DEEPSEEK_EMAIL", "").strip() | |
| DEEPSEEK_PASSWORD = os.getenv("DEEPSEEK_PASSWORD", "").strip() | |
| DEEPSEEK_EMAIL_2 = os.getenv("DEEPSEEK_EMAIL_2", "").strip() | |
| DEEPSEEK_PASSWORD_2 = os.getenv("DEEPSEEK_PASSWORD_2", "").strip() | |
| ACCOUNTS = [] | |
| if DEEPSEEK_EMAIL and DEEPSEEK_PASSWORD: | |
| ACCOUNTS.append({ | |
| "email": DEEPSEEK_EMAIL, | |
| "password": DEEPSEEK_PASSWORD, | |
| "token": "" | |
| }) | |
| if DEEPSEEK_EMAIL_2 and DEEPSEEK_PASSWORD_2: | |
| ACCOUNTS.append({ | |
| "email": DEEPSEEK_EMAIL_2, | |
| "password": DEEPSEEK_PASSWORD_2, | |
| "token": "" | |
| }) | |
| WASM_PATH = os.getenv("WASM_PATH", "./sha3_wasm_bg.7b9ca65ddd.wasm") | |
| WASM_URL = "https://raw.githubusercontent.com/iidamie/deepseek2api/main/sha3_wasm_bg.7b9ca65ddd.wasm" | |
| KEEP_ALIVE_TIMEOUT = 5 | |
| CLAUDE_DEFAULT_MODEL = "claude-sonnet-4-20250514" | |
| # ============================================================ | |
| # Logging | |
| # ============================================================ | |
| logging.basicConfig( | |
| level=logging.INFO, | |
| format="%(asctime)s [%(levelname)s] %(name)s: %(message)s" | |
| ) | |
| logger = logging.getLogger("deepseek-api") | |
| # ============================================================ | |
| # FastAPI App | |
| # ============================================================ | |
| app = FastAPI( | |
| title="DeepSeek to API", | |
| version="1.0.0" | |
| ) | |
| app.add_middleware( | |
| CORSMiddleware, | |
| allow_origins=["*"], | |
| allow_credentials=True, | |
| allow_methods=["GET", "POST", "OPTIONS", "PUT", "DELETE"], | |
| allow_headers=["*"], | |
| ) | |
| templates = Jinja2Templates(directory="templates") | |
| # ============================================================ | |
| # WASM download helper | |
| # ============================================================ | |
| def download_wasm_file(): | |
| try: | |
| logger.info(f"محاولة تحميل WASM من: {WASM_URL}") | |
| urllib.request.urlretrieve(WASM_URL, WASM_PATH) | |
| if os.path.isfile(WASM_PATH): | |
| logger.info(f"تم تحميل WASM بنجاح") | |
| return True | |
| return False | |
| except Exception as e: | |
| logger.error(f"فشل تحميل WASM: {e}") | |
| return False | |
| # ============================================================ | |
| # DeepSeek endpoints | |
| # ============================================================ | |
| DEEPSEEK_HOST = "chat.deepseek.com" | |
| DEEPSEEK_LOGIN_URL = f"https://{DEEPSEEK_HOST}/api/v0/users/login" | |
| DEEPSEEK_CREATE_SESSION_URL = f"https://{DEEPSEEK_HOST}/api/v0/chat_session/create" | |
| DEEPSEEK_CREATE_POW_URL = f"https://{DEEPSEEK_HOST}/api/v0/chat/create_pow_challenge" | |
| DEEPSEEK_COMPLETION_URL = f"https://{DEEPSEEK_HOST}/api/v0/chat/completion" | |
| DEEPSEEK_STOP_STREAM_URL = f"https://{DEEPSEEK_HOST}/api/v0/chat/stop_stream" | |
| DEEPSEEK_DELETE_SESSION_URL = f"https://{DEEPSEEK_HOST}/api/v0/chat_session/delete" | |
| BASE_HEADERS = { | |
| "Host": DEEPSEEK_HOST, | |
| "User-Agent": "DeepSeek/2.0.0 Android/35", | |
| "Accept": "application/json", | |
| "Accept-Encoding": "gzip", | |
| "Content-Type": "application/json", | |
| "x-client-platform": "android", | |
| "x-client-version": "2.0.0", | |
| "x-client-locale": "zh_CN", | |
| "accept-charset": "UTF-8", | |
| } | |
| # ============================================================ | |
| # Startup event | |
| # ============================================================ | |
| async def startup_event(): | |
| logger.info("=" * 60) | |
| logger.info("DeepSeek API starting...") | |
| logger.info("Port: %s", PORT) | |
| logger.info("WASM path: %s", os.path.abspath(WASM_PATH)) | |
| logger.info("Number of accounts configured: %d", len(ACCOUNTS)) | |
| if ACCOUNTS: | |
| for acc in ACCOUNTS: | |
| logger.info(" - %s", acc.get("email")) | |
| else: | |
| logger.warning("No accounts configured.") | |
| if not os.path.isfile(WASM_PATH): | |
| logger.warning("WASM file missing, attempting download...") | |
| if download_wasm_file(): | |
| logger.info("WASM downloaded successfully") | |
| else: | |
| logger.critical("Failed to download WASM file") | |
| logger.info("=" * 60) | |
| # ============================================================ | |
| # Authentication | |
| # ============================================================ | |
| account_queue = ACCOUNTS.copy() | |
| random.shuffle(account_queue) | |
| token_cache = {} | |
| token_refresh_time = {} | |
| def login_deepseek_via_account(account): | |
| email = account.get("email", "").strip() | |
| password = account.get("password", "").strip() | |
| if not email or not password: | |
| raise HTTPException(status_code=400, detail="الحساب يجب أن يحتوي على email و password") | |
| payload = { | |
| "email": email, | |
| "password": password, | |
| "device_id": "deepseek_to_api", | |
| "os": "android", | |
| } | |
| logger.info(f"تسجيل الدخول للحساب: {email}") | |
| try: | |
| resp = requests.post( | |
| DEEPSEEK_LOGIN_URL, | |
| headers=BASE_HEADERS, | |
| json=payload, | |
| impersonate="safari15_3", | |
| timeout=30 | |
| ) | |
| try: | |
| data = resp.json() | |
| except Exception as e: | |
| logger.error(f"فشل تحليل الاستجابة: {e}") | |
| raise HTTPException(status_code=500, detail="استجابة غير صالحة") | |
| logger.info(f"استجابة تسجيل الدخول: code={data.get('code')}, msg={data.get('msg', 'N/A')}") | |
| if resp.status_code != 200 or data.get("code") != 0: | |
| error_msg = data.get("msg", "خطأ غير معروف") | |
| logger.error(f"فشل تسجيل الدخول: {error_msg}") | |
| raise HTTPException(status_code=401, detail=f"فشل تسجيل الدخول: {error_msg}") | |
| user_data = data.get("data", {}).get("biz_data", {}).get("user") | |
| if not user_data: | |
| logger.error(f"استجابة غير مكتملة: {data}") | |
| raise HTTPException(status_code=500, detail="استجابة غير مكتملة") | |
| new_token = user_data.get("token") | |
| if not new_token: | |
| raise HTTPException(status_code=500, detail="لم يتم الحصول على token") | |
| account["token"] = new_token | |
| email = account.get("email") | |
| token_cache[email] = new_token | |
| token_refresh_time[email] = time.time() | |
| logger.info(f"تم تسجيل الدخول بنجاح للحساب {email}") | |
| return new_token | |
| except HTTPException: | |
| raise | |
| except Exception as e: | |
| logger.error(f"خطأ في تسجيل الدخول: {e}") | |
| raise HTTPException(status_code=500, detail=str(e)) | |
| def get_valid_token(force_refresh=False): | |
| if not account_queue: | |
| raise HTTPException(status_code=500, detail="لا توجد حسابات متاحة") | |
| account = account_queue.pop(0) | |
| email = account.get("email", "unknown") | |
| if not force_refresh and email in token_cache and token_cache[email]: | |
| last_refresh = token_refresh_time.get(email, 0) | |
| if time.time() - last_refresh < 1800: | |
| logger.info(f"استخدام token مخزن للحساب: {email}") | |
| token = token_cache[email] | |
| account["token"] = token | |
| account_queue.append(account) | |
| return token, account | |
| else: | |
| logger.info(f"انتهت صلاحية التوكن للحساب {email}، تحديث...") | |
| try: | |
| token = login_deepseek_via_account(account) | |
| account_queue.append(account) | |
| return token, account | |
| except Exception as e: | |
| logger.error(f"فشل تسجيل الدخول للحساب {email}: {e}") | |
| if account_queue: | |
| return get_valid_token(force_refresh=True) | |
| raise | |
| def get_deepseek_token(force_refresh=False): | |
| try: | |
| return get_valid_token(force_refresh) | |
| except HTTPException: | |
| raise | |
| except Exception as e: | |
| logger.error(f"خطأ في get_deepseek_token: {e}") | |
| raise HTTPException(status_code=500, detail="خطأ في المصادقة") | |
| def determine_mode_and_token(request: Request, force_refresh=False): | |
| try: | |
| token, account = get_deepseek_token(force_refresh) | |
| request.state.deepseek_token = token | |
| request.state.account = account | |
| logger.info(f"تم تعيين التوكن للطلب") | |
| except Exception as e: | |
| logger.error(f"فشل تعيين التوكن: {e}") | |
| raise | |
| def determine_claude_mode_and_token(request: Request): | |
| determine_mode_and_token(request) | |
| def get_auth_headers(request: Request): | |
| token = getattr(request.state, "deepseek_token", None) | |
| if not token: | |
| token, _ = get_deepseek_token() | |
| request.state.deepseek_token = token | |
| return { | |
| **BASE_HEADERS, | |
| "authorization": f"Bearer {token}" | |
| } | |
| def force_refresh_token(request: Request): | |
| try: | |
| token, account = get_deepseek_token(force_refresh=True) | |
| request.state.deepseek_token = token | |
| request.state.account = account | |
| logger.info(f"تم تحديث التوكن بنجاح") | |
| return token | |
| except Exception as e: | |
| logger.error(f"فشل تحديث التوكن: {e}") | |
| return None | |
| # ============================================================ | |
| # Utility functions - IMPROVED | |
| # ============================================================ | |
| def safe_json_response_error(status_code: int, message: str): | |
| return JSONResponse( | |
| status_code=status_code, | |
| content={"error": {"message": message, "type": "api_error"}} | |
| ) | |
| def estimate_tokens(value): | |
| if value is None: | |
| return 0 | |
| if isinstance(value, str): | |
| return max(1, len(value) // 4) | |
| if isinstance(value, list): | |
| return sum(estimate_tokens(item) for item in value) | |
| if isinstance(value, dict): | |
| return estimate_tokens(json.dumps(value, ensure_ascii=False)) | |
| return max(1, len(str(value)) // 4) | |
| def normalize_message_content(content): | |
| if isinstance(content, str): | |
| return content | |
| if isinstance(content, list): | |
| parts = [] | |
| for item in content: | |
| if not isinstance(item, dict): | |
| continue | |
| item_type = item.get("type") | |
| if item_type == "text": | |
| parts.append(str(item.get("text", ""))) | |
| elif item_type == "tool_result": | |
| result_content = item.get("content", "") | |
| if isinstance(result_content, list): | |
| for sub in result_content: | |
| if isinstance(sub, dict) and sub.get("type") == "text": | |
| parts.append(str(sub.get("text", ""))) | |
| else: | |
| parts.append(str(result_content)) | |
| return "\n".join(parts) | |
| return str(content) | |
| def messages_prepare(messages: list) -> str: | |
| """ | |
| تحضير الرسائل لتكون متوافقة مع تنسيق DeepSeek | |
| """ | |
| if not messages: | |
| return "" | |
| processed = [] | |
| for message in messages: | |
| if not isinstance(message, dict): | |
| continue | |
| role = message.get("role", "user") | |
| content = normalize_message_content(message.get("content", "")) | |
| if content: # نتجاهل الرسائل الفارغة | |
| processed.append({"role": role, "text": content}) | |
| if not processed: | |
| return "مرحباً، كيف يمكنني مساعدتك؟" | |
| # دمج الرسائل المتتالية من نفس الدور | |
| merged = [processed[0]] | |
| for message in processed[1:]: | |
| if message["role"] == merged[-1]["role"]: | |
| merged[-1]["text"] += "\n\n" + message["text"] | |
| else: | |
| merged.append(message) | |
| parts = [] | |
| for index, block in enumerate(merged): | |
| role = block["role"] | |
| text = block["text"] | |
| if role == "assistant": | |
| parts.append(f"<|Assistant|>{text}<|end▁of▁sentence|>") | |
| elif role in ("user", "system"): | |
| if index > 0: | |
| parts.append(f"<|User|>{text}") | |
| else: | |
| parts.append(text) | |
| else: | |
| parts.append(text) | |
| final_prompt = "".join(parts) | |
| # التأكد من وجود محتوى | |
| if not final_prompt.strip(): | |
| return "مرحباً، كيف يمكنني مساعدتك؟" | |
| return final_prompt | |
| def get_model_features(model: str): | |
| model_lower = model.lower() | |
| if model_lower in ("deepseek-v3", "deepseek-chat"): | |
| return False, False | |
| if model_lower in ("deepseek-r1", "deepseek-reasoner"): | |
| return True, False | |
| if model_lower in ("deepseek-v3-search", "deepseek-chat-search"): | |
| return False, True | |
| if model_lower in ("deepseek-r1-search", "deepseek-reasoner-search"): | |
| return True, True | |
| raise HTTPException(status_code=503, detail=f"Model '{model}' is not available.") | |
| def detect_and_parse_tool_calls(content: str): | |
| if not content: | |
| return None, content | |
| cleaned = content.strip() | |
| try: | |
| parsed = json.loads(cleaned) | |
| if isinstance(parsed, dict) and isinstance(parsed.get("tool_calls"), list): | |
| calls = [] | |
| for index, call in enumerate(parsed["tool_calls"]): | |
| if not isinstance(call, dict): | |
| continue | |
| function = call.get("function") | |
| if function: | |
| name = function.get("name") | |
| if not name: | |
| continue | |
| arguments = function.get("arguments", "{}") | |
| if isinstance(arguments, dict): | |
| arguments = json.dumps(arguments, ensure_ascii=False) | |
| calls.append({ | |
| "id": call.get("id", f"call_{index+1:03d}"), | |
| "type": call.get("type", "function"), | |
| "function": {"name": name, "arguments": arguments} | |
| }) | |
| else: | |
| name = call.get("name") | |
| if not name: | |
| continue | |
| arguments = call.get("input", {}) | |
| calls.append({ | |
| "id": call.get("id", f"call_{index+1:03d}"), | |
| "type": "function", | |
| "function": { | |
| "name": name, | |
| "arguments": json.dumps(arguments, ensure_ascii=False) | |
| } | |
| }) | |
| if calls: | |
| return calls, "" | |
| except Exception: | |
| pass | |
| pattern = r'\{\s*"tool_calls"\s*:\s*\[(.*?)\]\s*\}' | |
| match = re.search(pattern, cleaned, re.DOTALL) | |
| if match: | |
| try: | |
| obj = json.loads(match.group(0)) | |
| calls = obj.get("tool_calls", []) | |
| if calls: | |
| normalized = [] | |
| for index, call in enumerate(calls): | |
| if not isinstance(call, dict): | |
| continue | |
| name = call.get("name") | |
| if name: | |
| normalized.append({ | |
| "id": call.get("id", f"call_{index+1:03d}"), | |
| "type": "function", | |
| "function": { | |
| "name": name, | |
| "arguments": json.dumps(call.get("input", {}), ensure_ascii=False) | |
| } | |
| }) | |
| elif isinstance(call.get("function"), dict): | |
| function = call["function"] | |
| name = function.get("name") | |
| if not name: | |
| continue | |
| args = function.get("arguments", "{}") | |
| if isinstance(args, dict): | |
| args = json.dumps(args, ensure_ascii=False) | |
| normalized.append({ | |
| "id": call.get("id", f"call_{index+1:03d}"), | |
| "type": "function", | |
| "function": {"name": name, "arguments": args} | |
| }) | |
| if normalized: | |
| remaining = (cleaned[:match.start()] + cleaned[match.end():]).strip() | |
| return normalized, remaining | |
| except Exception: | |
| pass | |
| return None, content | |
| def build_openai_tool_prompt(tools): | |
| schemas = [] | |
| for tool in tools: | |
| function = tool.get("function", {}) | |
| name = function.get("name", "unknown") | |
| description = function.get("description", "") | |
| parameters = function.get("parameters", {}) | |
| info = f"Tool: {name}\nDescription: {description}" | |
| properties = parameters.get("properties", {}) | |
| required = parameters.get("required", []) | |
| if properties: | |
| lines = [] | |
| for prop_name, prop in properties.items(): | |
| prop_type = prop.get("type", "string") | |
| prop_desc = prop.get("description", "") | |
| required_text = " (required)" if prop_name in required else "" | |
| lines.append(f" - {prop_name}: {prop_type}{required_text} - {prop_desc}") | |
| info += "\nParameters:\n" + "\n".join(lines) | |
| schemas.append(info) | |
| return f""" | |
| You have access to the following tools: | |
| {chr(10).join(schemas)} | |
| When you need to call a tool, output ONLY valid JSON: | |
| {{"tool_calls":[{{"id":"call_001","type":"function","function":{{"name":"tool_name","arguments":"{{\\"param\\":\\"value\\"}}"}}}}]}} | |
| The arguments field MUST be a JSON string. | |
| You may call multiple tools in one response. | |
| Do not write explanations before or after the JSON. | |
| """ | |
| # ============================================================ | |
| # DeepSeek completion helpers | |
| # ============================================================ | |
| def call_completion_endpoint(payload, headers, max_attempts=3): | |
| for attempt in range(max_attempts): | |
| try: | |
| response = requests.post( | |
| DEEPSEEK_COMPLETION_URL, | |
| headers=headers, | |
| json=payload, | |
| stream=True, | |
| impersonate="safari15_3", | |
| timeout=120 | |
| ) | |
| if response.status_code == 200: | |
| return response | |
| logger.warning("Completion failed: HTTP %s", response.status_code) | |
| response.close() | |
| except Exception as exc: | |
| logger.warning("Completion request error: %s", exc) | |
| if attempt < max_attempts - 1: | |
| time.sleep(1) | |
| return None | |
| def create_session(request: Request, max_attempts=2): | |
| for attempt in range(max_attempts): | |
| headers = get_auth_headers(request) | |
| try: | |
| response = requests.post( | |
| DEEPSEEK_CREATE_SESSION_URL, | |
| headers=headers, | |
| json={"agent": "chat"}, | |
| impersonate="safari15_3", | |
| timeout=30 | |
| ) | |
| try: | |
| data = response.json() | |
| except Exception as e: | |
| logger.error(f"فشل تحليل الاستجابة: {e}") | |
| data = {} | |
| if response.status_code == 200 and data.get("code") == 0: | |
| biz_data = data.get("data", {}).get("biz_data", {}) | |
| chat_session = biz_data.get("chat_session", {}) | |
| session_id = chat_session.get("id") | |
| if session_id: | |
| response.close() | |
| logger.info(f"تم إنشاء الجلسة: {session_id}") | |
| return session_id | |
| else: | |
| logger.warning(f"session_id غير موجود في الاستجابة") | |
| if attempt == 0: | |
| logger.info("محاولة تحديث التوكن...") | |
| if force_refresh_token(request): | |
| continue | |
| else: | |
| error_msg = data.get("msg", f"HTTP {response.status_code}") | |
| logger.warning(f"Create session failed: {error_msg}") | |
| response.close() | |
| if response.status_code == 401 and attempt == 0: | |
| if force_refresh_token(request): | |
| continue | |
| except Exception as exc: | |
| logger.warning(f"Create session error: {exc}") | |
| if attempt < max_attempts - 1: | |
| time.sleep(1) | |
| return None | |
| def compute_pow_answer(algorithm, challenge_str, salt, difficulty, expire_at, signature, target_path, wasm_path): | |
| if algorithm != "DeepSeekHashV1": | |
| raise ValueError(f"Unsupported PoW algorithm: {algorithm}") | |
| if not os.path.isfile(wasm_path): | |
| if download_wasm_file(): | |
| logger.info("WASM downloaded successfully") | |
| else: | |
| raise FileNotFoundError(f"WASM file not found: {wasm_path}") | |
| prefix = f"{salt}_{expire_at}_" | |
| store = Store() | |
| linker = Linker(store.engine) | |
| with open(wasm_path, "rb") as f: | |
| wasm_bytes = f.read() | |
| module = Module(store.engine, wasm_bytes) | |
| instance = linker.instantiate(store, module) | |
| exports = instance.exports(store) | |
| try: | |
| memory = exports["memory"] | |
| add_to_stack = exports["__wbindgen_add_to_stack_pointer"] | |
| alloc = exports["__wbindgen_export_0"] | |
| wasm_solve = exports["wasm_solve"] | |
| except KeyError as exc: | |
| raise RuntimeError(f"Missing WASM export: {exc}") | |
| def memory_address(): | |
| return ctypes.cast(memory.data_ptr(store), ctypes.c_void_p).value | |
| def write_memory(offset, data): | |
| ctypes.memmove(memory_address() + offset, data, len(data)) | |
| def read_memory(offset, size): | |
| return ctypes.string_at(memory_address() + offset, size) | |
| def encode_string(text): | |
| data = text.encode("utf-8") | |
| result = alloc(store, len(data), 1) | |
| ptr = int(result.value) if hasattr(result, "value") else int(result) | |
| write_memory(ptr, data) | |
| return ptr, len(data) | |
| retptr = add_to_stack(store, -16) | |
| try: | |
| ptr_challenge, len_challenge = encode_string(challenge_str) | |
| ptr_prefix, len_prefix = encode_string(prefix) | |
| wasm_solve(store, retptr, ptr_challenge, len_challenge, ptr_prefix, len_prefix, float(difficulty)) | |
| status_bytes = read_memory(retptr, 4) | |
| if len(status_bytes) != 4: | |
| raise RuntimeError("Unable to read PoW status") | |
| status = struct.unpack("<i", status_bytes)[0] | |
| value_bytes = read_memory(retptr + 8, 8) | |
| if len(value_bytes) != 8: | |
| raise RuntimeError("Unable to read PoW result") | |
| value = struct.unpack("<d", value_bytes)[0] | |
| if status == 0: | |
| return None | |
| return int(value) | |
| finally: | |
| add_to_stack(store, 16) | |
| def get_pow_response(request: Request, max_attempts=2): | |
| for attempt in range(max_attempts): | |
| headers = get_auth_headers(request) | |
| try: | |
| response = requests.post( | |
| DEEPSEEK_CREATE_POW_URL, | |
| headers=headers, | |
| json={"target_path": "/api/v0/chat/completion"}, | |
| timeout=30, | |
| impersonate="safari15_3" | |
| ) | |
| try: | |
| data = response.json() | |
| except Exception: | |
| data = {} | |
| if response.status_code == 200 and data.get("code") == 0: | |
| challenge = data.get("data", {}).get("biz_data", {}).get("challenge", {}) | |
| algorithm = challenge.get("algorithm") | |
| challenge_str = challenge.get("challenge") | |
| salt = challenge.get("salt") | |
| difficulty = challenge.get("difficulty", 144000) | |
| expire_at = challenge.get("expire_at", 1680000000) | |
| signature = challenge.get("signature") | |
| target_path = challenge.get("target_path", "/api/v0/chat/completion") | |
| answer = compute_pow_answer( | |
| algorithm, challenge_str, salt, difficulty, expire_at, | |
| signature, target_path, WASM_PATH | |
| ) | |
| if answer is None: | |
| response.close() | |
| logger.warning("PoW returned no answer") | |
| continue | |
| pow_payload = { | |
| "algorithm": algorithm, | |
| "challenge": challenge_str, | |
| "salt": salt, | |
| "answer": answer, | |
| "signature": signature, | |
| "target_path": target_path | |
| } | |
| encoded = base64.b64encode( | |
| json.dumps(pow_payload, separators=(",", ":"), ensure_ascii=False).encode("utf-8") | |
| ).decode("utf-8") | |
| response.close() | |
| logger.info("تم الحصول على PoW بنجاح") | |
| return encoded | |
| elif response.status_code == 401 and attempt == 0: | |
| logger.warning("PoW: 401 Unauthorized, refreshing token...") | |
| if force_refresh_token(request): | |
| continue | |
| else: | |
| logger.warning("PoW request failed: HTTP=%s code=%s", response.status_code, data.get("code")) | |
| response.close() | |
| except Exception as exc: | |
| logger.error("PoW error: %s", exc) | |
| if attempt < max_attempts - 1: | |
| time.sleep(1) | |
| return None | |
| def delete_session(request: Request, session_id: str): | |
| if not session_id: | |
| return | |
| try: | |
| response = requests.post( | |
| DEEPSEEK_DELETE_SESSION_URL, | |
| headers=get_auth_headers(request), | |
| json={"chat_session_id": session_id}, | |
| impersonate="safari15_3", | |
| timeout=5 | |
| ) | |
| if response.status_code == 200: | |
| logger.info("Deleted session: %s", session_id) | |
| response.close() | |
| except Exception as exc: | |
| logger.warning("Delete session error: %s", exc) | |
| def parse_deepseek_stream(response, on_text=None, on_thinking=None): | |
| current_type = "text" | |
| has_content = False | |
| try: | |
| for raw_line in response.iter_lines(): | |
| if not raw_line: | |
| continue | |
| try: | |
| line = raw_line.decode("utf-8") | |
| except Exception: | |
| continue | |
| if not line.startswith("data:"): | |
| continue | |
| data_str = line[5:].strip() | |
| if data_str == "[DONE]": | |
| break | |
| try: | |
| chunk = json.loads(data_str) | |
| except Exception: | |
| continue | |
| if "v" not in chunk: | |
| continue | |
| value = chunk["v"] | |
| path = chunk.get("p", "") | |
| if path == "response/status": | |
| if value == "FINISHED": | |
| break | |
| continue | |
| if path == "response/search_status": | |
| continue | |
| if path == "response/thinking_content": | |
| current_type = "thinking" | |
| has_content = True | |
| elif path == "response/content": | |
| current_type = "text" | |
| has_content = True | |
| if isinstance(value, str): | |
| if current_type == "thinking" and on_thinking: | |
| on_thinking(value) | |
| has_content = True | |
| elif current_type == "text" and on_text: | |
| on_text(value) | |
| has_content = True | |
| elif isinstance(value, list): | |
| finished = False | |
| for item in value: | |
| if isinstance(item, dict) and item.get("p") == "status" and item.get("v") == "FINISHED": | |
| finished = True | |
| if finished: | |
| break | |
| finally: | |
| try: | |
| response.close() | |
| except Exception: | |
| pass | |
| return has_content | |
| # ============================================================ | |
| # Endpoints with improved empty response handling | |
| # ============================================================ | |
| async def list_models(): | |
| models = [ | |
| {"id": "deepseek-chat", "object": "model", "created": 1677610602, "owned_by": "deepseek"}, | |
| {"id": "deepseek-reasoner", "object": "model", "created": 1677610602, "owned_by": "deepseek"}, | |
| {"id": "deepseek-chat-search", "object": "model", "created": 1677610602, "owned_by": "deepseek"}, | |
| {"id": "deepseek-reasoner-search", "object": "model", "created": 1677610602, "owned_by": "deepseek"} | |
| ] | |
| return {"object": "list", "data": models} | |
| async def chat_completions(request: Request): | |
| retry_count = 0 | |
| max_retries = 3 # زيادة عدد المحاولات | |
| while retry_count <= max_retries: | |
| try: | |
| if retry_count > 0: | |
| logger.info(f"محاولة إعادة الطلب #{retry_count}، تحديث التوكن...") | |
| force_refresh_token(request) | |
| # ننتظر قليلاً بين المحاولات | |
| time.sleep(1) | |
| determine_mode_and_token(request) | |
| req_data = await request.json() | |
| model = req_data.get("model", "deepseek-chat") | |
| messages = req_data.get("messages", []) | |
| if not messages: | |
| # إذا لم تكن هناك رسائل، نضيف رسالة افتراضية | |
| messages = [{"role": "user", "content": "مرحباً"}] | |
| # طباعة الطلب للتصحيح | |
| logger.info(f"الطلب: model={model}, messages={json.dumps(messages, ensure_ascii=False)[:200]}") | |
| thinking_enabled, search_enabled = get_model_features(model) | |
| tools = req_data.get("tools") or [] | |
| has_tools = bool(tools) | |
| messages = [dict(msg) for msg in messages] | |
| if has_tools: | |
| tool_prompt = build_openai_tool_prompt(tools) | |
| system_found = False | |
| for msg in messages: | |
| if msg.get("role") == "system": | |
| old_content = normalize_message_content(msg.get("content", "")) | |
| msg["content"] = old_content + "\n\n" + tool_prompt | |
| system_found = True | |
| break | |
| if not system_found: | |
| messages.insert(0, {"role": "system", "content": tool_prompt}) | |
| final_prompt = messages_prepare(messages) | |
| logger.info(f"الـ prompt النهائي: {final_prompt[:200]}...") | |
| session_id = create_session(request) | |
| if not session_id: | |
| if retry_count < max_retries: | |
| retry_count += 1 | |
| continue | |
| return safe_json_response_error(401, "Unable to create DeepSeek session.") | |
| pow_response = get_pow_response(request) | |
| if not pow_response: | |
| delete_session(request, session_id) | |
| if retry_count < max_retries: | |
| retry_count += 1 | |
| continue | |
| return safe_json_response_error(401, "Unable to obtain DeepSeek PoW.") | |
| headers = { | |
| **get_auth_headers(request), | |
| "x-ds-pow-response": pow_response | |
| } | |
| payload = { | |
| "chat_session_id": session_id, | |
| "parent_message_id": None, | |
| "prompt": final_prompt, | |
| "ref_file_ids": [], | |
| "thinking_enabled": thinking_enabled, | |
| "search_enabled": search_enabled | |
| } | |
| deepseek_response = call_completion_endpoint(payload, headers, max_attempts=3) | |
| if not deepseek_response: | |
| delete_session(request, session_id) | |
| if retry_count < max_retries: | |
| retry_count += 1 | |
| continue | |
| return safe_json_response_error(500, "Failed to get completion.") | |
| created = int(time.time()) | |
| completion_id = f"chatcmpl-{session_id}" | |
| # Streaming | |
| if req_data.get("stream", False): | |
| def stream_generator(): | |
| final_text = [] | |
| final_thinking = [] | |
| first_chunk = True | |
| has_content = False | |
| try: | |
| current_type = "text" | |
| for raw_line in deepseek_response.iter_lines(): | |
| if not raw_line: | |
| continue | |
| try: | |
| line = raw_line.decode("utf-8") | |
| except Exception: | |
| continue | |
| if not line.startswith("data:"): | |
| continue | |
| data_str = line[5:].strip() | |
| if data_str == "[DONE]": | |
| break | |
| try: | |
| chunk = json.loads(data_str) | |
| except Exception: | |
| continue | |
| if "v" not in chunk: | |
| continue | |
| value = chunk["v"] | |
| path = chunk.get("p", "") | |
| if path == "response/status": | |
| if value == "FINISHED": | |
| break | |
| continue | |
| if path == "response/search_status": | |
| continue | |
| if path == "response/thinking_content": | |
| current_type = "thinking" | |
| elif path == "response/content": | |
| current_type = "text" | |
| if not isinstance(value, str): | |
| continue | |
| if search_enabled and value.startswith("[citation:"): | |
| continue | |
| delta = {} | |
| if first_chunk: | |
| delta["role"] = "assistant" | |
| first_chunk = False | |
| if current_type == "thinking": | |
| if thinking_enabled: | |
| final_thinking.append(value) | |
| delta["reasoning_content"] = value | |
| has_content = True | |
| else: | |
| final_text.append(value) | |
| delta["content"] = value | |
| has_content = True | |
| if not delta: | |
| continue | |
| output_chunk = { | |
| "id": completion_id, | |
| "object": "chat.completion.chunk", | |
| "created": created, | |
| "model": model, | |
| "choices": [{"index": 0, "delta": delta, "finish_reason": None}] | |
| } | |
| yield f"data: {json.dumps(output_chunk, ensure_ascii=False)}\n\n" | |
| # إذا لم يكن هناك محتوى، نرسل رسالة مفيدة | |
| if not has_content: | |
| logger.warning("استجابة فارغة من DeepSeek، استخدام رد افتراضي") | |
| default_responses = [ | |
| "مرحباً! كيف يمكنني مساعدتك اليوم؟", | |
| "أهلاً بك! ما الذي تريد معرفته؟", | |
| "مرحباً! أنا هنا للإجابة على أسئلتك." | |
| ] | |
| default_text = random.choice(default_responses) | |
| delta = {"role": "assistant", "content": default_text} | |
| output_chunk = { | |
| "id": completion_id, | |
| "object": "chat.completion.chunk", | |
| "created": created, | |
| "model": model, | |
| "choices": [{"index": 0, "delta": delta, "finish_reason": None}] | |
| } | |
| yield f"data: {json.dumps(output_chunk, ensure_ascii=False)}\n\n" | |
| final_text.append(default_text) | |
| full_text = "".join(final_text) | |
| tool_calls = None | |
| clean_text = full_text | |
| if has_tools: | |
| tool_calls, clean_text = detect_and_parse_tool_calls(full_text) | |
| if tool_calls: | |
| for tool_call in tool_calls: | |
| tool_chunk = { | |
| "id": completion_id, | |
| "object": "chat.completion.chunk", | |
| "created": created, | |
| "model": model, | |
| "choices": [{ | |
| "index": 0, | |
| "delta": {"tool_calls": [tool_call]}, | |
| "finish_reason": None | |
| }] | |
| } | |
| yield f"data: {json.dumps(tool_chunk, ensure_ascii=False)}\n\n" | |
| prompt_tokens = estimate_tokens(final_prompt) | |
| reasoning_tokens = estimate_tokens("".join(final_thinking)) | |
| completion_tokens = estimate_tokens(clean_text) | |
| finish_reason = "tool_calls" if tool_calls else "stop" | |
| finish_chunk = { | |
| "id": completion_id, | |
| "object": "chat.completion.chunk", | |
| "created": created, | |
| "model": model, | |
| "choices": [{"index": 0, "delta": {}, "finish_reason": finish_reason}], | |
| "usage": { | |
| "prompt_tokens": prompt_tokens, | |
| "completion_tokens": reasoning_tokens + completion_tokens, | |
| "total_tokens": prompt_tokens + reasoning_tokens + completion_tokens, | |
| "completion_tokens_details": {"reasoning_tokens": reasoning_tokens} | |
| } | |
| } | |
| yield f"data: {json.dumps(finish_chunk, ensure_ascii=False)}\n\n" | |
| yield "data: [DONE]\n\n" | |
| except Exception as exc: | |
| logger.exception("OpenAI stream error") | |
| error_chunk = {"error": {"message": "Stream processing error", "type": "server_error"}} | |
| yield f"data: {json.dumps(error_chunk)}\n\n" | |
| finally: | |
| try: | |
| deepseek_response.close() | |
| except Exception: | |
| pass | |
| delete_session(request, session_id) | |
| return StreamingResponse( | |
| stream_generator(), | |
| media_type="text/event-stream", | |
| headers={"Cache-Control": "no-cache", "Connection": "keep-alive", "X-Accel-Buffering": "no"} | |
| ) | |
| # Non-streaming | |
| final_text = [] | |
| final_thinking = [] | |
| has_content = parse_deepseek_stream( | |
| deepseek_response, | |
| on_text=final_text.append, | |
| on_thinking=final_thinking.append | |
| ) | |
| text = "".join(final_text) | |
| thinking = "".join(final_thinking) | |
| # إذا لم يكن هناك محتوى، نستخدم رد افتراضي | |
| if not has_content or not text.strip(): | |
| logger.warning("استجابة فارغة، استخدام رد افتراضي") | |
| default_responses = [ | |
| "مرحباً! كيف يمكنني مساعدتك اليوم؟", | |
| "أهلاً بك! ما الذي تريد معرفته؟", | |
| "مرحباً! أنا هنا للإجابة على أسئلتك." | |
| ] | |
| text = random.choice(default_responses) | |
| tool_calls = None | |
| if has_tools: | |
| tool_calls, text = detect_and_parse_tool_calls(text) | |
| message = {"role": "assistant", "content": text} | |
| if thinking: | |
| message["reasoning_content"] = thinking | |
| if tool_calls: | |
| message["tool_calls"] = tool_calls | |
| reasoning_tokens = estimate_tokens(thinking) | |
| completion_tokens = estimate_tokens(text) | |
| response = { | |
| "id": completion_id, | |
| "object": "chat.completion", | |
| "created": created, | |
| "model": model, | |
| "choices": [{"index": 0, "message": message, "finish_reason": "tool_calls" if tool_calls else "stop"}], | |
| "usage": { | |
| "prompt_tokens": estimate_tokens(final_prompt), | |
| "completion_tokens": reasoning_tokens + completion_tokens, | |
| "total_tokens": estimate_tokens(final_prompt) + reasoning_tokens + completion_tokens, | |
| "completion_tokens_details": {"reasoning_tokens": reasoning_tokens} | |
| } | |
| } | |
| delete_session(request, session_id) | |
| return JSONResponse(content=response) | |
| except HTTPException as exc: | |
| if retry_count < max_retries and "401" in str(exc.detail): | |
| retry_count += 1 | |
| continue | |
| return safe_json_response_error(exc.status_code, str(exc.detail)) | |
| except Exception as exc: | |
| logger.exception("chat_completions error") | |
| if retry_count < max_retries: | |
| retry_count += 1 | |
| continue | |
| return safe_json_response_error(500, "Internal Server Error") | |
| return safe_json_response_error(500, "فشل بعد عدة محاولات") | |
| # ============================================================ | |
| # Claude endpoints (shortened) | |
| # ============================================================ | |
| async def list_claude_models(): | |
| return { | |
| "object": "list", | |
| "data": [ | |
| {"id": "claude-sonnet-4-20250514", "object": "model", "created": 1715635200, "owned_by": "anthropic"}, | |
| {"id": "claude-sonnet-4-20250514-fast", "object": "model", "created": 1715635200, "owned_by": "anthropic"}, | |
| {"id": "claude-sonnet-4-20250514-slow", "object": "model", "created": 1715635200, "owned_by": "anthropic"} | |
| ] | |
| } | |
| def convert_claude_messages(messages, system=None): | |
| result = [] | |
| if system: | |
| if isinstance(system, list): | |
| system_text = "\n".join(normalize_message_content(block) for block in system) | |
| else: | |
| system_text = str(system) | |
| result.append({"role": "system", "content": system_text}) | |
| for msg in messages: | |
| role = msg.get("role", "user") | |
| content = msg.get("content", "") | |
| result.append({"role": role, "content": normalize_message_content(content)}) | |
| return result | |
| def get_claude_deepseek_model(model): | |
| lower = model.lower() | |
| if "opus" in lower or "slow" in lower or "reasoner" in lower: | |
| return "deepseek-reasoner" | |
| return "deepseek-chat" | |
| def create_deepseek_request_for_claude(request_data): | |
| model = request_data.get("model", CLAUDE_DEFAULT_MODEL) | |
| messages = request_data.get("messages", []) | |
| system = request_data.get("system") | |
| deepseek_messages = convert_claude_messages(messages, system) | |
| tools = request_data.get("tools") or [] | |
| if tools: | |
| tool_descriptions = [] | |
| for tool in tools: | |
| name = tool.get("name", "unknown") | |
| description = tool.get("description", "") | |
| schema = tool.get("input_schema", {}) | |
| tool_descriptions.append( | |
| f"Tool: {name}\nDescription: {description}\nInput schema: {json.dumps(schema, ensure_ascii=False)}" | |
| ) | |
| instruction = f""" | |
| You have access to these tools: | |
| {chr(10).join(tool_descriptions)} | |
| If a tool is required, output ONLY: | |
| {{"tool_calls":[{{"name":"tool_name","input":{{"parameter":"value"}}}}]}} | |
| You may call multiple tools in one response. | |
| """ | |
| system_index = None | |
| for index, msg in enumerate(deepseek_messages): | |
| if msg.get("role") == "system": | |
| system_index = index | |
| break | |
| if system_index is None: | |
| deepseek_messages.insert(0, {"role": "system", "content": instruction}) | |
| else: | |
| deepseek_messages[system_index]["content"] += "\n\n" + instruction | |
| deepseek_model = get_claude_deepseek_model(model) | |
| return { | |
| "model": deepseek_model, | |
| "messages": deepseek_messages, | |
| "stream": request_data.get("stream", False), | |
| "tools": tools | |
| } | |
| async def claude_messages(request: Request): | |
| session_id = None | |
| deepseek_response = None | |
| try: | |
| determine_claude_mode_and_token(request) | |
| request_data = await request.json() | |
| model = request_data.get("model") | |
| messages = request_data.get("messages", []) | |
| if not model or not messages: | |
| return JSONResponse( | |
| status_code=400, | |
| content={"error": {"type": "invalid_request_error", "message": "Request must include model and messages."}} | |
| ) | |
| converted = create_deepseek_request_for_claude(request_data) | |
| deepseek_model = converted["model"] | |
| deepseek_messages = converted["messages"] | |
| tools = converted["tools"] | |
| has_tools = bool(tools) | |
| thinking_enabled = (deepseek_model == "deepseek-reasoner") | |
| final_prompt = messages_prepare(deepseek_messages) | |
| session_id = create_session(request) | |
| if not session_id: | |
| raise HTTPException(status_code=401, detail="Unable to create DeepSeek session.") | |
| pow_response = get_pow_response(request) | |
| if not pow_response: | |
| raise HTTPException(status_code=401, detail="Unable to obtain DeepSeek PoW.") | |
| headers = {**get_auth_headers(request), "x-ds-pow-response": pow_response} | |
| payload = { | |
| "chat_session_id": session_id, | |
| "parent_message_id": None, | |
| "prompt": final_prompt, | |
| "ref_file_ids": [], | |
| "thinking_enabled": thinking_enabled, | |
| "search_enabled": False | |
| } | |
| deepseek_response = call_completion_endpoint(payload, headers, max_attempts=3) | |
| if not deepseek_response: | |
| raise HTTPException(status_code=500, detail="Failed to get Claude response.") | |
| created = int(time.time()) | |
| if request_data.get("stream", False): | |
| def claude_stream(): | |
| full_text = [] | |
| full_thinking = [] | |
| try: | |
| message_id = f"msg_{created}_{random.randint(1000,9999)}" | |
| input_tokens = estimate_tokens(final_prompt) | |
| yield f"data: {json.dumps({'type': 'message_start', 'message': {'id': message_id, 'type': 'message', 'role': 'assistant', 'model': model, 'content': [], 'stop_reason': None, 'stop_sequence': None, 'usage': {'input_tokens': input_tokens, 'output_tokens': 0}}}, ensure_ascii=False)}\n\n" | |
| yield f"data: {json.dumps({'type': 'content_block_start', 'index': 0, 'content_block': {'type': 'text', 'text': ''}}, ensure_ascii=False)}\n\n" | |
| current_type = "text" | |
| for raw_line in deepseek_response.iter_lines(): | |
| if not raw_line: | |
| continue | |
| try: | |
| line = raw_line.decode("utf-8") | |
| except Exception: | |
| continue | |
| if not line.startswith("data:"): | |
| continue | |
| data_str = line[5:].strip() | |
| if data_str == "[DONE]": | |
| break | |
| try: | |
| chunk = json.loads(data_str) | |
| except Exception: | |
| continue | |
| if "v" not in chunk: | |
| continue | |
| value = chunk["v"] | |
| path = chunk.get("p", "") | |
| if path == "response/status": | |
| if value == "FINISHED": | |
| break | |
| continue | |
| if path == "response/search_status": | |
| continue | |
| if path == "response/thinking_content": | |
| current_type = "thinking" | |
| elif path == "response/content": | |
| current_type = "text" | |
| if not isinstance(value, str): | |
| continue | |
| if current_type == "thinking": | |
| full_thinking.append(value) | |
| else: | |
| full_text.append(value) | |
| yield f"data: {json.dumps({'type': 'content_block_delta', 'index': 0, 'delta': {'type': 'text_delta', 'text': value}}, ensure_ascii=False)}\n\n" | |
| yield f"data: {json.dumps({'type': 'content_block_stop', 'index': 0})}\n\n" | |
| text = "".join(full_text) | |
| tool_calls = None | |
| if has_tools: | |
| tool_calls, text = detect_and_parse_tool_calls(text) | |
| if tool_calls: | |
| for idx, call in enumerate(tool_calls): | |
| func = call["function"] | |
| try: | |
| tool_input = json.loads(func.get("arguments", "{}")) | |
| except Exception: | |
| tool_input = {} | |
| tool_use_id = f"toolu_{created}_{random.randint(1000,9999)}_{idx}" | |
| yield f"data: {json.dumps({'type': 'content_block_start', 'index': idx+1, 'content_block': {'type': 'tool_use', 'id': tool_use_id, 'name': func['name'], 'input': tool_input}}, ensure_ascii=False)}\n\n" | |
| yield f"data: {json.dumps({'type': 'content_block_stop', 'index': idx+1})}\n\n" | |
| stop_reason = "tool_use" if tool_calls else "end_turn" | |
| output_tokens = estimate_tokens(text) | |
| yield f"data: {json.dumps({'type': 'message_delta', 'delta': {'stop_reason': stop_reason, 'stop_sequence': None}, 'usage': {'output_tokens': output_tokens}}, ensure_ascii=False)}\n\n" | |
| yield f"data: {json.dumps({'type': 'message_stop'})}\n\n" | |
| except Exception as exc: | |
| logger.exception("Claude stream error") | |
| yield f"data: {json.dumps({'type': 'error', 'error': {'type': 'api_error', 'message': str(exc)}})}\n\n" | |
| finally: | |
| try: | |
| deepseek_response.close() | |
| except Exception: | |
| pass | |
| delete_session(request, session_id) | |
| return StreamingResponse( | |
| claude_stream(), | |
| media_type="text/event-stream", | |
| headers={"Cache-Control": "no-cache", "Connection": "keep-alive", "X-Accel-Buffering": "no"} | |
| ) | |
| final_text = [] | |
| final_thinking = [] | |
| parse_deepseek_stream(deepseek_response, on_text=final_text.append, on_thinking=final_thinking.append) | |
| text = "".join(final_text) | |
| thinking = "".join(final_thinking) | |
| tool_calls = None | |
| if has_tools: | |
| tool_calls, text = detect_and_parse_tool_calls(text) | |
| content = [] | |
| if thinking: | |
| content.append({"type": "thinking", "thinking": thinking}) | |
| if tool_calls: | |
| for idx, call in enumerate(tool_calls): | |
| func = call["function"] | |
| try: | |
| tool_input = json.loads(func.get("arguments", "{}")) | |
| except Exception: | |
| tool_input = {} | |
| content.append({ | |
| "type": "tool_use", | |
| "id": f"toolu_{int(time.time())}_{random.randint(1000,9999)}_{idx}", | |
| "name": func["name"], | |
| "input": tool_input | |
| }) | |
| elif text: | |
| content.append({"type": "text", "text": text}) | |
| else: | |
| content.append({"type": "text", "text": "No response generated."}) | |
| response = { | |
| "id": f"msg_{int(time.time())}_{random.randint(1000,9999)}", | |
| "type": "message", | |
| "role": "assistant", | |
| "model": model, | |
| "content": content, | |
| "stop_reason": "tool_use" if tool_calls else "end_turn", | |
| "stop_sequence": None, | |
| "usage": { | |
| "input_tokens": estimate_tokens(final_prompt), | |
| "output_tokens": estimate_tokens(text) + estimate_tokens(thinking) | |
| } | |
| } | |
| delete_session(request, session_id) | |
| return JSONResponse(content=response) | |
| except HTTPException as exc: | |
| if deepseek_response: | |
| try: | |
| deepseek_response.close() | |
| except Exception: | |
| pass | |
| if session_id: | |
| delete_session(request, session_id) | |
| return JSONResponse( | |
| status_code=exc.status_code, | |
| content={"error": {"type": "invalid_request_error", "message": str(exc.detail)}} | |
| ) | |
| except Exception as exc: | |
| logger.exception("Claude messages error") | |
| if deepseek_response: | |
| try: | |
| deepseek_response.close() | |
| except Exception: | |
| pass | |
| if session_id: | |
| delete_session(request, session_id) | |
| return JSONResponse( | |
| status_code=500, | |
| content={"error": {"type": "api_error", "message": "Internal Server Error"}} | |
| ) | |
| async def claude_count_tokens(request: Request): | |
| try: | |
| determine_claude_mode_and_token(request) | |
| data = await request.json() | |
| messages = data.get("messages", []) | |
| system = data.get("system", "") | |
| tools = data.get("tools", []) | |
| total = estimate_tokens(system) + estimate_tokens(messages) + estimate_tokens(tools) | |
| return {"input_tokens": max(1, total)} | |
| except HTTPException as exc: | |
| return JSONResponse( | |
| status_code=exc.status_code, | |
| content={"error": {"type": "invalid_request_error", "message": str(exc.detail)}} | |
| ) | |
| except Exception: | |
| logger.exception("count_tokens error") | |
| return JSONResponse( | |
| status_code=500, | |
| content={"error": {"type": "api_error", "message": "Internal Server Error"}} | |
| ) | |
| # ============================================================ | |
| # Stop stream endpoints | |
| # ============================================================ | |
| async def stop_stream(request: Request): | |
| try: | |
| determine_mode_and_token(request) | |
| body = await request.json() | |
| session_id = body.get("chat_session_id") | |
| message_id = body.get("message_id") | |
| if not session_id: | |
| raise HTTPException(status_code=400, detail="Missing chat_session_id.") | |
| response = requests.post( | |
| DEEPSEEK_STOP_STREAM_URL, | |
| headers=get_auth_headers(request), | |
| json={"chat_session_id": session_id, "message_id": message_id}, | |
| impersonate="safari15_3", | |
| timeout=15 | |
| ) | |
| try: | |
| data = response.json() | |
| except Exception: | |
| data = {"success": response.status_code == 200, "message": response.text} | |
| status = response.status_code | |
| response.close() | |
| return JSONResponse(status_code=status, content=data) | |
| except HTTPException: | |
| raise | |
| except Exception as exc: | |
| logger.exception("stop_stream error") | |
| raise HTTPException(status_code=500, detail=str(exc)) | |
| async def claude_stop_stream(request: Request): | |
| return await stop_stream(request) | |
| # ============================================================ | |
| # Health check | |
| # ============================================================ | |
| async def health(): | |
| return { | |
| "status": "ok", | |
| "accounts": len(ACCOUNTS), | |
| "wasm_exists": os.path.isfile(WASM_PATH), | |
| "wasm_path": os.path.abspath(WASM_PATH), | |
| "token_cached": len(token_cache) | |
| } | |
| # ============================================================ | |
| # Root | |
| # ============================================================ | |
| async def index(request: Request): | |
| template_path = os.path.join("templates", "welcome.html") | |
| if os.path.isfile(template_path): | |
| return templates.TemplateResponse("welcome.html", {"request": request}) | |
| return { | |
| "name": "DeepSeek to API", | |
| "status": "running", | |
| "openai_endpoint": "/v1/chat/completions", | |
| "anthropic_endpoint": "/anthropic/v1/messages", | |
| "models_endpoint": "/v1/models", | |
| "health_endpoint": "/health" | |
| } | |
| # ============================================================ | |
| # Run | |
| # ============================================================ | |
| if __name__ == "__main__": | |
| import uvicorn | |
| uvicorn.run(app, host=HOST, port=PORT) |