- TaskRecord 去 session_data dict,直存 principal/task_params/directives + 可变 goal_progress 字段(__slots__) - create_chat_task 签名改为收 RuntimeContext 必填,TaskRecord.__init__ 同步精简(身份/消息/模式等全部从 ctx 派生) - _run_chat_task 身份还原/门闸认领/事件注入/terminal 属性设置 全部改为三层属性访问;goal_progress 回写改独立可变字段 - RuntimeService.create_task 原样透传 ctx;to_session_data() 删除 - task_public_payload / api.py 单任务载荷改三层读取 - 契约/注释/i18n 文案同步去 session_data 化;测试重写为三层断言 验证:75 测试全量回归,失败恰为 4 项存量(host_workspace_manager / skills_manager / token_usage_extractor / conversation_workspace_storage)
157 lines
6.4 KiB
Python
157 lines
6.4 KiB
Python
"""运行时上下文模型(RuntimeContext 三层分离)。
|
||
|
||
契约见 docs/runtime_contract.md §4.1:
|
||
|
||
- ``TrustedPrincipal``:可信身份与资源范围。只能由适配层在完成认证后构造;
|
||
禁止接受客户端或未来 Schedule payload 自报的 role/is_api_user。
|
||
- ``TaskParams``:本次任务参数(消息、媒体、模型/模式覆盖等)。
|
||
- ``InternalDirectives``:内部执行信息(门闸 token、通知回放等),
|
||
仅内部调用方(通知链/工作流/派发器)使用,普通客户端不可提交。
|
||
|
||
默认值解析优先级:本次显式传参(TaskParams)> 对话元数据绑定 >
|
||
用户偏好快照(TrustedPrincipal.preferred_*)> 系统默认。
|
||
"""
|
||
from __future__ import annotations
|
||
|
||
from dataclasses import dataclass, field
|
||
from typing import Any, Dict, List, Optional
|
||
|
||
|
||
@dataclass(frozen=True)
|
||
class TrustedPrincipal:
|
||
"""可信身份与资源范围(适配层认证后构造)。
|
||
|
||
preferred_* 是「用户偏好快照」:资源装配(get_user_resources)新建 terminal 时
|
||
的默认值来源;与 TaskParams 中的「本次覆盖」字段语义不同,不要混用。
|
||
"""
|
||
|
||
username: str
|
||
workspace_id: str
|
||
role: str = "user"
|
||
is_api_user: bool = False
|
||
host_mode: bool = False
|
||
host_workspace_id: Optional[str] = None
|
||
preferred_model_key: Optional[str] = None
|
||
preferred_run_mode: Optional[str] = None
|
||
preferred_thinking_mode: Optional[bool] = None
|
||
|
||
def validate(self) -> None:
|
||
if not str(self.username or "").strip():
|
||
raise ValueError("runtime_context: username 不能为空")
|
||
if not str(self.workspace_id or "").strip():
|
||
raise ValueError("runtime_context: workspace_id 不能为空")
|
||
|
||
|
||
@dataclass(frozen=True)
|
||
class TaskParams:
|
||
"""本次任务参数(优先级最高的覆盖层)。"""
|
||
|
||
message: str = ""
|
||
images: List[Any] = field(default_factory=list)
|
||
videos: List[Any] = field(default_factory=list)
|
||
files: List[str] = field(default_factory=list)
|
||
conversation_id: Optional[str] = None
|
||
model_key: Optional[str] = None
|
||
run_mode: Optional[str] = None
|
||
thinking_mode: Optional[bool] = None
|
||
max_iterations: Optional[int] = None
|
||
goal_mode: bool = False
|
||
skill_context_messages: List[Dict[str, str]] = field(default_factory=list)
|
||
message_source: Optional[str] = None
|
||
task_type: str = "chat"
|
||
# 审批/提问等待超时透传(秒)。None = 保持既有默认语义(3600s)不变;
|
||
# 超时后的语义(拒绝当前动作继续 vs 结束任务)属阶段三产品决策,
|
||
# 本阶段仅建立透传机制,适配层暂不向 Web 客户端暴露该字段。
|
||
approval_timeout_seconds: Optional[int] = None
|
||
|
||
def validate(self) -> None:
|
||
if self.run_mode is not None:
|
||
normalized = str(self.run_mode).lower()
|
||
if normalized not in {"fast", "thinking", "deep"}:
|
||
raise ValueError("runtime_context: run_mode 只支持 fast/thinking/deep")
|
||
|
||
|
||
@dataclass(frozen=True)
|
||
class InternalDirectives:
|
||
"""内部执行信息。仅内部调用方使用;HTTP 适配层禁止从请求体构造本结构。"""
|
||
|
||
# 派发方预占的主任务门闸 token,随任务移交由任务线程认领
|
||
main_task_gate_token: Optional[str] = None
|
||
# 通知类任务:把 user 消息写入任务事件流,保证轮询客户端/刷新后可见
|
||
auto_user_message_event: bool = False
|
||
auto_user_message_payload: Optional[Dict[str, Any]] = None
|
||
# 本批通知池中除触发消息外的前置通知,随任务事件流回放
|
||
preceding_user_notices: Optional[List[Dict[str, Any]]] = None
|
||
|
||
|
||
@dataclass(frozen=True)
|
||
class RuntimeContext:
|
||
"""一轮 Run 的完整显式上下文:可信身份 + 任务参数 + 内部指令。"""
|
||
|
||
principal: TrustedPrincipal
|
||
params: TaskParams
|
||
directives: InternalDirectives = field(default_factory=InternalDirectives)
|
||
|
||
def validate(self) -> None:
|
||
self.principal.validate()
|
||
self.params.validate()
|
||
|
||
@classmethod
|
||
def from_terminal(
|
||
cls,
|
||
terminal: Any,
|
||
workspace: Any,
|
||
username: str,
|
||
params: TaskParams,
|
||
directives: Optional[InternalDirectives] = None,
|
||
) -> "RuntimeContext":
|
||
"""从对话级 terminal/工作区构造(通知链、多智能体派发等内部调用方)。
|
||
|
||
host_mode 取 workspace.username == "host";偏好快照取 terminal 当前值。
|
||
"""
|
||
workspace_id = getattr(workspace, "workspace_id", None) or "default"
|
||
host_mode = bool(getattr(workspace, "username", None) == "host")
|
||
role = getattr(terminal, "user_role", None) or "user"
|
||
return cls(
|
||
principal=TrustedPrincipal(
|
||
username=username,
|
||
workspace_id=workspace_id,
|
||
role=role,
|
||
is_api_user=(role == "api"),
|
||
host_mode=host_mode,
|
||
host_workspace_id=workspace_id if host_mode else None,
|
||
preferred_model_key=getattr(terminal, "model_key", None),
|
||
preferred_run_mode=getattr(terminal, "run_mode", None),
|
||
preferred_thinking_mode=getattr(terminal, "thinking_mode", None),
|
||
),
|
||
params=params,
|
||
directives=directives or InternalDirectives(),
|
||
)
|
||
|
||
|
||
|
||
def principal_from_session_snapshot(
|
||
session_snapshot: Dict[str, Any],
|
||
workspace_id: str,
|
||
username: Optional[str] = None,
|
||
) -> TrustedPrincipal:
|
||
"""适配层工具:从 Flask session(或其 dict 快照)构造可信 principal。
|
||
|
||
调用方必须在完成认证后调用;``session_snapshot`` 由适配层显式传入
|
||
(本模块不 import flask,保持服务层可测试、无隐式上下文依赖)。
|
||
"""
|
||
snap = session_snapshot or {}
|
||
resolved_workspace = str(workspace_id or snap.get("workspace_id") or "default")
|
||
host_mode = bool(snap.get("host_mode"))
|
||
return TrustedPrincipal(
|
||
username=str(username or snap.get("username") or ""),
|
||
workspace_id=resolved_workspace,
|
||
role=str(snap.get("role") or "user"),
|
||
is_api_user=bool(snap.get("is_api_user")),
|
||
host_mode=host_mode,
|
||
host_workspace_id=str(snap.get("host_workspace_id") or (resolved_workspace if host_mode else "") or "") or None,
|
||
preferred_model_key=snap.get("model_key"),
|
||
preferred_run_mode=snap.get("run_mode"),
|
||
preferred_thinking_mode=snap.get("thinking_mode"),
|
||
)
|