import logging import os import time from typing import Dict, Optional import aiohttp from fastapi import FastAPI, HTTPException, Request from fastapi.responses import HTMLResponse, Response, StreamingResponse logger = logging.getLogger("modelscope_proxy") logging.basicConfig(level=os.getenv("LOG_LEVEL", "INFO").upper()) MODELSCOPE_COOKIE = os.getenv("MODELSCOPE_COOKIE", "") TOKEN_TTL_SECONDS = int(os.getenv("MODELSCOPE_TOKEN_TTL_SECONDS", "3300")) TOKEN_URL = "https://modelscope.cn/api/v1/studios/token" ROUTES = { "/image": { "url": "https://chuansir-qwen-image.ms.show/image", "methods": {"POST"}, }, "/edit-image": { "url": "https://chuansir-qwen-image.ms.show/edit-image", "methods": {"POST"}, }, "/v1/chat/completions": { "url": "https://chuansir-qwen3-5-27b-claude-4-6-opus-reasoning-dis.ms.show/v1/chat/completions", "methods": {"POST"}, }, "/v1/messages": { "url": "https://chuansir-qwen3-5-27b-claude-4-6-opus-reasoning-dis.ms.show/v1/messages", "methods": {"POST"}, }, "/v1/models": { "url": "https://chuansir-qwen3-5-27b-claude-4-6-opus-reasoning-dis.ms.show/v1/models", "methods": {"GET"}, }, "/tts": { "url": "https://chuansir-index-tts-vllm.ms.show/tts", "methods": {"GET", "POST"}, }, "/stt/transcribe": { "url": "https://chuansir-index-tts-vllm.ms.show/stt/transcribe", "methods": {"POST"}, }, } HOP_BY_HOP_HEADERS = { "connection", "keep-alive", "proxy-authenticate", "proxy-authorization", "te", "trailers", "transfer-encoding", "upgrade", "host", "content-length", } app = FastAPI(title="ModelScope Studio API Proxy") class ModelScopeTokenCache: def __init__(self, cookie: str, ttl_seconds: int) -> None: self.cookie = cookie self.ttl_seconds = ttl_seconds self._token: Optional[str] = None self._expires_at = 0.0 async def get(self, session: aiohttp.ClientSession) -> str: now = time.time() if self._token and now < self._expires_at: return self._token token = await self._get_modelscope_token(session, self._token_headers()) self._token = token self._expires_at = now + self.ttl_seconds return token async def _get_modelscope_token( self, session: aiohttp.ClientSession, headers: Dict[str, str] ) -> str: async with session.get(TOKEN_URL, headers=headers) as response: res_text = await response.text() logger.debug(res_text) response.raise_for_status() token_data = await response.json() return token_data["Data"]["Token"] def _token_headers(self) -> Dict[str, str]: if not self.cookie: raise HTTPException( status_code=500, detail="MODELSCOPE_COOKIE environment variable is not set", ) return { "User-Agent": ( "Mozilla/5.0 (Windows NT 10.0; Win64; x64) " "AppleWebKit/537.36 (KHTML, like Gecko) " "Chrome/121.0.0.0 Safari/537.36" ), "Cookie": self.cookie, } token_cache = ModelScopeTokenCache(MODELSCOPE_COOKIE, TOKEN_TTL_SECONDS) @app.get("/", response_class=HTMLResponse) async def index() -> str: return """