agent-Specialization/server/gateway_api.py
JOJO aa9bf377a3 feat(runtime): Gateway 收尾——session 传输暴露、host Bearer 通道、flask 依赖拆解、审批链路收口
- session.* 传输暴露:新增 gateway_api 蓝图(list/create/history),RuntimeService 补 create_session
- host Bearer 通道:gateway_auth 双通道认证(host 模式+回环限定,token 存 DATA_DIR/host_api_token),tasks/approval 路由接入
- api_v1 会话路由转调公共入口消双轨,会话索引/列表补齐 run_mode/model_key/custom_prompt_name/personalization_name
- flask 包依赖拆解:_flask_bridge 延迟桥接 + context 子包 PEP 562 懒加载,import server.tasks/runtime 不再拉起 flask
- 审批链路:mark_expired 终态回写(超时/软停止/取消三路径)+ 终态 TTL 惰性清理(3600s)
- 测试 patch 点随迁;全量 75 测试失败恰为 4 项存量,独立启动验收 4/4 全绿

Co-authored-by: Astrion powered by Kimi-K3 <astrion-agent@users.noreply.github.com>
2026-09-08 11:37:51 +08:00

112 lines
4.8 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""Gateway 公共协议路由2026-09-08 新增)。
定位docs/runtime_protocol.md 的 session.* 能力传输暴露——
为 CLI/GUI 等非 Web 客户端提供不依赖 Web 专属会话路由的公共入口,
Web 端既有 /api/conversations 路由保持不动。
路由全部支持双通道认证Web session 或 host Bearer token
- GET /api/runtime/sessions → session.list
- POST /api/runtime/sessions → session.create显式
- GET /api/runtime/sessions/<cid>/history → session.history
「加载会话用于展示」= session.history「让 agent 在某会话中执行」=
run.start 携带 conversation_id。不单独提供 session.load 命令
Web 的 load 语义是切换 terminal 内存态,属 Web 适配层视图概念)。
"""
from __future__ import annotations
from flask import Blueprint, jsonify, request, session
from modules.i18n import tr
from server.gateway_auth import api_login_or_host_token_required
from server.runtime.context import principal_from_session_snapshot
from server.runtime.service import runtime_service
gateway_bp = Blueprint("gateway", __name__)
def _resolve_workspace_id(explicit: str = "") -> str:
"""查询/创建未显式指定工作区时,回落当前会话工作区。"""
return str(explicit or session.get("workspace_id") or "default")
@gateway_bp.route("/api/runtime/sessions", methods=["GET"])
@api_login_or_host_token_required
def list_runtime_sessions():
"""session.list会话列表工作区/数量/多智能体筛选)。"""
workspace_id = _resolve_workspace_id(request.args.get("workspace_id", ""))
limit = request.args.get("limit", 50, type=int)
offset = request.args.get("offset", 0, type=int)
ma_param = request.args.get("multi_agent_mode", None)
multi_agent_mode = None if ma_param is None else ma_param in ("1", "true", "True")
username = str(session.get("username") or "")
principal = principal_from_session_snapshot(dict(session), workspace_id, username)
try:
result = runtime_service.list_sessions(
username,
workspace_id,
principal,
limit=limit,
offset=offset,
multi_agent_mode=multi_agent_mode,
)
return jsonify({"success": True, **(result or {})})
except PermissionError as exc:
return jsonify({"success": False, "error": str(exc)}), 403
except ValueError as exc:
return jsonify({"success": False, "error": str(exc)}), 400
except RuntimeError as exc:
return jsonify({"success": False, "error": str(exc)}), 503
@gateway_bp.route("/api/runtime/sessions", methods=["POST"])
@api_login_or_host_token_required
def create_runtime_session():
"""session.create显式创建会话返回 conversation_id。"""
data = request.get_json(silent=True) or {}
workspace_id = _resolve_workspace_id(str(data.get("workspace_id") or ""))
username = str(session.get("username") or "")
principal = principal_from_session_snapshot(dict(session), workspace_id, username)
try:
result = runtime_service.create_session(
username,
workspace_id,
principal,
run_mode=data.get("run_mode"),
thinking_mode=data.get("thinking_mode"),
model_key=data.get("model_key"),
multi_agent_mode=bool(data.get("multi_agent_mode")),
)
return jsonify({"success": True, **result})
except PermissionError as exc:
return jsonify({"success": False, "error": str(exc)}), 403
except ValueError as exc:
return jsonify({"success": False, "error": str(exc)}), 400
except RuntimeError as exc:
return jsonify({"success": False, "error": str(exc)}), 503
@gateway_bp.route("/api/runtime/sessions/<conversation_id>/history", methods=["GET"])
@api_login_or_host_token_required
def get_runtime_session_history(conversation_id: str):
"""session.history会话历史磁盘权威快照"""
workspace_id = _resolve_workspace_id(request.args.get("workspace_id", ""))
username = str(session.get("username") or "")
principal = principal_from_session_snapshot(dict(session), workspace_id, username)
try:
result = runtime_service.get_session_history(
username, workspace_id, conversation_id, principal
)
except PermissionError as exc:
return jsonify({"success": False, "error": str(exc)}), 403
except ValueError as exc:
return jsonify({"success": False, "error": str(exc)}), 400
except RuntimeError as exc:
return jsonify({"success": False, "error": str(exc)}), 503
if result is None:
return jsonify({"success": False, "error": tr("ctx_mgr.conversation_not_found", conversation_id=conversation_id)}), 404
return jsonify({"success": True, "conversation": result})
__all__ = ["gateway_bp"]