"""Patch backend: replace AVAILABLE_MODELS (remove old 5, add 16 new) + OpenRouter routing.""" import ast import os 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() # Replace the default model constants first content = content.replace( 'DEFAULT_FREE_MODEL_ID = KIMI_K26_MODEL_ID', 'DEFAULT_FREE_MODEL_ID = "openai/openrouter/owl-alpha"' ) # Replace _available_models() function body to return only new models old_func = '''def _available_models() -> list[dict[str, Any]]: models = [ { "id": DEFAULT_OPUS_MODEL_ID, "label": "Claude Opus 4.8", "provider": "huggingface", "recommended": True, }, { "id": DEFAULT_GPT_MODEL_ID, "label": "GPT-5.5", "provider": "huggingface", }, { "id": DEFAULT_FREE_MODEL_ID, "label": "Kimi K2.6", "provider": "huggingface", }, { "id": MINIMAX_M27_MODEL_ID, "label": "MiniMax M2.7", "provider": "huggingface", }, { "id": GLM_51_MODEL_ID, "label": "GLM 5.1", "provider": "huggingface", }, { "id": DEEPSEEK_V4_PRO_MODEL_ID, "label": "DeepSeek V4 Pro", "provider": "huggingface", }, ] return models''' 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", "label": "DeepSeek V4 Pro", "provider": "huggingface", "tier": "free", "recommended": True, }, { "id": "deepseek-ai/DeepSeek-V4-Flash", "label": "DeepSeek V4 Flash", "provider": "huggingface", "tier": "free", }, { "id": "openai/gpt-5.5:fal-ai", "label": "GPT-5.5", "provider": "huggingface", "tier": "free", }, { "id": "Qwen/Qwen3-Coder-Next", "label": "Qwen3 Coder Next", "provider": "huggingface", "tier": "free", "recommended": True, }, { "id": "google/gemma-3-1b-it", "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:free", "label": "DeepSeek V4 Flash (OR)", "provider": "openrouter", "tier": "free", }, { "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/sourceful/riverflow-v2.5-fast:free", "label": "Riverflow V2.5 Fast", "provider": "openrouter", "tier": "free", "recommended": True, }, { "id": "openai/sourceful/riverflow-v2.5-pro:free", "label": "Riverflow V2.5 Pro", "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", }, ] return models''' if old_func in content: content = content.replace(old_func, new_func) print("OK: Replaced _available_models() with 16 new models") else: print("WARN: Could not find old _available_models(), 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", "label": "DeepSeek V4 Pro", "provider": "huggingface", "tier": "free", "recommended": True},\n' ' {"id": "deepseek-ai/DeepSeek-V4-Flash", "label": "DeepSeek V4 Flash", "provider": "huggingface", "tier": "free"},\n' ' {"id": "openai/gpt-5.5:fal-ai", "label": "GPT-5.5", "provider": "huggingface", "tier": "free"},\n' ' {"id": "Qwen/Qwen3-Coder-Next", "label": "Qwen3 Coder Next", "provider": "huggingface", "tier": "free", "recommended": True},\n' ' {"id": "google/gemma-3-1b-it", "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:free", "label": "DeepSeek V4 Flash (OR)", "provider": "openrouter", "tier": "free"},\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/sourceful/riverflow-v2.5-fast:free", "label": "Riverflow V2.5 Fast", "provider": "openrouter", "tier": "free", "recommended": True},\n' ' {"id": "openai/sourceful/riverflow-v2.5-pro:free", "label": "Riverflow V2.5 Pro", "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' "])\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 === # IMPORTANT: Keep the "openai/" prefix when sending to OpenRouter! # LiteLLM needs it to identify the provider as OpenAI-compatible. with open(LLM_PARAMS_FILE) as f: llm_content = f.read() old_block = """ hf_model = normalized_model api_key = _resolve_hf_router_token(session_hf_token) params = { "model": f"openai/{hf_model}", "api_base": HF_ROUTER_BASE_URL, "api_key": api_key, }""" new_block = """ hf_model = normalized_model api_key = _resolve_hf_router_token(session_hf_token) # === PATCH: Route OpenRouter models === # Models with openai// prefix go to OpenRouter. # Keep the "openai/" prefix — LiteLLM needs it for provider detection. _or_prefixes = ("openai/google/", "openai/deepseek/", "openai/nvidia/", "openai/poolside/", "openai/sourceful/", "openai/openrouter/") if normalized_model.startswith(_or_prefixes): 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 === params = { "model": f"openai/{hf_model}", "api_base": HF_ROUTER_BASE_URL, "api_key": api_key, }""" if old_block in llm_content: llm_content = llm_content.replace(old_block, new_block) print("OK: Patched _resolve_llm_params") elif "openai/openrouter/" in llm_content: print("OK: Already patched") else: idx = llm_content.find("hf_model = normalized_model") if idx >= 0: print(f"WARN: Target block not found at idx {idx}") import sys sys.exit(1) try: ast.parse(llm_content) print("OK: llm_params.py syntax OK") except SyntaxError as e: print(f"FAIL: Syntax error: {e}") raise with open(LLM_PARAMS_FILE, "w") as f: f.write(llm_content) print("OK: Backend patched - 16 models, owl-alpha default, OpenRouter routing (keep openai/ prefix)")