"""Patch backend: replace AVAILABLE_MODELS (remove old 6, add 18 new) + OpenRouter routing.""" import ast import os import re AGENT_FILE = "/app/backend/routes/agent.py" LLM_PARAMS_FILE = "/app/agent/core/llm_params.py" # === Step 1: Replace _available_models() to return ONLY new models === with open(AGENT_FILE) as f: content = f.read() # Make DEFAULT_MODEL_ID point to our default content = content.replace( "DEFAULT_MODEL_ID = GLM_52_MODEL_ID", 'DEFAULT_MODEL_ID = "deepseek-ai/DeepSeek-V4-Pro:novita"' ) content = content.replace( "DEFAULT_GPT_MODEL_ID = GPT_55_MODEL_ID", 'DEFAULT_GPT_MODEL_ID = "openai/gpt-oss-120b"' ) # Find and replace the _available_models() function using regex func_pattern = re.compile( r'def _available_models\(\) -> list\[dict\[str, Any\]\]:\s*\n' r'\s+models\s*=\s*\[.*?\]\s*\n\s+return models', re.DOTALL ) new_func = '''def _available_models() -> list[dict[str, Any]]: models = [ { "id": "openai/openrouter/owl-alpha", "label": "Owl Alpha", "provider": "openrouter", "tier": "free", "recommended": True, }, { "id": "deepseek-ai/DeepSeek-V4-Pro:novita", "label": "DeepSeek V4 Pro", "provider": "huggingface", "tier": "free", "recommended": True, }, { "id": "deepseek-ai/DeepSeek-V4-Flash:novita", "label": "DeepSeek V4 Flash", "provider": "huggingface", "tier": "free", }, { "id": "openai/gpt-oss-120b", "label": "gpt-oss-120b", "provider": "openrouter", "tier": "free", }, { "id": "Qwen/Qwen3-Coder-Next:novita", "label": "Qwen3 Coder Next", "provider": "huggingface", "tier": "free", "recommended": True, }, { "id": "google/gemma-3-1b-it:novita", "label": "Gemma 3 1B", "provider": "huggingface", "tier": "free", "recommended": True, }, { "id": "openai/google/gemini-2.0-flash-001", "label": "Gemini 2.0 Flash", "provider": "openrouter", "tier": "free", }, { "id": "openai/deepseek/deepseek-v4-flash", "label": "DeepSeek V4 Flash (OR)", "provider": "openrouter", "tier": "paid", }, { "id": "openai/google/gemma-4-9b-it:free", "label": "Gemma 4 9B", "provider": "openrouter", "tier": "free", "recommended": True, }, { "id": "openai/google/gemma-4-31b-it:free", "label": "Gemma 4 31B", "provider": "openrouter", "tier": "free", "recommended": True, }, { "id": "openai/tencent/hy3-preview", "label": "Hy3 Preview", "provider": "openrouter", "tier": "paid", "recommended": True, }, { "id": "openai/cohere/north-mini-code:free", "label": "north-mini-code:free", "provider": "openrouter", "tier": "free", "recommended": True, }, { "id": "openai/nvidia/nemotron-3-super-120b-a12b:free", "label": "Nemotron 3 Super 120B", "provider": "openrouter", "tier": "free", }, { "id": "openai/nvidia/nemotron-3-ultra-550b-a55b:free", "label": "Nemotron 3 Ultra 550B", "provider": "openrouter", "tier": "free", "recommended": True, }, { "id": "openai/poolside/laguna-m.1:free", "label": "Laguna M.1", "provider": "openrouter", "tier": "free", "recommended": True, }, { "id": "openai/poolside/laguna-xs.2:free", "label": "Laguna XS.2", "provider": "openrouter", "tier": "free", }, { "id": "openai/qwen/qwen3.7-flash", "label": "Qwen3.7 Flash", "provider": "openrouter", "tier": "free", }, { "id": "openai/inclusionai/ling-2.6-flash", "label": "Ling 2.6 Flash", "provider": "openrouter", "tier": "free", }, ] return models''' func_match = func_pattern.search(content) if func_match: content = content[:func_match.start()] + new_func + content[func_match.end():] print("OK: Replaced _available_models() with 18 new models via regex") else: print("WARN: Could not find _available_models() via regex, trying extend approach...") target = "AVAILABLE_MODELS = _available_models()\n" if target in content: content = content.replace( target, target + "AVAILABLE_MODELS.clear()\n" + "AVAILABLE_MODELS.extend([\n" ' {"id": "openai/openrouter/owl-alpha", "label": "Owl Alpha", "provider": "openrouter", "tier": "free", "recommended": True},\n' ' {"id": "deepseek-ai/DeepSeek-V4-Pro:novita", "label": "DeepSeek V4 Pro", "provider": "huggingface", "tier": "free", "recommended": True},\n' ' {"id": "deepseek-ai/DeepSeek-V4-Flash:novita", "label": "DeepSeek V4 Flash", "provider": "huggingface", "tier": "free"},\n' ' {"id": "openai/gpt-oss-120b", "label": "gpt-oss-120b", "provider": "openrouter", "tier": "free"},\n' ' {"id": "Qwen/Qwen3-Coder-Next:novita", "label": "Qwen3 Coder Next", "provider": "huggingface", "tier": "free", "recommended": True},\n' ' {"id": "google/gemma-3-1b-it:novita", "label": "Gemma 3 1B", "provider": "huggingface", "tier": "free", "recommended": True},\n' ' {"id": "openai/google/gemini-2.0-flash-001", "label": "Gemini 2.0 Flash", "provider": "openrouter", "tier": "free"},\n' ' {"id": "openai/deepseek/deepseek-v4-flash", "label": "DeepSeek V4 Flash (OR)", "provider": "openrouter", "tier": "paid"},\n' ' {"id": "openai/google/gemma-4-9b-it:free", "label": "Gemma 4 9B", "provider": "openrouter", "tier": "free", "recommended": True},\n' ' {"id": "openai/google/gemma-4-31b-it:free", "label": "Gemma 4 31B", "provider": "openrouter", "tier": "free", "recommended": True},\n' ' {"id": "openai/tencent/hy3-preview", "label": "Hy3 Preview", "provider": "openrouter", "tier": "paid", "recommended": True},\n' ' {"id": "openai/cohere/north-mini-code:free", "label": "north-mini-code:free", "provider": "openrouter", "tier": "free", "recommended": True},\n' ' {"id": "openai/nvidia/nemotron-3-super-120b-a12b:free", "label": "Nemotron 3 Super 120B", "provider": "openrouter", "tier": "free"},\n' ' {"id": "openai/nvidia/nemotron-3-ultra-550b-a55b:free", "label": "Nemotron 3 Ultra 550B", "provider": "openrouter", "tier": "free", "recommended": True},\n' ' {"id": "openai/poolside/laguna-m.1:free", "label": "Laguna M.1", "provider": "openrouter", "tier": "free", "recommended": True},\n' ' {"id": "openai/poolside/laguna-xs.2:free", "label": "Laguna XS.2", "provider": "openrouter", "tier": "free"},\n' ' {"id": "openai/qwen/qwen3.7-flash", "label": "Qwen3.7 Flash", "provider": "openrouter", "tier": "free"},\n' ' {"id": "openai/inclusionai/ling-2.6-flash", "label": "Ling 2.6 Flash", "provider": "openrouter", "tier": "free"},\n' "])\n" 'DEFAULT_FREE_MODEL_ID = "openai/openrouter/owl-alpha"\n' ) print("OK: Used extend approach") else: print("FAIL: Cannot patch agent.py!") import sys sys.exit(1) try: ast.parse(content) print("OK: agent.py syntax OK") except SyntaxError as e: print(f"FAIL: Syntax error in agent.py: {e}") raise with open(AGENT_FILE, "w") as f: f.write(content) # === Step 2: Patch _resolve_llm_params for OpenRouter routing === with open(LLM_PARAMS_FILE) as f: llm_content = f.read() # Insert the OpenRouter routing before the params block api_key_match = re.search(r'api_key\s*=\s*_resolve_hf_router_token\(session_hf_token\)', llm_content) if api_key_match: line_end = llm_content.find('\n', api_key_match.end()) + 1 if line_end > 0: routing_insert = ''' # === PATCH: Route ALL openai/-prefixed models to OpenRouter === if normalized_model.startswith("openai/"): return { "model": normalized_model, "api_base": "https://openrouter.ai/api/v1", "api_key": os.environ.get("OPENROUTER_API_KEY") or api_key or "", } # === END PATCH === ''' llm_content = llm_content[:line_end] + routing_insert + llm_content[line_end:] print("OK: Patched _resolve_llm_params (catch-all openai/ -> OR)") else: print("OK: Could not find end of api_key line") else: print("OK: api_key line not found, may already be patched") try: ast.parse(llm_content) print("OK: llm_params.py syntax OK") except SyntaxError as e: print(f"FAIL: Syntax error in llm_params.py: {e}") raise with open(LLM_PARAMS_FILE, "w") as f: f.write(llm_content) print("OK: Backend patched - 18 models, ALL openai/ -> OR")