agent-Specialization/server/gateway_api.py
JOJO 5459d82637 feat(cli): 面板与状态栏接真实数据,/new 对齐草稿语义;host 设置面双通道化
CLI(bun 跑源码即生效):
- boot:loadBootData 一次性加载模型清单(含 context_window)/个性化默认
  (含压缩设置)/path 授权/会话列表;新会话 unshift 标 current
- /session:Enter 真实加载(timeline.reset + getSessionHistory 渲染历史文本)
- /new:对齐 web /new 页面语义——空对话草稿不立即建会话,首条消息
  createTask 不带 cid 由服务端惰性创建,响应回 id 后插入列表标 current
- /context + 状态栏:token_update 事件(运行期事件流)+ 打开面板时查询
  新端点 GET /api/runtime/sessions/<cid>/token-stats;百分比算法对齐 web
  InputComposer(自动压缩阈值优先于模型窗口)
- /model:Enter = 本地生效 + createTask 带 model_key/run_mode/thinking_mode
  覆盖(会话级即时)+ savePersonalization patch 存默认(effort default 存 null)
- /path:增删后 savePathAuths 全量提交两组(服务端 deny 校验保留)
- /agents /tasks /workflow /rewind:打开时拉真实列表(sub_agents/
  background_commands/workflows/versioning-checkpoints)
- 发送链路统一 sendWithSettings 携带 /model 生效值(含排队队列)

后端:
- 8 端点装饰器 api_login_required → api_login_or_host_token_required
  (personalization×2 / path-authorization×2 / sub_agents / background_commands
  / workflows / versioning-checkpoints),web session 行为不变
- headless_app 用 add_url_rule 共享上述视图函数(URL 与 full 一致);
  sub_agents/background_commands 支持 query 显式传 conversation_id
  (Bearer 装配的 terminal 无当前对话概念)
- RuntimeService 新增 get_session_token_stats;gateway_api 挂 token-stats 路由
- headless 404 兜底:非 /api/ 的 GET 路径回落告知页(对齐 SPA fallback)

验证:tsc  py_compile  冒烟 6/6  headless 装配 54 路由 
localconfig 无头实测读到真实配置 ;真实服务端到端待用户重启 8091 实机验收

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

132 lines
5.8 KiB
Python
Raw Permalink 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})
@gateway_bp.route("/api/runtime/sessions/<conversation_id>/token-stats", methods=["GET"])
@api_login_or_host_token_required
def get_runtime_session_token_stats(conversation_id: str):
"""session.token_stats会话 token 统计(累计输入/输出/缓存/当前上下文)。"""
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:
stats = runtime_service.get_session_token_stats(
username, workspace_id, conversation_id, principal
)
return jsonify({"success": True, "stats": stats})
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
__all__ = ["gateway_bp"]