Files
smartmate/backend/services/agent/sv/task_rpc_adapter.go
Losita 3b6fca44a6 Version: 0.9.77.dev.260505
后端:
1.阶段 6 CP4/CP5 目录收口与共享边界纯化
- 将 backend 根目录收口为 services、client、gateway、cmd、shared 五个一级目录
- 收拢 bootstrap、inits、infra/kafka、infra/outbox、conv、respond、pkg、middleware,移除根目录旧实现与空目录
- 将 utils 下沉到 services/userauth/internal/auth,将 logic 下沉到 services/schedule/core/planning
- 将迁移期 runtime 桥接实现统一收拢到 services/runtime/{conv,dao,eventsvc,model},删除 shared/legacy 与未再被 import 的旧 service 实现
- 将 gateway/shared/respond 收口为 HTTP/Gin 错误写回适配,shared/respond 仅保留共享错误语义与状态映射
- 将 HTTP IdempotencyMiddleware 与 RateLimitMiddleware 收口到 gateway/middleware
- 将 GormCachePlugin 下沉到 shared/infra/gormcache,将共享 RateLimiter 下沉到 shared/infra/ratelimit,将 agent token budget 下沉到 services/agent/shared
- 删除 InitEino 兼容壳,收缩 cmd/internal/coreinit 仅保留旧组合壳残留域初始化语义
- 更新微服务迁移计划与桌面 checklist,补齐 CP4/CP5 当前切流点、目录终态与验证结果
- 完成 go test ./...、git diff --check 与最终真实 smoke;health、register/login、task/create+get、schedule/today、task-class/list、memory/items、agent chat/meta/timeline/context-stats 全部 200,SSE 合并结果为 CP5_OK 且 [DONE] 只有 1 个
2026-05-05 23:25:07 +08:00

195 lines
5.8 KiB
Go

package sv
import (
"context"
"encoding/json"
"errors"
"strings"
"time"
agentmodel "github.com/LoveLosita/smartflow/backend/services/agent/model"
"github.com/LoveLosita/smartflow/backend/services/runtime/model"
taskcontracts "github.com/LoveLosita/smartflow/backend/shared/contracts/task"
"github.com/LoveLosita/smartflow/backend/shared/respond"
)
const quickTaskCreateRPCTimeout = 3 * time.Second
// TaskRPCClient 描述 agent 快捷任务链路访问 task zrpc 所需的最小能力。
//
// 职责边界:
// 1. 只覆盖快捷任务的创建和查询,不暴露 task DAO 或其它写接口;
// 2. 不要求 agent 编排层感知 pb / grpc 细节;
// 3. 错误原样返回,由 quick_task 节点转换成面向用户的失败文案。
type TaskRPCClient interface {
AddTask(ctx context.Context, req taskcontracts.AddTaskRequest) (json.RawMessage, error)
GetUserTasks(ctx context.Context, userID int) (json.RawMessage, error)
}
type taskRPCAdapter struct {
client TaskRPCClient
}
// NewTaskRPCQuickTaskDeps 把 task zrpc client 适配成 agent 快捷任务依赖。
//
// 职责边界:
// 1. 只替换 agent quick task 的 task DAO 直连路径;
// 2. 不迁移 task-class upsert、schedule provider 或 agent 编排本体;
// 3. client 为空时返回零值依赖,让 quick_task 节点沿用既有“依赖缺失则报错”语义。
func NewTaskRPCQuickTaskDeps(client TaskRPCClient) agentmodel.QuickTaskDeps {
if client == nil {
return agentmodel.QuickTaskDeps{}
}
adapter := &taskRPCAdapter{client: client}
return agentmodel.QuickTaskDeps{
CreateTask: adapter.CreateTask,
QueryTasks: adapter.QueryTasks,
}
}
// CreateTask 通过 task zrpc 创建四象限任务,返回 task_id。
func (a *taskRPCAdapter) CreateTask(
userID int,
title string,
priorityGroup int,
estimatedSections int,
deadlineAt *time.Time,
urgencyThresholdAt *time.Time,
) (int, error) {
if a == nil || a.client == nil {
return 0, errors.New("task rpc client is nil")
}
ctx, cancel := context.WithTimeout(context.Background(), quickTaskCreateRPCTimeout)
defer cancel()
// 调用目的:把 quick_task 节点产出的结构化任务写入 task 服务,避免 agent 继续直连 tasks 表。
raw, err := a.client.AddTask(ctx, taskcontracts.AddTaskRequest{
UserID: userID,
Title: strings.TrimSpace(title),
PriorityGroup: priorityGroup,
EstimatedSections: estimatedSections,
DeadlineAt: deadlineAt,
UrgencyThresholdAt: urgencyThresholdAt,
})
if err != nil {
return 0, err
}
var resp taskcontracts.AddTaskResponse
if err := json.Unmarshal(raw, &resp); err != nil {
return 0, err
}
if resp.ID <= 0 {
return 0, errors.New("task rpc add task returned invalid task id")
}
return resp.ID, nil
}
// QueryTasks 通过 task zrpc 读取用户任务,再复用 agent 侧既有过滤、排序和展示转换语义。
func (a *taskRPCAdapter) QueryTasks(
ctx context.Context,
userID int,
params agentmodel.TaskQueryParams,
) ([]agentmodel.TaskQueryResult, error) {
if a == nil || a.client == nil {
return nil, errors.New("task rpc client is nil")
}
raw, err := a.client.GetUserTasks(ctx, userID)
if err != nil {
if errors.Is(err, respond.UserTasksEmpty) {
return []agentmodel.TaskQueryResult{}, nil
}
return nil, err
}
var items []taskcontracts.TaskListItem
if len(raw) > 0 && string(raw) != "null" {
if err := json.Unmarshal(raw, &items); err != nil {
return nil, err
}
}
tasks := taskListItemsToModels(items)
req := agentmodel.TaskQueryRequest{
UserID: userID,
Quadrant: params.Quadrant,
SortBy: params.SortBy,
Order: params.Order,
Limit: params.Limit,
IncludeCompleted: params.IncludeCompleted,
Keyword: params.Keyword,
DeadlineBefore: params.DeadlineBefore,
DeadlineAfter: params.DeadlineAfter,
}
filtered := make([]model.Task, 0, len(tasks))
for _, task := range tasks {
if !taskMatchesQueryFilter(task, req) {
continue
}
filtered = append(filtered, task)
}
sortTasksForQuery(filtered, req)
if req.Limit > 0 && len(filtered) > req.Limit {
filtered = filtered[:req.Limit]
}
return taskModelsToQueryResults(filtered), nil
}
func taskListItemsToModels(items []taskcontracts.TaskListItem) []model.Task {
if len(items) == 0 {
return nil
}
result := make([]model.Task, 0, len(items))
for _, item := range items {
result = append(result, model.Task{
ID: item.ID,
UserID: item.UserID,
Title: item.Title,
Priority: item.PriorityGroup,
EstimatedSections: model.NormalizeEstimatedSections(&item.EstimatedSections),
IsCompleted: item.IsCompleted,
DeadlineAt: parseTaskListTime(item.Deadline),
UrgencyThresholdAt: parseTaskListTime(item.UrgencyThresholdAt),
})
}
return result
}
func taskModelsToQueryResults(tasks []model.Task) []agentmodel.TaskQueryResult {
if len(tasks) == 0 {
return []agentmodel.TaskQueryResult{}
}
results := make([]agentmodel.TaskQueryResult, 0, len(tasks))
for _, task := range tasks {
deadlineStr := ""
if task.DeadlineAt != nil {
deadlineStr = task.DeadlineAt.In(time.Local).Format("2006-01-02 15:04")
}
results = append(results, agentmodel.TaskQueryResult{
ID: task.ID,
Title: task.Title,
PriorityGroup: task.Priority,
EstimatedSections: model.NormalizeEstimatedSections(&task.EstimatedSections),
IsCompleted: task.IsCompleted,
DeadlineAt: deadlineStr,
})
}
return results
}
func parseTaskListTime(value string) *time.Time {
value = strings.TrimSpace(value)
if value == "" {
return nil
}
for _, layout := range []string{time.RFC3339Nano, time.RFC3339, "2006-01-02 15:04:05", "2006-01-02 15:04"} {
parsed, err := time.ParseInLocation(layout, value, time.Local)
if err == nil {
return &parsed
}
}
return nil
}