18c9354095
- 注入数据库、运行时配置、统一响应、文件存储和平台 AI 能力,补充业务读写工具与客户快捷操作契约。 - 移除模块内重复的组织、客户、工单、标签、技能、旧工作流、MCP 和迁移实现,将身份权限与业务主体交由宿主管理。 - 使用 libSQL 重构向量存储,并完善图片消息、访客身份、排队调度、企业微信和支持聊天页面。 - 统一 HTTP、DTO 与 WebSocket 的 snake_case 协议,补齐模块初始化、业务动作和公共载荷等回归测试。
101 lines
3.0 KiB
Go
101 lines
3.0 KiB
Go
package runtime
|
|
|
|
import (
|
|
"encoding/json"
|
|
"strings"
|
|
"time"
|
|
|
|
applicationruntime "code.tczkiot.com/wlw/ai-agent/internal/ai/application/runtime"
|
|
"code.tczkiot.com/wlw/ai-agent/internal/models"
|
|
"code.tczkiot.com/wlw/ai-agent/internal/pkg/i18nx"
|
|
svc "code.tczkiot.com/wlw/ai-agent/internal/services"
|
|
)
|
|
|
|
type interruptMessagePreview struct {
|
|
Message string `json:"message"`
|
|
}
|
|
|
|
func buildConversationInterrupt(conversation models.Conversation, message models.Message, aiAgent models.AIAgent, summary *applicationruntime.RunResult) *models.ConversationInterrupt {
|
|
if summary == nil {
|
|
return nil
|
|
}
|
|
now := time.Now()
|
|
item := svc.ConversationInterruptService.GetByCheckPointID(summary.CheckPointID)
|
|
if item == nil {
|
|
item = &models.ConversationInterrupt{
|
|
CheckPointID: summary.CheckPointID,
|
|
CreatedAt: now,
|
|
}
|
|
}
|
|
item.ConversationID = conversation.ID
|
|
item.AIAgentID = aiAgent.ID
|
|
item.AgentRunID = summary.AgentRunID
|
|
item.SourceMessageID = message.ID
|
|
item.InterruptID = firstInterruptID(summary)
|
|
item.InterruptType = firstInterruptType(summary)
|
|
item.Status = "pending"
|
|
item.PromptText = resolveInterruptPrompt(summary)
|
|
item.RequestData = strings.TrimSpace(summary.CheckPointData)
|
|
item.UpdatedAt = now
|
|
return item
|
|
}
|
|
|
|
func resolveInterruptPrompt(summary *applicationruntime.RunResult) string {
|
|
if summary == nil || len(summary.Interrupts) == 0 {
|
|
return i18nx.Get("conversation.interrupt.defaultPrompt")
|
|
}
|
|
interrupt := summary.Interrupts[0]
|
|
if prompt := strings.TrimSpace(interrupt.PromptText); prompt != "" {
|
|
return prompt
|
|
}
|
|
if prompt := extractInterruptMessage(interrupt.InfoPreview); prompt != "" {
|
|
return prompt
|
|
}
|
|
if prompt := strings.TrimSpace(summary.ReplyText); prompt != "" {
|
|
return prompt
|
|
}
|
|
if interrupt.Type != "tool_confirmation" {
|
|
if prompt := strings.TrimSpace(interrupt.InfoPreview); prompt != "" {
|
|
return prompt
|
|
}
|
|
}
|
|
if displayName := strings.TrimSpace(interrupt.DisplayName); displayName != "" {
|
|
return "即将执行“" + displayName + "”,是否确认继续?"
|
|
}
|
|
return i18nx.Get("conversation.interrupt.defaultPrompt")
|
|
}
|
|
|
|
func extractInterruptMessage(infoPreview string) string {
|
|
infoPreview = strings.TrimSpace(infoPreview)
|
|
if infoPreview == "" {
|
|
return ""
|
|
}
|
|
var payload interruptMessagePreview
|
|
if err := json.Unmarshal([]byte(infoPreview), &payload); err != nil {
|
|
return ""
|
|
}
|
|
return strings.TrimSpace(payload.Message)
|
|
}
|
|
|
|
func firstInterruptID(summary *applicationruntime.RunResult) string {
|
|
if summary == nil || len(summary.Interrupts) == 0 {
|
|
return ""
|
|
}
|
|
return strings.TrimSpace(summary.Interrupts[0].ID)
|
|
}
|
|
|
|
func firstInterruptType(summary *applicationruntime.RunResult) string {
|
|
if summary == nil || len(summary.Interrupts) == 0 {
|
|
return ""
|
|
}
|
|
return strings.TrimSpace(summary.Interrupts[0].Type)
|
|
}
|
|
|
|
func isCheckpointMissingError(err error) bool {
|
|
if err == nil {
|
|
return false
|
|
}
|
|
message := strings.ToLower(strings.TrimSpace(err.Error()))
|
|
return strings.Contains(message, "failed to load from checkpoint") && strings.Contains(message, "not exist")
|
|
}
|