plugin_api.py 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142
  1. """Authenticated Dashboard API, mounted at /api/plugins/yaoyao-bot-bridge."""
  2. import asyncio
  3. import importlib.util
  4. from pathlib import Path
  5. from fastapi import APIRouter, Request
  6. from fastapi.responses import JSONResponse
  7. _spec = importlib.util.spec_from_file_location(
  8. "_yaoyao_bot_bridge_bootstrap", Path(__file__).parents[1] / "bootstrap.py"
  9. )
  10. if _spec is None or _spec.loader is None:
  11. raise ImportError("夭夭工具桥加载失败")
  12. _bootstrap = importlib.util.module_from_spec(_spec)
  13. _spec.loader.exec_module(_bootstrap)
  14. bridge = _bootstrap.runtime()
  15. router = APIRouter()
  16. _model_spec = importlib.util.spec_from_file_location("_yaoyao_bot_model_settings", Path(__file__).parents[1] / "model_settings.py")
  17. _models = importlib.util.module_from_spec(_model_spec)
  18. _model_spec.loader.exec_module(_models)
  19. @router.get("/model-options")
  20. async def model_options(request: Request, profile: str = "default"):
  21. try:
  22. bridge.request_identity(request)
  23. return await asyncio.to_thread(_models.options, profile)
  24. except bridge.BridgeError as exc:
  25. return _error(exc)
  26. except _models.ModelSettingsError as exc:
  27. return JSONResponse(status_code=400, content={"detail": str(exc), "code": "invalid_model_settings"})
  28. except Exception:
  29. return JSONResponse(status_code=503, content={"detail": "模型工具桥与 Hermes 不兼容或模型服务暂不可用,请检查并重试", "code": "model_settings_unavailable"})
  30. @router.post("/model-settings/resolve")
  31. async def resolve_model_settings(request: Request):
  32. try:
  33. bridge.request_identity(request)
  34. body = await _body(request)
  35. return await asyncio.to_thread(_models.resolve, body.get("profile"), body.get("settings"))
  36. except bridge.BridgeError as exc:
  37. return _error(exc)
  38. except _models.ModelSettingsError as exc:
  39. return JSONResponse(status_code=400, content={"detail": str(exc), "code": "invalid_model_settings"})
  40. except Exception:
  41. return JSONResponse(status_code=503, content={"detail": "无法解析 Bot 模型配置,请检查工具桥与模型服务", "code": "model_settings_unavailable"})
  42. def _error(exc):
  43. return JSONResponse(
  44. status_code=exc.status,
  45. content={"detail": str(exc), "code": exc.code},
  46. )
  47. async def _body(request, limit=16384):
  48. raw = await request.body()
  49. if len(raw) > limit:
  50. raise bridge.BridgeError("绑定请求过大", 413, "invalid_request")
  51. try:
  52. body = await request.json()
  53. except Exception:
  54. raise bridge.BridgeError("绑定请求必须是 JSON 对象", 400, "invalid_request") from None
  55. if not isinstance(body, dict):
  56. raise bridge.BridgeError("绑定请求必须是 JSON 对象", 400, "invalid_request")
  57. return body
  58. @router.post("/computer-file")
  59. async def computer_file(request: Request):
  60. try:
  61. identity = bridge.request_identity(request)
  62. body = await _body(request, 36 * 1024 * 1024)
  63. return await asyncio.to_thread(bridge.computer_file, identity, body)
  64. except bridge.BridgeError as exc:
  65. return _error(exc)
  66. except Exception:
  67. return JSONResponse(status_code=502, content={"code": "computer_file_failed", "detail": "Hermes 文件传输失败"})
  68. @router.post("/memory-extract")
  69. async def memory_extract(request: Request):
  70. try:
  71. bridge.request_identity(request)
  72. body = await _body(request, 512 * 1024)
  73. return await bridge.extract_memory(body.get("profile"), body.get("prompt"))
  74. except bridge.BridgeError as exc:
  75. return _error(exc)
  76. except Exception:
  77. return JSONResponse(status_code=502, content={"code": "memory_extraction_failed", "detail": "Hermes 记忆提炼失败"})
  78. @router.get("/capabilities")
  79. def capabilities(request: Request, profile: str = "default"):
  80. try:
  81. bridge.request_identity(request)
  82. return bridge.capabilities(profile)
  83. except bridge.BridgeError as exc:
  84. return _error(exc)
  85. @router.post("/bind")
  86. async def bind(request: Request):
  87. try:
  88. identity = bridge.request_identity(request)
  89. body = await _body(request)
  90. return await asyncio.to_thread(bridge.bind, identity, body)
  91. except bridge.BridgeError as exc:
  92. return _error(exc)
  93. @router.post("/unbind")
  94. async def unbind(request: Request):
  95. try:
  96. identity = bridge.request_identity(request)
  97. return bridge.unbind(identity, await _body(request))
  98. except bridge.BridgeError as exc:
  99. return _error(exc)
  100. @router.post('/context')
  101. async def context(request: Request):
  102. try:
  103. identity = bridge.request_identity(request)
  104. body = await _body(request, 8 * 1024 * 1024)
  105. return await asyncio.to_thread(bridge.prepare_context, identity, body)
  106. except bridge.BridgeError as exc:
  107. return _error(exc)
  108. @router.post("/sampling")
  109. async def sampling(request: Request):
  110. try:
  111. identity = bridge.request_identity(request)
  112. return await bridge.sampling_request(identity, await _body(request))
  113. except bridge.BridgeError as exc:
  114. return _error(exc)
  115. except Exception:
  116. return JSONResponse(status_code=502, content={"code": "sampling_failed", "detail": "Hermes MCP 采样失败"})