feat(x): add 28 new X API v2 tool integrations and expand OAuth scopes (#3365)

* feat(x): add 28 new X API v2 tool integrations and expand OAuth scopes

* fix(x): add missing nextToken param to search tweets and fix XCreateTweetParams type

* fix(x): correct API spec issues in retweeted_by, quote_tweets, personalized_trends, and usage tools

* fix(x): add missing newestId and oldestId to error meta in get_liked_tweets and get_quote_tweets

* fix(x): add missing newestId/oldestId to get_liked_tweets success branch and includes to XTweetListResponse

* fix(x): add error handling to create_tweet and delete_tweet transformResponse

* fix(x): add error handling and logger to all X tools

* fix(x): revert block requiredScopes to match current operations

* feat(x): update block to support all 28 new X API v2 tools

* fix(x): add missing text output and fix hiddenResult output key mismatch

* docs(x): regenerate docs for all 28 new X API v2 tools
This commit is contained in:
Waleed
2026-02-26 22:40:57 -08:00
committed by GitHub
parent 78901ef517
commit 9233d4ebc9
34 changed files with 5381 additions and 154 deletions

View File

@@ -29,74 +29,65 @@ In Sim, the X integration enables sophisticated social media automation scenario
## Usage Instructions
Integrate X into the workflow. Can post a new tweet, get tweet details, search tweets, and get user profile.
Integrate X into the workflow. Search tweets, manage bookmarks, follow/block/mute users, like and retweet, view trends, and more.
## Tools
### `x_write`
### `x_create_tweet`
Post new tweets, reply to tweets, or create polls on X (Twitter)
Create a new tweet, reply, or quote tweet on X
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `text` | string | Yes | The text content of your tweet \(max 280 characters\) |
| `replyTo` | string | No | ID of the tweet to reply to \(e.g., 1234567890123456789\) |
| `mediaIds` | array | No | Array of media IDs to attach to the tweet |
| `poll` | object | No | Poll configuration for the tweet |
| `text` | string | Yes | The text content of the tweet \(max 280 characters\) |
| `replyToTweetId` | string | No | Tweet ID to reply to |
| `quoteTweetId` | string | No | Tweet ID to quote |
| `mediaIds` | string | No | Comma-separated media IDs to attach \(up to 4\) |
| `replySettings` | string | No | Who can reply: "mentionedUsers", "following", "subscribers", or "verified" |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `tweet` | object | The newly created tweet data |
| ↳ `id` | string | Tweet ID |
| ↳ `text` | string | Tweet content text |
| ↳ `createdAt` | string | Tweet creation timestamp |
| ↳ `authorId` | string | ID of the tweet author |
| ↳ `conversationId` | string | Conversation thread ID |
| ↳ `attachments` | object | Media or poll attachments |
| ↳ `mediaKeys` | array | Media attachment keys |
| ↳ `pollId` | string | Poll ID if poll attached |
| `id` | string | The ID of the created tweet |
| `text` | string | The text of the created tweet |
### `x_read`
### `x_delete_tweet`
Read tweet details, including replies and conversation context
Delete a tweet authored by the authenticated user
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `tweetId` | string | Yes | ID of the tweet to read \(e.g., 1234567890123456789\) |
| `includeReplies` | boolean | No | Whether to include replies to the tweet |
| `tweetId` | string | Yes | The ID of the tweet to delete |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `tweet` | object | The main tweet data |
| ↳ `id` | string | Tweet ID |
| ↳ `text` | string | Tweet content text |
| ↳ `createdAt` | string | Tweet creation timestamp |
| ↳ `authorId` | string | ID of the tweet author |
| `context` | object | Conversation context including parent and root tweets |
| `deleted` | boolean | Whether the tweet was successfully deleted |
### `x_search`
### `x_search_tweets`
Search for tweets using keywords, hashtags, or advanced queries
Search for recent tweets using keywords, hashtags, or advanced query operators
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `query` | string | Yes | Search query \(e.g., "AI news", "#technology", "from:username"\). Supports X search operators |
| `maxResults` | number | No | Maximum number of results to return \(e.g., 10, 25, 50\). Default: 10, max: 100 |
| `startTime` | string | No | Start time for search \(ISO 8601 format\) |
| `endTime` | string | No | End time for search \(ISO 8601 format\) |
| `sortOrder` | string | No | Sort order for results \(recency or relevancy\) |
| `query` | string | Yes | Search query \(supports operators like "from:", "to:", "#hashtag", "has:images", "is:retweet", "lang:"\) |
| `maxResults` | number | No | Maximum number of results \(10-100, default 10\) |
| `startTime` | string | No | Oldest UTC timestamp in ISO 8601 format \(e.g., 2024-01-01T00:00:00Z\) |
| `endTime` | string | No | Newest UTC timestamp in ISO 8601 format |
| `sinceId` | string | No | Returns tweets with ID greater than this |
| `untilId` | string | No | Returns tweets with ID less than this |
| `sortOrder` | string | No | Sort order: "recency" or "relevancy" |
| `nextToken` | string | No | Pagination token for next page of results |
#### Output
@@ -104,38 +95,748 @@ Search for tweets using keywords, hashtags, or advanced queries
| --------- | ---- | ----------- |
| `tweets` | array | Array of tweets matching the search query |
| ↳ `id` | string | Tweet ID |
| ↳ `text` | string | Tweet content |
| ↳ `createdAt` | string | Creation timestamp |
| ↳ `text` | string | Tweet text content |
| ↳ `createdAt` | string | Tweet creation timestamp |
| ↳ `authorId` | string | Author user ID |
| `includes` | object | Additional data including user profiles and media |
| ↳ `conversationId` | string | Conversation thread ID |
| ↳ `inReplyToUserId` | string | User ID being replied to |
| ↳ `publicMetrics` | object | Engagement metrics |
| ↳ `retweetCount` | number | Number of retweets |
| ↳ `replyCount` | number | Number of replies |
| ↳ `likeCount` | number | Number of likes |
| ↳ `quoteCount` | number | Number of quotes |
| `includes` | object | Additional data including user profiles |
| ↳ `users` | array | Array of user objects referenced in tweets |
| ↳ `id` | string | User ID |
| ↳ `username` | string | Username without @ symbol |
| ↳ `name` | string | Display name |
| ↳ `description` | string | User bio |
| ↳ `profileImageUrl` | string | Profile image URL |
| ↳ `verified` | boolean | Whether the user is verified |
| ↳ `metrics` | object | User statistics |
| ↳ `followersCount` | number | Number of followers |
| ↳ `followingCount` | number | Number of users following |
| ↳ `tweetCount` | number | Total number of tweets |
| `meta` | object | Search metadata including result count and pagination tokens |
| ↳ `resultCount` | number | Number of results returned |
| ↳ `newestId` | string | ID of the newest tweet |
| ↳ `oldestId` | string | ID of the oldest tweet |
| ↳ `nextToken` | string | Pagination token for next page |
### `x_user`
### `x_get_tweets_by_ids`
Get user profile information
Look up multiple tweets by their IDs (up to 100)
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `username` | string | Yes | Username to look up without @ symbol \(e.g., elonmusk, openai\) |
| `ids` | string | Yes | Comma-separated tweet IDs \(up to 100\) |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `user` | object | X user profile information |
| `tweets` | array | Array of tweets matching the provided IDs |
| ↳ `id` | string | Tweet ID |
| ↳ `text` | string | Tweet text content |
| ↳ `createdAt` | string | Tweet creation timestamp |
| ↳ `authorId` | string | Author user ID |
| ↳ `conversationId` | string | Conversation thread ID |
| ↳ `inReplyToUserId` | string | User ID being replied to |
| ↳ `publicMetrics` | object | Engagement metrics |
| ↳ `retweetCount` | number | Number of retweets |
| ↳ `replyCount` | number | Number of replies |
| ↳ `likeCount` | number | Number of likes |
| ↳ `quoteCount` | number | Number of quotes |
| `includes` | object | Additional data including user profiles |
| ↳ `users` | array | Array of user objects referenced in tweets |
| ↳ `id` | string | User ID |
| ↳ `username` | string | Username without @ symbol |
| ↳ `name` | string | Display name |
| ↳ `description` | string | User bio |
| ↳ `profileImageUrl` | string | Profile image URL |
| ↳ `verified` | boolean | Whether the user is verified |
| ↳ `metrics` | object | User statistics |
| ↳ `followersCount` | number | Number of followers |
| ↳ `followingCount` | number | Number of users following |
| ↳ `tweetCount` | number | Total number of tweets |
### `x_get_quote_tweets`
Get tweets that quote a specific tweet
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `tweetId` | string | Yes | The tweet ID to get quote tweets for |
| `maxResults` | number | No | Maximum number of results \(10-100, default 10\) |
| `paginationToken` | string | No | Pagination token for next page |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `tweets` | array | Array of quote tweets |
| ↳ `id` | string | Tweet ID |
| ↳ `text` | string | Tweet text content |
| ↳ `createdAt` | string | Tweet creation timestamp |
| ↳ `authorId` | string | Author user ID |
| ↳ `conversationId` | string | Conversation thread ID |
| ↳ `inReplyToUserId` | string | User ID being replied to |
| ↳ `publicMetrics` | object | Engagement metrics |
| ↳ `retweetCount` | number | Number of retweets |
| ↳ `replyCount` | number | Number of replies |
| ↳ `likeCount` | number | Number of likes |
| ↳ `quoteCount` | number | Number of quotes |
| `meta` | object | Pagination metadata |
| ↳ `resultCount` | number | Number of results returned |
| ↳ `nextToken` | string | Token for next page |
### `x_hide_reply`
Hide or unhide a reply to a tweet authored by the authenticated user
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `tweetId` | string | Yes | The reply tweet ID to hide or unhide |
| `hidden` | boolean | Yes | Set to true to hide the reply, false to unhide |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `hidden` | boolean | Whether the reply is now hidden |
### `x_get_user_tweets`
Get tweets authored by a specific user
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `userId` | string | Yes | The user ID whose tweets to retrieve |
| `maxResults` | number | No | Maximum number of results \(5-100, default 10\) |
| `paginationToken` | string | No | Pagination token for next page of results |
| `startTime` | string | No | Oldest UTC timestamp in ISO 8601 format |
| `endTime` | string | No | Newest UTC timestamp in ISO 8601 format |
| `sinceId` | string | No | Returns tweets with ID greater than this |
| `untilId` | string | No | Returns tweets with ID less than this |
| `exclude` | string | No | Comma-separated types to exclude: "retweets", "replies" |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `tweets` | array | Array of tweets by the user |
| ↳ `id` | string | Tweet ID |
| ↳ `text` | string | Tweet text content |
| ↳ `createdAt` | string | Tweet creation timestamp |
| ↳ `authorId` | string | Author user ID |
| ↳ `conversationId` | string | Conversation thread ID |
| ↳ `inReplyToUserId` | string | User ID being replied to |
| ↳ `publicMetrics` | object | Engagement metrics |
| ↳ `retweetCount` | number | Number of retweets |
| ↳ `replyCount` | number | Number of replies |
| ↳ `likeCount` | number | Number of likes |
| ↳ `quoteCount` | number | Number of quotes |
| `includes` | object | Additional data including user profiles |
| ↳ `users` | array | Array of user objects referenced in tweets |
| ↳ `id` | string | User ID |
| ↳ `username` | string | Username without @ symbol |
| ↳ `name` | string | Display name |
| ↳ `description` | string | User bio |
| ↳ `profileImageUrl` | string | Profile image URL |
| ↳ `verified` | boolean | Whether the user is verified |
| ↳ `metrics` | object | User statistics |
| ↳ `followersCount` | number | Number of followers |
| ↳ `followingCount` | number | Number of users following |
| ↳ `tweetCount` | number | Total number of tweets |
| `meta` | object | Pagination metadata |
| ↳ `resultCount` | number | Number of results returned |
| ↳ `newestId` | string | ID of the newest tweet |
| ↳ `oldestId` | string | ID of the oldest tweet |
| ↳ `nextToken` | string | Token for next page |
| ↳ `previousToken` | string | Token for previous page |
### `x_get_user_mentions`
Get tweets that mention a specific user
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `userId` | string | Yes | The user ID whose mentions to retrieve |
| `maxResults` | number | No | Maximum number of results \(5-100, default 10\) |
| `paginationToken` | string | No | Pagination token for next page of results |
| `startTime` | string | No | Oldest UTC timestamp in ISO 8601 format |
| `endTime` | string | No | Newest UTC timestamp in ISO 8601 format |
| `sinceId` | string | No | Returns tweets with ID greater than this |
| `untilId` | string | No | Returns tweets with ID less than this |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `tweets` | array | Array of tweets mentioning the user |
| ↳ `id` | string | Tweet ID |
| ↳ `text` | string | Tweet text content |
| ↳ `createdAt` | string | Tweet creation timestamp |
| ↳ `authorId` | string | Author user ID |
| ↳ `conversationId` | string | Conversation thread ID |
| ↳ `inReplyToUserId` | string | User ID being replied to |
| ↳ `publicMetrics` | object | Engagement metrics |
| ↳ `retweetCount` | number | Number of retweets |
| ↳ `replyCount` | number | Number of replies |
| ↳ `likeCount` | number | Number of likes |
| ↳ `quoteCount` | number | Number of quotes |
| `includes` | object | Additional data including user profiles |
| ↳ `users` | array | Array of user objects referenced in tweets |
| ↳ `id` | string | User ID |
| ↳ `username` | string | Username without @ symbol |
| ↳ `name` | string | Display name |
| ↳ `description` | string | User bio |
| ↳ `profileImageUrl` | string | Profile image URL |
| ↳ `verified` | boolean | Whether the user is verified |
| ↳ `metrics` | object | User statistics |
| ↳ `followersCount` | number | Number of followers |
| ↳ `followingCount` | number | Number of users following |
| ↳ `tweetCount` | number | Total number of tweets |
| `meta` | object | Pagination metadata |
| ↳ `resultCount` | number | Number of results returned |
| ↳ `newestId` | string | ID of the newest tweet |
| ↳ `oldestId` | string | ID of the oldest tweet |
| ↳ `nextToken` | string | Token for next page |
| ↳ `previousToken` | string | Token for previous page |
### `x_get_user_timeline`
Get the reverse chronological home timeline for the authenticated user
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `userId` | string | Yes | The authenticated user ID |
| `maxResults` | number | No | Maximum number of results \(1-100, default 10\) |
| `paginationToken` | string | No | Pagination token for next page of results |
| `startTime` | string | No | Oldest UTC timestamp in ISO 8601 format |
| `endTime` | string | No | Newest UTC timestamp in ISO 8601 format |
| `sinceId` | string | No | Returns tweets with ID greater than this |
| `untilId` | string | No | Returns tweets with ID less than this |
| `exclude` | string | No | Comma-separated types to exclude: "retweets", "replies" |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `tweets` | array | Array of timeline tweets |
| ↳ `id` | string | Tweet ID |
| ↳ `text` | string | Tweet text content |
| ↳ `createdAt` | string | Tweet creation timestamp |
| ↳ `authorId` | string | Author user ID |
| ↳ `conversationId` | string | Conversation thread ID |
| ↳ `inReplyToUserId` | string | User ID being replied to |
| ↳ `publicMetrics` | object | Engagement metrics |
| ↳ `retweetCount` | number | Number of retweets |
| ↳ `replyCount` | number | Number of replies |
| ↳ `likeCount` | number | Number of likes |
| ↳ `quoteCount` | number | Number of quotes |
| `includes` | object | Additional data including user profiles |
| ↳ `users` | array | Array of user objects referenced in tweets |
| ↳ `id` | string | User ID |
| ↳ `username` | string | Username without @ symbol |
| ↳ `name` | string | Display name |
| ↳ `description` | string | User bio |
| ↳ `profileImageUrl` | string | Profile image URL |
| ↳ `verified` | boolean | Whether the user is verified |
| ↳ `metrics` | object | User statistics |
| ↳ `followersCount` | number | Number of followers |
| ↳ `followingCount` | number | Number of users following |
| ↳ `tweetCount` | number | Total number of tweets |
| `meta` | object | Pagination metadata |
| ↳ `resultCount` | number | Number of results returned |
| ↳ `newestId` | string | ID of the newest tweet |
| ↳ `oldestId` | string | ID of the oldest tweet |
| ↳ `nextToken` | string | Token for next page |
| ↳ `previousToken` | string | Token for previous page |
### `x_manage_like`
Like or unlike a tweet on X
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `userId` | string | Yes | The authenticated user ID |
| `tweetId` | string | Yes | The tweet ID to like or unlike |
| `action` | string | Yes | Action to perform: "like" or "unlike" |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `liked` | boolean | Whether the tweet is now liked |
### `x_manage_retweet`
Retweet or unretweet a tweet on X
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `userId` | string | Yes | The authenticated user ID |
| `tweetId` | string | Yes | The tweet ID to retweet or unretweet |
| `action` | string | Yes | Action to perform: "retweet" or "unretweet" |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `retweeted` | boolean | Whether the tweet is now retweeted |
### `x_get_liked_tweets`
Get tweets liked by a specific user
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `userId` | string | Yes | The user ID whose liked tweets to retrieve |
| `maxResults` | number | No | Maximum number of results \(5-100\) |
| `paginationToken` | string | No | Pagination token for next page |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `tweets` | array | Array of liked tweets |
| ↳ `id` | string | Tweet ID |
| ↳ `text` | string | Tweet content |
| ↳ `createdAt` | string | Creation timestamp |
| ↳ `authorId` | string | Author user ID |
| `meta` | object | Pagination metadata |
| ↳ `resultCount` | number | Number of results returned |
| ↳ `nextToken` | string | Token for next page |
### `x_get_liking_users`
Get the list of users who liked a specific tweet
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `tweetId` | string | Yes | The tweet ID to get liking users for |
| `maxResults` | number | No | Maximum number of results \(1-100, default 100\) |
| `paginationToken` | string | No | Pagination token for next page |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `users` | array | Array of users who liked the tweet |
| ↳ `id` | string | User ID |
| ↳ `username` | string | Username without @ symbol |
| ↳ `name` | string | Display name |
| ↳ `description` | string | User bio/description |
| ↳ `description` | string | User bio |
| ↳ `profileImageUrl` | string | Profile image URL |
| ↳ `verified` | boolean | Whether the user is verified |
| ↳ `metrics` | object | User statistics |
| ↳ `followersCount` | number | Number of followers |
| ↳ `followingCount` | number | Number of users following |
| ↳ `tweetCount` | number | Total number of tweets |
| `meta` | object | Pagination metadata |
| ↳ `resultCount` | number | Number of results returned |
| ↳ `nextToken` | string | Token for next page |
### `x_get_retweeted_by`
Get the list of users who retweeted a specific tweet
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `tweetId` | string | Yes | The tweet ID to get retweeters for |
| `maxResults` | number | No | Maximum number of results \(1-100, default 100\) |
| `paginationToken` | string | No | Pagination token for next page |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `users` | array | Array of users who retweeted the tweet |
| ↳ `id` | string | User ID |
| ↳ `username` | string | Username without @ symbol |
| ↳ `name` | string | Display name |
| ↳ `description` | string | User bio |
| ↳ `profileImageUrl` | string | Profile image URL |
| ↳ `verified` | boolean | Whether the user is verified |
| ↳ `metrics` | object | User statistics |
| ↳ `followersCount` | number | Number of followers |
| ↳ `followingCount` | number | Number of users following |
| ↳ `tweetCount` | number | Total number of tweets |
| `meta` | object | Metadata |
| ↳ `resultCount` | number | Number of results returned |
| ↳ `nextToken` | string | Token for next page |
### `x_get_bookmarks`
Get bookmarked tweets for the authenticated user
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `userId` | string | Yes | The authenticated user ID |
| `maxResults` | number | No | Maximum number of results \(1-100\) |
| `paginationToken` | string | No | Pagination token for next page of results |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `tweets` | array | Array of bookmarked tweets |
| ↳ `id` | string | Tweet ID |
| ↳ `text` | string | Tweet text content |
| ↳ `createdAt` | string | Tweet creation timestamp |
| ↳ `authorId` | string | Author user ID |
| ↳ `conversationId` | string | Conversation thread ID |
| ↳ `inReplyToUserId` | string | User ID being replied to |
| ↳ `publicMetrics` | object | Engagement metrics |
| ↳ `retweetCount` | number | Number of retweets |
| ↳ `replyCount` | number | Number of replies |
| ↳ `likeCount` | number | Number of likes |
| ↳ `quoteCount` | number | Number of quotes |
| `includes` | object | Additional data including user profiles |
| ↳ `users` | array | Array of user objects referenced in tweets |
| ↳ `id` | string | User ID |
| ↳ `username` | string | Username without @ symbol |
| ↳ `name` | string | Display name |
| ↳ `description` | string | User bio |
| ↳ `profileImageUrl` | string | Profile image URL |
| ↳ `verified` | boolean | Whether the user is verified |
| ↳ `metrics` | object | User statistics |
| ↳ `followersCount` | number | Number of followers |
| ↳ `followingCount` | number | Number of users following |
| ↳ `tweetCount` | number | Total number of tweets |
| `meta` | object | Pagination metadata |
| ↳ `resultCount` | number | Number of results returned |
| ↳ `newestId` | string | ID of the newest tweet |
| ↳ `oldestId` | string | ID of the oldest tweet |
| ↳ `nextToken` | string | Token for next page |
| ↳ `previousToken` | string | Token for previous page |
### `x_create_bookmark`
Bookmark a tweet for the authenticated user
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `userId` | string | Yes | The authenticated user ID |
| `tweetId` | string | Yes | The tweet ID to bookmark |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `bookmarked` | boolean | Whether the tweet was successfully bookmarked |
### `x_delete_bookmark`
Remove a tweet from the authenticated user
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `userId` | string | Yes | The authenticated user ID |
| `tweetId` | string | Yes | The tweet ID to remove from bookmarks |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `bookmarked` | boolean | Whether the tweet is still bookmarked \(should be false after deletion\) |
### `x_get_me`
Get the authenticated user
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `user` | object | Authenticated user profile |
| ↳ `id` | string | User ID |
| ↳ `username` | string | Username without @ symbol |
| ↳ `name` | string | Display name |
| ↳ `description` | string | User bio |
| ↳ `profileImageUrl` | string | Profile image URL |
| ↳ `verified` | boolean | Whether the user is verified |
| ↳ `metrics` | object | User statistics |
| ↳ `followersCount` | number | Number of followers |
| ↳ `followingCount` | number | Number of users following |
| ↳ `tweetCount` | number | Total number of tweets |
### `x_search_users`
Search for X users by name, username, or bio
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `query` | string | Yes | Search keyword \(1-50 chars, matches name, username, or bio\) |
| `maxResults` | number | No | Maximum number of results \(1-1000, default 100\) |
| `nextToken` | string | No | Pagination token for next page |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `users` | array | Array of users matching the search query |
| ↳ `id` | string | User ID |
| ↳ `username` | string | Username without @ symbol |
| ↳ `name` | string | Display name |
| ↳ `description` | string | User bio |
| ↳ `profileImageUrl` | string | Profile image URL |
| ↳ `verified` | boolean | Whether the user is verified |
| ↳ `metrics` | object | User statistics |
| ↳ `followersCount` | number | Number of followers |
| ↳ `followingCount` | number | Number of users following |
| ↳ `tweetCount` | number | Total number of tweets |
| `meta` | object | Search metadata |
| ↳ `resultCount` | number | Number of results returned |
| ↳ `nextToken` | string | Pagination token for next page |
### `x_get_followers`
Get the list of followers for a user
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `userId` | string | Yes | The user ID whose followers to retrieve |
| `maxResults` | number | No | Maximum number of results \(1-1000, default 100\) |
| `paginationToken` | string | No | Pagination token for next page |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `users` | array | Array of follower user profiles |
| ↳ `id` | string | User ID |
| ↳ `username` | string | Username without @ symbol |
| ↳ `name` | string | Display name |
| ↳ `description` | string | User bio |
| ↳ `profileImageUrl` | string | Profile image URL |
| ↳ `verified` | boolean | Whether the user is verified |
| ↳ `metrics` | object | User statistics |
| ↳ `followersCount` | number | Number of followers |
| ↳ `followingCount` | number | Number of users following |
| ↳ `tweetCount` | number | Total number of tweets |
| `meta` | object | Pagination metadata |
| ↳ `resultCount` | number | Number of results returned |
| ↳ `nextToken` | string | Token for next page |
### `x_get_following`
Get the list of users that a user is following
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `userId` | string | Yes | The user ID whose following list to retrieve |
| `maxResults` | number | No | Maximum number of results \(1-1000, default 100\) |
| `paginationToken` | string | No | Pagination token for next page |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `users` | array | Array of users being followed |
| ↳ `id` | string | User ID |
| ↳ `username` | string | Username without @ symbol |
| ↳ `name` | string | Display name |
| ↳ `description` | string | User bio |
| ↳ `profileImageUrl` | string | Profile image URL |
| ↳ `verified` | boolean | Whether the user is verified |
| ↳ `metrics` | object | User statistics |
| ↳ `followersCount` | number | Number of followers |
| ↳ `followingCount` | number | Number of users following |
| ↳ `tweetCount` | number | Total number of tweets |
| `meta` | object | Pagination metadata |
| ↳ `resultCount` | number | Number of results returned |
| ↳ `nextToken` | string | Token for next page |
### `x_manage_follow`
Follow or unfollow a user on X
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `userId` | string | Yes | The authenticated user ID |
| `targetUserId` | string | Yes | The user ID to follow or unfollow |
| `action` | string | Yes | Action to perform: "follow" or "unfollow" |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `following` | boolean | Whether you are now following the user |
| `pendingFollow` | boolean | Whether the follow request is pending \(for protected accounts\) |
### `x_manage_block`
Block or unblock a user on X
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `userId` | string | Yes | The authenticated user ID |
| `targetUserId` | string | Yes | The user ID to block or unblock |
| `action` | string | Yes | Action to perform: "block" or "unblock" |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `blocking` | boolean | Whether you are now blocking the user |
### `x_get_blocking`
Get the list of users blocked by the authenticated user
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `userId` | string | Yes | The authenticated user ID |
| `maxResults` | number | No | Maximum number of results \(1-1000\) |
| `paginationToken` | string | No | Pagination token for next page |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `users` | array | Array of blocked user profiles |
| ↳ `id` | string | User ID |
| ↳ `username` | string | Username without @ symbol |
| ↳ `name` | string | Display name |
| ↳ `description` | string | User bio |
| ↳ `profileImageUrl` | string | Profile image URL |
| ↳ `verified` | boolean | Whether the user is verified |
| ↳ `metrics` | object | User statistics |
| ↳ `followersCount` | number | Number of followers |
| ↳ `followingCount` | number | Number of users following |
| ↳ `tweetCount` | number | Total number of tweets |
| `meta` | object | Pagination metadata |
| ↳ `resultCount` | number | Number of results returned |
| ↳ `nextToken` | string | Token for next page |
### `x_manage_mute`
Mute or unmute a user on X
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `userId` | string | Yes | The authenticated user ID |
| `targetUserId` | string | Yes | The user ID to mute or unmute |
| `action` | string | Yes | Action to perform: "mute" or "unmute" |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `muting` | boolean | Whether you are now muting the user |
### `x_get_trends_by_woeid`
Get trending topics for a specific location by WOEID (e.g., 1 for worldwide, 23424977 for US)
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `woeid` | string | Yes | Yahoo Where On Earth ID \(e.g., "1" for worldwide, "23424977" for US, "23424975" for UK\) |
| `maxTrends` | number | No | Maximum number of trends to return \(1-50, default 20\) |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `trends` | array | Array of trending topics |
| ↳ `trendName` | string | Name of the trending topic |
| ↳ `tweetCount` | number | Number of tweets for this trend |
### `x_get_personalized_trends`
Get personalized trending topics for the authenticated user
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `trends` | array | Array of personalized trending topics |
| ↳ `trendName` | string | Name of the trending topic |
| ↳ `postCount` | number | Number of posts for this trend |
| ↳ `category` | string | Category of the trend |
| ↳ `trendingSince` | string | ISO 8601 timestamp of when the topic started trending |
### `x_get_usage`
Get the API usage data for your X project
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `days` | number | No | Number of days of usage data to return \(1-90, default 7\) |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `capResetDay` | number | Day of month when usage cap resets |
| `projectId` | string | The project ID |
| `projectCap` | number | The project tweet consumption cap |
| `projectUsage` | number | Total tweets consumed in current period |
| `dailyProjectUsage` | array | Daily project usage breakdown |
| ↳ `date` | string | Usage date in ISO 8601 format |
| ↳ `usage` | number | Number of tweets consumed |
| `dailyClientAppUsage` | array | Daily per-app usage breakdown |
| ↳ `clientAppId` | string | Client application ID |
| ↳ `usage` | array | Daily usage entries for this app |
| ↳ `date` | string | Usage date in ISO 8601 format |
| ↳ `usage` | number | Number of tweets consumed |

View File

@@ -1,18 +1,17 @@
import { xIcon } from '@/components/icons'
import type { BlockConfig } from '@/blocks/types'
import { AuthMode } from '@/blocks/types'
import type { XResponse } from '@/tools/x/types'
export const XBlock: BlockConfig<XResponse> = {
export const XBlock: BlockConfig = {
type: 'x',
name: 'X',
description: 'Interact with X',
authMode: AuthMode.OAuth,
longDescription:
'Integrate X into the workflow. Can post a new tweet, get tweet details, search tweets, and get user profile.',
'Integrate X into the workflow. Search tweets, manage bookmarks, follow/block/mute users, like and retweet, view trends, and more.',
docsLink: 'https://docs.sim.ai/tools/x',
category: 'tools',
bgColor: '#000000', // X's black color
bgColor: '#000000',
icon: xIcon,
subBlocks: [
{
@@ -20,13 +19,46 @@ export const XBlock: BlockConfig<XResponse> = {
title: 'Operation',
type: 'dropdown',
options: [
{ label: 'Post a New Tweet', id: 'x_write' },
{ label: 'Get Tweet Details', id: 'x_read' },
{ label: 'Search Tweets', id: 'x_search' },
{ label: 'Get User Profile', id: 'x_user' },
// Tweet Operations
{ label: 'Create Tweet', id: 'x_create_tweet' },
{ label: 'Delete Tweet', id: 'x_delete_tweet' },
{ label: 'Search Tweets', id: 'x_search_tweets' },
{ label: 'Get Tweets by IDs', id: 'x_get_tweets_by_ids' },
{ label: 'Get Quote Tweets', id: 'x_get_quote_tweets' },
{ label: 'Hide Reply', id: 'x_hide_reply' },
// User Tweet Operations
{ label: 'Get User Tweets', id: 'x_get_user_tweets' },
{ label: 'Get User Mentions', id: 'x_get_user_mentions' },
{ label: 'Get User Timeline', id: 'x_get_user_timeline' },
// Engagement Operations
{ label: 'Like / Unlike', id: 'x_manage_like' },
{ label: 'Retweet / Unretweet', id: 'x_manage_retweet' },
{ label: 'Get Liked Tweets', id: 'x_get_liked_tweets' },
{ label: 'Get Liking Users', id: 'x_get_liking_users' },
{ label: 'Get Retweeted By', id: 'x_get_retweeted_by' },
// Bookmark Operations
{ label: 'Get Bookmarks', id: 'x_get_bookmarks' },
{ label: 'Create Bookmark', id: 'x_create_bookmark' },
{ label: 'Delete Bookmark', id: 'x_delete_bookmark' },
// User Operations
{ label: 'Get My Profile', id: 'x_get_me' },
{ label: 'Search Users', id: 'x_search_users' },
{ label: 'Get Followers', id: 'x_get_followers' },
{ label: 'Get Following', id: 'x_get_following' },
// User Relationship Operations
{ label: 'Follow / Unfollow', id: 'x_manage_follow' },
{ label: 'Block / Unblock', id: 'x_manage_block' },
{ label: 'Get Blocked Users', id: 'x_get_blocking' },
{ label: 'Mute / Unmute', id: 'x_manage_mute' },
// Trends Operations
{ label: 'Get Trends by Location', id: 'x_get_trends_by_woeid' },
{ label: 'Get Personalized Trends', id: 'x_get_personalized_trends' },
// Usage Operations
{ label: 'Get API Usage', id: 'x_get_usage' },
],
value: () => 'x_write',
value: () => 'x_create_tweet',
},
// --- OAuth Credential ---
{
id: 'credential',
title: 'X Account',
@@ -34,7 +66,23 @@ export const XBlock: BlockConfig<XResponse> = {
serviceId: 'x',
canonicalParamId: 'oauthCredential',
mode: 'basic',
requiredScopes: ['tweet.read', 'tweet.write', 'users.read', 'offline.access'],
requiredScopes: [
'tweet.read',
'tweet.write',
'tweet.moderate.write',
'users.read',
'follows.read',
'follows.write',
'bookmark.read',
'bookmark.write',
'like.read',
'like.write',
'block.read',
'block.write',
'mute.read',
'mute.write',
'offline.access',
],
placeholder: 'Select X account',
},
{
@@ -45,79 +93,270 @@ export const XBlock: BlockConfig<XResponse> = {
mode: 'advanced',
placeholder: 'Enter credential ID',
},
// --- Create Tweet fields ---
{
id: 'text',
title: 'Tweet Text',
type: 'long-input',
placeholder: "What's happening?",
condition: { field: 'operation', value: 'x_write' },
condition: { field: 'operation', value: 'x_create_tweet' },
required: true,
},
{
id: 'replyTo',
title: 'Reply To (Tweet ID)',
id: 'replyToTweetId',
title: 'Reply To Tweet ID',
type: 'short-input',
placeholder: 'Enter tweet ID to reply to',
condition: { field: 'operation', value: 'x_write' },
condition: { field: 'operation', value: 'x_create_tweet' },
},
{
id: 'quoteTweetId',
title: 'Quote Tweet ID',
type: 'short-input',
placeholder: 'Enter tweet ID to quote',
condition: { field: 'operation', value: 'x_create_tweet' },
},
{
id: 'mediaIds',
title: 'Media IDs',
type: 'short-input',
placeholder: 'Enter comma-separated media IDs',
condition: { field: 'operation', value: 'x_write' },
placeholder: 'Comma-separated media IDs (up to 4)',
condition: { field: 'operation', value: 'x_create_tweet' },
},
{
id: 'replySettings',
title: 'Reply Settings',
type: 'dropdown',
options: [
{ label: 'Everyone', id: '' },
{ label: 'Mentioned Users', id: 'mentionedUsers' },
{ label: 'Following', id: 'following' },
{ label: 'Subscribers', id: 'subscribers' },
{ label: 'Verified', id: 'verified' },
],
value: () => '',
condition: { field: 'operation', value: 'x_create_tweet' },
},
// --- Tweet ID field (shared by multiple operations) ---
{
id: 'tweetId',
title: 'Tweet ID',
type: 'short-input',
placeholder: 'Enter tweet ID to read',
condition: { field: 'operation', value: 'x_read' },
required: true,
placeholder: 'Enter tweet ID',
condition: {
field: 'operation',
value: [
'x_delete_tweet',
'x_get_quote_tweets',
'x_hide_reply',
'x_manage_like',
'x_manage_retweet',
'x_get_liking_users',
'x_get_retweeted_by',
'x_create_bookmark',
'x_delete_bookmark',
],
},
required: {
field: 'operation',
value: [
'x_delete_tweet',
'x_get_quote_tweets',
'x_hide_reply',
'x_manage_like',
'x_manage_retweet',
'x_get_liking_users',
'x_get_retweeted_by',
'x_create_bookmark',
'x_delete_bookmark',
],
},
},
// --- Hide Reply toggle ---
{
id: 'includeReplies',
title: 'Include Replies',
id: 'hidden',
title: 'Hidden',
type: 'dropdown',
options: [
{ label: 'true', id: 'true' },
{ label: 'false', id: 'false' },
{ label: 'Hide', id: 'true' },
{ label: 'Unhide', id: 'false' },
],
value: () => 'false',
condition: { field: 'operation', value: 'x_read' },
value: () => 'true',
condition: { field: 'operation', value: 'x_hide_reply' },
},
// --- Tweet IDs (batch lookup) ---
{
id: 'ids',
title: 'Tweet IDs',
type: 'long-input',
placeholder: 'Comma-separated tweet IDs (up to 100)',
condition: { field: 'operation', value: 'x_get_tweets_by_ids' },
required: true,
},
// --- Search query fields ---
{
id: 'query',
title: 'Search Query',
type: 'long-input',
placeholder: 'Enter search terms (supports X search operators)',
condition: { field: 'operation', value: 'x_search' },
required: true,
},
{
id: 'maxResults',
title: 'Max Results',
type: 'short-input',
placeholder: '10',
condition: { field: 'operation', value: 'x_search' },
condition: { field: 'operation', value: ['x_search_tweets', 'x_search_users'] },
required: { field: 'operation', value: ['x_search_tweets', 'x_search_users'] },
},
{
id: 'sortOrder',
title: 'Sort Order',
type: 'dropdown',
options: [
{ label: 'recency', id: 'recency' },
{ label: 'relevancy', id: 'relevancy' },
{ label: 'Recent', id: 'recency' },
{ label: 'Relevant', id: 'relevancy' },
],
value: () => 'recency',
condition: { field: 'operation', value: 'x_search' },
condition: { field: 'operation', value: 'x_search_tweets' },
},
// --- User ID field (shared by many operations) ---
{
id: 'userId',
title: 'User ID',
type: 'short-input',
placeholder: 'Enter user ID',
condition: {
field: 'operation',
value: [
'x_get_user_tweets',
'x_get_user_mentions',
'x_get_user_timeline',
'x_get_liked_tweets',
'x_get_bookmarks',
'x_create_bookmark',
'x_delete_bookmark',
'x_get_followers',
'x_get_following',
'x_get_blocking',
'x_manage_follow',
'x_manage_block',
'x_manage_mute',
'x_manage_like',
'x_manage_retweet',
],
},
required: {
field: 'operation',
value: [
'x_get_user_tweets',
'x_get_user_mentions',
'x_get_user_timeline',
'x_get_liked_tweets',
'x_get_bookmarks',
'x_create_bookmark',
'x_delete_bookmark',
'x_get_followers',
'x_get_following',
'x_get_blocking',
'x_manage_follow',
'x_manage_block',
'x_manage_mute',
'x_manage_like',
'x_manage_retweet',
],
},
},
// --- Target User ID (for follow/block/mute) ---
{
id: 'targetUserId',
title: 'Target User ID',
type: 'short-input',
placeholder: 'Enter target user ID',
condition: {
field: 'operation',
value: ['x_manage_follow', 'x_manage_block', 'x_manage_mute'],
},
required: {
field: 'operation',
value: ['x_manage_follow', 'x_manage_block', 'x_manage_mute'],
},
},
// --- Action dropdowns for manage operations ---
{
id: 'action',
title: 'Action',
type: 'dropdown',
options: [
{ label: 'Like', id: 'like' },
{ label: 'Unlike', id: 'unlike' },
],
value: () => 'like',
condition: { field: 'operation', value: 'x_manage_like' },
},
{
id: 'retweetAction',
title: 'Action',
type: 'dropdown',
options: [
{ label: 'Retweet', id: 'retweet' },
{ label: 'Unretweet', id: 'unretweet' },
],
value: () => 'retweet',
condition: { field: 'operation', value: 'x_manage_retweet' },
},
{
id: 'followAction',
title: 'Action',
type: 'dropdown',
options: [
{ label: 'Follow', id: 'follow' },
{ label: 'Unfollow', id: 'unfollow' },
],
value: () => 'follow',
condition: { field: 'operation', value: 'x_manage_follow' },
},
{
id: 'blockAction',
title: 'Action',
type: 'dropdown',
options: [
{ label: 'Block', id: 'block' },
{ label: 'Unblock', id: 'unblock' },
],
value: () => 'block',
condition: { field: 'operation', value: 'x_manage_block' },
},
{
id: 'muteAction',
title: 'Action',
type: 'dropdown',
options: [
{ label: 'Mute', id: 'mute' },
{ label: 'Unmute', id: 'unmute' },
],
value: () => 'mute',
condition: { field: 'operation', value: 'x_manage_mute' },
},
// --- Exclude filter (for user tweets/timeline) ---
{
id: 'exclude',
title: 'Exclude',
type: 'short-input',
placeholder: 'Comma-separated: retweets, replies',
condition: {
field: 'operation',
value: ['x_get_user_tweets', 'x_get_user_timeline'],
},
},
// --- Time range fields (shared by tweet search and user tweet operations) ---
{
id: 'startTime',
title: 'Start Time',
type: 'short-input',
placeholder: 'YYYY-MM-DDTHH:mm:ssZ',
condition: { field: 'operation', value: 'x_search' },
condition: {
field: 'operation',
value: [
'x_search_tweets',
'x_get_user_tweets',
'x_get_user_mentions',
'x_get_user_timeline',
],
},
wandConfig: {
enabled: true,
prompt: `Generate an ISO 8601 timestamp based on the user's description.
@@ -138,7 +377,15 @@ Return ONLY the timestamp string - no explanations, no quotes, no extra text.`,
title: 'End Time',
type: 'short-input',
placeholder: 'YYYY-MM-DDTHH:mm:ssZ',
condition: { field: 'operation', value: 'x_search' },
condition: {
field: 'operation',
value: [
'x_search_tweets',
'x_get_user_tweets',
'x_get_user_mentions',
'x_get_user_timeline',
],
},
wandConfig: {
enabled: true,
prompt: `Generate an ISO 8601 timestamp based on the user's description.
@@ -154,55 +401,149 @@ Return ONLY the timestamp string - no explanations, no quotes, no extra text.`,
generationType: 'timestamp',
},
},
// --- Max Results (shared by many operations) ---
{
id: 'username',
title: 'Username',
id: 'maxResults',
title: 'Max Results',
type: 'short-input',
placeholder: 'Enter username (without @)',
condition: { field: 'operation', value: 'x_user' },
placeholder: '10',
condition: {
field: 'operation',
value: [
'x_search_tweets',
'x_get_user_tweets',
'x_get_user_mentions',
'x_get_user_timeline',
'x_get_liked_tweets',
'x_get_bookmarks',
'x_get_quote_tweets',
'x_get_liking_users',
'x_get_retweeted_by',
'x_search_users',
'x_get_followers',
'x_get_following',
'x_get_blocking',
],
},
},
// --- Pagination Token (shared by many operations) ---
{
id: 'paginationToken',
title: 'Pagination Token',
type: 'short-input',
placeholder: 'Token for next page',
condition: {
field: 'operation',
value: [
'x_get_user_tweets',
'x_get_user_mentions',
'x_get_user_timeline',
'x_get_liked_tweets',
'x_get_bookmarks',
'x_get_quote_tweets',
'x_get_liking_users',
'x_get_retweeted_by',
'x_get_followers',
'x_get_following',
'x_get_blocking',
],
},
},
// --- Next Token (for search operations that use nextToken instead of paginationToken) ---
{
id: 'nextToken',
title: 'Pagination Token',
type: 'short-input',
placeholder: 'Token for next page',
condition: {
field: 'operation',
value: ['x_search_tweets', 'x_search_users'],
},
},
// --- Trends fields ---
{
id: 'woeid',
title: 'WOEID',
type: 'short-input',
placeholder: '1 (worldwide), 23424977 (US)',
condition: { field: 'operation', value: 'x_get_trends_by_woeid' },
required: true,
},
{
id: 'maxTrends',
title: 'Max Trends',
type: 'short-input',
placeholder: '20',
condition: { field: 'operation', value: 'x_get_trends_by_woeid' },
},
// --- Usage fields ---
{
id: 'days',
title: 'Days',
type: 'short-input',
placeholder: '7 (1-90)',
condition: { field: 'operation', value: 'x_get_usage' },
},
],
tools: {
access: ['x_write', 'x_read', 'x_search', 'x_user'],
access: [
'x_create_tweet',
'x_delete_tweet',
'x_search_tweets',
'x_get_tweets_by_ids',
'x_get_quote_tweets',
'x_hide_reply',
'x_get_user_tweets',
'x_get_user_mentions',
'x_get_user_timeline',
'x_manage_like',
'x_manage_retweet',
'x_get_liked_tweets',
'x_get_liking_users',
'x_get_retweeted_by',
'x_get_bookmarks',
'x_create_bookmark',
'x_delete_bookmark',
'x_get_me',
'x_search_users',
'x_get_followers',
'x_get_following',
'x_manage_follow',
'x_manage_block',
'x_get_blocking',
'x_manage_mute',
'x_get_trends_by_woeid',
'x_get_personalized_trends',
'x_get_usage',
],
config: {
tool: (params) => {
switch (params.operation) {
case 'x_write':
return 'x_write'
case 'x_read':
return 'x_read'
case 'x_search':
return 'x_search'
case 'x_user':
return 'x_user'
default:
return 'x_write'
}
},
tool: (params) => params.operation,
params: (params) => {
const { oauthCredential, ...rest } = params
const parsedParams: Record<string, any> = {
const parsedParams: Record<string, unknown> = {
credential: oauthCredential,
}
Object.keys(rest).forEach((key) => {
const value = rest[key]
for (const [key, value] of Object.entries(rest)) {
if (value === undefined || value === null || value === '') continue
if (value === 'true' || value === 'false') {
parsedParams[key] = value === 'true'
} else if (key === 'maxResults' && value) {
if (key === 'maxResults' || key === 'maxTrends' || key === 'days') {
parsedParams[key] = Number.parseInt(value as string, 10)
} else if (key === 'mediaIds' && typeof value === 'string') {
parsedParams[key] = value
.split(',')
.map((id) => id.trim())
.filter((id) => id !== '')
} else if (key === 'hidden') {
parsedParams[key] = value === 'true'
} else if (key === 'retweetAction') {
parsedParams.action = value
} else if (key === 'followAction') {
parsedParams.action = value
} else if (key === 'blockAction') {
parsedParams.action = value
} else if (key === 'muteAction') {
parsedParams.action = value
} else {
parsedParams[key] = value
}
})
}
return parsedParams
},
@@ -211,64 +552,207 @@ Return ONLY the timestamp string - no explanations, no quotes, no extra text.`,
inputs: {
operation: { type: 'string', description: 'Operation to perform' },
oauthCredential: { type: 'string', description: 'X account credential' },
// Tweet fields
text: { type: 'string', description: 'Tweet text content' },
replyTo: { type: 'string', description: 'Reply to tweet ID' },
mediaIds: { type: 'string', description: 'Media identifiers' },
poll: { type: 'json', description: 'Poll configuration' },
replyToTweetId: { type: 'string', description: 'Tweet ID to reply to' },
quoteTweetId: { type: 'string', description: 'Tweet ID to quote' },
mediaIds: { type: 'string', description: 'Comma-separated media IDs' },
replySettings: { type: 'string', description: 'Reply permission setting' },
tweetId: { type: 'string', description: 'Tweet identifier' },
includeReplies: { type: 'boolean', description: 'Include replies' },
query: { type: 'string', description: 'Search query terms' },
maxResults: { type: 'number', description: 'Maximum search results' },
startTime: { type: 'string', description: 'Search start time' },
endTime: { type: 'string', description: 'Search end time' },
sortOrder: { type: 'string', description: 'Result sort order' },
username: { type: 'string', description: 'User profile name' },
includeRecentTweets: { type: 'boolean', description: 'Include recent tweets' },
ids: { type: 'string', description: 'Comma-separated tweet IDs' },
hidden: { type: 'string', description: 'Hide or unhide reply' },
// User fields
userId: { type: 'string', description: 'User identifier' },
targetUserId: { type: 'string', description: 'Target user identifier' },
// Action fields
action: { type: 'string', description: 'Action to perform (like/unlike, etc.)' },
retweetAction: { type: 'string', description: 'Retweet action' },
followAction: { type: 'string', description: 'Follow action' },
blockAction: { type: 'string', description: 'Block action' },
muteAction: { type: 'string', description: 'Mute action' },
// Search/filter fields
query: { type: 'string', description: 'Search query' },
sortOrder: { type: 'string', description: 'Sort order' },
exclude: { type: 'string', description: 'Exclusion filter' },
// Time/pagination fields
startTime: { type: 'string', description: 'Start time filter' },
endTime: { type: 'string', description: 'End time filter' },
maxResults: { type: 'number', description: 'Maximum results' },
paginationToken: { type: 'string', description: 'Pagination token' },
nextToken: { type: 'string', description: 'Next page token' },
// Trends fields
woeid: { type: 'string', description: 'Where On Earth ID' },
maxTrends: { type: 'number', description: 'Maximum trends to return' },
// Usage fields
days: { type: 'number', description: 'Days of usage data' },
},
outputs: {
// Write and Read operation outputs
tweet: {
type: 'json',
description: 'Tweet data including contextAnnotations and publicMetrics',
condition: { field: 'operation', value: ['x_write', 'x_read'] },
// Create Tweet outputs
id: {
type: 'string',
description: 'Created tweet ID',
condition: { field: 'operation', value: 'x_create_tweet' },
},
// Read operation outputs
replies: {
type: 'json',
description: 'Tweet replies (when includeReplies is true)',
condition: { field: 'operation', value: 'x_read' },
text: {
type: 'string',
description: 'Text of the created tweet',
condition: { field: 'operation', value: 'x_create_tweet' },
},
context: {
type: 'json',
description: 'Tweet context (parent and quoted tweets)',
condition: { field: 'operation', value: 'x_read' },
// Delete Tweet output
deleted: {
type: 'boolean',
description: 'Whether the tweet was deleted',
condition: { field: 'operation', value: 'x_delete_tweet' },
},
// Search operation outputs
// Bookmark outputs
bookmarked: {
type: 'boolean',
description: 'Whether the tweet is bookmarked',
condition: { field: 'operation', value: ['x_create_bookmark', 'x_delete_bookmark'] },
},
// Hide Reply output
hidden: {
type: 'boolean',
description: 'Whether the reply is hidden',
condition: { field: 'operation', value: 'x_hide_reply' },
},
// Like output
liked: {
type: 'boolean',
description: 'Whether the tweet is liked',
condition: { field: 'operation', value: 'x_manage_like' },
},
// Retweet output
retweeted: {
type: 'boolean',
description: 'Whether the tweet is retweeted',
condition: { field: 'operation', value: 'x_manage_retweet' },
},
// Follow output
following: {
type: 'boolean',
description: 'Whether following the user',
condition: { field: 'operation', value: 'x_manage_follow' },
},
pendingFollow: {
type: 'boolean',
description: 'Whether a follow request is pending',
condition: { field: 'operation', value: 'x_manage_follow' },
},
// Block output
blocking: {
type: 'boolean',
description: 'Whether blocking the user',
condition: { field: 'operation', value: 'x_manage_block' },
},
// Mute output
muting: {
type: 'boolean',
description: 'Whether muting the user',
condition: { field: 'operation', value: 'x_manage_mute' },
},
// Tweet list outputs (shared by many operations)
tweets: {
type: 'json',
description: 'Tweets data including contextAnnotations and publicMetrics',
condition: { field: 'operation', value: 'x_search' },
description: 'Array of tweets',
condition: {
field: 'operation',
value: [
'x_search_tweets',
'x_get_tweets_by_ids',
'x_get_user_tweets',
'x_get_user_mentions',
'x_get_user_timeline',
'x_get_liked_tweets',
'x_get_bookmarks',
'x_get_quote_tweets',
],
},
},
includes: {
// User list outputs
users: {
type: 'json',
description: 'Additional data (users, media, polls)',
condition: { field: 'operation', value: 'x_search' },
description: 'Array of users',
condition: {
field: 'operation',
value: [
'x_search_users',
'x_get_followers',
'x_get_following',
'x_get_blocking',
'x_get_liking_users',
'x_get_retweeted_by',
],
},
},
meta: {
type: 'json',
description: 'Response metadata',
condition: { field: 'operation', value: 'x_search' },
},
// User operation outputs
// Single user output
user: {
type: 'json',
description: 'User profile data',
condition: { field: 'operation', value: 'x_user' },
condition: { field: 'operation', value: 'x_get_me' },
},
recentTweets: {
// Pagination metadata
meta: {
type: 'json',
description: 'Recent tweets data',
condition: { field: 'operation', value: 'x_user' },
description: 'Pagination metadata (resultCount, nextToken)',
condition: {
field: 'operation',
value: [
'x_search_tweets',
'x_get_user_tweets',
'x_get_user_mentions',
'x_get_user_timeline',
'x_get_liked_tweets',
'x_get_bookmarks',
'x_get_quote_tweets',
'x_get_liking_users',
'x_get_retweeted_by',
'x_search_users',
'x_get_followers',
'x_get_following',
'x_get_blocking',
],
},
},
// Trends outputs
trends: {
type: 'json',
description: 'Array of trending topics',
condition: {
field: 'operation',
value: ['x_get_trends_by_woeid', 'x_get_personalized_trends'],
},
},
// Usage outputs
capResetDay: {
type: 'number',
description: 'Day of month when usage cap resets',
condition: { field: 'operation', value: 'x_get_usage' },
},
projectId: {
type: 'string',
description: 'Project identifier',
condition: { field: 'operation', value: 'x_get_usage' },
},
projectCap: {
type: 'number',
description: 'Monthly project usage cap',
condition: { field: 'operation', value: 'x_get_usage' },
},
projectUsage: {
type: 'number',
description: 'Current project usage count',
condition: { field: 'operation', value: 'x_get_usage' },
},
dailyProjectUsage: {
type: 'json',
description: 'Daily usage breakdown',
condition: { field: 'operation', value: 'x_get_usage' },
},
dailyClientAppUsage: {
type: 'json',
description: 'Daily client app usage breakdown',
condition: { field: 'operation', value: 'x_get_usage' },
},
},
}

View File

@@ -310,7 +310,23 @@ export const OAUTH_PROVIDERS: Record<string, OAuthProviderConfig> = {
providerId: 'x',
icon: xIcon,
baseProviderIcon: xIcon,
scopes: ['tweet.read', 'tweet.write', 'users.read', 'offline.access'],
scopes: [
'tweet.read',
'tweet.write',
'tweet.moderate.write',
'users.read',
'follows.read',
'follows.write',
'bookmark.read',
'bookmark.write',
'like.read',
'like.write',
'block.read',
'block.write',
'mute.read',
'mute.write',
'offline.access',
],
},
},
defaultService: 'x',

View File

@@ -2132,7 +2132,40 @@ import {
wordpressUploadMediaTool,
} from '@/tools/wordpress'
import { workflowExecutorTool } from '@/tools/workflow'
import { xReadTool, xSearchTool, xUserTool, xWriteTool } from '@/tools/x'
import {
xCreateBookmarkTool,
xCreateTweetTool,
xDeleteBookmarkTool,
xDeleteTweetTool,
xGetBlockingTool,
xGetBookmarksTool,
xGetFollowersTool,
xGetFollowingTool,
xGetLikedTweetsTool,
xGetLikingUsersTool,
xGetMeTool,
xGetPersonalizedTrendsTool,
xGetQuoteTweetsTool,
xGetRetweetedByTool,
xGetTrendsByWoeidTool,
xGetTweetsByIdsTool,
xGetUsageTool,
xGetUserMentionsTool,
xGetUserTimelineTool,
xGetUserTweetsTool,
xHideReplyTool,
xManageBlockTool,
xManageFollowTool,
xManageLikeTool,
xManageMuteTool,
xManageRetweetTool,
xReadTool,
xSearchTool,
xSearchTweetsTool,
xSearchUsersTool,
xUserTool,
xWriteTool,
} from '@/tools/x'
import {
youtubeChannelInfoTool,
youtubeChannelPlaylistsTool,
@@ -2665,6 +2698,34 @@ export const tools: Record<string, ToolConfig> = {
x_read: xReadTool,
x_search: xSearchTool,
x_user: xUserTool,
x_search_tweets: xSearchTweetsTool,
x_get_user_tweets: xGetUserTweetsTool,
x_get_user_mentions: xGetUserMentionsTool,
x_get_user_timeline: xGetUserTimelineTool,
x_get_tweets_by_ids: xGetTweetsByIdsTool,
x_get_bookmarks: xGetBookmarksTool,
x_create_bookmark: xCreateBookmarkTool,
x_delete_bookmark: xDeleteBookmarkTool,
x_create_tweet: xCreateTweetTool,
x_delete_tweet: xDeleteTweetTool,
x_get_me: xGetMeTool,
x_search_users: xSearchUsersTool,
x_get_followers: xGetFollowersTool,
x_get_following: xGetFollowingTool,
x_manage_follow: xManageFollowTool,
x_get_blocking: xGetBlockingTool,
x_manage_block: xManageBlockTool,
x_get_liked_tweets: xGetLikedTweetsTool,
x_get_liking_users: xGetLikingUsersTool,
x_manage_like: xManageLikeTool,
x_manage_retweet: xManageRetweetTool,
x_get_retweeted_by: xGetRetweetedByTool,
x_get_quote_tweets: xGetQuoteTweetsTool,
x_get_trends_by_woeid: xGetTrendsByWoeidTool,
x_get_personalized_trends: xGetPersonalizedTrendsTool,
x_get_usage: xGetUsageTool,
x_hide_reply: xHideReplyTool,
x_manage_mute: xManageMuteTool,
pinecone_fetch: pineconeFetchTool,
pinecone_generate_embeddings: pineconeGenerateEmbeddingsTool,
pinecone_search_text: pineconeSearchTextTool,

View File

@@ -0,0 +1,79 @@
import { createLogger } from '@sim/logger'
import type { ToolConfig } from '@/tools/types'
import type { XCreateBookmarkParams, XCreateBookmarkResponse } from '@/tools/x/types'
const logger = createLogger('XCreateBookmarkTool')
export const xCreateBookmarkTool: ToolConfig<XCreateBookmarkParams, XCreateBookmarkResponse> = {
id: 'x_create_bookmark',
name: 'X Create Bookmark',
description: 'Bookmark a tweet for the authenticated user',
version: '1.0.0',
oauth: {
required: true,
provider: 'x',
},
params: {
accessToken: {
type: 'string',
required: true,
visibility: 'hidden',
description: 'X OAuth access token',
},
userId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'The authenticated user ID',
},
tweetId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'The tweet ID to bookmark',
},
},
request: {
url: (params) => `https://api.x.com/2/users/${params.userId.trim()}/bookmarks`,
method: 'POST',
headers: (params) => ({
Authorization: `Bearer ${params.accessToken}`,
'Content-Type': 'application/json',
}),
body: (params) => ({
tweet_id: params.tweetId.trim(),
}),
},
transformResponse: async (response) => {
const data = await response.json()
if (!data.data) {
logger.error('X Create Bookmark API Error:', JSON.stringify(data, null, 2))
return {
success: false,
error: data.errors?.[0]?.detail || 'Failed to bookmark tweet',
output: {
bookmarked: false,
},
}
}
return {
success: true,
output: {
bookmarked: data.data.bookmarked ?? false,
},
}
},
outputs: {
bookmarked: {
type: 'boolean',
description: 'Whether the tweet was successfully bookmarked',
},
},
}

View File

@@ -0,0 +1,129 @@
import { createLogger } from '@sim/logger'
import type { ToolConfig } from '@/tools/types'
import type { XCreateTweetParams, XCreateTweetResponse } from '@/tools/x/types'
const logger = createLogger('XCreateTweetTool')
export const xCreateTweetTool: ToolConfig<XCreateTweetParams, XCreateTweetResponse> = {
id: 'x_create_tweet',
name: 'X Create Tweet',
description: 'Create a new tweet, reply, or quote tweet on X',
version: '1.0.0',
oauth: {
required: true,
provider: 'x',
},
params: {
accessToken: {
type: 'string',
required: true,
visibility: 'hidden',
description: 'X OAuth access token',
},
text: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'The text content of the tweet (max 280 characters)',
},
replyToTweetId: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Tweet ID to reply to',
},
quoteTweetId: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Tweet ID to quote',
},
mediaIds: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Comma-separated media IDs to attach (up to 4)',
},
replySettings: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Who can reply: "mentionedUsers", "following", "subscribers", or "verified"',
},
},
request: {
url: 'https://api.x.com/2/tweets',
method: 'POST',
headers: (params) => ({
Authorization: `Bearer ${params.accessToken}`,
'Content-Type': 'application/json',
}),
body: (params) => {
const body: Record<string, unknown> = {
text: params.text,
}
if (params.replyToTweetId) {
body.reply = { in_reply_to_tweet_id: params.replyToTweetId.trim() }
}
if (params.quoteTweetId) {
body.quote_tweet_id = params.quoteTweetId.trim()
}
if (params.mediaIds) {
const ids = params.mediaIds
.split(',')
.map((id) => id.trim())
.filter(Boolean)
if (ids.length > 0) {
body.media = { media_ids: ids }
}
}
if (params.replySettings) {
body.reply_settings = params.replySettings
}
return body
},
},
transformResponse: async (response) => {
const data = await response.json()
if (!data.data) {
logger.error('X Create Tweet API Error:', JSON.stringify(data, null, 2))
return {
success: false,
error: data.errors?.[0]?.detail || 'Failed to create tweet',
output: {
id: '',
text: '',
},
}
}
return {
success: true,
output: {
id: data.data.id ?? '',
text: data.data.text ?? '',
},
}
},
outputs: {
id: {
type: 'string',
description: 'The ID of the created tweet',
},
text: {
type: 'string',
description: 'The text of the created tweet',
},
},
}

View File

@@ -0,0 +1,77 @@
import { createLogger } from '@sim/logger'
import type { ToolConfig } from '@/tools/types'
import type { XDeleteBookmarkParams, XDeleteBookmarkResponse } from '@/tools/x/types'
const logger = createLogger('XDeleteBookmarkTool')
export const xDeleteBookmarkTool: ToolConfig<XDeleteBookmarkParams, XDeleteBookmarkResponse> = {
id: 'x_delete_bookmark',
name: 'X Delete Bookmark',
description: "Remove a tweet from the authenticated user's bookmarks",
version: '1.0.0',
oauth: {
required: true,
provider: 'x',
},
params: {
accessToken: {
type: 'string',
required: true,
visibility: 'hidden',
description: 'X OAuth access token',
},
userId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'The authenticated user ID',
},
tweetId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'The tweet ID to remove from bookmarks',
},
},
request: {
url: (params) =>
`https://api.x.com/2/users/${params.userId.trim()}/bookmarks/${params.tweetId.trim()}`,
method: 'DELETE',
headers: (params) => ({
Authorization: `Bearer ${params.accessToken}`,
'Content-Type': 'application/json',
}),
},
transformResponse: async (response) => {
const data = await response.json()
if (!data.data) {
logger.error('X Delete Bookmark API Error:', JSON.stringify(data, null, 2))
return {
success: false,
error: data.errors?.[0]?.detail || 'Failed to remove bookmark',
output: {
bookmarked: false,
},
}
}
return {
success: true,
output: {
bookmarked: data.data.bookmarked ?? false,
},
}
},
outputs: {
bookmarked: {
type: 'boolean',
description: 'Whether the tweet is still bookmarked (should be false after deletion)',
},
},
}

View File

@@ -0,0 +1,70 @@
import { createLogger } from '@sim/logger'
import type { ToolConfig } from '@/tools/types'
import type { XDeleteTweetParams, XDeleteTweetResponse } from '@/tools/x/types'
const logger = createLogger('XDeleteTweetTool')
export const xDeleteTweetTool: ToolConfig<XDeleteTweetParams, XDeleteTweetResponse> = {
id: 'x_delete_tweet',
name: 'X Delete Tweet',
description: 'Delete a tweet authored by the authenticated user',
version: '1.0.0',
oauth: {
required: true,
provider: 'x',
},
params: {
accessToken: {
type: 'string',
required: true,
visibility: 'hidden',
description: 'X OAuth access token',
},
tweetId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'The ID of the tweet to delete',
},
},
request: {
url: (params) => `https://api.x.com/2/tweets/${params.tweetId.trim()}`,
method: 'DELETE',
headers: (params) => ({
Authorization: `Bearer ${params.accessToken}`,
'Content-Type': 'application/json',
}),
},
transformResponse: async (response) => {
const data = await response.json()
if (!data.data) {
logger.error('X Delete Tweet API Error:', JSON.stringify(data, null, 2))
return {
success: false,
error: data.errors?.[0]?.detail || 'Failed to delete tweet',
output: {
deleted: false,
},
}
}
return {
success: true,
output: {
deleted: data.data.deleted ?? false,
},
}
},
outputs: {
deleted: {
type: 'boolean',
description: 'Whether the tweet was successfully deleted',
},
},
}

View File

@@ -0,0 +1,128 @@
import { createLogger } from '@sim/logger'
import type { ToolConfig } from '@/tools/types'
import type { XGetBlockingParams, XUserListResponse } from '@/tools/x/types'
import { transformUser } from '@/tools/x/types'
const logger = createLogger('XGetBlockingTool')
export const xGetBlockingTool: ToolConfig<XGetBlockingParams, XUserListResponse> = {
id: 'x_get_blocking',
name: 'X Get Blocking',
description: 'Get the list of users blocked by the authenticated user',
version: '1.0.0',
oauth: {
required: true,
provider: 'x',
},
params: {
accessToken: {
type: 'string',
required: true,
visibility: 'hidden',
description: 'X OAuth access token',
},
userId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'The authenticated user ID',
},
maxResults: {
type: 'number',
required: false,
visibility: 'user-or-llm',
description: 'Maximum number of results (1-1000)',
},
paginationToken: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Pagination token for next page',
},
},
request: {
url: (params) => {
const queryParams = new URLSearchParams({
'user.fields': 'created_at,description,profile_image_url,verified,public_metrics',
})
if (params.maxResults) {
const max = Math.max(1, Math.min(1000, Number(params.maxResults)))
queryParams.append('max_results', max.toString())
}
if (params.paginationToken) queryParams.append('pagination_token', params.paginationToken)
return `https://api.x.com/2/users/${params.userId.trim()}/blocking?${queryParams.toString()}`
},
method: 'GET',
headers: (params) => ({
Authorization: `Bearer ${params.accessToken}`,
'Content-Type': 'application/json',
}),
},
transformResponse: async (response) => {
const data = await response.json()
if (!data.data || !Array.isArray(data.data)) {
logger.error('X Get Blocking API Error:', JSON.stringify(data, null, 2))
return {
success: false,
output: {
users: [],
meta: { resultCount: 0, nextToken: null },
},
error: data.errors?.[0]?.detail ?? 'No blocked users found or invalid response',
}
}
return {
success: true,
output: {
users: data.data.map(transformUser),
meta: {
resultCount: data.meta?.result_count ?? data.data.length,
nextToken: data.meta?.next_token ?? null,
},
},
}
},
outputs: {
users: {
type: 'array',
description: 'Array of blocked user profiles',
items: {
type: 'object',
properties: {
id: { type: 'string', description: 'User ID' },
username: { type: 'string', description: 'Username without @ symbol' },
name: { type: 'string', description: 'Display name' },
description: { type: 'string', description: 'User bio', optional: true },
profileImageUrl: { type: 'string', description: 'Profile image URL', optional: true },
verified: { type: 'boolean', description: 'Whether the user is verified' },
metrics: {
type: 'object',
description: 'User statistics',
properties: {
followersCount: { type: 'number', description: 'Number of followers' },
followingCount: { type: 'number', description: 'Number of users following' },
tweetCount: { type: 'number', description: 'Total number of tweets' },
},
},
},
},
},
meta: {
type: 'object',
description: 'Pagination metadata',
properties: {
resultCount: { type: 'number', description: 'Number of results returned' },
nextToken: { type: 'string', description: 'Token for next page', optional: true },
},
},
},
}

View File

@@ -0,0 +1,183 @@
import { createLogger } from '@sim/logger'
import type { ToolConfig } from '@/tools/types'
import type { XGetBookmarksParams, XTweetListResponse } from '@/tools/x/types'
import { transformTweet, transformUser } from '@/tools/x/types'
const logger = createLogger('XGetBookmarksTool')
export const xGetBookmarksTool: ToolConfig<XGetBookmarksParams, XTweetListResponse> = {
id: 'x_get_bookmarks',
name: 'X Get Bookmarks',
description: 'Get bookmarked tweets for the authenticated user',
version: '1.0.0',
oauth: {
required: true,
provider: 'x',
},
params: {
accessToken: {
type: 'string',
required: true,
visibility: 'hidden',
description: 'X OAuth access token',
},
userId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'The authenticated user ID',
},
maxResults: {
type: 'number',
required: false,
visibility: 'user-or-llm',
description: 'Maximum number of results (1-100)',
},
paginationToken: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Pagination token for next page of results',
},
},
request: {
url: (params) => {
const queryParams = new URLSearchParams({
expansions: 'author_id,referenced_tweets.id,attachments.media_keys,attachments.poll_ids',
'tweet.fields':
'created_at,conversation_id,in_reply_to_user_id,attachments,context_annotations,public_metrics',
'user.fields': 'name,username,description,profile_image_url,verified,public_metrics',
})
if (params.maxResults) {
const max = Math.max(1, Math.min(100, Number(params.maxResults)))
queryParams.append('max_results', max.toString())
}
if (params.paginationToken) queryParams.append('pagination_token', params.paginationToken)
return `https://api.x.com/2/users/${params.userId.trim()}/bookmarks?${queryParams.toString()}`
},
method: 'GET',
headers: (params) => ({
Authorization: `Bearer ${params.accessToken}`,
'Content-Type': 'application/json',
}),
},
transformResponse: async (response) => {
const data = await response.json()
if (!data.data || !Array.isArray(data.data)) {
logger.error('X Get Bookmarks API Error:', JSON.stringify(data, null, 2))
return {
success: false,
error: data.errors?.[0]?.detail || 'No bookmarks found or invalid response',
output: {
tweets: [],
meta: {
resultCount: 0,
newestId: null,
oldestId: null,
nextToken: null,
previousToken: null,
},
},
}
}
return {
success: true,
output: {
tweets: data.data.map(transformTweet),
includes: {
users: data.includes?.users?.map(transformUser) ?? [],
},
meta: {
resultCount: data.meta?.result_count ?? 0,
newestId: data.meta?.newest_id ?? null,
oldestId: data.meta?.oldest_id ?? null,
nextToken: data.meta?.next_token ?? null,
previousToken: data.meta?.previous_token ?? null,
},
},
}
},
outputs: {
tweets: {
type: 'array',
description: 'Array of bookmarked tweets',
items: {
type: 'object',
properties: {
id: { type: 'string', description: 'Tweet ID' },
text: { type: 'string', description: 'Tweet text content' },
createdAt: { type: 'string', description: 'Tweet creation timestamp' },
authorId: { type: 'string', description: 'Author user ID' },
conversationId: { type: 'string', description: 'Conversation thread ID', optional: true },
inReplyToUserId: {
type: 'string',
description: 'User ID being replied to',
optional: true,
},
publicMetrics: {
type: 'object',
description: 'Engagement metrics',
optional: true,
properties: {
retweetCount: { type: 'number', description: 'Number of retweets' },
replyCount: { type: 'number', description: 'Number of replies' },
likeCount: { type: 'number', description: 'Number of likes' },
quoteCount: { type: 'number', description: 'Number of quotes' },
},
},
},
},
},
includes: {
type: 'object',
description: 'Additional data including user profiles',
optional: true,
properties: {
users: {
type: 'array',
description: 'Array of user objects referenced in tweets',
items: {
type: 'object',
properties: {
id: { type: 'string', description: 'User ID' },
username: { type: 'string', description: 'Username without @ symbol' },
name: { type: 'string', description: 'Display name' },
description: { type: 'string', description: 'User bio', optional: true },
profileImageUrl: { type: 'string', description: 'Profile image URL', optional: true },
verified: { type: 'boolean', description: 'Whether the user is verified' },
metrics: {
type: 'object',
description: 'User statistics',
properties: {
followersCount: { type: 'number', description: 'Number of followers' },
followingCount: { type: 'number', description: 'Number of users following' },
tweetCount: { type: 'number', description: 'Total number of tweets' },
},
},
},
},
},
},
},
meta: {
type: 'object',
description: 'Pagination metadata',
properties: {
resultCount: { type: 'number', description: 'Number of results returned' },
newestId: { type: 'string', description: 'ID of the newest tweet', optional: true },
oldestId: { type: 'string', description: 'ID of the oldest tweet', optional: true },
nextToken: { type: 'string', description: 'Token for next page', optional: true },
previousToken: { type: 'string', description: 'Token for previous page', optional: true },
},
},
},
}

View File

@@ -0,0 +1,128 @@
import { createLogger } from '@sim/logger'
import type { ToolConfig } from '@/tools/types'
import type { XGetFollowersParams, XUserListResponse } from '@/tools/x/types'
import { transformUser } from '@/tools/x/types'
const logger = createLogger('XGetFollowersTool')
export const xGetFollowersTool: ToolConfig<XGetFollowersParams, XUserListResponse> = {
id: 'x_get_followers',
name: 'X Get Followers',
description: 'Get the list of followers for a user',
version: '1.0.0',
oauth: {
required: true,
provider: 'x',
},
params: {
accessToken: {
type: 'string',
required: true,
visibility: 'hidden',
description: 'X OAuth access token',
},
userId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'The user ID whose followers to retrieve',
},
maxResults: {
type: 'number',
required: false,
visibility: 'user-or-llm',
description: 'Maximum number of results (1-1000, default 100)',
},
paginationToken: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Pagination token for next page',
},
},
request: {
url: (params) => {
const queryParams = new URLSearchParams({
'user.fields': 'created_at,description,profile_image_url,verified,public_metrics,location',
})
if (params.maxResults) {
const max = Math.max(1, Math.min(1000, Number(params.maxResults)))
queryParams.append('max_results', max.toString())
}
if (params.paginationToken) queryParams.append('pagination_token', params.paginationToken)
return `https://api.x.com/2/users/${params.userId.trim()}/followers?${queryParams.toString()}`
},
method: 'GET',
headers: (params) => ({
Authorization: `Bearer ${params.accessToken}`,
'Content-Type': 'application/json',
}),
},
transformResponse: async (response) => {
const data = await response.json()
if (!data.data || !Array.isArray(data.data)) {
logger.error('X Get Followers API Error:', JSON.stringify(data, null, 2))
return {
success: false,
error: data.errors?.[0]?.detail || 'No followers found or invalid response',
output: {
users: [],
meta: { resultCount: 0, nextToken: null },
},
}
}
return {
success: true,
output: {
users: data.data.map(transformUser),
meta: {
resultCount: data.meta?.result_count ?? data.data.length,
nextToken: data.meta?.next_token ?? null,
},
},
}
},
outputs: {
users: {
type: 'array',
description: 'Array of follower user profiles',
items: {
type: 'object',
properties: {
id: { type: 'string', description: 'User ID' },
username: { type: 'string', description: 'Username without @ symbol' },
name: { type: 'string', description: 'Display name' },
description: { type: 'string', description: 'User bio', optional: true },
profileImageUrl: { type: 'string', description: 'Profile image URL', optional: true },
verified: { type: 'boolean', description: 'Whether the user is verified' },
metrics: {
type: 'object',
description: 'User statistics',
properties: {
followersCount: { type: 'number', description: 'Number of followers' },
followingCount: { type: 'number', description: 'Number of users following' },
tweetCount: { type: 'number', description: 'Total number of tweets' },
},
},
},
},
},
meta: {
type: 'object',
description: 'Pagination metadata',
properties: {
resultCount: { type: 'number', description: 'Number of results returned' },
nextToken: { type: 'string', description: 'Token for next page', optional: true },
},
},
},
}

View File

@@ -0,0 +1,128 @@
import { createLogger } from '@sim/logger'
import type { ToolConfig } from '@/tools/types'
import type { XGetFollowingParams, XUserListResponse } from '@/tools/x/types'
import { transformUser } from '@/tools/x/types'
const logger = createLogger('XGetFollowingTool')
export const xGetFollowingTool: ToolConfig<XGetFollowingParams, XUserListResponse> = {
id: 'x_get_following',
name: 'X Get Following',
description: 'Get the list of users that a user is following',
version: '1.0.0',
oauth: {
required: true,
provider: 'x',
},
params: {
accessToken: {
type: 'string',
required: true,
visibility: 'hidden',
description: 'X OAuth access token',
},
userId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'The user ID whose following list to retrieve',
},
maxResults: {
type: 'number',
required: false,
visibility: 'user-or-llm',
description: 'Maximum number of results (1-1000, default 100)',
},
paginationToken: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Pagination token for next page',
},
},
request: {
url: (params) => {
const queryParams = new URLSearchParams({
'user.fields': 'created_at,description,profile_image_url,verified,public_metrics,location',
})
if (params.maxResults) {
const max = Math.max(1, Math.min(1000, Number(params.maxResults)))
queryParams.append('max_results', max.toString())
}
if (params.paginationToken) queryParams.append('pagination_token', params.paginationToken)
return `https://api.x.com/2/users/${params.userId.trim()}/following?${queryParams.toString()}`
},
method: 'GET',
headers: (params) => ({
Authorization: `Bearer ${params.accessToken}`,
'Content-Type': 'application/json',
}),
},
transformResponse: async (response) => {
const data = await response.json()
if (!data.data || !Array.isArray(data.data)) {
logger.error('X Get Following API Error:', JSON.stringify(data, null, 2))
return {
success: false,
error: data.errors?.[0]?.detail || 'No following data found or invalid response',
output: {
users: [],
meta: { resultCount: 0, nextToken: null },
},
}
}
return {
success: true,
output: {
users: data.data.map(transformUser),
meta: {
resultCount: data.meta?.result_count ?? data.data.length,
nextToken: data.meta?.next_token ?? null,
},
},
}
},
outputs: {
users: {
type: 'array',
description: 'Array of users being followed',
items: {
type: 'object',
properties: {
id: { type: 'string', description: 'User ID' },
username: { type: 'string', description: 'Username without @ symbol' },
name: { type: 'string', description: 'Display name' },
description: { type: 'string', description: 'User bio', optional: true },
profileImageUrl: { type: 'string', description: 'Profile image URL', optional: true },
verified: { type: 'boolean', description: 'Whether the user is verified' },
metrics: {
type: 'object',
description: 'User statistics',
properties: {
followersCount: { type: 'number', description: 'Number of followers' },
followingCount: { type: 'number', description: 'Number of users following' },
tweetCount: { type: 'number', description: 'Total number of tweets' },
},
},
},
},
},
meta: {
type: 'object',
description: 'Pagination metadata',
properties: {
resultCount: { type: 'number', description: 'Number of results returned' },
nextToken: { type: 'string', description: 'Token for next page', optional: true },
},
},
},
}

View File

@@ -0,0 +1,131 @@
import { createLogger } from '@sim/logger'
import type { ToolConfig } from '@/tools/types'
import type { XGetLikedTweetsParams, XTweetListResponse } from '@/tools/x/types'
import { transformTweet, transformUser } from '@/tools/x/types'
const logger = createLogger('XGetLikedTweetsTool')
export const xGetLikedTweetsTool: ToolConfig<XGetLikedTweetsParams, XTweetListResponse> = {
id: 'x_get_liked_tweets',
name: 'X Get Liked Tweets',
description: 'Get tweets liked by a specific user',
version: '1.0.0',
oauth: {
required: true,
provider: 'x',
},
params: {
accessToken: {
type: 'string',
required: true,
visibility: 'hidden',
description: 'X OAuth access token',
},
userId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'The user ID whose liked tweets to retrieve',
},
maxResults: {
type: 'number',
required: false,
visibility: 'user-or-llm',
description: 'Maximum number of results (5-100)',
},
paginationToken: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Pagination token for next page',
},
},
request: {
url: (params) => {
const queryParams = new URLSearchParams({
expansions: 'author_id,attachments.media_keys',
'tweet.fields': 'created_at,conversation_id,public_metrics,context_annotations',
'user.fields': 'name,username,description,profile_image_url,verified,public_metrics',
})
if (params.maxResults) {
const max = Math.max(5, Math.min(100, Number(params.maxResults)))
queryParams.append('max_results', max.toString())
}
if (params.paginationToken) queryParams.append('pagination_token', params.paginationToken)
return `https://api.x.com/2/users/${params.userId.trim()}/liked_tweets?${queryParams.toString()}`
},
method: 'GET',
headers: (params) => ({
Authorization: `Bearer ${params.accessToken}`,
'Content-Type': 'application/json',
}),
},
transformResponse: async (response) => {
const data = await response.json()
if (!data.data || !Array.isArray(data.data)) {
logger.error('X Get Liked Tweets API Error:', JSON.stringify(data, null, 2))
return {
success: false,
output: {
tweets: [],
meta: {
resultCount: 0,
newestId: null,
oldestId: null,
nextToken: null,
previousToken: null,
},
},
error: data.errors?.[0]?.detail ?? 'No liked tweets found or invalid response',
}
}
return {
success: true,
output: {
tweets: data.data.map(transformTweet),
includes: {
users: data.includes?.users?.map(transformUser) ?? [],
},
meta: {
resultCount: data.meta?.result_count ?? 0,
newestId: data.meta?.newest_id ?? null,
oldestId: data.meta?.oldest_id ?? null,
nextToken: data.meta?.next_token ?? null,
previousToken: data.meta?.previous_token ?? null,
},
},
}
},
outputs: {
tweets: {
type: 'array',
description: 'Array of liked tweets',
items: {
type: 'object',
properties: {
id: { type: 'string', description: 'Tweet ID' },
text: { type: 'string', description: 'Tweet content' },
createdAt: { type: 'string', description: 'Creation timestamp' },
authorId: { type: 'string', description: 'Author user ID' },
},
},
},
meta: {
type: 'object',
description: 'Pagination metadata',
properties: {
resultCount: { type: 'number', description: 'Number of results returned' },
nextToken: { type: 'string', description: 'Token for next page', optional: true },
},
},
},
}

View File

@@ -0,0 +1,128 @@
import { createLogger } from '@sim/logger'
import type { ToolConfig } from '@/tools/types'
import type { XGetLikingUsersParams, XUserListResponse } from '@/tools/x/types'
import { transformUser } from '@/tools/x/types'
const logger = createLogger('XGetLikingUsersTool')
export const xGetLikingUsersTool: ToolConfig<XGetLikingUsersParams, XUserListResponse> = {
id: 'x_get_liking_users',
name: 'X Get Liking Users',
description: 'Get the list of users who liked a specific tweet',
version: '1.0.0',
oauth: {
required: true,
provider: 'x',
},
params: {
accessToken: {
type: 'string',
required: true,
visibility: 'hidden',
description: 'X OAuth access token',
},
tweetId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'The tweet ID to get liking users for',
},
maxResults: {
type: 'number',
required: false,
visibility: 'user-or-llm',
description: 'Maximum number of results (1-100, default 100)',
},
paginationToken: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Pagination token for next page',
},
},
request: {
url: (params) => {
const queryParams = new URLSearchParams({
'user.fields': 'created_at,description,profile_image_url,verified,public_metrics',
})
if (params.maxResults) {
const max = Math.max(1, Math.min(100, Number(params.maxResults)))
queryParams.append('max_results', max.toString())
}
if (params.paginationToken) queryParams.append('pagination_token', params.paginationToken)
return `https://api.x.com/2/tweets/${params.tweetId.trim()}/liking_users?${queryParams.toString()}`
},
method: 'GET',
headers: (params) => ({
Authorization: `Bearer ${params.accessToken}`,
'Content-Type': 'application/json',
}),
},
transformResponse: async (response) => {
const data = await response.json()
if (!data.data || !Array.isArray(data.data)) {
logger.error('X Get Liking Users API Error:', JSON.stringify(data, null, 2))
return {
success: false,
output: {
users: [],
meta: { resultCount: 0, nextToken: null },
},
error: data.errors?.[0]?.detail ?? 'No liking users found or invalid response',
}
}
return {
success: true,
output: {
users: data.data.map(transformUser),
meta: {
resultCount: data.meta?.result_count ?? data.data.length,
nextToken: data.meta?.next_token ?? null,
},
},
}
},
outputs: {
users: {
type: 'array',
description: 'Array of users who liked the tweet',
items: {
type: 'object',
properties: {
id: { type: 'string', description: 'User ID' },
username: { type: 'string', description: 'Username without @ symbol' },
name: { type: 'string', description: 'Display name' },
description: { type: 'string', description: 'User bio', optional: true },
profileImageUrl: { type: 'string', description: 'Profile image URL', optional: true },
verified: { type: 'boolean', description: 'Whether the user is verified' },
metrics: {
type: 'object',
description: 'User statistics',
properties: {
followersCount: { type: 'number', description: 'Number of followers' },
followingCount: { type: 'number', description: 'Number of users following' },
tweetCount: { type: 'number', description: 'Total number of tweets' },
},
},
},
},
},
meta: {
type: 'object',
description: 'Pagination metadata',
properties: {
resultCount: { type: 'number', description: 'Number of results returned' },
nextToken: { type: 'string', description: 'Token for next page', optional: true },
},
},
},
}

View File

@@ -0,0 +1,88 @@
import { createLogger } from '@sim/logger'
import type { ToolConfig } from '@/tools/types'
import type { XGetMeParams, XGetMeResponse } from '@/tools/x/types'
import { transformUser } from '@/tools/x/types'
const logger = createLogger('XGetMeTool')
export const xGetMeTool: ToolConfig<XGetMeParams, XGetMeResponse> = {
id: 'x_get_me',
name: 'X Get Me',
description: "Get the authenticated user's profile information",
version: '1.0.0',
oauth: {
required: true,
provider: 'x',
},
params: {
accessToken: {
type: 'string',
required: true,
visibility: 'hidden',
description: 'X OAuth access token',
},
},
request: {
url: () => {
const queryParams = new URLSearchParams({
'user.fields':
'created_at,description,profile_image_url,verified,public_metrics,location,url',
})
return `https://api.x.com/2/users/me?${queryParams.toString()}`
},
method: 'GET',
headers: (params) => ({
Authorization: `Bearer ${params.accessToken}`,
'Content-Type': 'application/json',
}),
},
transformResponse: async (response) => {
const data = await response.json()
if (!data.data) {
logger.error('X Get Me API Error:', JSON.stringify(data, null, 2))
return {
success: false,
error: data.errors?.[0]?.detail || 'Failed to get authenticated user info',
output: {
user: {} as XGetMeResponse['output']['user'],
},
}
}
return {
success: true,
output: {
user: transformUser(data.data),
},
}
},
outputs: {
user: {
type: 'object',
description: 'Authenticated user profile',
properties: {
id: { type: 'string', description: 'User ID' },
username: { type: 'string', description: 'Username without @ symbol' },
name: { type: 'string', description: 'Display name' },
description: { type: 'string', description: 'User bio', optional: true },
profileImageUrl: { type: 'string', description: 'Profile image URL', optional: true },
verified: { type: 'boolean', description: 'Whether the user is verified' },
metrics: {
type: 'object',
description: 'User statistics',
properties: {
followersCount: { type: 'number', description: 'Number of followers' },
followingCount: { type: 'number', description: 'Number of users following' },
tweetCount: { type: 'number', description: 'Total number of tweets' },
},
},
},
},
},
}

View File

@@ -0,0 +1,89 @@
import { createLogger } from '@sim/logger'
import type { ToolConfig } from '@/tools/types'
import type { XGetPersonalizedTrendsParams, XPersonalizedTrendListResponse } from '@/tools/x/types'
import { transformPersonalizedTrend } from '@/tools/x/types'
const logger = createLogger('XGetPersonalizedTrendsTool')
export const xGetPersonalizedTrendsTool: ToolConfig<
XGetPersonalizedTrendsParams,
XPersonalizedTrendListResponse
> = {
id: 'x_get_personalized_trends',
name: 'X Get Personalized Trends',
description: 'Get personalized trending topics for the authenticated user',
version: '1.0.0',
oauth: {
required: true,
provider: 'x',
},
params: {
accessToken: {
type: 'string',
required: true,
visibility: 'hidden',
description: 'X OAuth access token',
},
},
request: {
url: 'https://api.x.com/2/users/personalized_trends?personalized_trend.fields=category,post_count,trend_name,trending_since',
method: 'GET',
headers: (params) => ({
Authorization: `Bearer ${params.accessToken}`,
'Content-Type': 'application/json',
}),
},
transformResponse: async (response) => {
const data = await response.json()
if (!data.data || !Array.isArray(data.data)) {
logger.error('X Get Personalized Trends API Error:', JSON.stringify(data, null, 2))
return {
success: false,
error: data.errors?.[0]?.detail || 'No personalized trends found or invalid response',
output: {
trends: [],
},
}
}
return {
success: true,
output: {
trends: data.data.map(transformPersonalizedTrend),
},
}
},
outputs: {
trends: {
type: 'array',
description: 'Array of personalized trending topics',
items: {
type: 'object',
properties: {
trendName: { type: 'string', description: 'Name of the trending topic' },
postCount: {
type: 'number',
description: 'Number of posts for this trend',
optional: true,
},
category: {
type: 'string',
description: 'Category of the trend',
optional: true,
},
trendingSince: {
type: 'string',
description: 'ISO 8601 timestamp of when the topic started trending',
optional: true,
},
},
},
},
},
}

View File

@@ -0,0 +1,152 @@
import { createLogger } from '@sim/logger'
import type { ToolConfig } from '@/tools/types'
import type { XGetQuoteTweetsParams, XTweetListResponse } from '@/tools/x/types'
import { transformTweet, transformUser } from '@/tools/x/types'
const logger = createLogger('XGetQuoteTweetsTool')
export const xGetQuoteTweetsTool: ToolConfig<XGetQuoteTweetsParams, XTweetListResponse> = {
id: 'x_get_quote_tweets',
name: 'X Get Quote Tweets',
description: 'Get tweets that quote a specific tweet',
version: '1.0.0',
oauth: {
required: true,
provider: 'x',
},
params: {
accessToken: {
type: 'string',
required: true,
visibility: 'hidden',
description: 'X OAuth access token',
},
tweetId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'The tweet ID to get quote tweets for',
},
maxResults: {
type: 'number',
required: false,
visibility: 'user-or-llm',
description: 'Maximum number of results (10-100, default 10)',
},
paginationToken: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Pagination token for next page',
},
},
request: {
url: (params) => {
const queryParams = new URLSearchParams({
expansions: 'author_id,attachments.media_keys',
'tweet.fields': 'created_at,conversation_id,public_metrics,context_annotations',
'user.fields': 'name,username,description,profile_image_url,verified,public_metrics',
})
if (params.maxResults) {
const max = Math.max(10, Math.min(100, Number(params.maxResults)))
queryParams.append('max_results', max.toString())
}
if (params.paginationToken) queryParams.append('pagination_token', params.paginationToken)
return `https://api.x.com/2/tweets/${params.tweetId.trim()}/quote_tweets?${queryParams.toString()}`
},
method: 'GET',
headers: (params) => ({
Authorization: `Bearer ${params.accessToken}`,
'Content-Type': 'application/json',
}),
},
transformResponse: async (response) => {
const data = await response.json()
if (!data.data || !Array.isArray(data.data)) {
logger.error('X Get Quote Tweets API Error:', JSON.stringify(data, null, 2))
return {
success: false,
error: data.errors?.[0]?.detail || 'No quote tweets found or invalid response',
output: {
tweets: [],
meta: {
resultCount: 0,
newestId: null,
oldestId: null,
nextToken: null,
previousToken: null,
},
},
}
}
return {
success: true,
output: {
tweets: data.data.map(transformTweet),
includes: {
users: data.includes?.users?.map(transformUser) ?? [],
},
meta: {
resultCount: data.meta?.result_count ?? 0,
newestId: data.meta?.newest_id ?? null,
oldestId: data.meta?.oldest_id ?? null,
nextToken: data.meta?.next_token ?? null,
previousToken: data.meta?.previous_token ?? null,
},
},
}
},
outputs: {
tweets: {
type: 'array',
description: 'Array of quote tweets',
items: {
type: 'object',
properties: {
id: { type: 'string', description: 'Tweet ID' },
text: { type: 'string', description: 'Tweet text content' },
createdAt: { type: 'string', description: 'Tweet creation timestamp' },
authorId: { type: 'string', description: 'Author user ID' },
conversationId: {
type: 'string',
description: 'Conversation thread ID',
optional: true,
},
inReplyToUserId: {
type: 'string',
description: 'User ID being replied to',
optional: true,
},
publicMetrics: {
type: 'object',
description: 'Engagement metrics',
optional: true,
properties: {
retweetCount: { type: 'number', description: 'Number of retweets' },
replyCount: { type: 'number', description: 'Number of replies' },
likeCount: { type: 'number', description: 'Number of likes' },
quoteCount: { type: 'number', description: 'Number of quotes' },
},
},
},
},
},
meta: {
type: 'object',
description: 'Pagination metadata',
properties: {
resultCount: { type: 'number', description: 'Number of results returned' },
nextToken: { type: 'string', description: 'Token for next page', optional: true },
},
},
},
}

View File

@@ -0,0 +1,128 @@
import { createLogger } from '@sim/logger'
import type { ToolConfig } from '@/tools/types'
import type { XGetRetweetedByParams, XUserListResponse } from '@/tools/x/types'
import { transformUser } from '@/tools/x/types'
const logger = createLogger('XGetRetweetedByTool')
export const xGetRetweetedByTool: ToolConfig<XGetRetweetedByParams, XUserListResponse> = {
id: 'x_get_retweeted_by',
name: 'X Get Retweeted By',
description: 'Get the list of users who retweeted a specific tweet',
version: '1.0.0',
oauth: {
required: true,
provider: 'x',
},
params: {
accessToken: {
type: 'string',
required: true,
visibility: 'hidden',
description: 'X OAuth access token',
},
tweetId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'The tweet ID to get retweeters for',
},
maxResults: {
type: 'number',
required: false,
visibility: 'user-or-llm',
description: 'Maximum number of results (1-100, default 100)',
},
paginationToken: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Pagination token for next page',
},
},
request: {
url: (params) => {
const queryParams = new URLSearchParams({
'user.fields': 'created_at,description,profile_image_url,verified,public_metrics',
})
if (params.maxResults) {
const max = Math.max(1, Math.min(100, Number(params.maxResults)))
queryParams.append('max_results', max.toString())
}
if (params.paginationToken) queryParams.append('pagination_token', params.paginationToken)
return `https://api.x.com/2/tweets/${params.tweetId.trim()}/retweeted_by?${queryParams.toString()}`
},
method: 'GET',
headers: (params) => ({
Authorization: `Bearer ${params.accessToken}`,
'Content-Type': 'application/json',
}),
},
transformResponse: async (response) => {
const data = await response.json()
if (!data.data || !Array.isArray(data.data)) {
logger.error('X Get Retweeted By API Error:', JSON.stringify(data, null, 2))
return {
success: false,
error: data.errors?.[0]?.detail || 'No retweeters found or invalid response',
output: {
users: [],
meta: { resultCount: 0, nextToken: null },
},
}
}
return {
success: true,
output: {
users: data.data.map(transformUser),
meta: {
resultCount: data.meta?.result_count ?? data.data.length,
nextToken: data.meta?.next_token ?? null,
},
},
}
},
outputs: {
users: {
type: 'array',
description: 'Array of users who retweeted the tweet',
items: {
type: 'object',
properties: {
id: { type: 'string', description: 'User ID' },
username: { type: 'string', description: 'Username without @ symbol' },
name: { type: 'string', description: 'Display name' },
description: { type: 'string', description: 'User bio', optional: true },
profileImageUrl: { type: 'string', description: 'Profile image URL', optional: true },
verified: { type: 'boolean', description: 'Whether the user is verified' },
metrics: {
type: 'object',
description: 'User statistics',
properties: {
followersCount: { type: 'number', description: 'Number of followers' },
followingCount: { type: 'number', description: 'Number of users following' },
tweetCount: { type: 'number', description: 'Total number of tweets' },
},
},
},
},
},
meta: {
type: 'object',
description: 'Metadata',
properties: {
resultCount: { type: 'number', description: 'Number of results returned' },
nextToken: { type: 'string', description: 'Token for next page', optional: true },
},
},
},
}

View File

@@ -0,0 +1,100 @@
import { createLogger } from '@sim/logger'
import type { ToolConfig } from '@/tools/types'
import type { XGetTrendsByWoeidParams, XTrendListResponse } from '@/tools/x/types'
import { transformTrend } from '@/tools/x/types'
const logger = createLogger('XGetTrendsByWoeidTool')
export const xGetTrendsByWoeidTool: ToolConfig<XGetTrendsByWoeidParams, XTrendListResponse> = {
id: 'x_get_trends_by_woeid',
name: 'X Get Trends By WOEID',
description:
'Get trending topics for a specific location by WOEID (e.g., 1 for worldwide, 23424977 for US)',
version: '1.0.0',
oauth: {
required: true,
provider: 'x',
},
params: {
accessToken: {
type: 'string',
required: true,
visibility: 'hidden',
description: 'X OAuth access token',
},
woeid: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description:
'Yahoo Where On Earth ID (e.g., "1" for worldwide, "23424977" for US, "23424975" for UK)',
},
maxTrends: {
type: 'number',
required: false,
visibility: 'user-or-llm',
description: 'Maximum number of trends to return (1-50, default 20)',
},
},
request: {
url: (params) => {
const queryParams = new URLSearchParams({
'trend.fields': 'trend_name,tweet_count',
})
if (params.maxTrends) {
queryParams.append('max_trends', Number(params.maxTrends).toString())
}
return `https://api.x.com/2/trends/by/woeid/${params.woeid.trim()}?${queryParams.toString()}`
},
method: 'GET',
headers: (params) => ({
Authorization: `Bearer ${params.accessToken}`,
'Content-Type': 'application/json',
}),
},
transformResponse: async (response) => {
const data = await response.json()
if (!data.data || !Array.isArray(data.data)) {
logger.error('X Get Trends API Error:', JSON.stringify(data, null, 2))
return {
success: false,
error: data.errors?.[0]?.detail || 'No trends found or invalid response',
output: {
trends: [],
},
}
}
return {
success: true,
output: {
trends: data.data.map(transformTrend),
},
}
},
outputs: {
trends: {
type: 'array',
description: 'Array of trending topics',
items: {
type: 'object',
properties: {
trendName: { type: 'string', description: 'Name of the trending topic' },
tweetCount: {
type: 'number',
description: 'Number of tweets for this trend',
optional: true,
},
},
},
},
},
}

View File

@@ -0,0 +1,141 @@
import { createLogger } from '@sim/logger'
import type { ToolConfig } from '@/tools/types'
import type { XGetTweetsByIdsParams, XGetTweetsByIdsResponse } from '@/tools/x/types'
import { transformTweet, transformUser } from '@/tools/x/types'
const logger = createLogger('XGetTweetsByIdsTool')
export const xGetTweetsByIdsTool: ToolConfig<XGetTweetsByIdsParams, XGetTweetsByIdsResponse> = {
id: 'x_get_tweets_by_ids',
name: 'X Get Tweets By IDs',
description: 'Look up multiple tweets by their IDs (up to 100)',
version: '1.0.0',
oauth: {
required: true,
provider: 'x',
},
params: {
accessToken: {
type: 'string',
required: true,
visibility: 'hidden',
description: 'X OAuth access token',
},
ids: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Comma-separated tweet IDs (up to 100)',
},
},
request: {
url: (params) => {
const queryParams = new URLSearchParams({
ids: params.ids.trim(),
expansions: 'author_id,referenced_tweets.id,attachments.media_keys,attachments.poll_ids',
'tweet.fields':
'created_at,conversation_id,in_reply_to_user_id,attachments,context_annotations,public_metrics',
'user.fields': 'name,username,description,profile_image_url,verified,public_metrics',
})
return `https://api.x.com/2/tweets?${queryParams.toString()}`
},
method: 'GET',
headers: (params) => ({
Authorization: `Bearer ${params.accessToken}`,
'Content-Type': 'application/json',
}),
},
transformResponse: async (response) => {
const data = await response.json()
if (!data.data || !Array.isArray(data.data)) {
logger.error('X Get Tweets By IDs API Error:', JSON.stringify(data, null, 2))
return {
success: false,
error: data.errors?.[0]?.detail || 'No tweets found or invalid response',
output: {
tweets: [],
},
}
}
return {
success: true,
output: {
tweets: data.data.map(transformTweet),
includes: {
users: data.includes?.users?.map(transformUser) ?? [],
},
},
}
},
outputs: {
tweets: {
type: 'array',
description: 'Array of tweets matching the provided IDs',
items: {
type: 'object',
properties: {
id: { type: 'string', description: 'Tweet ID' },
text: { type: 'string', description: 'Tweet text content' },
createdAt: { type: 'string', description: 'Tweet creation timestamp' },
authorId: { type: 'string', description: 'Author user ID' },
conversationId: { type: 'string', description: 'Conversation thread ID', optional: true },
inReplyToUserId: {
type: 'string',
description: 'User ID being replied to',
optional: true,
},
publicMetrics: {
type: 'object',
description: 'Engagement metrics',
optional: true,
properties: {
retweetCount: { type: 'number', description: 'Number of retweets' },
replyCount: { type: 'number', description: 'Number of replies' },
likeCount: { type: 'number', description: 'Number of likes' },
quoteCount: { type: 'number', description: 'Number of quotes' },
},
},
},
},
},
includes: {
type: 'object',
description: 'Additional data including user profiles',
optional: true,
properties: {
users: {
type: 'array',
description: 'Array of user objects referenced in tweets',
items: {
type: 'object',
properties: {
id: { type: 'string', description: 'User ID' },
username: { type: 'string', description: 'Username without @ symbol' },
name: { type: 'string', description: 'Display name' },
description: { type: 'string', description: 'User bio', optional: true },
profileImageUrl: { type: 'string', description: 'Profile image URL', optional: true },
verified: { type: 'boolean', description: 'Whether the user is verified' },
metrics: {
type: 'object',
description: 'User statistics',
properties: {
followersCount: { type: 'number', description: 'Number of followers' },
followingCount: { type: 'number', description: 'Number of users following' },
tweetCount: { type: 'number', description: 'Total number of tweets' },
},
},
},
},
},
},
},
},
}

View File

@@ -0,0 +1,151 @@
import { createLogger } from '@sim/logger'
import type { ToolConfig } from '@/tools/types'
import type { XGetUsageParams, XGetUsageResponse } from '@/tools/x/types'
const logger = createLogger('XGetUsageTool')
export const xGetUsageTool: ToolConfig<XGetUsageParams, XGetUsageResponse> = {
id: 'x_get_usage',
name: 'X Get Usage',
description: 'Get the API usage data for your X project',
version: '1.0.0',
oauth: {
required: true,
provider: 'x',
},
params: {
accessToken: {
type: 'string',
required: true,
visibility: 'hidden',
description: 'X OAuth access token',
},
days: {
type: 'number',
required: false,
visibility: 'user-or-llm',
description: 'Number of days of usage data to return (1-90, default 7)',
},
},
request: {
url: (params) => {
const queryParams = new URLSearchParams({
'usage.fields':
'cap_reset_day,daily_client_app_usage,daily_project_usage,project_cap,project_id,project_usage',
})
if (params.days) {
queryParams.append('days', Number(params.days).toString())
}
return `https://api.x.com/2/usage/tweets?${queryParams.toString()}`
},
method: 'GET',
headers: (params) => ({
Authorization: `Bearer ${params.accessToken}`,
'Content-Type': 'application/json',
}),
},
transformResponse: async (response) => {
const data = await response.json()
if (!data.data) {
logger.error('X Get Usage API Error:', JSON.stringify(data, null, 2))
return {
success: false,
error: data.errors?.[0]?.detail || 'Failed to get usage data',
output: {
capResetDay: null,
projectId: '',
projectCap: null,
projectUsage: null,
dailyProjectUsage: [],
dailyClientAppUsage: [],
},
}
}
return {
success: true,
output: {
capResetDay: data.data.cap_reset_day ?? null,
projectId: String(data.data.project_id ?? ''),
projectCap: data.data.project_cap ?? null,
projectUsage: data.data.project_usage ?? null,
dailyProjectUsage: (data.data.daily_project_usage?.usage ?? []).map(
(u: { date: string; usage: number }) => ({
date: u.date,
usage: u.usage ?? 0,
})
),
dailyClientAppUsage: (data.data.daily_client_app_usage ?? []).map(
(app: { client_app_id: string; usage: { date: string; usage: number }[] }) => ({
clientAppId: String(app.client_app_id ?? ''),
usage: (app.usage ?? []).map((u: { date: string; usage: number }) => ({
date: u.date,
usage: u.usage ?? 0,
})),
})
),
},
}
},
outputs: {
capResetDay: {
type: 'number',
description: 'Day of month when usage cap resets',
optional: true,
},
projectId: {
type: 'string',
description: 'The project ID',
},
projectCap: {
type: 'number',
description: 'The project tweet consumption cap',
optional: true,
},
projectUsage: {
type: 'number',
description: 'Total tweets consumed in current period',
optional: true,
},
dailyProjectUsage: {
type: 'array',
description: 'Daily project usage breakdown',
items: {
type: 'object',
properties: {
date: { type: 'string', description: 'Usage date in ISO 8601 format' },
usage: { type: 'number', description: 'Number of tweets consumed' },
},
},
},
dailyClientAppUsage: {
type: 'array',
description: 'Daily per-app usage breakdown',
items: {
type: 'object',
properties: {
clientAppId: { type: 'string', description: 'Client application ID' },
usage: {
type: 'array',
description: 'Daily usage entries for this app',
items: {
type: 'object',
properties: {
date: { type: 'string', description: 'Usage date in ISO 8601 format' },
usage: { type: 'number', description: 'Number of tweets consumed' },
},
},
},
},
},
},
},
}

View File

@@ -0,0 +1,211 @@
import { createLogger } from '@sim/logger'
import type { ToolConfig } from '@/tools/types'
import type { XGetUserMentionsParams, XTweetListResponse } from '@/tools/x/types'
import { transformTweet, transformUser } from '@/tools/x/types'
const logger = createLogger('XGetUserMentionsTool')
export const xGetUserMentionsTool: ToolConfig<XGetUserMentionsParams, XTweetListResponse> = {
id: 'x_get_user_mentions',
name: 'X Get User Mentions',
description: 'Get tweets that mention a specific user',
version: '1.0.0',
oauth: {
required: true,
provider: 'x',
},
params: {
accessToken: {
type: 'string',
required: true,
visibility: 'hidden',
description: 'X OAuth access token',
},
userId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'The user ID whose mentions to retrieve',
},
maxResults: {
type: 'number',
required: false,
visibility: 'user-or-llm',
description: 'Maximum number of results (5-100, default 10)',
},
paginationToken: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Pagination token for next page of results',
},
startTime: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Oldest UTC timestamp in ISO 8601 format',
},
endTime: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Newest UTC timestamp in ISO 8601 format',
},
sinceId: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Returns tweets with ID greater than this',
},
untilId: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Returns tweets with ID less than this',
},
},
request: {
url: (params) => {
const queryParams = new URLSearchParams({
expansions: 'author_id,referenced_tweets.id,attachments.media_keys,attachments.poll_ids',
'tweet.fields':
'created_at,conversation_id,in_reply_to_user_id,attachments,context_annotations,public_metrics',
'user.fields': 'name,username,description,profile_image_url,verified,public_metrics',
})
if (params.maxResults) {
const max = Math.max(5, Math.min(100, Number(params.maxResults)))
queryParams.append('max_results', max.toString())
}
if (params.paginationToken) queryParams.append('pagination_token', params.paginationToken)
if (params.startTime) queryParams.append('start_time', params.startTime)
if (params.endTime) queryParams.append('end_time', params.endTime)
if (params.sinceId) queryParams.append('since_id', params.sinceId)
if (params.untilId) queryParams.append('until_id', params.untilId)
return `https://api.x.com/2/users/${params.userId.trim()}/mentions?${queryParams.toString()}`
},
method: 'GET',
headers: (params) => ({
Authorization: `Bearer ${params.accessToken}`,
'Content-Type': 'application/json',
}),
},
transformResponse: async (response) => {
const data = await response.json()
if (!data.data || !Array.isArray(data.data)) {
logger.error('X Get User Mentions API Error:', JSON.stringify(data, null, 2))
return {
success: false,
error: data.errors?.[0]?.detail || 'No mentions found or invalid response',
output: {
tweets: [],
meta: {
resultCount: 0,
newestId: null,
oldestId: null,
nextToken: null,
previousToken: null,
},
},
}
}
return {
success: true,
output: {
tweets: data.data.map(transformTweet),
includes: {
users: data.includes?.users?.map(transformUser) ?? [],
},
meta: {
resultCount: data.meta?.result_count ?? 0,
newestId: data.meta?.newest_id ?? null,
oldestId: data.meta?.oldest_id ?? null,
nextToken: data.meta?.next_token ?? null,
previousToken: data.meta?.previous_token ?? null,
},
},
}
},
outputs: {
tweets: {
type: 'array',
description: 'Array of tweets mentioning the user',
items: {
type: 'object',
properties: {
id: { type: 'string', description: 'Tweet ID' },
text: { type: 'string', description: 'Tweet text content' },
createdAt: { type: 'string', description: 'Tweet creation timestamp' },
authorId: { type: 'string', description: 'Author user ID' },
conversationId: { type: 'string', description: 'Conversation thread ID', optional: true },
inReplyToUserId: {
type: 'string',
description: 'User ID being replied to',
optional: true,
},
publicMetrics: {
type: 'object',
description: 'Engagement metrics',
optional: true,
properties: {
retweetCount: { type: 'number', description: 'Number of retweets' },
replyCount: { type: 'number', description: 'Number of replies' },
likeCount: { type: 'number', description: 'Number of likes' },
quoteCount: { type: 'number', description: 'Number of quotes' },
},
},
},
},
},
includes: {
type: 'object',
description: 'Additional data including user profiles',
optional: true,
properties: {
users: {
type: 'array',
description: 'Array of user objects referenced in tweets',
items: {
type: 'object',
properties: {
id: { type: 'string', description: 'User ID' },
username: { type: 'string', description: 'Username without @ symbol' },
name: { type: 'string', description: 'Display name' },
description: { type: 'string', description: 'User bio', optional: true },
profileImageUrl: { type: 'string', description: 'Profile image URL', optional: true },
verified: { type: 'boolean', description: 'Whether the user is verified' },
metrics: {
type: 'object',
description: 'User statistics',
properties: {
followersCount: { type: 'number', description: 'Number of followers' },
followingCount: { type: 'number', description: 'Number of users following' },
tweetCount: { type: 'number', description: 'Total number of tweets' },
},
},
},
},
},
},
},
meta: {
type: 'object',
description: 'Pagination metadata',
properties: {
resultCount: { type: 'number', description: 'Number of results returned' },
newestId: { type: 'string', description: 'ID of the newest tweet', optional: true },
oldestId: { type: 'string', description: 'ID of the oldest tweet', optional: true },
nextToken: { type: 'string', description: 'Token for next page', optional: true },
previousToken: { type: 'string', description: 'Token for previous page', optional: true },
},
},
},
}

View File

@@ -0,0 +1,218 @@
import { createLogger } from '@sim/logger'
import type { ToolConfig } from '@/tools/types'
import type { XGetUserTimelineParams, XTweetListResponse } from '@/tools/x/types'
import { transformTweet, transformUser } from '@/tools/x/types'
const logger = createLogger('XGetUserTimelineTool')
export const xGetUserTimelineTool: ToolConfig<XGetUserTimelineParams, XTweetListResponse> = {
id: 'x_get_user_timeline',
name: 'X Get User Timeline',
description: 'Get the reverse chronological home timeline for the authenticated user',
version: '1.0.0',
oauth: {
required: true,
provider: 'x',
},
params: {
accessToken: {
type: 'string',
required: true,
visibility: 'hidden',
description: 'X OAuth access token',
},
userId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'The authenticated user ID',
},
maxResults: {
type: 'number',
required: false,
visibility: 'user-or-llm',
description: 'Maximum number of results (1-100, default 10)',
},
paginationToken: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Pagination token for next page of results',
},
startTime: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Oldest UTC timestamp in ISO 8601 format',
},
endTime: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Newest UTC timestamp in ISO 8601 format',
},
sinceId: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Returns tweets with ID greater than this',
},
untilId: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Returns tweets with ID less than this',
},
exclude: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Comma-separated types to exclude: "retweets", "replies"',
},
},
request: {
url: (params) => {
const queryParams = new URLSearchParams({
expansions: 'author_id,referenced_tweets.id,attachments.media_keys,attachments.poll_ids',
'tweet.fields':
'created_at,conversation_id,in_reply_to_user_id,attachments,context_annotations,public_metrics',
'user.fields': 'name,username,description,profile_image_url,verified,public_metrics',
})
if (params.maxResults) {
const max = Math.max(1, Math.min(100, Number(params.maxResults)))
queryParams.append('max_results', max.toString())
}
if (params.paginationToken) queryParams.append('pagination_token', params.paginationToken)
if (params.startTime) queryParams.append('start_time', params.startTime)
if (params.endTime) queryParams.append('end_time', params.endTime)
if (params.sinceId) queryParams.append('since_id', params.sinceId)
if (params.untilId) queryParams.append('until_id', params.untilId)
if (params.exclude) queryParams.append('exclude', params.exclude)
return `https://api.x.com/2/users/${params.userId.trim()}/timelines/reverse_chronological?${queryParams.toString()}`
},
method: 'GET',
headers: (params) => ({
Authorization: `Bearer ${params.accessToken}`,
'Content-Type': 'application/json',
}),
},
transformResponse: async (response) => {
const data = await response.json()
if (!data.data || !Array.isArray(data.data)) {
logger.error('X Get User Timeline API Error:', JSON.stringify(data, null, 2))
return {
success: false,
error: data.errors?.[0]?.detail || 'No timeline data found or invalid response',
output: {
tweets: [],
meta: {
resultCount: 0,
newestId: null,
oldestId: null,
nextToken: null,
previousToken: null,
},
},
}
}
return {
success: true,
output: {
tweets: data.data.map(transformTweet),
includes: {
users: data.includes?.users?.map(transformUser) ?? [],
},
meta: {
resultCount: data.meta?.result_count ?? 0,
newestId: data.meta?.newest_id ?? null,
oldestId: data.meta?.oldest_id ?? null,
nextToken: data.meta?.next_token ?? null,
previousToken: data.meta?.previous_token ?? null,
},
},
}
},
outputs: {
tweets: {
type: 'array',
description: 'Array of timeline tweets',
items: {
type: 'object',
properties: {
id: { type: 'string', description: 'Tweet ID' },
text: { type: 'string', description: 'Tweet text content' },
createdAt: { type: 'string', description: 'Tweet creation timestamp' },
authorId: { type: 'string', description: 'Author user ID' },
conversationId: { type: 'string', description: 'Conversation thread ID', optional: true },
inReplyToUserId: {
type: 'string',
description: 'User ID being replied to',
optional: true,
},
publicMetrics: {
type: 'object',
description: 'Engagement metrics',
optional: true,
properties: {
retweetCount: { type: 'number', description: 'Number of retweets' },
replyCount: { type: 'number', description: 'Number of replies' },
likeCount: { type: 'number', description: 'Number of likes' },
quoteCount: { type: 'number', description: 'Number of quotes' },
},
},
},
},
},
includes: {
type: 'object',
description: 'Additional data including user profiles',
optional: true,
properties: {
users: {
type: 'array',
description: 'Array of user objects referenced in tweets',
items: {
type: 'object',
properties: {
id: { type: 'string', description: 'User ID' },
username: { type: 'string', description: 'Username without @ symbol' },
name: { type: 'string', description: 'Display name' },
description: { type: 'string', description: 'User bio', optional: true },
profileImageUrl: { type: 'string', description: 'Profile image URL', optional: true },
verified: { type: 'boolean', description: 'Whether the user is verified' },
metrics: {
type: 'object',
description: 'User statistics',
properties: {
followersCount: { type: 'number', description: 'Number of followers' },
followingCount: { type: 'number', description: 'Number of users following' },
tweetCount: { type: 'number', description: 'Total number of tweets' },
},
},
},
},
},
},
},
meta: {
type: 'object',
description: 'Pagination metadata',
properties: {
resultCount: { type: 'number', description: 'Number of results returned' },
newestId: { type: 'string', description: 'ID of the newest tweet', optional: true },
oldestId: { type: 'string', description: 'ID of the oldest tweet', optional: true },
nextToken: { type: 'string', description: 'Token for next page', optional: true },
previousToken: { type: 'string', description: 'Token for previous page', optional: true },
},
},
},
}

View File

@@ -0,0 +1,218 @@
import { createLogger } from '@sim/logger'
import type { ToolConfig } from '@/tools/types'
import type { XGetUserTweetsParams, XTweetListResponse } from '@/tools/x/types'
import { transformTweet, transformUser } from '@/tools/x/types'
const logger = createLogger('XGetUserTweetsTool')
export const xGetUserTweetsTool: ToolConfig<XGetUserTweetsParams, XTweetListResponse> = {
id: 'x_get_user_tweets',
name: 'X Get User Tweets',
description: 'Get tweets authored by a specific user',
version: '1.0.0',
oauth: {
required: true,
provider: 'x',
},
params: {
accessToken: {
type: 'string',
required: true,
visibility: 'hidden',
description: 'X OAuth access token',
},
userId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'The user ID whose tweets to retrieve',
},
maxResults: {
type: 'number',
required: false,
visibility: 'user-or-llm',
description: 'Maximum number of results (5-100, default 10)',
},
paginationToken: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Pagination token for next page of results',
},
startTime: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Oldest UTC timestamp in ISO 8601 format',
},
endTime: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Newest UTC timestamp in ISO 8601 format',
},
sinceId: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Returns tweets with ID greater than this',
},
untilId: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Returns tweets with ID less than this',
},
exclude: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Comma-separated types to exclude: "retweets", "replies"',
},
},
request: {
url: (params) => {
const queryParams = new URLSearchParams({
expansions: 'author_id,referenced_tweets.id,attachments.media_keys,attachments.poll_ids',
'tweet.fields':
'created_at,conversation_id,in_reply_to_user_id,attachments,context_annotations,public_metrics',
'user.fields': 'name,username,description,profile_image_url,verified,public_metrics',
})
if (params.maxResults) {
const max = Math.max(5, Math.min(100, Number(params.maxResults)))
queryParams.append('max_results', max.toString())
}
if (params.paginationToken) queryParams.append('pagination_token', params.paginationToken)
if (params.startTime) queryParams.append('start_time', params.startTime)
if (params.endTime) queryParams.append('end_time', params.endTime)
if (params.sinceId) queryParams.append('since_id', params.sinceId)
if (params.untilId) queryParams.append('until_id', params.untilId)
if (params.exclude) queryParams.append('exclude', params.exclude)
return `https://api.x.com/2/users/${params.userId.trim()}/tweets?${queryParams.toString()}`
},
method: 'GET',
headers: (params) => ({
Authorization: `Bearer ${params.accessToken}`,
'Content-Type': 'application/json',
}),
},
transformResponse: async (response) => {
const data = await response.json()
if (!data.data || !Array.isArray(data.data)) {
logger.error('X Get User Tweets API Error:', JSON.stringify(data, null, 2))
return {
success: false,
error: data.errors?.[0]?.detail || 'No tweets found or invalid response',
output: {
tweets: [],
meta: {
resultCount: 0,
newestId: null,
oldestId: null,
nextToken: null,
previousToken: null,
},
},
}
}
return {
success: true,
output: {
tweets: data.data.map(transformTweet),
includes: {
users: data.includes?.users?.map(transformUser) ?? [],
},
meta: {
resultCount: data.meta?.result_count ?? 0,
newestId: data.meta?.newest_id ?? null,
oldestId: data.meta?.oldest_id ?? null,
nextToken: data.meta?.next_token ?? null,
previousToken: data.meta?.previous_token ?? null,
},
},
}
},
outputs: {
tweets: {
type: 'array',
description: 'Array of tweets by the user',
items: {
type: 'object',
properties: {
id: { type: 'string', description: 'Tweet ID' },
text: { type: 'string', description: 'Tweet text content' },
createdAt: { type: 'string', description: 'Tweet creation timestamp' },
authorId: { type: 'string', description: 'Author user ID' },
conversationId: { type: 'string', description: 'Conversation thread ID', optional: true },
inReplyToUserId: {
type: 'string',
description: 'User ID being replied to',
optional: true,
},
publicMetrics: {
type: 'object',
description: 'Engagement metrics',
optional: true,
properties: {
retweetCount: { type: 'number', description: 'Number of retweets' },
replyCount: { type: 'number', description: 'Number of replies' },
likeCount: { type: 'number', description: 'Number of likes' },
quoteCount: { type: 'number', description: 'Number of quotes' },
},
},
},
},
},
includes: {
type: 'object',
description: 'Additional data including user profiles',
optional: true,
properties: {
users: {
type: 'array',
description: 'Array of user objects referenced in tweets',
items: {
type: 'object',
properties: {
id: { type: 'string', description: 'User ID' },
username: { type: 'string', description: 'Username without @ symbol' },
name: { type: 'string', description: 'Display name' },
description: { type: 'string', description: 'User bio', optional: true },
profileImageUrl: { type: 'string', description: 'Profile image URL', optional: true },
verified: { type: 'boolean', description: 'Whether the user is verified' },
metrics: {
type: 'object',
description: 'User statistics',
properties: {
followersCount: { type: 'number', description: 'Number of followers' },
followingCount: { type: 'number', description: 'Number of users following' },
tweetCount: { type: 'number', description: 'Total number of tweets' },
},
},
},
},
},
},
},
meta: {
type: 'object',
description: 'Pagination metadata',
properties: {
resultCount: { type: 'number', description: 'Number of results returned' },
newestId: { type: 'string', description: 'ID of the newest tweet', optional: true },
oldestId: { type: 'string', description: 'ID of the oldest tweet', optional: true },
nextToken: { type: 'string', description: 'Token for next page', optional: true },
previousToken: { type: 'string', description: 'Token for previous page', optional: true },
},
},
},
}

View File

@@ -0,0 +1,79 @@
import { createLogger } from '@sim/logger'
import type { ToolConfig } from '@/tools/types'
import type { XHideReplyParams, XHideReplyResponse } from '@/tools/x/types'
const logger = createLogger('XHideReplyTool')
export const xHideReplyTool: ToolConfig<XHideReplyParams, XHideReplyResponse> = {
id: 'x_hide_reply',
name: 'X Hide Reply',
description: 'Hide or unhide a reply to a tweet authored by the authenticated user',
version: '1.0.0',
oauth: {
required: true,
provider: 'x',
},
params: {
accessToken: {
type: 'string',
required: true,
visibility: 'hidden',
description: 'X OAuth access token',
},
tweetId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'The reply tweet ID to hide or unhide',
},
hidden: {
type: 'boolean',
required: true,
visibility: 'user-or-llm',
description: 'Set to true to hide the reply, false to unhide',
},
},
request: {
url: (params) => `https://api.x.com/2/tweets/${params.tweetId.trim()}/hidden`,
method: 'PUT',
headers: (params) => ({
Authorization: `Bearer ${params.accessToken}`,
'Content-Type': 'application/json',
}),
body: (params) => ({
hidden: params.hidden,
}),
},
transformResponse: async (response) => {
const data = await response.json()
if (!data.data) {
logger.error('X Hide Reply API Error:', JSON.stringify(data, null, 2))
return {
success: false,
error: data.errors?.[0]?.detail || 'Failed to hide/unhide reply',
output: {
hidden: false,
},
}
}
return {
success: true,
output: {
hidden: data.data?.hidden ?? false,
},
}
},
outputs: {
hidden: {
type: 'boolean',
description: 'Whether the reply is now hidden',
},
},
}

View File

@@ -7,3 +7,33 @@ export { xReadTool }
export { xWriteTool }
export { xSearchTool }
export { xUserTool }
export { xCreateBookmarkTool } from '@/tools/x/create_bookmark'
export { xCreateTweetTool } from '@/tools/x/create_tweet'
export { xDeleteBookmarkTool } from '@/tools/x/delete_bookmark'
export { xDeleteTweetTool } from '@/tools/x/delete_tweet'
export { xGetBlockingTool } from '@/tools/x/get_blocking'
export { xGetBookmarksTool } from '@/tools/x/get_bookmarks'
export { xGetFollowersTool } from '@/tools/x/get_followers'
export { xGetFollowingTool } from '@/tools/x/get_following'
export { xGetLikedTweetsTool } from '@/tools/x/get_liked_tweets'
export { xGetLikingUsersTool } from '@/tools/x/get_liking_users'
export { xGetMeTool } from '@/tools/x/get_me'
export { xGetPersonalizedTrendsTool } from '@/tools/x/get_personalized_trends'
export { xGetQuoteTweetsTool } from '@/tools/x/get_quote_tweets'
export { xGetRetweetedByTool } from '@/tools/x/get_retweeted_by'
export { xGetTrendsByWoeidTool } from '@/tools/x/get_trends_by_woeid'
export { xGetTweetsByIdsTool } from '@/tools/x/get_tweets_by_ids'
export { xGetUsageTool } from '@/tools/x/get_usage'
export { xGetUserMentionsTool } from '@/tools/x/get_user_mentions'
export { xGetUserTimelineTool } from '@/tools/x/get_user_timeline'
export { xGetUserTweetsTool } from '@/tools/x/get_user_tweets'
export { xHideReplyTool } from '@/tools/x/hide_reply'
export { xManageBlockTool } from '@/tools/x/manage_block'
export { xManageFollowTool } from '@/tools/x/manage_follow'
export { xManageLikeTool } from '@/tools/x/manage_like'
export { xManageMuteTool } from '@/tools/x/manage_mute'
export { xManageRetweetTool } from '@/tools/x/manage_retweet'
export { xSearchTweetsTool } from '@/tools/x/search_tweets'
export { xSearchUsersTool } from '@/tools/x/search_users'
export * from '@/tools/x/types'

View File

@@ -0,0 +1,93 @@
import { createLogger } from '@sim/logger'
import type { ToolConfig } from '@/tools/types'
import type { XManageBlockParams, XManageBlockResponse } from '@/tools/x/types'
const logger = createLogger('XManageBlockTool')
export const xManageBlockTool: ToolConfig<XManageBlockParams, XManageBlockResponse> = {
id: 'x_manage_block',
name: 'X Manage Block',
description: 'Block or unblock a user on X',
version: '1.0.0',
oauth: {
required: true,
provider: 'x',
},
params: {
accessToken: {
type: 'string',
required: true,
visibility: 'hidden',
description: 'X OAuth access token',
},
userId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'The authenticated user ID',
},
targetUserId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'The user ID to block or unblock',
},
action: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Action to perform: "block" or "unblock"',
},
},
request: {
url: (params) => {
if (params.action === 'unblock') {
return `https://api.x.com/2/users/${params.userId.trim()}/blocking/${params.targetUserId.trim()}`
}
return `https://api.x.com/2/users/${params.userId.trim()}/blocking`
},
method: (params) => (params.action === 'unblock' ? 'DELETE' : 'POST'),
headers: (params) => ({
Authorization: `Bearer ${params.accessToken}`,
'Content-Type': 'application/json',
}),
body: (params) => {
if (params.action === 'unblock') return undefined
return {
target_user_id: params.targetUserId.trim(),
}
},
},
transformResponse: async (response) => {
const data = await response.json()
if (!data.data) {
logger.error('X Manage Block API Error:', JSON.stringify(data, null, 2))
return {
success: false,
output: {
blocking: false,
},
error: data.errors?.[0]?.detail ?? 'Failed to manage block',
}
}
return {
success: true,
output: {
blocking: data.data?.blocking ?? false,
},
}
},
outputs: {
blocking: {
type: 'boolean',
description: 'Whether you are now blocking the user',
},
},
}

View File

@@ -0,0 +1,99 @@
import { createLogger } from '@sim/logger'
import type { ToolConfig } from '@/tools/types'
import type { XManageFollowParams, XManageFollowResponse } from '@/tools/x/types'
const logger = createLogger('XManageFollowTool')
export const xManageFollowTool: ToolConfig<XManageFollowParams, XManageFollowResponse> = {
id: 'x_manage_follow',
name: 'X Manage Follow',
description: 'Follow or unfollow a user on X',
version: '1.0.0',
oauth: {
required: true,
provider: 'x',
},
params: {
accessToken: {
type: 'string',
required: true,
visibility: 'hidden',
description: 'X OAuth access token',
},
userId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'The authenticated user ID',
},
targetUserId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'The user ID to follow or unfollow',
},
action: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Action to perform: "follow" or "unfollow"',
},
},
request: {
url: (params) => {
if (params.action === 'unfollow') {
return `https://api.x.com/2/users/${params.userId.trim()}/following/${params.targetUserId.trim()}`
}
return `https://api.x.com/2/users/${params.userId.trim()}/following`
},
method: (params) => (params.action === 'unfollow' ? 'DELETE' : 'POST'),
headers: (params) => ({
Authorization: `Bearer ${params.accessToken}`,
'Content-Type': 'application/json',
}),
body: (params) => {
if (params.action === 'unfollow') return undefined
return {
target_user_id: params.targetUserId.trim(),
}
},
},
transformResponse: async (response) => {
const data = await response.json()
if (!data.data) {
logger.error('X Manage Follow API Error:', JSON.stringify(data, null, 2))
return {
success: false,
output: {
following: false,
pendingFollow: false,
},
error: data.errors?.[0]?.detail ?? 'Failed to manage follow',
}
}
return {
success: true,
output: {
following: data.data?.following ?? false,
pendingFollow: data.data?.pending_follow ?? false,
},
}
},
outputs: {
following: {
type: 'boolean',
description: 'Whether you are now following the user',
},
pendingFollow: {
type: 'boolean',
description: 'Whether the follow request is pending (for protected accounts)',
},
},
}

View File

@@ -0,0 +1,93 @@
import { createLogger } from '@sim/logger'
import type { ToolConfig } from '@/tools/types'
import type { XManageLikeParams, XManageLikeResponse } from '@/tools/x/types'
const logger = createLogger('XManageLikeTool')
export const xManageLikeTool: ToolConfig<XManageLikeParams, XManageLikeResponse> = {
id: 'x_manage_like',
name: 'X Manage Like',
description: 'Like or unlike a tweet on X',
version: '1.0.0',
oauth: {
required: true,
provider: 'x',
},
params: {
accessToken: {
type: 'string',
required: true,
visibility: 'hidden',
description: 'X OAuth access token',
},
userId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'The authenticated user ID',
},
tweetId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'The tweet ID to like or unlike',
},
action: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Action to perform: "like" or "unlike"',
},
},
request: {
url: (params) => {
if (params.action === 'unlike') {
return `https://api.x.com/2/users/${params.userId.trim()}/likes/${params.tweetId.trim()}`
}
return `https://api.x.com/2/users/${params.userId.trim()}/likes`
},
method: (params) => (params.action === 'unlike' ? 'DELETE' : 'POST'),
headers: (params) => ({
Authorization: `Bearer ${params.accessToken}`,
'Content-Type': 'application/json',
}),
body: (params) => {
if (params.action === 'unlike') return undefined
return {
tweet_id: params.tweetId.trim(),
}
},
},
transformResponse: async (response) => {
const data = await response.json()
if (!data.data) {
logger.error('X Manage Like API Error:', JSON.stringify(data, null, 2))
return {
success: false,
output: {
liked: false,
},
error: data.errors?.[0]?.detail ?? 'Failed to manage like',
}
}
return {
success: true,
output: {
liked: data.data?.liked ?? false,
},
}
},
outputs: {
liked: {
type: 'boolean',
description: 'Whether the tweet is now liked',
},
},
}

View File

@@ -0,0 +1,93 @@
import { createLogger } from '@sim/logger'
import type { ToolConfig } from '@/tools/types'
import type { XManageMuteParams, XManageMuteResponse } from '@/tools/x/types'
const logger = createLogger('XManageMuteTool')
export const xManageMuteTool: ToolConfig<XManageMuteParams, XManageMuteResponse> = {
id: 'x_manage_mute',
name: 'X Manage Mute',
description: 'Mute or unmute a user on X',
version: '1.0.0',
oauth: {
required: true,
provider: 'x',
},
params: {
accessToken: {
type: 'string',
required: true,
visibility: 'hidden',
description: 'X OAuth access token',
},
userId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'The authenticated user ID',
},
targetUserId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'The user ID to mute or unmute',
},
action: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Action to perform: "mute" or "unmute"',
},
},
request: {
url: (params) => {
if (params.action === 'unmute') {
return `https://api.x.com/2/users/${params.userId.trim()}/muting/${params.targetUserId.trim()}`
}
return `https://api.x.com/2/users/${params.userId.trim()}/muting`
},
method: (params) => (params.action === 'unmute' ? 'DELETE' : 'POST'),
headers: (params) => ({
Authorization: `Bearer ${params.accessToken}`,
'Content-Type': 'application/json',
}),
body: (params) => {
if (params.action === 'unmute') return undefined
return {
target_user_id: params.targetUserId.trim(),
}
},
},
transformResponse: async (response) => {
const data = await response.json()
if (!data.data) {
logger.error('X Manage Mute API Error:', JSON.stringify(data, null, 2))
return {
success: false,
error: data.errors?.[0]?.detail || 'Failed to mute/unmute user',
output: {
muting: false,
},
}
}
return {
success: true,
output: {
muting: data.data?.muting ?? false,
},
}
},
outputs: {
muting: {
type: 'boolean',
description: 'Whether you are now muting the user',
},
},
}

View File

@@ -0,0 +1,93 @@
import { createLogger } from '@sim/logger'
import type { ToolConfig } from '@/tools/types'
import type { XManageRetweetParams, XManageRetweetResponse } from '@/tools/x/types'
const logger = createLogger('XManageRetweetTool')
export const xManageRetweetTool: ToolConfig<XManageRetweetParams, XManageRetweetResponse> = {
id: 'x_manage_retweet',
name: 'X Manage Retweet',
description: 'Retweet or unretweet a tweet on X',
version: '1.0.0',
oauth: {
required: true,
provider: 'x',
},
params: {
accessToken: {
type: 'string',
required: true,
visibility: 'hidden',
description: 'X OAuth access token',
},
userId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'The authenticated user ID',
},
tweetId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'The tweet ID to retweet or unretweet',
},
action: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Action to perform: "retweet" or "unretweet"',
},
},
request: {
url: (params) => {
if (params.action === 'unretweet') {
return `https://api.x.com/2/users/${params.userId.trim()}/retweets/${params.tweetId.trim()}`
}
return `https://api.x.com/2/users/${params.userId.trim()}/retweets`
},
method: (params) => (params.action === 'unretweet' ? 'DELETE' : 'POST'),
headers: (params) => ({
Authorization: `Bearer ${params.accessToken}`,
'Content-Type': 'application/json',
}),
body: (params) => {
if (params.action === 'unretweet') return undefined
return {
tweet_id: params.tweetId.trim(),
}
},
},
transformResponse: async (response) => {
const data = await response.json()
if (!data.data) {
logger.error('X Manage Retweet API Error:', JSON.stringify(data, null, 2))
return {
success: false,
output: {
retweeted: false,
},
error: data.errors?.[0]?.detail ?? 'Failed to manage retweet',
}
}
return {
success: true,
output: {
retweeted: data.data?.retweeted ?? false,
},
}
},
outputs: {
retweeted: {
type: 'boolean',
description: 'Whether the tweet is now retweeted',
},
},
}

View File

@@ -0,0 +1,239 @@
import { createLogger } from '@sim/logger'
import type { ToolConfig } from '@/tools/types'
import type { XSearchTweetsParams, XTweet, XUser } from '@/tools/x/types'
import { transformTweet, transformUser } from '@/tools/x/types'
const logger = createLogger('XSearchTweetsTool')
interface XSearchTweetsResponse {
success: boolean
output: {
tweets: XTweet[]
includes?: { users: XUser[] }
meta: {
resultCount: number
newestId: string | null
oldestId: string | null
nextToken: string | null
}
}
}
export const xSearchTweetsTool: ToolConfig<XSearchTweetsParams, XSearchTweetsResponse> = {
id: 'x_search_tweets',
name: 'X Search Tweets',
description: 'Search for recent tweets using keywords, hashtags, or advanced query operators',
version: '1.0.0',
oauth: {
required: true,
provider: 'x',
},
params: {
accessToken: {
type: 'string',
required: true,
visibility: 'hidden',
description: 'X OAuth access token',
},
query: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description:
'Search query (supports operators like "from:", "to:", "#hashtag", "has:images", "is:retweet", "lang:")',
},
maxResults: {
type: 'number',
required: false,
visibility: 'user-or-llm',
description: 'Maximum number of results (10-100, default 10)',
},
startTime: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Oldest UTC timestamp in ISO 8601 format (e.g., 2024-01-01T00:00:00Z)',
},
endTime: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Newest UTC timestamp in ISO 8601 format',
},
sinceId: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Returns tweets with ID greater than this',
},
untilId: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Returns tweets with ID less than this',
},
sortOrder: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Sort order: "recency" or "relevancy"',
},
nextToken: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Pagination token for next page of results',
},
},
request: {
url: (params) => {
const queryParams = new URLSearchParams({
query: params.query,
expansions: 'author_id,referenced_tweets.id,attachments.media_keys,attachments.poll_ids',
'tweet.fields':
'created_at,conversation_id,in_reply_to_user_id,attachments,context_annotations,public_metrics',
'user.fields': 'name,username,description,profile_image_url,verified,public_metrics',
})
if (params.maxResults) {
const max = Math.max(10, Math.min(100, Number(params.maxResults)))
queryParams.append('max_results', max.toString())
}
if (params.startTime) queryParams.append('start_time', params.startTime)
if (params.endTime) queryParams.append('end_time', params.endTime)
if (params.sinceId) queryParams.append('since_id', params.sinceId)
if (params.untilId) queryParams.append('until_id', params.untilId)
if (params.sortOrder) queryParams.append('sort_order', params.sortOrder)
if (params.nextToken) queryParams.append('next_token', params.nextToken)
return `https://api.x.com/2/tweets/search/recent?${queryParams.toString()}`
},
method: 'GET',
headers: (params) => ({
Authorization: `Bearer ${params.accessToken}`,
'Content-Type': 'application/json',
}),
},
transformResponse: async (response) => {
const data = await response.json()
if (!data.data || !Array.isArray(data.data)) {
logger.error('X Search Tweets API Error:', JSON.stringify(data, null, 2))
return {
success: false,
error:
data.errors?.[0]?.detail ||
data.errors?.[0]?.title ||
'No results found or invalid response from X API',
output: {
tweets: [],
includes: { users: [] },
meta: {
resultCount: 0,
newestId: null,
oldestId: null,
nextToken: null,
},
},
}
}
return {
success: true,
output: {
tweets: data.data.map(transformTweet),
includes: {
users: data.includes?.users?.map(transformUser) ?? [],
},
meta: {
resultCount: data.meta?.result_count ?? 0,
newestId: data.meta?.newest_id ?? null,
oldestId: data.meta?.oldest_id ?? null,
nextToken: data.meta?.next_token ?? null,
},
},
}
},
outputs: {
tweets: {
type: 'array',
description: 'Array of tweets matching the search query',
items: {
type: 'object',
properties: {
id: { type: 'string', description: 'Tweet ID' },
text: { type: 'string', description: 'Tweet text content' },
createdAt: { type: 'string', description: 'Tweet creation timestamp' },
authorId: { type: 'string', description: 'Author user ID' },
conversationId: { type: 'string', description: 'Conversation thread ID', optional: true },
inReplyToUserId: {
type: 'string',
description: 'User ID being replied to',
optional: true,
},
publicMetrics: {
type: 'object',
description: 'Engagement metrics',
optional: true,
properties: {
retweetCount: { type: 'number', description: 'Number of retweets' },
replyCount: { type: 'number', description: 'Number of replies' },
likeCount: { type: 'number', description: 'Number of likes' },
quoteCount: { type: 'number', description: 'Number of quotes' },
},
},
},
},
},
includes: {
type: 'object',
description: 'Additional data including user profiles',
optional: true,
properties: {
users: {
type: 'array',
description: 'Array of user objects referenced in tweets',
items: {
type: 'object',
properties: {
id: { type: 'string', description: 'User ID' },
username: { type: 'string', description: 'Username without @ symbol' },
name: { type: 'string', description: 'Display name' },
description: { type: 'string', description: 'User bio', optional: true },
profileImageUrl: { type: 'string', description: 'Profile image URL', optional: true },
verified: { type: 'boolean', description: 'Whether the user is verified' },
metrics: {
type: 'object',
description: 'User statistics',
properties: {
followersCount: { type: 'number', description: 'Number of followers' },
followingCount: { type: 'number', description: 'Number of users following' },
tweetCount: { type: 'number', description: 'Total number of tweets' },
},
},
},
},
},
},
},
meta: {
type: 'object',
description: 'Search metadata including result count and pagination tokens',
properties: {
resultCount: { type: 'number', description: 'Number of results returned' },
newestId: { type: 'string', description: 'ID of the newest tweet', optional: true },
oldestId: { type: 'string', description: 'ID of the oldest tweet', optional: true },
nextToken: {
type: 'string',
description: 'Pagination token for next page',
optional: true,
},
},
},
},
}

View File

@@ -0,0 +1,133 @@
import { createLogger } from '@sim/logger'
import type { ToolConfig } from '@/tools/types'
import type { XSearchUsersParams, XUserListResponse } from '@/tools/x/types'
import { transformUser } from '@/tools/x/types'
const logger = createLogger('XSearchUsersTool')
export const xSearchUsersTool: ToolConfig<XSearchUsersParams, XUserListResponse> = {
id: 'x_search_users',
name: 'X Search Users',
description: 'Search for X users by name, username, or bio',
version: '1.0.0',
oauth: {
required: true,
provider: 'x',
},
params: {
accessToken: {
type: 'string',
required: true,
visibility: 'hidden',
description: 'X OAuth access token',
},
query: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Search keyword (1-50 chars, matches name, username, or bio)',
},
maxResults: {
type: 'number',
required: false,
visibility: 'user-or-llm',
description: 'Maximum number of results (1-1000, default 100)',
},
nextToken: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Pagination token for next page',
},
},
request: {
url: (params) => {
const queryParams = new URLSearchParams({
query: params.query,
'user.fields': 'created_at,description,profile_image_url,verified,public_metrics,location',
})
if (params.maxResults) {
const max = Math.max(1, Math.min(1000, Number(params.maxResults)))
queryParams.append('max_results', max.toString())
}
if (params.nextToken) queryParams.append('next_token', params.nextToken)
return `https://api.x.com/2/users/search?${queryParams.toString()}`
},
method: 'GET',
headers: (params) => ({
Authorization: `Bearer ${params.accessToken}`,
'Content-Type': 'application/json',
}),
},
transformResponse: async (response) => {
const data = await response.json()
if (!data.data || !Array.isArray(data.data)) {
logger.error('X Search Users API Error:', JSON.stringify(data, null, 2))
return {
success: false,
error: data.errors?.[0]?.detail || 'No users found or invalid response',
output: {
users: [],
meta: { resultCount: 0, nextToken: null },
},
}
}
return {
success: true,
output: {
users: data.data.map(transformUser),
meta: {
resultCount: data.meta?.result_count ?? data.data.length,
nextToken: data.meta?.next_token ?? null,
},
},
}
},
outputs: {
users: {
type: 'array',
description: 'Array of users matching the search query',
items: {
type: 'object',
properties: {
id: { type: 'string', description: 'User ID' },
username: { type: 'string', description: 'Username without @ symbol' },
name: { type: 'string', description: 'Display name' },
description: { type: 'string', description: 'User bio', optional: true },
profileImageUrl: { type: 'string', description: 'Profile image URL', optional: true },
verified: { type: 'boolean', description: 'Whether the user is verified' },
metrics: {
type: 'object',
description: 'User statistics',
properties: {
followersCount: { type: 'number', description: 'Number of followers' },
followingCount: { type: 'number', description: 'Number of users following' },
tweetCount: { type: 'number', description: 'Total number of tweets' },
},
},
},
},
},
meta: {
type: 'object',
description: 'Search metadata',
properties: {
resultCount: { type: 'number', description: 'Number of results returned' },
nextToken: {
type: 'string',
description: 'Pagination token for next page',
optional: true,
},
},
},
},
}

View File

@@ -184,3 +184,339 @@ export const transformUser = (user: any): XUser => ({
tweetCount: user.public_metrics?.tweet_count || 0,
},
})
/**
* Trend object from X API (WOEID trends)
*/
export interface XTrend {
trendName: string
tweetCount: number | null
}
/**
* Personalized trend object from X API
*/
export interface XPersonalizedTrend {
trendName: string
postCount: number | null
category: string | null
trendingSince: string | null
}
/**
* Transforms raw X API trend data (WOEID) into the XTrend format
*/
export const transformTrend = (trend: any): XTrend => ({
trendName: trend.trend_name ?? trend.name ?? '',
tweetCount: trend.tweet_count ?? null,
})
/**
* Transforms raw X API personalized trend data into the XPersonalizedTrend format
*/
export const transformPersonalizedTrend = (trend: any): XPersonalizedTrend => ({
trendName: trend.trend_name ?? '',
postCount: trend.post_count ?? null,
category: trend.category ?? null,
trendingSince: trend.trending_since ?? null,
})
// --- New Tool Parameter Interfaces ---
export interface XSearchTweetsParams extends XBaseParams {
query: string
maxResults?: number
startTime?: string
endTime?: string
sinceId?: string
untilId?: string
sortOrder?: string
nextToken?: string
}
export interface XGetUserTweetsParams extends XBaseParams {
userId: string
maxResults?: number
startTime?: string
endTime?: string
sinceId?: string
untilId?: string
exclude?: string
paginationToken?: string
}
export interface XGetUserMentionsParams extends XBaseParams {
userId: string
maxResults?: number
startTime?: string
endTime?: string
sinceId?: string
untilId?: string
paginationToken?: string
}
export interface XGetUserTimelineParams extends XBaseParams {
userId: string
maxResults?: number
startTime?: string
endTime?: string
sinceId?: string
untilId?: string
exclude?: string
paginationToken?: string
}
export interface XGetTweetsByIdsParams extends XBaseParams {
ids: string
}
export interface XGetTweetsByIdsResponse extends ToolResponse {
output: {
tweets: XTweet[]
}
}
export interface XGetBookmarksParams extends XBaseParams {
userId: string
maxResults?: number
paginationToken?: string
}
export interface XCreateBookmarkParams extends XBaseParams {
userId: string
tweetId: string
}
export interface XCreateBookmarkResponse extends ToolResponse {
output: {
bookmarked: boolean
}
}
export interface XDeleteBookmarkParams extends XBaseParams {
userId: string
tweetId: string
}
export interface XDeleteBookmarkResponse extends ToolResponse {
output: {
bookmarked: boolean
}
}
export interface XCreateTweetParams extends XBaseParams {
text: string
replyToTweetId?: string
quoteTweetId?: string
mediaIds?: string
replySettings?: string
}
export interface XCreateTweetResponse extends ToolResponse {
output: {
id: string
text: string
}
}
export interface XDeleteTweetParams extends XBaseParams {
tweetId: string
}
export interface XDeleteTweetResponse extends ToolResponse {
output: {
deleted: boolean
}
}
export interface XGetMeParams extends XBaseParams {}
export interface XGetMeResponse extends ToolResponse {
output: {
user: XUser
}
}
export interface XSearchUsersParams extends XBaseParams {
query: string
maxResults?: number
nextToken?: string
}
export interface XGetFollowersParams extends XBaseParams {
userId: string
maxResults?: number
paginationToken?: string
}
export interface XGetFollowingParams extends XBaseParams {
userId: string
maxResults?: number
paginationToken?: string
}
export interface XManageFollowParams extends XBaseParams {
userId: string
targetUserId: string
action: string
}
export interface XManageFollowResponse extends ToolResponse {
output: {
following: boolean
pendingFollow: boolean
}
}
export interface XGetBlockingParams extends XBaseParams {
userId: string
maxResults?: number
paginationToken?: string
}
export interface XManageBlockParams extends XBaseParams {
userId: string
targetUserId: string
action: string
}
export interface XManageBlockResponse extends ToolResponse {
output: {
blocking: boolean
}
}
export interface XGetLikedTweetsParams extends XBaseParams {
userId: string
maxResults?: number
paginationToken?: string
}
export interface XGetLikingUsersParams extends XBaseParams {
tweetId: string
maxResults?: number
paginationToken?: string
}
export interface XManageLikeParams extends XBaseParams {
userId: string
tweetId: string
action: string
}
export interface XManageLikeResponse extends ToolResponse {
output: {
liked: boolean
}
}
export interface XManageRetweetParams extends XBaseParams {
userId: string
tweetId: string
action: string
}
export interface XManageRetweetResponse extends ToolResponse {
output: {
retweeted: boolean
}
}
export interface XGetRetweetedByParams extends XBaseParams {
tweetId: string
maxResults?: number
paginationToken?: string
}
export interface XGetQuoteTweetsParams extends XBaseParams {
tweetId: string
maxResults?: number
paginationToken?: string
}
export interface XGetTrendsByWoeidParams extends XBaseParams {
woeid: string
maxTrends?: number
}
export interface XGetPersonalizedTrendsParams extends XBaseParams {}
export interface XGetUsageParams extends XBaseParams {
days?: number
}
export interface XGetUsageResponse extends ToolResponse {
output: {
capResetDay: number | null
projectId: string
projectCap: number | null
projectUsage: number | null
dailyProjectUsage: Array<{ date: string; usage: number }>
dailyClientAppUsage: Array<{
clientAppId: string
usage: Array<{ date: string; usage: number }>
}>
}
}
export interface XHideReplyParams extends XBaseParams {
tweetId: string
hidden: boolean
}
export interface XHideReplyResponse extends ToolResponse {
output: {
hidden: boolean
}
}
export interface XManageMuteParams extends XBaseParams {
userId: string
targetUserId: string
action: string
}
export interface XManageMuteResponse extends ToolResponse {
output: {
muting: boolean
}
}
// Common response types for list endpoints
export interface XTweetListResponse extends ToolResponse {
output: {
tweets: XTweet[]
includes?: {
users: XUser[]
}
meta: {
resultCount: number
newestId: string | null
oldestId: string | null
nextToken: string | null
previousToken: string | null
}
}
}
export interface XUserListResponse extends ToolResponse {
output: {
users: XUser[]
meta: {
resultCount: number
nextToken: string | null
}
}
}
export interface XTrendListResponse extends ToolResponse {
output: {
trends: XTrend[]
}
}
export interface XPersonalizedTrendListResponse extends ToolResponse {
output: {
trends: XPersonalizedTrend[]
}
}