Skip to content

Commit 0997989

Browse files
authored
fix(x): fix x optional tool params (#2307)
* fix(x): fix x optional tool params * ack pr comments
1 parent 7fd912d commit 0997989

File tree

6 files changed

+224
-99
lines changed

6 files changed

+224
-99
lines changed

apps/sim/blocks/blocks/x.ts

Lines changed: 47 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -145,32 +145,23 @@ export const XBlock: BlockConfig<XResponse> = {
145145
params: (params) => {
146146
const { credential, ...rest } = params
147147

148-
// Convert string values to appropriate types
149148
const parsedParams: Record<string, any> = {
150149
credential: credential,
151150
}
152151

153-
// Add other params
154152
Object.keys(rest).forEach((key) => {
155153
const value = rest[key]
156154

157-
// Convert string boolean values to actual booleans
158155
if (value === 'true' || value === 'false') {
159156
parsedParams[key] = value === 'true'
160-
}
161-
// Convert numeric strings to numbers where appropriate
162-
else if (key === 'maxResults' && value) {
157+
} else if (key === 'maxResults' && value) {
163158
parsedParams[key] = Number.parseInt(value as string, 10)
164-
}
165-
// Handle mediaIds conversion from comma-separated string to array
166-
else if (key === 'mediaIds' && typeof value === 'string') {
159+
} else if (key === 'mediaIds' && typeof value === 'string') {
167160
parsedParams[key] = value
168161
.split(',')
169162
.map((id) => id.trim())
170163
.filter((id) => id !== '')
171-
}
172-
// Keep other values as is
173-
else {
164+
} else {
174165
parsedParams[key] = value
175166
}
176167
})
@@ -197,13 +188,49 @@ export const XBlock: BlockConfig<XResponse> = {
197188
includeRecentTweets: { type: 'boolean', description: 'Include recent tweets' },
198189
},
199190
outputs: {
200-
tweet: { type: 'json', description: 'Tweet data' },
201-
replies: { type: 'json', description: 'Tweet replies' },
202-
context: { type: 'json', description: 'Tweet context' },
203-
tweets: { type: 'json', description: 'Tweets data' },
204-
includes: { type: 'json', description: 'Additional data' },
205-
meta: { type: 'json', description: 'Response metadata' },
206-
user: { type: 'json', description: 'User profile data' },
207-
recentTweets: { type: 'json', description: 'Recent tweets data' },
191+
// Write and Read operation outputs
192+
tweet: {
193+
type: 'json',
194+
description: 'Tweet data including contextAnnotations and publicMetrics',
195+
condition: { field: 'operation', value: ['x_write', 'x_read'] },
196+
},
197+
// Read operation outputs
198+
replies: {
199+
type: 'json',
200+
description: 'Tweet replies (when includeReplies is true)',
201+
condition: { field: 'operation', value: 'x_read' },
202+
},
203+
context: {
204+
type: 'json',
205+
description: 'Tweet context (parent and quoted tweets)',
206+
condition: { field: 'operation', value: 'x_read' },
207+
},
208+
// Search operation outputs
209+
tweets: {
210+
type: 'json',
211+
description: 'Tweets data including contextAnnotations and publicMetrics',
212+
condition: { field: 'operation', value: 'x_search' },
213+
},
214+
includes: {
215+
type: 'json',
216+
description: 'Additional data (users, media, polls)',
217+
condition: { field: 'operation', value: 'x_search' },
218+
},
219+
meta: {
220+
type: 'json',
221+
description: 'Response metadata',
222+
condition: { field: 'operation', value: 'x_search' },
223+
},
224+
// User operation outputs
225+
user: {
226+
type: 'json',
227+
description: 'User profile data',
228+
condition: { field: 'operation', value: 'x_user' },
229+
},
230+
recentTweets: {
231+
type: 'json',
232+
description: 'Recent tweets data',
233+
condition: { field: 'operation', value: 'x_user' },
234+
},
208235
},
209236
}

apps/sim/tools/x/read.ts

Lines changed: 90 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
1+
import { createLogger } from '@/lib/logs/console/logger'
12
import type { ToolConfig } from '@/tools/types'
23
import type { XReadParams, XReadResponse, XTweet } from '@/tools/x/types'
4+
import { transformTweet } from '@/tools/x/types'
5+
6+
const logger = createLogger('XReadTool')
37

48
export const xReadTool: ToolConfig<XReadParams, XReadResponse> = {
59
id: 'x_read',
@@ -39,11 +43,36 @@ export const xReadTool: ToolConfig<XReadParams, XReadResponse> = {
3943
'author_id',
4044
'in_reply_to_user_id',
4145
'referenced_tweets.id',
46+
'referenced_tweets.id.author_id',
4247
'attachments.media_keys',
4348
'attachments.poll_ids',
4449
].join(',')
4550

46-
return `https://api.twitter.com/2/tweets/${params.tweetId}?expansions=${expansions}`
51+
const tweetFields = [
52+
'created_at',
53+
'conversation_id',
54+
'in_reply_to_user_id',
55+
'attachments',
56+
'context_annotations',
57+
'public_metrics',
58+
].join(',')
59+
60+
const userFields = [
61+
'name',
62+
'username',
63+
'description',
64+
'profile_image_url',
65+
'verified',
66+
'public_metrics',
67+
].join(',')
68+
69+
const queryParams = new URLSearchParams({
70+
expansions,
71+
'tweet.fields': tweetFields,
72+
'user.fields': userFields,
73+
})
74+
75+
return `https://api.twitter.com/2/tweets/${params.tweetId}?${queryParams.toString()}`
4776
},
4877
method: 'GET',
4978
headers: (params) => ({
@@ -52,47 +81,88 @@ export const xReadTool: ToolConfig<XReadParams, XReadResponse> = {
5281
}),
5382
},
5483

55-
transformResponse: async (response) => {
84+
transformResponse: async (response, params) => {
5685
const data = await response.json()
5786

58-
const transformTweet = (tweet: any): XTweet => ({
59-
id: tweet.id,
60-
text: tweet.text,
61-
createdAt: tweet.created_at,
62-
authorId: tweet.author_id,
63-
conversationId: tweet.conversation_id,
64-
inReplyToUserId: tweet.in_reply_to_user_id,
65-
attachments: {
66-
mediaKeys: tweet.attachments?.media_keys,
67-
pollId: tweet.attachments?.poll_ids?.[0],
68-
},
69-
})
87+
if (data.errors && !data.data) {
88+
logger.error('X Read API Error:', JSON.stringify(data, null, 2))
89+
return {
90+
success: false,
91+
error: data.errors?.[0]?.detail || data.errors?.[0]?.message || 'Failed to fetch tweet',
92+
output: {
93+
tweet: {} as XTweet,
94+
},
95+
}
96+
}
7097

7198
const mainTweet = transformTweet(data.data)
7299
const context: { parentTweet?: XTweet; rootTweet?: XTweet } = {}
73100

74-
// Get parent and root tweets if available
75101
if (data.includes?.tweets) {
76102
const referencedTweets = data.data.referenced_tweets || []
77103
const parentTweetRef = referencedTweets.find((ref: any) => ref.type === 'replied_to')
78-
const rootTweetRef = referencedTweets.find((ref: any) => ref.type === 'replied_to_root')
104+
const quotedTweetRef = referencedTweets.find((ref: any) => ref.type === 'quoted')
79105

80106
if (parentTweetRef) {
81107
const parentTweet = data.includes.tweets.find((t: any) => t.id === parentTweetRef.id)
82108
if (parentTweet) context.parentTweet = transformTweet(parentTweet)
83109
}
84110

85-
if (rootTweetRef) {
86-
const rootTweet = data.includes.tweets.find((t: any) => t.id === rootTweetRef.id)
87-
if (rootTweet) context.rootTweet = transformTweet(rootTweet)
111+
if (!parentTweetRef && quotedTweetRef) {
112+
const quotedTweet = data.includes.tweets.find((t: any) => t.id === quotedTweetRef.id)
113+
if (quotedTweet) context.rootTweet = transformTweet(quotedTweet)
114+
}
115+
}
116+
117+
let replies: XTweet[] = []
118+
if (params?.includeReplies && mainTweet.id) {
119+
try {
120+
const repliesExpansions = ['author_id', 'referenced_tweets.id'].join(',')
121+
const repliesTweetFields = [
122+
'created_at',
123+
'conversation_id',
124+
'in_reply_to_user_id',
125+
'public_metrics',
126+
].join(',')
127+
128+
const conversationId = mainTweet.conversationId || mainTweet.id
129+
const searchQuery = `conversation_id:${conversationId}`
130+
const searchParams = new URLSearchParams({
131+
query: searchQuery,
132+
expansions: repliesExpansions,
133+
'tweet.fields': repliesTweetFields,
134+
max_results: '100', // Max allowed
135+
})
136+
137+
const repliesResponse = await fetch(
138+
`https://api.twitter.com/2/tweets/search/recent?${searchParams.toString()}`,
139+
{
140+
method: 'GET',
141+
headers: {
142+
Authorization: `Bearer ${params?.accessToken || ''}`,
143+
'Content-Type': 'application/json',
144+
},
145+
}
146+
)
147+
148+
const repliesData = await repliesResponse.json()
149+
150+
if (repliesData.data && Array.isArray(repliesData.data)) {
151+
replies = repliesData.data
152+
.filter((tweet: any) => tweet.id !== mainTweet.id)
153+
.map(transformTweet)
154+
}
155+
} catch (error) {
156+
logger.warn('Failed to fetch replies:', error)
88157
}
89158
}
90159

91160
return {
92161
success: true,
93162
output: {
94163
tweet: mainTweet,
95-
context,
164+
replies: replies.length > 0 ? replies : undefined,
165+
context: Object.keys(context).length > 0 ? context : undefined,
96166
},
97167
}
98168
},

apps/sim/tools/x/search.ts

Lines changed: 4 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { createLogger } from '@/lib/logs/console/logger'
22
import type { ToolConfig } from '@/tools/types'
3-
import type { XSearchParams, XSearchResponse, XTweet, XUser } from '@/tools/x/types'
3+
import type { XSearchParams, XSearchResponse } from '@/tools/x/types'
4+
import { transformTweet, transformUser } from '@/tools/x/types'
45

56
const logger = createLogger('XSearchTool')
67

@@ -67,7 +68,8 @@ export const xSearchTool: ToolConfig<XSearchParams, XSearchResponse> = {
6768
const queryParams = new URLSearchParams({
6869
query,
6970
expansions,
70-
'tweet.fields': 'created_at,conversation_id,in_reply_to_user_id,attachments',
71+
'tweet.fields':
72+
'created_at,conversation_id,in_reply_to_user_id,attachments,context_annotations,public_metrics',
7173
'user.fields': 'name,username,description,profile_image_url,verified,public_metrics',
7274
})
7375

@@ -92,7 +94,6 @@ export const xSearchTool: ToolConfig<XSearchParams, XSearchResponse> = {
9294
transformResponse: async (response) => {
9395
const data = await response.json()
9496

95-
// Check if data.data is undefined/null or not an array
9697
if (!data.data || !Array.isArray(data.data)) {
9798
logger.error('X Search API Error:', JSON.stringify(data, null, 2))
9899
return {
@@ -118,33 +119,6 @@ export const xSearchTool: ToolConfig<XSearchParams, XSearchResponse> = {
118119
}
119120
}
120121

121-
const transformTweet = (tweet: any): XTweet => ({
122-
id: tweet.id,
123-
text: tweet.text,
124-
createdAt: tweet.created_at,
125-
authorId: tweet.author_id,
126-
conversationId: tweet.conversation_id,
127-
inReplyToUserId: tweet.in_reply_to_user_id,
128-
attachments: {
129-
mediaKeys: tweet.attachments?.media_keys,
130-
pollId: tweet.attachments?.poll_ids?.[0],
131-
},
132-
})
133-
134-
const transformUser = (user: any): XUser => ({
135-
id: user.id,
136-
username: user.username,
137-
name: user.name,
138-
description: user.description,
139-
profileImageUrl: user.profile_image_url,
140-
verified: user.verified,
141-
metrics: {
142-
followersCount: user.public_metrics.followers_count,
143-
followingCount: user.public_metrics.following_count,
144-
tweetCount: user.public_metrics.tweet_count,
145-
},
146-
})
147-
148122
return {
149123
success: true,
150124
output: {

0 commit comments

Comments
 (0)