Spaces:
Running
Running
| """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-oss-120b", | |
| "label": "gpt-oss-120b", | |
| "provider": "openrouter", | |
| "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", | |
| "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", | |
| }, | |
| ] | |
| 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-oss-120b", "label": "gpt-oss-120b", "provider": "openrouter", "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", "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' | |
| "])\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! | |
| # OpenRouter accepts the full slug like "openai/openrouter/owl-alpha". | |
| # LiteLLM needs "openai/" for provider detection. | |
| with open(LLM_PARAMS_FILE) as f: | |
| llm_content = f.read() | |
| # First, try to replace the full block (original version) | |
| 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 ALL openai/-prefixed models to OpenRouter === | |
| # Keep the "openai/" prefix — both LiteLLM and OpenRouter need it. | |
| 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 === | |
| 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 (catch-all openai/ -> OR, keep prefix)") | |
| elif "normalized_model[len" in llm_content or "actual_model" in llm_content: | |
| # Revert the strip approach — keep openai/ prefix | |
| idx = llm_content.find("if normalized_model.startswith(\"openai/\"):") | |
| if idx >= 0: | |
| or_start = llm_content.find("# === PATCH:", idx) | |
| if or_start < 0: | |
| or_start = idx | |
| or_end = llm_content.find("# === END PATCH", or_start) | |
| if or_end < 0: | |
| or_end = llm_content.find("params = {", or_start) | |
| # Find the return block and fix it | |
| ret_line = llm_content.find("return {", or_start, or_end) | |
| if ret_line > 0: | |
| model_line_start = llm_content.find('"model":', ret_line) | |
| if model_line_start > 0: | |
| line_start = llm_content.rfind('\n', 0, model_line_start) + 1 | |
| line_end = llm_content.find('\n', line_start) | |
| old_model_line = llm_content[line_start:line_end] | |
| new_model_line = ' "model": normalized_model,' | |
| llm_content = llm_content.replace(old_model_line, new_model_line) | |
| print("OK: Reverted to keep openai/ prefix for OpenRouter") | |
| else: | |
| print("OK: Could not find model line") | |
| else: | |
| print("OK: Could not find return block") | |
| else: | |
| print("OK: Already using correct routing") | |
| else: | |
| print("OK: Routing already correct") | |
| 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 - 16 models, ALL openai/ -> OR (keep prefix), GPT-5.5→gpt-oss-120b, Riverflow→north-mini-code:free") |