mirror of
https://github.com/tiennm99/goclaw.git
synced 2026-09-20 04:23:29 +00:00
* feat(feishu): add list_group_members tool + native @mention support
Add agent tool to list all members in a Lark group chat via
GET /open-apis/im/v1/chats/{chat_id}/members with auto-pagination.
- New tool `list_group_members`: returns member IDs + display names
- Native @mention conversion: @ou_xxx in outbound messages becomes
real Lark mentions (post: "at" element, card: <at> markdown tag)
- Auto-sync discovered members into ContactStore
- GroupMemberProvider interface on Channel for extensibility
- Add im:chat.members:read scope to channel setup UI
Requires Lark app scope: im:chat.members:read
* fix(feishu): URL-encode API params + channel-aware tool filtering
- URL-encode chatID (PathEscape) and pageToken (QueryEscape) in
ListChatMembers to prevent path traversal and query corruption
- Add slog.Warn when ListGroupMembers fails for observability
- Add ChannelAware interface so tools can declare required channel types
- ListGroupMembersTool now declares RequiredChannelTypes=["feishu"]
- loop.go filters out channel-specific tools when channel type mismatches,
so list_group_members only appears for Feishu conversations
---------
Co-authored-by: Luvu182 <208665161+Luvu182@users.noreply.github.com>
Co-authored-by: viettranx <viettranx@gmail.com>
98 lines
2.9 KiB
Go
98 lines
2.9 KiB
Go
package tools
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
|
|
"github.com/nextlevelbuilder/goclaw/internal/channels"
|
|
)
|
|
|
|
// GroupMemberLister abstracts listing members of a group chat.
|
|
// Implemented by channels.Manager.ListGroupMembers.
|
|
type GroupMemberLister func(ctx context.Context, channel, chatID string) ([]channels.GroupMember, error)
|
|
|
|
// GroupMemberListerAware tools can receive a group member lister function.
|
|
type GroupMemberListerAware interface {
|
|
SetGroupMemberLister(GroupMemberLister)
|
|
}
|
|
|
|
// ListGroupMembersTool allows the agent to list members in a group chat.
|
|
type ListGroupMembersTool struct {
|
|
lister GroupMemberLister
|
|
}
|
|
|
|
func NewListGroupMembersTool() *ListGroupMembersTool {
|
|
return &ListGroupMembersTool{}
|
|
}
|
|
|
|
func (t *ListGroupMembersTool) SetGroupMemberLister(l GroupMemberLister) { t.lister = l }
|
|
|
|
func (t *ListGroupMembersTool) RequiredChannelTypes() []string { return []string{"feishu"} }
|
|
|
|
func (t *ListGroupMembersTool) Name() string { return "list_group_members" }
|
|
func (t *ListGroupMembersTool) Description() string {
|
|
return "List all members of the current group chat. Returns member IDs (open_id) and display names. Only works in group conversations on supported channels (Lark/Feishu). Use this to find out who is in the group, check attendance, or identify members. To @mention a member in a message, use @member_id (e.g. @ou_abc123) — it will be converted to a native mention with notification."
|
|
}
|
|
|
|
func (t *ListGroupMembersTool) Parameters() map[string]any {
|
|
return map[string]any{
|
|
"type": "object",
|
|
"properties": map[string]any{
|
|
"channel": map[string]any{
|
|
"type": "string",
|
|
"description": "Channel name (default: current channel from context)",
|
|
},
|
|
"chat_id": map[string]any{
|
|
"type": "string",
|
|
"description": "Group chat ID (default: current chat from context)",
|
|
},
|
|
},
|
|
"required": []string{},
|
|
}
|
|
}
|
|
|
|
func (t *ListGroupMembersTool) Execute(ctx context.Context, args map[string]any) *Result {
|
|
if t.lister == nil {
|
|
return ErrorResult("list_group_members: no group member lister available")
|
|
}
|
|
|
|
channel, _ := args["channel"].(string)
|
|
if channel == "" {
|
|
channel = ToolChannelFromCtx(ctx)
|
|
}
|
|
if channel == "" {
|
|
return ErrorResult("channel is required (no current channel in context)")
|
|
}
|
|
|
|
chatID, _ := args["chat_id"].(string)
|
|
if chatID == "" {
|
|
chatID = ToolChatIDFromCtx(ctx)
|
|
}
|
|
if chatID == "" {
|
|
return ErrorResult("chat_id is required (no current chat in context)")
|
|
}
|
|
|
|
members, err := t.lister(ctx, channel, chatID)
|
|
if err != nil {
|
|
return ErrorResult(fmt.Sprintf("failed to list group members: %v", err))
|
|
}
|
|
|
|
type memberOut struct {
|
|
MemberID string `json:"member_id"`
|
|
Name string `json:"name"`
|
|
}
|
|
out := make([]memberOut, len(members))
|
|
for i, m := range members {
|
|
out[i] = memberOut{MemberID: m.MemberID, Name: m.Name}
|
|
}
|
|
|
|
data, _ := json.Marshal(map[string]any{
|
|
"channel": channel,
|
|
"chat_id": chatID,
|
|
"count": len(out),
|
|
"members": out,
|
|
})
|
|
return NewResult(string(data))
|
|
}
|