a6bf8632ce
Typed result structs replace empty envelopes. Equity place sends ref_id only so live additionalProperties:false schemas accept the call.
337 lines
8.7 KiB
Go
337 lines
8.7 KiB
Go
package watchlists
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
|
|
"s1d3sw1ped/robinhood-agentic-mcp/client"
|
|
"s1d3sw1ped/robinhood-agentic-mcp/internal/wire"
|
|
)
|
|
|
|
const (
|
|
toolLists = "get_watchlists"
|
|
toolItems = "get_watchlist_items"
|
|
toolOptionList = "get_option_watchlist"
|
|
toolPopular = "get_popular_watchlists"
|
|
toolCreate = "create_watchlist"
|
|
toolUpdate = "update_watchlist"
|
|
toolFollow = "follow_watchlist"
|
|
toolUnfollow = "unfollow_watchlist"
|
|
toolAdd = "add_to_watchlist"
|
|
toolRemove = "remove_from_watchlist"
|
|
toolAddOption = "add_option_to_watchlist"
|
|
toolRemoveOption = "remove_option_from_watchlist"
|
|
)
|
|
|
|
// ListsRequest is the argument set for get_watchlists (none).
|
|
type ListsRequest struct{}
|
|
|
|
// Watchlist is one list from get_watchlists. Title falls back to name.
|
|
type Watchlist struct {
|
|
ID string
|
|
Title string
|
|
}
|
|
|
|
// ListsResult is the parsed get_watchlists payload.
|
|
type ListsResult struct {
|
|
Watchlists []Watchlist
|
|
}
|
|
|
|
// ItemsRequest is the argument set for get_watchlist_items.
|
|
type ItemsRequest struct {
|
|
ListID string
|
|
}
|
|
|
|
// Instrument is the nested instrument object on a watchlist item.
|
|
type Instrument struct {
|
|
Symbol string
|
|
Type string
|
|
}
|
|
|
|
// Item is one row from get_watchlist_items. Object types are not filtered.
|
|
type Item struct {
|
|
Symbol string
|
|
ObjectType string
|
|
Instrument *Instrument
|
|
}
|
|
|
|
// ItemsResult is the parsed get_watchlist_items payload.
|
|
type ItemsResult struct {
|
|
Items []Item
|
|
}
|
|
|
|
// OptionListRequest is the argument set for get_option_watchlist (none).
|
|
type OptionListRequest struct{}
|
|
|
|
// OptionWatchItem is one contract on the options watchlist.
|
|
type OptionWatchItem struct {
|
|
OptionID string
|
|
Symbol string
|
|
Title string
|
|
PositionType string
|
|
}
|
|
|
|
// OptionListResult is the parsed get_option_watchlist payload.
|
|
type OptionListResult struct {
|
|
Items []OptionWatchItem
|
|
}
|
|
|
|
// PopularRequest is the argument set for get_popular_watchlists (none).
|
|
type PopularRequest struct{}
|
|
|
|
// PopularResult is the parsed get_popular_watchlists payload.
|
|
type PopularResult struct {
|
|
Watchlists []Watchlist
|
|
}
|
|
|
|
// CreateRequest is the argument set for create_watchlist.
|
|
type CreateRequest struct {
|
|
DisplayName string
|
|
IconEmoji string
|
|
DisplayDescription string
|
|
}
|
|
|
|
// UpdateRequest is the argument set for update_watchlist.
|
|
type UpdateRequest struct {
|
|
ListID string
|
|
DisplayName string
|
|
IconEmoji string
|
|
DisplayDescription string
|
|
}
|
|
|
|
// ListIDRequest is a single list_id argument.
|
|
type ListIDRequest struct {
|
|
ListID string
|
|
}
|
|
|
|
// AddRequest is the argument set for add_to_watchlist.
|
|
type AddRequest struct {
|
|
ListID string
|
|
Symbols []string
|
|
CurrencyPairIDs []string
|
|
IndexIDs []string
|
|
}
|
|
|
|
// RemoveRequest is the argument set for remove_from_watchlist.
|
|
type RemoveRequest struct {
|
|
ListID string
|
|
Symbols []string
|
|
CurrencyPairIDs []string
|
|
IndexIDs []string
|
|
}
|
|
|
|
// OptionMutateRequest is the argument set for add/remove option watchlist tools.
|
|
type OptionMutateRequest struct {
|
|
OptionIDs []string
|
|
PositionType string
|
|
}
|
|
|
|
// Lists calls get_watchlists.
|
|
func (c *Client) Lists(ctx context.Context, req ListsRequest) (ListsResult, error) {
|
|
raw, err := c.c.Call(ctx, toolLists, map[string]any{})
|
|
if err != nil {
|
|
return ListsResult{}, err
|
|
}
|
|
data := wire.Unwrap(raw)
|
|
var wrap struct {
|
|
Watchlists []watchlistJSON `json:"watchlists"`
|
|
}
|
|
if err := json.Unmarshal(data, &wrap); err != nil {
|
|
var list []watchlistJSON
|
|
if err := json.Unmarshal(data, &list); err != nil {
|
|
return ListsResult{}, client.ToolErrorf(toolLists, "parse: %w", err)
|
|
}
|
|
wrap.Watchlists = list
|
|
}
|
|
out := make([]Watchlist, 0, len(wrap.Watchlists))
|
|
for _, w := range wrap.Watchlists {
|
|
title := w.Title
|
|
if title == "" {
|
|
title = w.Name
|
|
}
|
|
out = append(out, Watchlist{ID: w.ID, Title: title})
|
|
}
|
|
return ListsResult{Watchlists: out}, nil
|
|
}
|
|
|
|
// Items calls get_watchlist_items. Every object_type is kept.
|
|
func (c *Client) Items(ctx context.Context, req ItemsRequest) (ItemsResult, error) {
|
|
raw, err := c.c.Call(ctx, toolItems, listIDArgs(req.ListID))
|
|
if err != nil {
|
|
return ItemsResult{}, err
|
|
}
|
|
data := wire.Unwrap(raw)
|
|
var wrap struct {
|
|
Items []itemJSON `json:"items"`
|
|
}
|
|
if err := json.Unmarshal(data, &wrap); err != nil {
|
|
var list []itemJSON
|
|
if err := json.Unmarshal(data, &list); err != nil {
|
|
return ItemsResult{}, client.ToolErrorf(toolItems, "parse: %w", err)
|
|
}
|
|
wrap.Items = list
|
|
}
|
|
out := make([]Item, 0, len(wrap.Items))
|
|
for _, it := range wrap.Items {
|
|
sym := it.Symbol
|
|
typ := it.ObjectType
|
|
var inst *Instrument
|
|
if it.Instrument != nil {
|
|
inst = &Instrument{Symbol: it.Instrument.Symbol, Type: it.Instrument.Type}
|
|
if typ == "" {
|
|
typ = it.Instrument.Type
|
|
}
|
|
if sym == "" {
|
|
sym = it.Instrument.Symbol
|
|
}
|
|
}
|
|
out = append(out, Item{Symbol: sym, ObjectType: typ, Instrument: inst})
|
|
}
|
|
return ItemsResult{Items: out}, nil
|
|
}
|
|
|
|
// OptionList calls get_option_watchlist.
|
|
func (c *Client) OptionList(ctx context.Context, req OptionListRequest) (OptionListResult, error) {
|
|
var out OptionListResult
|
|
if err := c.parse(ctx, toolOptionList, map[string]any{}, &out); err != nil {
|
|
return OptionListResult{}, err
|
|
}
|
|
return out, nil
|
|
}
|
|
|
|
// Popular calls get_popular_watchlists.
|
|
func (c *Client) Popular(ctx context.Context, req PopularRequest) (PopularResult, error) {
|
|
var out PopularResult
|
|
if err := c.parse(ctx, toolPopular, map[string]any{}, &out); err != nil {
|
|
return PopularResult{}, err
|
|
}
|
|
return out, nil
|
|
}
|
|
|
|
// Create calls create_watchlist.
|
|
func (c *Client) Create(ctx context.Context, req CreateRequest) error {
|
|
args := map[string]any{}
|
|
if req.DisplayName != "" {
|
|
args["display_name"] = req.DisplayName
|
|
}
|
|
if req.IconEmoji != "" {
|
|
args["icon_emoji"] = req.IconEmoji
|
|
}
|
|
if req.DisplayDescription != "" {
|
|
args["display_description"] = req.DisplayDescription
|
|
}
|
|
_, err := c.c.Call(ctx, toolCreate, args)
|
|
return err
|
|
}
|
|
|
|
// Update calls update_watchlist.
|
|
func (c *Client) Update(ctx context.Context, req UpdateRequest) error {
|
|
args := listIDArgs(req.ListID)
|
|
if req.DisplayName != "" {
|
|
args["display_name"] = req.DisplayName
|
|
}
|
|
if req.IconEmoji != "" {
|
|
args["icon_emoji"] = req.IconEmoji
|
|
}
|
|
if req.DisplayDescription != "" {
|
|
args["display_description"] = req.DisplayDescription
|
|
}
|
|
_, err := c.c.Call(ctx, toolUpdate, args)
|
|
return err
|
|
}
|
|
|
|
// Follow calls follow_watchlist.
|
|
func (c *Client) Follow(ctx context.Context, req ListIDRequest) error {
|
|
_, err := c.c.Call(ctx, toolFollow, listIDArgs(req.ListID))
|
|
return err
|
|
}
|
|
|
|
// Unfollow calls unfollow_watchlist.
|
|
func (c *Client) Unfollow(ctx context.Context, req ListIDRequest) error {
|
|
_, err := c.c.Call(ctx, toolUnfollow, listIDArgs(req.ListID))
|
|
return err
|
|
}
|
|
|
|
// Add calls add_to_watchlist.
|
|
func (c *Client) Add(ctx context.Context, req AddRequest) error {
|
|
_, err := c.c.Call(ctx, toolAdd, mutateArgs(req.ListID, req.Symbols, req.CurrencyPairIDs, req.IndexIDs))
|
|
return err
|
|
}
|
|
|
|
// Remove calls remove_from_watchlist.
|
|
func (c *Client) Remove(ctx context.Context, req RemoveRequest) error {
|
|
_, err := c.c.Call(ctx, toolRemove, mutateArgs(req.ListID, req.Symbols, req.CurrencyPairIDs, req.IndexIDs))
|
|
return err
|
|
}
|
|
|
|
// AddOption calls add_option_to_watchlist.
|
|
func (c *Client) AddOption(ctx context.Context, req OptionMutateRequest) error {
|
|
_, err := c.c.Call(ctx, toolAddOption, optionArgs(req))
|
|
return err
|
|
}
|
|
|
|
// RemoveOption calls remove_option_from_watchlist.
|
|
func (c *Client) RemoveOption(ctx context.Context, req OptionMutateRequest) error {
|
|
_, err := c.c.Call(ctx, toolRemoveOption, optionArgs(req))
|
|
return err
|
|
}
|
|
|
|
type watchlistJSON struct {
|
|
ID string `json:"id"`
|
|
Title string `json:"title"`
|
|
Name string `json:"name"`
|
|
}
|
|
|
|
type itemJSON struct {
|
|
Symbol string `json:"symbol"`
|
|
ObjectType string `json:"object_type"`
|
|
Instrument *struct {
|
|
Symbol string `json:"symbol"`
|
|
Type string `json:"type"`
|
|
} `json:"instrument"`
|
|
}
|
|
|
|
func listIDArgs(id string) map[string]any {
|
|
args := map[string]any{}
|
|
if id != "" {
|
|
args["list_id"] = id
|
|
}
|
|
return args
|
|
}
|
|
|
|
func mutateArgs(listID string, symbols, pairIDs, indexIDs []string) map[string]any {
|
|
args := listIDArgs(listID)
|
|
if len(symbols) > 0 {
|
|
args["symbols"] = symbols
|
|
}
|
|
if len(pairIDs) > 0 {
|
|
args["currency_pair_ids"] = pairIDs
|
|
}
|
|
if len(indexIDs) > 0 {
|
|
args["index_ids"] = indexIDs
|
|
}
|
|
return args
|
|
}
|
|
|
|
func optionArgs(req OptionMutateRequest) map[string]any {
|
|
args := map[string]any{}
|
|
if len(req.OptionIDs) > 0 {
|
|
args["option_ids"] = req.OptionIDs
|
|
}
|
|
if req.PositionType != "" {
|
|
args["position_type"] = req.PositionType
|
|
}
|
|
return args
|
|
}
|
|
|
|
func (c *Client) parse(ctx context.Context, tool string, args map[string]any, dest any) error {
|
|
raw, err := c.c.Call(ctx, tool, args)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if err := json.Unmarshal(wire.Unwrap(raw), dest); err != nil {
|
|
return client.ToolErrorf(tool, "parse: %w", err)
|
|
}
|
|
return nil
|
|
}
|