新建会话
  • 网站
  • 文档
  • PPT
  • 表格
  • 深度研究
Kimi Code Kimi Claw
历史会话
  • 查看手机应用
  • 关于我们
    • 探索月之暗面
    • 开放平台
    • 社会招聘
    • 校园招聘
    • 用户协议
    • 隐私协议
  • Language
  • 用户反馈

Secure Agent Data Pull Tool

Write me a tool to pull my bookmarked posts from X. It should call a secrets store to get the keys rather than env vars
I'll create a tool that pulls your X (Twitter) bookmarks using a secrets store for authentication. This keeps keys out of environment variables and agent reach.
Python
复制
"""
X Bookmarks Tool - Secure bookmark retrieval for agents.
Uses a secrets store (e.g., AWS Secrets Manager, HashiCorp Vault, 1Password)
instead of environment variables for credential management.
"""

import json
import requests
from typing import List, Dict, Optional, Protocol
from dataclasses import dataclass
from datetime import datetime


class SecretsStore(Protocol):
    """Protocol for secrets store implementations."""
    def get_secret(self, secret_name: str) -> Dict[str, str]: ...


@dataclass
class XPost:
    """Sanitized post data safe to pass to agents."""
    id: str
    text: str
    author_username: str
    created_at: datetime
    like_count: int
    bookmarked_at: Optional[datetime] = None
    
    def to_dict(self) -> Dict:
        return {
            "id": self.id,
            "text": self.text,
            "author": self.author_username,
            "created": self.created_at.isoformat(),
            "likes": self.like_count,
            "saved": self.bookmarked_at.isoformat() if self.bookmarked_at else None
        }


class AWSSecretsManager:
    """AWS Secrets Manager implementation."""
    
    def __init__(self, region: str = "us-east-1"):
        import boto3
        self.client = boto3.client("secretsmanager", region_name=region)
    
    def get_secret(self, secret_name: str) -> Dict[str, str]:
        response = self.client.get_secret_value(SecretId=secret_name)
        return json.loads(response["SecretString"])


class HashiCorpVault:
    """HashiCorp Vault implementation."""
    
    def __init__(self, vault_addr: str, role_id: str, secret_id: str):
        self.vault_addr = vault_addr.rstrip("/")
        # Authenticate with AppRole
        auth_resp = requests.post(
            f"{self.vault_addr}/v1/auth/approle/login",
            json={"role_id": role_id, "secret_id": secret_id}
        )
        self.token = auth_resp.json()["auth"]["client_token"]
    
    def get_secret(self, path: str) -> Dict[str, str]:
        headers = {"X-Vault-Token": self.token}
        response = requests.get(
            f"{self.vault_addr}/v1/secret/data/{path}",
            headers=headers
        )
        return response.json()["data"]["data"]


class OnePasswordConnect:
    """1Password Connect Server implementation."""
    
    def __init__(self, connect_host: str, connect_token: str):
        self.host = connect_host.rstrip("/")
        self.headers = {"Authorization": f"Bearer {connect_token}"}
    
    def get_secret(self, item_uuid: str) -> Dict[str, str]:
        response = requests.get(
            f"{self.host}/v1/items/{item_uuid}",
            headers=self.headers
        )
        item = response.json()
        # Extract fields from 1Password item structure
        fields = {f["label"]: f["value"] for f in item.get("fields", [])}
        return fields


class XBookmarksTool:
    """
    Tool for fetching X/Twitter bookmarks.
    Agents interact with this, never with X API directly.
    """
    
    X_API_BASE = "https://api.twitter.com/2"
    
    def __init__(
        self,
        secrets_store: SecretsStore,
        secret_path: str = "x-api-credentials",
        max_results: int = 100
    ):
        self.secrets = secrets_store
        self.secret_path = secret_path
        self.max_results = max_results
        self._credentials: Optional[Dict] = None
    
    def _load_credentials(self) -> Dict[str, str]:
        """Lazy load credentials from secrets store."""
        if self._credentials is None:
            self._credentials = self.secrets.get_secret(self.secret_path)
            required = {"bearer_token", "api_key", "api_secret"}
            if not required.issubset(self._credentials.keys()):
                raise ValueError(f"Missing required secrets: {required - set(self._credentials.keys())}")
        return self._credentials
    
    def _get_auth_header(self) -> Dict[str, str]:
        creds = self._load_credentials()
        return {"Authorization": f"Bearer {creds['bearer_token']}"}
    
    def _fetch_bookmarks_page(
        self,
        user_id: str,
        pagination_token: Optional[str] = None
    ) -> Dict:
        """Fetch single page of bookmarks from X API."""
        url = f"{self.X_API_BASE}/users/{user_id}/bookmarks"
        params = {
            "max_results": min(self.max_results, 100),
            "tweet.fields": "created_at,public_metrics,author_id",
            "expansions": "author_id",
            "user.fields": "username"
        }
        if pagination_token:
            params["pagination_token"] = pagination_token
        
        response = requests.get(
            url,
            headers=self._get_auth_header(),
            params=params,
            timeout=30
        )
        response.raise_for_status()
        return response.json()
    
    def _parse_posts(self, data: Dict) -> List[XPost]:
        """Parse API response into sanitized XPost objects."""
        tweets = data.get("data", [])
        users = {
            u["id"]: u["username"] 
            for u in data.get("includes", {}).get("users", [])
        }
        
        posts = []
        for tweet in tweets:
            metrics = tweet.get("public_metrics", {})
            posts.append(XPost(
                id=tweet["id"],
                text=tweet["text"],
                author_username=users.get(tweet["author_id"], "unknown"),
                created_at=datetime.fromisoformat(
                    tweet["created_at"].replace("Z", "+00:00")
                ),
                like_count=metrics.get("like_count", 0),
                bookmarked_at=None  # X API doesn't expose this
            ))
        return posts
    
    def get_bookmarks(
        self,
        user_id: Optional[str] = None,
        limit: Optional[int] = None
    ) -> List[Dict]:
        """
        Fetch bookmarks for a user.
        
        Args:
            user_id: X user ID (if None, fetches authenticated user)
            limit: Max posts to return (None for all)
        
        Returns:
            List of sanitized post dictionaries safe for agents
        """
        # If no user_id provided, look up authenticated user
        if user_id is None:
            user_id = self._get_authenticated_user_id()
        
        all_posts = []
        next_token = None
        
        while True:
            data = self._fetch_bookmarks_page(user_id, next_token)
            posts = self._parse_posts(data)
            all_posts.extend(posts)
            
            # Respect limit if provided
            if limit and len(all_posts) >= limit:
                all_posts = all_posts[:limit]
                break
            
            next_token = data.get("meta", {}).get("next_token")
            if not next_token:
                break
        
        return [post.to_dict() for post in all_posts]
    
    def _get_authenticated_user_id(self) -> str:
        """Get user ID from bearer token."""
        response = requests.get(
            f"{self.X_API_BASE}/users/me",
            headers=self._get_auth_header(),
            params={"user.fields": "id"}
        )
        response.raise_for_status()
        return response.json()["data"]["id"]
    
    def search_bookmarks(
        self,
        query: str,
        case_sensitive: bool = False
    ) -> List[Dict]:
        """
        Search bookmarked posts by text content.
        Safe for agents - no external API call, filters local data.
        """
        bookmarks = self.get_bookmarks()
        query = query if case_sensitive else query.lower()
        
        results = []
        for post in bookmarks:
            text = post["text"] if case_sensitive else post["text"].lower()
            if query in text:
                results.append(post)
        
        return results


# Example usage for different secrets stores
def create_tool_with_aws() -> XBookmarksTool:
    """Create tool using AWS Secrets Manager."""
    store = AWSSecretsManager(region="us-west-2")
    return XBookmarksTool(
        secrets_store=store,
        secret_path="prod/x-api/agent-readonly"
    )


def create_tool_with_vault() -> XBookmarksTool:
    """Create tool using HashiCorp Vault."""
    store = HashiCorpVault(
        vault_addr="https://vault.company.com",
        role_id="agent-role",
        secret_id="..."  # From secure distribution
    )
    return XBookmarksTool(
        secrets_store=store,
        secret_path="social-media/x-api"
    )


def create_tool_with_1password() -> XBookmarksTool:
    """Create tool using 1Password Connect."""
    store = OnePasswordConnect(
        connect_host="http://localhost:8080",
        connect_token="..."  # From OP_CONNECT_TOKEN
    )
    return XBookmarksTool(
        secrets_store=store,
        secret_path="uuid-of-x-api-item"
    )


# Agent-facing interface
class AgentBookmarkInterface:
    """
    Simplified interface exposed to agents.
    Only exposes safe, read-only operations.
    """
    
    def __init__(self, tool: XBookmarksTool):
        self._tool = tool
    
    def list_recent(self, count: int = 20) -> str:
        """Get recent bookmarks as JSON string."""
        posts = self._tool.get_bookmarks(limit=count)
        return json.dumps(posts, indent=2)
    
    def find_posts_about(self, topic: str) -> str:
        """Search bookmarks for topic."""
        results = self._tool.search_bookmarks(topic)
        return json.dumps(results, indent=2)
    
    def get_stats(self) -> str:
        """Get bookmark statistics."""
        posts = self._tool.get_bookmarks(limit=1000)
        return json.dumps({
            "total_fetched": len(posts),
            "top_authors": self._count_authors(posts),
            "date_range": self._date_range(posts)
        }, indent=2)
    
    def _count_authors(self, posts: List[Dict]) -> Dict[str, int]:
        from collections import Counter
        return dict(Counter(p["author"] for p in posts).most_common(5))
    
    def _date_range(self, posts: List[Dict]) -> Dict[str, str]:
        if not posts:
            return {}
        dates = [datetime.fromisoformat(p["created"]) for p in posts]
        return {
            "oldest": min(dates).isoformat(),
            "newest": max(dates).isoformat()
        }

Key Security Features

表格
FeatureImplementation
No env varsCredentials fetched from secrets store at runtime
Protocol-basedEasy to swap AWS, Vault, 1Password, etc.
Lazy loadingCredentials only fetched when needed
Sanitized outputXPost dataclass filters raw API response
No write opsRead-only interface for agents

Usage Example

Python
复制
# Setup (trusted code)
tool = create_tool_with_aws()
agent_interface = AgentBookmarkInterface(tool)

# Agent interaction (untrusted)
recent = agent_interface.list_recent(10)
python_posts = agent_interface.find_posts_about("python")
The agent sees only the AgentBookmarkInterface methods—no API keys, no raw HTTP, no credential access.