mirror of
https://github.com/tiennm99/goclaw.git
synced 2026-07-26 12:18:47 +00:00
Rework of #911 against dev with correctness fixes. Public-facing channels (Facebook, Telegram, Discord, Feishu, WhatsApp, Zalo OA, Zalo Personal, Pancake, Slack) no longer receive raw internal error text when an agent run fails — an empty outbound is published instead so channels still clean up placeholders / typing indicators. Errors continue to be logged server-side at Error level. Applied on both hot paths: - cmd/gateway_consumer_normal.go (agent run failure) - cmd/gateway_subagent_announce_queue.go (announce lead run failure) Fixes vs #911: - Whitelist uses channels.Type* constants (compile-time safe) instead of string literals. The original PR's literals ("zalo", "line") did not match any real channel type, so Zalo OA/Personal and Pancake leaked errors while "line" was a dead branch. - Added TypePancake and TypeSlack which were missing. - Empty Content in Facebook.Send and Pancake.Send now short-circuits (matching Telegram/Discord/Slack). Previously, suppressed errors flowed to Graph/Pancake APIs as empty payloads, triggering 400s and channel health degradation. - Added table-driven test for isExternalChannel covering all real channel types + empty + unknown + legacy "zalo" short form. Closes #911
129 lines
4.4 KiB
Go
129 lines
4.4 KiB
Go
package cmd
|
|
|
|
import (
|
|
"fmt"
|
|
"log/slog"
|
|
"strings"
|
|
|
|
"github.com/nextlevelbuilder/goclaw/internal/channels"
|
|
)
|
|
|
|
// Matching TS pi-embedded-helpers/errors.ts error classification.
|
|
// Never expose raw JSON/API payloads to the user.
|
|
func formatAgentError(err error) string {
|
|
raw := err.Error()
|
|
lower := strings.ToLower(raw)
|
|
|
|
// 1. Timeout — must be checked BEFORE context overflow because
|
|
// "context deadline exceeded" contains both "context" and "exceeded",
|
|
// which would false-positive match the context overflow heuristic.
|
|
if containsAny(lower, "timeout", "timed out", "deadline exceeded") {
|
|
return "⚠️ Request timed out. Please try again."
|
|
}
|
|
|
|
// 2. Context overflow
|
|
if isContextOverflowError(lower) {
|
|
return "⚠️ Context overflow — message too large for this model. Try /new to start a fresh session."
|
|
}
|
|
|
|
// 3. Role ordering / message format errors (tool_use_id mismatch, roles must alternate, etc.)
|
|
if isMessageFormatError(lower) {
|
|
return "⚠️ Session history conflict — please try again. If this persists, use /new to start a fresh session."
|
|
}
|
|
|
|
// 4. Rate limit
|
|
if containsAny(lower, "rate limit", "rate_limit", "too many requests", "429", "quota exceeded", "resource_exhausted") {
|
|
return "⚠️ API rate limit reached. Please try again later."
|
|
}
|
|
|
|
// 5. Overloaded
|
|
if strings.Contains(lower, "overloaded") {
|
|
return "⚠️ The AI service is temporarily overloaded. Please try again in a moment."
|
|
}
|
|
|
|
// 6. Billing
|
|
if containsAny(lower, "billing", "insufficient credits", "credit balance", "payment required", "402") {
|
|
return "⚠️ API billing error — your API key may have run out of credits. Check your provider's billing dashboard."
|
|
}
|
|
|
|
// 7. Auth errors
|
|
if containsAny(lower, "invalid api key", "invalid_api_key", "unauthorized", "forbidden", "authentication", "401", "403", "access denied") {
|
|
return "⚠️ Authentication error. Please check your API key configuration."
|
|
}
|
|
|
|
// 8. Model config
|
|
if strings.Contains(lower, "not a valid model") {
|
|
return "⚠️ Model configuration error. Please check your config and restart."
|
|
}
|
|
|
|
// 9. Generic — log the full error but show only a safe message to user
|
|
slog.Warn("unclassified agent error", "error", raw)
|
|
return "⚠️ Sorry, something went wrong processing your message. Please try again."
|
|
}
|
|
|
|
// isContextOverflowError checks for context window/size overflow patterns.
|
|
func isContextOverflowError(lower string) bool {
|
|
return containsAny(lower,
|
|
"request_too_large",
|
|
"context length exceeded",
|
|
"maximum context length",
|
|
"prompt is too long",
|
|
"exceeds model context window",
|
|
"request exceeds the maximum size",
|
|
) || (strings.Contains(lower, "context") &&
|
|
containsAny(lower, "overflow", "too large", "too long", "limit", "exceeded"))
|
|
}
|
|
|
|
// isExternalChannel reports whether a channel type serves end users on a
|
|
// public-facing platform (Facebook, Telegram, etc.). Internal error details
|
|
// must not be forwarded to these channels — the caller publishes an empty
|
|
// outbound instead so placeholders get cleaned up without leaking technical
|
|
// error text to end users. Internal types ("ws", "") return false.
|
|
func isExternalChannel(channelType string) bool {
|
|
switch channelType {
|
|
case channels.TypeFacebook,
|
|
channels.TypeTelegram,
|
|
channels.TypeDiscord,
|
|
channels.TypeFeishu,
|
|
channels.TypeWhatsApp,
|
|
channels.TypeZaloOA,
|
|
channels.TypeZaloPersonal,
|
|
channels.TypePancake,
|
|
channels.TypeSlack:
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
|
|
// isMessageFormatError checks for tool_use/tool_result mismatch, role ordering,
|
|
// and other message format errors that indicate corrupted session history.
|
|
func isMessageFormatError(lower string) bool {
|
|
return containsAny(lower,
|
|
"tool_use_id",
|
|
"tool_use.id",
|
|
"unexpected tool",
|
|
"roles must alternate",
|
|
"incorrect role information",
|
|
"invalid request format",
|
|
"tool_result block",
|
|
"tool_use block",
|
|
)
|
|
}
|
|
|
|
// containsAny returns true if s contains any of the given substrings.
|
|
func containsAny(s string, substrs ...string) bool {
|
|
for _, sub := range substrs {
|
|
if strings.Contains(s, sub) {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
// formatQuotaExceeded formats a user-friendly quota exceeded message.
|
|
func formatQuotaExceeded(result channels.QuotaResult) string {
|
|
labels := map[string]string{"hour": "Hourly", "day": "Daily", "week": "Weekly"}
|
|
return fmt.Sprintf("⚠️ %s request limit reached (%d/%d). Please try again later.",
|
|
labels[result.Window], result.Used, result.Limit)
|
|
}
|