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>
132 lines
5.8 KiB
Python
132 lines
5.8 KiB
Python
"""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"]
|