From f3722de0efd6e5c89e6ffeeb2041e0f7765bdd83 Mon Sep 17 00:00:00 2001 From: Yaroslavik Date: Wed, 17 Jun 2026 22:47:11 +0200 Subject: [PATCH 1/2] Fix file uploader (add file upload waiter) --- api.py | 80 +++++++++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 76 insertions(+), 4 deletions(-) diff --git a/api.py b/api.py index dd8ecc2..62a9889 100644 --- a/api.py +++ b/api.py @@ -288,8 +288,13 @@ async def _upload_single_file(self, file_path: str) -> str: raise APIError(text, response.status_code) result = json.loads(text) - return result['data']['biz_data']['id'] - + + if result['data'] is not None: + file_id = result['data']['biz_data']['id'] + # We wait for the file to be ready with a timeout of 60 seconds, polling every 2 seconds. + ready_file_id = await self._wait_for_file_ready(file_id, timeout=60.0, poll_interval=2.0) + return ready_file_id + except Exception as e: if retry_count >= max_retries - 1: raise NetworkError(f"Failed to upload {file_path}: {str(e)}") @@ -297,6 +302,68 @@ async def _upload_single_file(self, file_path: str) -> str: raise APIError(f"Failed to upload {file_path} after retries") + async def _wait_for_file_ready(self, file_id: str, timeout: float = 60.0, poll_interval: float = 2.0) -> str: + """ + Wait until uploaded file is successfully processed. + Returns file_id on success, empty string on failure/timeout. + """ + start = asyncio.get_event_loop().time() + last_error = None + + while True: + elapsed = asyncio.get_event_loop().time() - start + if elapsed > timeout: + print(f"\033[93mTimeout waiting for file {file_id} to be ready\033[0m", file=sys.stderr) + return '' + + try: + url = f"{self.BASE_URL}/file/fetch_files?file_ids={file_id}" + challenge = await self._get_pow_challenge() + pow_response = await self.pow_solver.solve_challenge(challenge) + headers = self._get_headers(pow_response) + + res = await self.session.get( + url, + headers=headers, + cookies=self.cookies, + impersonate='chrome120' + ) + text = res.text + if "" in text and "Just a moment" in text: + print("Cloudflare while polling file status", file=sys.stderr) + await self._refresh_cookies() + await asyncio.sleep(poll_interval) + continue + + result = json.loads(text) + + # Check file ready status + if result.get('data') and result['data'].get('biz_data'): + files = result['data']['biz_data'].get('files', []) + if files: + status = files[0].get('status') + if status == "SUCCESS": + return file_id + elif status == "PARSING": + await asyncio.sleep(poll_interval) + continue + else: + print(f"File {file_id} ended with unexpected status: {status}", file=sys.stderr) + return '' + else: + print(f"Unexpected API response while polling file {file_id}", file=sys.stderr) + return '' + + except json.JSONDecodeError as e: + last_error = f"JSON decode error: {e}" + except Exception as e: + last_error = str(e) + + # Pause before retrying after an error + print(f"\033[93mError polling file {file_id} (will retry): {last_error}\033[0m", file=sys.stderr) + await asyncio.sleep(poll_interval) + + async def upload_files(self, file_paths: List[str]) -> List[str]: """ Upload multiple files concurrently and return their IDs @@ -309,9 +376,11 @@ async def upload_files(self, file_paths: List[str]) -> List[str]: """ # Create tasks for concurrent uploads tasks = [self._upload_single_file(file_path) for file_path in file_paths] - + # Run all uploads concurrently file_ids = await asyncio.gather(*tasks) + + file_ids = [res for res in file_ids if res != ''] return file_ids @@ -353,6 +422,9 @@ async def chat_completion( 'ref_file_ids': ref_file_ids if ref_file_ids else [], 'thinking_enabled': thinking_enabled, 'search_enabled': search_enabled, + 'model_type': None, + 'preempt': False, + 'action': None } # Get challenge and solve it @@ -499,4 +571,4 @@ def _parse_chunk_sync(self, chunk: str) -> Optional[Dict[str, Any]]: return None async def close(self): """Close the async session""" - await self.session.close() \ No newline at end of file + await self.session.close() From 8c0e837b29073ba7dfe4f609c50009ef810e1486 Mon Sep 17 00:00:00 2001 From: Yaroslavik Date: Thu, 25 Jun 2026 03:27:45 +0200 Subject: [PATCH 2/2] Update api to version 2.0.0 (support new vision model) --- api.py | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/api.py b/api.py index 62a9889..451ed60 100644 --- a/api.py +++ b/api.py @@ -50,6 +50,9 @@ def __init__(self, message: str, status_code: Optional[int] = None): class DeepSeekAPI: BASE_URL = "https://chat.deepseek.com/api/v0" + MODEL_TYPE = 'default' # 'default' — supports files (only text data, images, and documents) + # 'expert' — does not support files (only prompts) + # 'vision' — supports files (any images and documents) def __init__(self, auth_token: str): if not auth_token or not isinstance(auth_token, str): @@ -88,7 +91,7 @@ def _get_headers(self, pow_response: Optional[str] = None) -> Dict[str, str]: 'x-app-version': '20241129.1', 'x-client-locale': 'en_US', 'x-client-platform': 'web', - 'x-client-version': '1.0.0-always', + 'x-client-version': '2.0.0', } if pow_response: @@ -211,7 +214,7 @@ async def create_chat_session(self) -> str: '/chat_session/create', {'character_id': None} ) - return response['data']['biz_data']['id'] + return response['data']['biz_data']['chat_session']['id'] except KeyError: raise APIError("Invalid session creation response format from server") @@ -230,7 +233,6 @@ async def delete_chat_session(self, chat_session_id: str) -> str: async def _upload_single_file(self, file_path: str) -> str: """Upload a single file and return its ID""" url = f"{self.BASE_URL}/file/upload_file" - # Get challenge and solve it challenge = await self._get_pow_challenge_for_upload() pow_response = await self.pow_solver.solve_challenge(challenge) @@ -243,11 +245,12 @@ async def _upload_single_file(self, file_path: str) -> str: 'origin': 'https://chat.deepseek.com', 'referer': 'https://chat.deepseek.com/', 'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.0.0 Safari/537.36', - 'x-app-version': '20241129.1', + 'x-app-version': '2.0.0', 'x-client-locale': 'en_US', 'x-client-platform': 'web', - 'x-client-version': '1.0.0-always', + 'x-client-version': '2.0.0', 'x-ds-pow-response': pow_response, + 'x-model-type': self.MODEL_TYPE } retry_count = 0 @@ -422,7 +425,7 @@ async def chat_completion( 'ref_file_ids': ref_file_ids if ref_file_ids else [], 'thinking_enabled': thinking_enabled, 'search_enabled': search_enabled, - 'model_type': None, + 'model_type': self.MODEL_TYPE, 'preempt': False, 'action': None }