a digital person for bluesky

Fix feed tool to be fully self-contained for sandboxing

- Move FEED_PRESETS dictionary inside function body
- Tools must be completely self-contained with no external dependencies
- Update Pydantic schema to use hardcoded list instead of dynamic reference

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>

Changed files
+12 -13
tools
+12 -13
tools/feed.py
··· 3 3 from typing import Optional, Union 4 4 5 5 6 - # Predefined feed mappings 7 - FEED_PRESETS = { 8 - "home": None, # Home timeline (default) 9 - "discover": "at://did:plc:z72i7hdynmk6r22z27h6tvur/app.bsky.feed.generator/whats-hot", 10 - "ai-for-grownups": "at://did:plc:gfrmhdmjvxn2sjedzboeudef/app.bsky.feed.generator/ai-for-grownups", 11 - "atmosphere": "at://did:plc:gfrmhdmjvxn2sjedzboeudef/app.bsky.feed.generator/the-atmosphere" 12 - } 13 - 14 - 15 6 class FeedArgs(BaseModel): 16 - feed_name: Optional[str] = Field(None, description=f"Named feed preset: {list(FEED_PRESETS.keys())}. If not provided, returns home timeline") 7 + feed_name: Optional[str] = Field(None, description="Named feed preset: ['home', 'discover', 'ai-for-grownups', 'atmosphere']. If not provided, returns home timeline") 17 8 feed_uri: Optional[str] = Field(None, description="Custom feed URI (e.g., 'at://did:plc:abc/app.bsky.feed.generator/feed-name'). Overrides feed_name if provided") 18 9 max_posts: int = Field(default=25, description="Maximum number of posts to retrieve (max 100)") 19 10 ··· 35 26 import requests 36 27 37 28 try: 29 + # Predefined feed mappings (must be inside function for sandboxing) 30 + feed_presets = { 31 + "home": None, # Home timeline (default) 32 + "discover": "at://did:plc:z72i7hdynmk6r22z27h6tvur/app.bsky.feed.generator/whats-hot", 33 + "ai-for-grownups": "at://did:plc:gfrmhdmjvxn2sjedzboeudef/app.bsky.feed.generator/ai-for-grownups", 34 + "atmosphere": "at://did:plc:gfrmhdmjvxn2sjedzboeudef/app.bsky.feed.generator/the-atmosphere" 35 + } 36 + 38 37 # Validate inputs 39 38 max_posts = min(max_posts, 100) 40 39 ··· 45 44 feed_display_name = feed_uri.split('/')[-1] if '/' in feed_uri else feed_uri 46 45 elif feed_name: 47 46 # Look up named preset 48 - if feed_name not in FEED_PRESETS: 49 - available_feeds = list(FEED_PRESETS.keys()) 47 + if feed_name not in feed_presets: 48 + available_feeds = list(feed_presets.keys()) 50 49 raise Exception(f"Unknown feed name '{feed_name}'. Available feeds: {available_feeds}") 51 - resolved_feed_uri = FEED_PRESETS[feed_name] 50 + resolved_feed_uri = feed_presets[feed_name] 52 51 feed_display_name = feed_name 53 52 else: 54 53 # Default to home timeline