// 画布 Agent 面向用户的错误文案净化。 // // 模型、工具与上游媒体服务的原始错误可能包含密钥、内部地址或冗长堆栈; // 这里统一转换成可读、可展示的中文说明,原始错误只进诊断日志。 package app import ( "errors" "strings" "unicode/utf8" "infinite-canvas/backend/internal/model" ) // Only expose known failure categories; raw provider errors can contain URLs and credentials. func cloudAgentModelFailure(task *model.Task) (string, string) { detail, reason := "模型任务未成功", "model_task_failed" if diagnostic := taskExecutionDiagnostic(task); diagnostic != nil && diagnostic.Code == string(ReasonUpstreamDNSFailed) { return "模型服务域名解析失败,请检查渠道域名和后端 DNS 配置;本轮已停止。请在任务中心检查模型任务 " + task.ID, string(ReasonUpstreamDNSFailed) } raw := strings.ToLower(task.Error) switch { case strings.Contains(task.Error, "没有返回内容"): // 思考模型的典型失败:整个输出预算被推理吃掉,正文与工具调用皆空。 detail, reason = "上游连续返回空内容(通常是思考占满输出预算);已自动关思考并放大预算重试仍失败,建议换用非思考模型或调小上下文", "model_empty_output" case strings.Contains(task.Error, cloudAgentStepTimeoutError): detail, reason = "单步模型调用超过执行时限仍未返回(长思考或上下文过大时常见);已自动关思考重试仍超时,可在管理端调大 Agent 单步超时", "model_step_timeout" case strings.Contains(raw, "connection reset by peer"): detail, reason = "模型连接被对端或中间网络设备重置", "model_connection_reset" case strings.Contains(raw, "timeout"), strings.Contains(raw, "deadline exceeded"): detail, reason = "模型请求超时", "model_request_timeout" case strings.Contains(raw, "connection refused"): detail, reason = "无法连接模型服务(连接被拒绝)", "model_connection_refused" } return detail + ";本轮已停止。请在任务中心检查模型任务 " + task.ID, reason } func cloudAgentSafeToolError(err error) string { if err == nil { return "" } var readLoopErr *cloudAgentReadLoopError if errors.As(err, &readLoopErr) { return readLoopErr.Error() } var appErr *AppError if errors.As(err, &appErr) && appErr != nil { message := strings.TrimSpace(appErr.Message) if cloudAgentSafeUserMessage(message) { return message } } return "工具执行失败,请检查输入或稍后重试" } func cloudAgentSafeUserMessage(message string) bool { if message == "" || !utf8.ValidString(message) || strings.ContainsAny(message, "\x00\r\n") || utf8.RuneCountInString(message) > 240 { return false } lower := strings.ToLower(message) for _, marker := range []string{ "http://", "https://", "ftp://", "file://", "authorization", "cookie", "secret", "token", "api_key", "apikey", "x-api-key", "/var/", "/tmp/", "\\", "stack trace", "traceback", " at ", "sql:", "sqlite", "postgres", } { if strings.Contains(lower, marker) { return false } } return true } // cloudAgentSafeMediaTaskError preserves a short, user-facing task diagnostic // while refusing provider details that commonly contain URLs, credentials, or // internal request metadata. Task.Error is not a safe presentation field. func cloudAgentSafeMediaTaskError(task *model.Task) string { if task == nil || strings.TrimSpace(task.Error) == "" { return "媒体任务未成功" } // 与画布节点展示同一套分类:网络/审核/存储/HTTP 状态归类,可读的供应商原因原样保留。 return userFacingTaskError(task.Error) }