package models import ( "fmt" "strings" ) // Address 是三维寻址 name@path.session 的解析结果 type Address struct { Name string `json:"name"` // Agent 实例名(或人类用户名) Path string `json:"path"` // 工作区路径(可含 /,可为空) Session string `json:"session"` // 会话别名;"new" = 新建;"" = 默认会话 Raw string `json:"raw"` // 原始字符串 } // SessionMode 是 session 位的三种语义 type SessionMode int const ( // SessionDefault:session 位省略 → 投递到 name@path 的默认会话(不存在则建立) SessionDefault SessionMode = iota // SessionNew:session 位为 new → 强制新建一个会话 SessionNew // SessionNamed:session 位为具体别名 → 必须已存在,否则无法送达 SessionNamed ) // Mode 返回该地址 session 位的语义 func (a Address) Mode() SessionMode { switch a.Session { case "": return SessionDefault case "new": return SessionNew default: return SessionNamed } } // IsNewSession 表示该地址要求新建会话(仅 session == "new")。 // 注意:session 位省略不等于 new,那是「默认会话」,见 Mode()。 func (a Address) IsNewSession() bool { return a.Mode() == SessionNew } // IsDefaultSession 表示该地址省略了 session 位,走默认会话 func (a Address) IsDefaultSession() bool { return a.Mode() == SessionDefault } func (a Address) String() string { return a.Raw } // ParseAddress 解析 name@path.session 三维地址。 // // 支持形态: // // deepseekharness@/program.updatefeature → name=deepseekharness path=/program session=updatefeature // pi@root.new → name=pi path=root session=new(新建) // builder@ModelRouter.fix-leak → name=builder path=ModelRouter session=fix-leak // human@.new → name=human path="" session=new // human → name=human path="" session=""(默认会话) // // 规则: // - 第一个 @ 之前是 name(必填) // - @ 之后按【最后一个 .】切成 path 与 session,因此 path 内可以包含 . 与 / // - 没有 . 时,整段视为 path,session 为空(默认会话) // // session 位三态语义见 Address.Mode():省略=默认会话,new=新建,其他=必须已存在。 func ParseAddress(s string) (Address, error) { raw := strings.TrimSpace(s) if raw == "" { return Address{}, fmt.Errorf("empty address") } // 只有形如 "@name@path.session" 时才剥掉前导 @; // "@ModelRouter.new" 缺少 name,应当报错而不是被当成名字。 trimmed := raw if strings.HasPrefix(raw, "@") && strings.Contains(raw[1:], "@") { trimmed = raw[1:] } at := strings.Index(trimmed, "@") if at < 0 { // 只有名字:human / builder name := strings.TrimSpace(trimmed) if name == "" { return Address{}, fmt.Errorf("missing agent name in %q", raw) } return Address{Name: name, Raw: raw}, nil } name := strings.TrimSpace(trimmed[:at]) if name == "" { return Address{}, fmt.Errorf("missing agent name in %q", raw) } rest := trimmed[at+1:] // 按最后一个 . 切 path / session;path 内允许 / 与 . var path, session string if dot := strings.LastIndex(rest, "."); dot >= 0 { path = rest[:dot] session = rest[dot+1:] } else { path = rest } return Address{ Name: name, Path: strings.TrimSpace(path), Session: strings.TrimSpace(session), Raw: raw, }, nil } // FormatAddress 把三段拼回可寻址的 name@path.session。 // // **必须走这个函数而不是自己拼字符串**:path 为空时(人类用户没有工作区) // 朴素拼接得到 "admin.silent-harbor",而它没有 @,ParseAddress 会把整串当成 // 名字,session 位丢失,地址静默失效。空 path 也必须留下那个 @ 与 . —— // "admin@.silent-harbor" 才解析成 name=admin path="" session=silent-harbor。 // // session 传空则省略该位(默认会话语义)。 func FormatAddress(name, path, session string) string { name = strings.TrimSpace(name) path = strings.TrimSpace(path) session = strings.TrimSpace(session) if name == "" { return "" } if session == "" { if path == "" { return name } return name + "@" + path } return name + "@" + path + "." + session } // WithSession 返回同一收件方在指定会话下的地址。 // 用于把 .new 换成刚建出来的会话别名 —— 参与方拿到的地址必须是能再次投递的那个。 func (a Address) WithSession(session string) string { return FormatAddress(a.Name, a.Path, session) } // ParseAddressList 解析逗号/分号/空白分隔的多个地址(用于 CC) func ParseAddressList(s string) ([]Address, error) { raw := strings.TrimSpace(s) if raw == "" { return nil, nil } fields := strings.FieldsFunc(raw, func(r rune) bool { return r == ',' || r == ';' || r == '\n' || r == '\t' || r == ' ' }) out := make([]Address, 0, len(fields)) for _, f := range fields { addr, err := ParseAddress(f) if err != nil { return nil, err } out = append(out, addr) } return out, nil }