Uh oh!
There was an error while loading. Please reload this page.
- Notifications
You must be signed in to change notification settings - Fork 3.8k
feat(x): add 28 new X API v2 tool integrations and expand OAuth scopes#3365
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Uh oh!
There was an error while loading. Please reload this page.
Changes from all commits
e10721cec7c2167107fbbd86838ee2804ad514a56dfa747e5404e51be1c922e95fbcb2d0a1f25File filter
Filter by extension
Conversations
Uh oh!
There was an error while loading. Please reload this page.
Jump to
Uh oh!
There was an error while loading. Please reload this page.
Diff view
Diff view
There are no files selected for viewing
Large diffs are not rendered by default.
Uh oh!
There was an error while loading. Please reload this page.
Large diffs are not rendered by default.
Uh oh!
There was an error while loading. Please reload this page.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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', | ||
| }, | ||
| }, | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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 ?? '', | ||
| }, | ||
| } | ||
waleedlatif1 marked this conversation as resolved.
Uh oh!There was an error while loading. Please reload this page. | ||
| }, | ||
| outputs: { | ||
| id: { | ||
| type: 'string', | ||
| description: 'The ID of the created tweet', | ||
| }, | ||
| text: { | ||
| type: 'string', | ||
| description: 'The text of the created tweet', | ||
| }, | ||
| }, | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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)', | ||
| }, | ||
| }, | ||
| } |
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.