"""Run with the existing .venv; no training/MuJoCo imports or env-file discovery.""" import argparse from pathlib import Path from urllib.parse import urlsplit from aiohttp import web from .credentials import deepseek_llm, openrouter_jev from .server import STATE, create_app def main(): parser = argparse.ArgumentParser(description="LeKiwi 本机模型服务(仅回环地址)") parser.add_argument("--port", type=int, default=8768) parser.add_argument("--state-dir", type=Path) parser.add_argument("--website-origin", help="显式网站同源模式") parser.add_argument("--website-key-file", type=Path, help="显式服务器密钥文件,仅两项固定角色") parser.add_argument( "--website-budget-file", type=Path, help="共享调用限额 SQLite 文件,重启不重置" ) parser.add_argument("--website-dev", action="store_true", help="仅回环 HTTP 开发模式") parser.add_argument("--bind", default="127.0.0.1", choices=["127.0.0.1", "0.0.0.0"]) parser.add_argument("--trusted-proxy", action="append", default=[]) parser.add_argument("--max-sessions", type=int, default=128) parser.add_argument("--max-inference", type=int, default=8) parser.add_argument("--max-codex", type=int, default=2) parser.add_argument( "--openrouter-env", type=Path, help="显式只读取 OPENROUTER_API_KEY,配置 Jev;不自动发起请求", ) parser.add_argument( "--deepseek-env", type=Path, help="显式只读取 DEEPSEEK_API_KEY,配置 deepseek-flash;不自动调用", ) parser.add_argument("--origin", action="append", help="额外允许的本机工作台 Origin") args = parser.parse_args() if not 1 <= args.port <= 65535: parser.error("端口不合法") if args.website_origin: from .web_server import create_website_app from .web_sessions import Limits if args.openrouter_env or args.deepseek_env or args.origin: parser.error("网站模式不接受共享凭据或额外 Origin") if min(args.max_sessions, args.max_inference, args.max_codex) < 1: parser.error("网站容量必须大于零") defaults = budget = None if bool(args.website_key_file) != bool(args.website_budget_file): parser.error("服务器密钥必须同时配置持久化调用限额") if args.website_key_file: from .hosted_budget import HostedBudget defaults = { "llm": deepseek_llm(args.website_key_file), "jev": openrouter_jev(args.website_key_file), } budget = HostedBudget(args.website_budget_file) app = create_website_app( args.website_origin, args.state_dir, development=args.website_dev, trusted_proxies=args.trusted_proxy, defaults=defaults, budget=budget, limits=Limits( sessions=args.max_sessions, inference=args.max_inference, codex=args.max_codex ), ) web.run_app(app, host=args.bind, port=args.port, access_log=None, handler_cancellation=True) return if ( args.bind != "127.0.0.1" or args.website_dev or args.trusted_proxy or args.website_key_file or args.website_budget_file ): parser.error("本机模式必须仅回环监听") origins = { "http://localhost:5173", "http://127.0.0.1:5173", "http://localhost:4173", "http://127.0.0.1:4173", } for origin in args.origin or []: url = urlsplit(origin) if ( url.scheme not in ("http", "https") or url.hostname not in ("127.0.0.1", "localhost", "::1") or url.path or url.query or url.fragment or url.username or url.password ): parser.error("Origin 必须是完整的本机来源,不支持通配符") origins.add(origin) app = create_app(args.state_dir, origins=origins, port=args.port) if args.openrouter_env: app[STATE].connections.values["jev"] = openrouter_jev(args.openrouter_env) if args.deepseek_env: app[STATE].connections.values["llm"] = deepseek_llm(args.deepseek_env) print("服务令牌(仅当前进程有效,工作台内填写,不要保存到浏览器或 Git):", app[STATE].token) web.run_app(app, host="127.0.0.1", port=args.port, access_log=None, handler_cancellation=True) if __name__ == "__main__": main()