Files
clawgo/pkg/api/server_common_helpers.go
2026-03-15 13:41:19 +08:00

293 lines
7.4 KiB
Go

package api
import (
"encoding/json"
"fmt"
"net"
"os"
"path/filepath"
"strings"
"time"
"github.com/YspCoder/clawgo/pkg/tools"
)
func resolveRelativeFilePath(root, raw string) (string, string, error) {
root = strings.TrimSpace(root)
if root == "" {
return "", "", fmt.Errorf("workspace not configured")
}
clean := filepath.Clean(strings.TrimSpace(raw))
if clean == "." || clean == "" || strings.HasPrefix(clean, "..") || filepath.IsAbs(clean) {
return "", "", fmt.Errorf("invalid path")
}
full := filepath.Join(root, clean)
cleanRoot := filepath.Clean(root)
if full != cleanRoot {
prefix := cleanRoot + string(os.PathSeparator)
if !strings.HasPrefix(filepath.Clean(full), prefix) {
return "", "", fmt.Errorf("invalid path")
}
}
return clean, full, nil
}
func relativeFilePathStatus(err error) int {
if err == nil {
return 200
}
switch {
case err.Error() == "workspace not configured":
return 500
default:
return 400
}
}
func readRelativeTextFile(root, raw string) (string, string, bool, error) {
clean, full, err := resolveRelativeFilePath(root, raw)
if err != nil {
return "", "", false, err
}
b, err := os.ReadFile(full)
if err != nil {
if os.IsNotExist(err) {
return clean, "", false, nil
}
return clean, "", false, err
}
return clean, string(b), true, nil
}
func writeRelativeTextFile(root, raw string, content string, ensureDir bool) (string, error) {
clean, full, err := resolveRelativeFilePath(root, raw)
if err != nil {
return "", err
}
if ensureDir {
if err := os.MkdirAll(filepath.Dir(full), 0755); err != nil {
return "", err
}
}
if err := os.WriteFile(full, []byte(content), 0644); err != nil {
return "", err
}
return clean, nil
}
func (s *Server) memoryFilePath(name string) string {
workspace := strings.TrimSpace(s.workspacePath)
if workspace == "" {
return ""
}
return filepath.Join(workspace, "memory", strings.TrimSpace(name))
}
func buildAgentTreeRoot(nodeID string, items []map[string]interface{}) map[string]interface{} {
rootID := "main"
for _, item := range items {
if strings.TrimSpace(stringFromMap(item, "type")) == "router" && strings.TrimSpace(stringFromMap(item, "agent_id")) != "" {
rootID = strings.TrimSpace(stringFromMap(item, "agent_id"))
break
}
}
nodesByID := make(map[string]map[string]interface{}, len(items)+1)
for _, item := range items {
id := strings.TrimSpace(stringFromMap(item, "agent_id"))
if id == "" {
continue
}
nodesByID[id] = map[string]interface{}{
"agent_id": id,
"display_name": stringFromMap(item, "display_name"),
"role": stringFromMap(item, "role"),
"type": stringFromMap(item, "type"),
"transport": fallbackString(stringFromMap(item, "transport"), "local"),
"managed_by": stringFromMap(item, "managed_by"),
"node_id": stringFromMap(item, "node_id"),
"parent_agent_id": stringFromMap(item, "parent_agent_id"),
"enabled": boolFromMap(item, "enabled"),
"children": []map[string]interface{}{},
}
}
root, ok := nodesByID[rootID]
if !ok {
root = map[string]interface{}{
"agent_id": rootID,
"display_name": "Main Agent",
"role": "orchestrator",
"type": "router",
"transport": "local",
"managed_by": "derived",
"enabled": true,
"children": []map[string]interface{}{},
}
nodesByID[rootID] = root
}
for _, item := range items {
id := strings.TrimSpace(stringFromMap(item, "agent_id"))
if id == "" || id == rootID {
continue
}
parentID := strings.TrimSpace(stringFromMap(item, "parent_agent_id"))
if parentID == "" {
parentID = rootID
}
parent, ok := nodesByID[parentID]
if !ok {
parent = root
}
children, _ := parent["children"].([]map[string]interface{})
parent["children"] = append(children, nodesByID[id])
}
return map[string]interface{}{
"node_id": nodeID,
"agent_id": root["agent_id"],
"root": root,
"child_cnt": len(root["children"].([]map[string]interface{})),
}
}
func stringFromMap(item map[string]interface{}, key string) string {
return tools.MapStringArg(item, key)
}
func boolFromMap(item map[string]interface{}, key string) bool {
if item == nil {
return false
}
v, _ := tools.MapBoolArg(item, key)
return v
}
func rawStringFromMap(item map[string]interface{}, key string) string {
return tools.MapRawStringArg(item, key)
}
func stringListFromMap(item map[string]interface{}, key string) []string {
return tools.MapStringListArg(item, key)
}
func intFromMap(item map[string]interface{}, key string, fallback int) int {
return tools.MapIntArg(item, key, fallback)
}
func fallbackString(value, fallback string) string {
value = strings.TrimSpace(value)
if value != "" {
return value
}
return strings.TrimSpace(fallback)
}
func firstNonEmptyString(values ...string) string {
for _, v := range values {
if strings.TrimSpace(v) != "" {
return strings.TrimSpace(v)
}
}
return ""
}
func detectLocalIP() string {
ifaces, err := net.Interfaces()
if err == nil {
for _, iface := range ifaces {
if iface.Flags&net.FlagUp == 0 || iface.Flags&net.FlagLoopback != 0 {
continue
}
addrs, _ := iface.Addrs()
for _, a := range addrs {
var ip net.IP
switch v := a.(type) {
case *net.IPNet:
ip = v.IP
case *net.IPAddr:
ip = v.IP
}
if ip == nil || ip.IsLoopback() {
continue
}
ip = ip.To4()
if ip == nil {
continue
}
return ip.String()
}
}
}
conn, err := net.Dial("udp", "8.8.8.8:80")
if err == nil {
defer conn.Close()
if ua, ok := conn.LocalAddr().(*net.UDPAddr); ok && ua.IP != nil {
if ip := ua.IP.To4(); ip != nil {
return ip.String()
}
}
}
return ""
}
func normalizeCronJob(v interface{}) map[string]interface{} {
if v == nil {
return map[string]interface{}{}
}
b, err := json.Marshal(v)
if err != nil {
return map[string]interface{}{"raw": fmt.Sprintf("%v", v)}
}
var m map[string]interface{}
if err := json.Unmarshal(b, &m); err != nil {
return map[string]interface{}{"raw": string(b)}
}
out := map[string]interface{}{}
for k, val := range m {
out[k] = val
}
if sch, ok := m["schedule"].(map[string]interface{}); ok {
kind := stringFromMap(sch, "kind")
if expr := stringFromMap(sch, "expr"); expr != "" {
out["expr"] = expr
} else if strings.EqualFold(strings.TrimSpace(kind), "every") {
if every := intFromMap(sch, "everyMs", 0); every > 0 {
out["expr"] = fmt.Sprintf("@every %s", (time.Duration(every) * time.Millisecond).String())
}
} else if strings.EqualFold(strings.TrimSpace(kind), "at") {
if at := intFromMap(sch, "atMs", 0); at > 0 {
out["expr"] = time.UnixMilli(int64(at)).Format(time.RFC3339)
}
}
}
if payload, ok := m["payload"].(map[string]interface{}); ok {
if msg, ok := payload["message"]; ok {
out["message"] = msg
}
if d, ok := payload["deliver"]; ok {
out["deliver"] = d
}
if c, ok := payload["channel"]; ok {
out["channel"] = c
}
if to, ok := payload["to"]; ok {
out["to"] = to
}
}
return out
}
func normalizeCronJobs(v interface{}) []map[string]interface{} {
b, err := json.Marshal(v)
if err != nil {
return []map[string]interface{}{}
}
var arr []interface{}
if err := json.Unmarshal(b, &arr); err != nil {
return []map[string]interface{}{}
}
out := make([]map[string]interface{}, 0, len(arr))
for _, it := range arr {
out = append(out, normalizeCronJob(it))
}
return out
}