diff --git a/agent/context_manager/manager.py b/agent/context_manager/manager.py index 85e96af0..c6b9290a 100644 --- a/agent/context_manager/manager.py +++ b/agent/context_manager/manager.py @@ -213,7 +213,7 @@ def _load_system_prompt( """Load and render the system prompt from YAML file with Jinja2""" prompt_file = Path(__file__).parent.parent / "prompts" / f"{prompt_file_suffix}" - with open(prompt_file, "r") as f: + with open(prompt_file, "r", encoding="utf-8") as f: prompt_data = yaml.safe_load(f) template_str = prompt_data.get("system_prompt", "") diff --git a/agent/core/agent_loop.py b/agent/core/agent_loop.py index 0eaa6e9d..63d06d43 100644 --- a/agent/core/agent_loop.py +++ b/agent/core/agent_loop.py @@ -683,8 +683,10 @@ def _extract_thinking_state( def _should_replay_thinking_state(model_name: str | None) -> bool: - """Only Anthropic's native adapter accepts replayed thinking metadata.""" - return bool(model_name and model_name.startswith("anthropic/")) + """Only Anthropic and DeepSeek accept/require replayed thinking metadata.""" + if not model_name: + return False + return model_name.startswith("anthropic/") or "deepseek" in model_name.lower() def _is_invalid_thinking_signature_error(exc: Exception) -> bool: diff --git a/agent/core/model_switcher.py b/agent/core/model_switcher.py index 34eaccdd..54b039d7 100644 --- a/agent/core/model_switcher.py +++ b/agent/core/model_switcher.py @@ -34,6 +34,8 @@ # ":cheapest" / ":preferred" / ":" to override the default # routing policy (auto = fastest with failover). SUGGESTED_MODELS = [ + {"id": "openai/deepseek-v4-flash", "label": "deepseek-v4-flash (Custom API)"}, + {"id": "openai/deepseek-v4-pro", "label": "deepseek-v4-pro (Custom API)"}, {"id": "openai/gpt-5.5", "label": "GPT-5.5"}, {"id": "openai/gpt-5.4", "label": "GPT-5.4"}, {"id": "anthropic/claude-opus-4-7", "label": "Claude Opus 4.7"}, diff --git a/agent/core/session.py b/agent/core/session.py index fb08c75f..9ee2d8f4 100644 --- a/agent/core/session.py +++ b/agent/core/session.py @@ -473,7 +473,7 @@ def save_trajectory_local( # Atomic-ish write: stage to .tmp then rename so a crash mid-write # doesn't leave a truncated JSON that breaks the retry scanner. tmp_path = filepath.with_suffix(filepath.suffix + ".tmp") - with open(tmp_path, "w") as f: + with open(tmp_path, "w", encoding="utf-8") as f: json.dump(trajectory, f, indent=2) tmp_path.replace(filepath) @@ -487,14 +487,14 @@ def update_local_save_status( ) -> bool: """Update the upload status of an existing local save file""" try: - with open(filepath, "r") as f: + with open(filepath, "r", encoding="utf-8") as f: data = json.load(f) data["upload_status"] = upload_status data["upload_url"] = dataset_url data["last_save_time"] = datetime.now().isoformat() - with open(filepath, "w") as f: + with open(filepath, "w", encoding="utf-8") as f: json.dump(data, f, indent=2) return True diff --git a/agent/core/session_uploader.py b/agent/core/session_uploader.py index 404fd224..ba2618e7 100644 --- a/agent/core/session_uploader.py +++ b/agent/core/session_uploader.py @@ -276,7 +276,7 @@ def _write_row_payload(data: dict, tmp_path: str) -> None: "tools": json.dumps(scrubbed["tools"]), } - with open(tmp_path, "w") as tmp: + with open(tmp_path, "w", encoding="utf-8") as tmp: json.dump(session_row, tmp) @@ -285,7 +285,7 @@ def _write_claude_code_payload(data: dict, tmp_path: str) -> None: # Scrub before conversion so secrets never reach the upload temp file. scrubbed = _scrub_session_for_upload(data) events = to_claude_code_jsonl(scrubbed) - with open(tmp_path, "w") as tmp: + with open(tmp_path, "w", encoding="utf-8") as tmp: for event in events: tmp.write(json.dumps(event)) tmp.write("\n") @@ -302,14 +302,20 @@ def _url_field(format: str) -> str: def _read_session_file(session_file: str) -> dict: """Read a local session file while respecting uploader file locks.""" - import fcntl + has_fcntl = True + try: + import fcntl + except ImportError: + has_fcntl = False - with open(session_file, "r") as f: - fcntl.flock(f, fcntl.LOCK_SH) + with open(session_file, "r", encoding="utf-8") as f: + if has_fcntl: + fcntl.flock(f, fcntl.LOCK_SH) try: return json.load(f) finally: - fcntl.flock(f, fcntl.LOCK_UN) + if has_fcntl: + fcntl.flock(f, fcntl.LOCK_UN) def _update_upload_status( @@ -325,10 +331,15 @@ def _update_upload_status( local session JSON file. Re-read under an exclusive lock so one uploader cannot clobber fields written by the other. """ - import fcntl + has_fcntl = True + try: + import fcntl + except ImportError: + has_fcntl = False - with open(session_file, "r+") as f: - fcntl.flock(f, fcntl.LOCK_EX) + with open(session_file, "r+", encoding="utf-8") as f: + if has_fcntl: + fcntl.flock(f, fcntl.LOCK_EX) try: data = json.load(f) data[status_key] = status @@ -341,7 +352,8 @@ def _update_upload_status( f.flush() os.fsync(f.fileno()) finally: - fcntl.flock(f, fcntl.LOCK_UN) + if has_fcntl: + fcntl.flock(f, fcntl.LOCK_UN) def dataset_card_readme(repo_id: str) -> str: diff --git a/run_test.py b/run_test.py new file mode 100644 index 00000000..d8084385 Binary files /dev/null and b/run_test.py differ diff --git a/test_litellm.py b/test_litellm.py new file mode 100644 index 00000000..77e59be1 --- /dev/null +++ b/test_litellm.py @@ -0,0 +1 @@ +import os, litellm; from litellm import completion; from dotenv import load_dotenv; load_dotenv(); response = completion(model='openai/deepseek-chat', messages=[{'role': 'user', 'content': 'hello'}]); print(response)