import { createLogger } from '@sim/logger' import type { ToolConfig } from '@/tools/types' import type { XUserParams, XUserResponse } from '@/tools/x/types' import { transformUser } from '@/tools/x/types' const logger = createLogger('XUserTool') export const xUserTool: ToolConfig = { id: 'x_user', name: 'X User', description: 'Get user profile information', version: '1.0.0', oauth: { required: true, provider: 'x', }, params: { accessToken: { type: 'string', required: true, visibility: 'hidden', description: 'X OAuth access token', }, username: { type: 'string', required: true, visibility: 'user-or-llm', description: 'Username to look up without @ symbol (e.g., elonmusk, openai)', }, }, request: { url: (params) => { const username = encodeURIComponent(params.username) // Keep fields minimal to reduce chance of rate limits const userFields = 'description,profile_image_url,verified,public_metrics' return `https://api.twitter.com/2/users/by/username/${username}?user.fields=${userFields}` }, method: 'GET', headers: (params) => ({ Authorization: `Bearer ${params.accessToken}`, 'Content-Type': 'application/json', }), }, transformResponse: async (response, params) => { // Handle rate limit issues (429 status code) if (response.status === 429) { logger.warn('X API rate limit exceeded', { status: response.status, headers: Object.fromEntries(response.headers.entries()), }) // Try to extract rate limit reset time from headers if available const resetTime = response.headers.get('x-rate-limit-reset') const message = resetTime ? `Rate limit exceeded. Please try again after ${new Date(Number.parseInt(resetTime) * 1000).toLocaleTimeString()}.` : 'X API rate limit exceeded. Please try again later.' throw new Error(message) } try { const responseData = await response.json() logger.debug('X API response', { status: response.status, headers: Object.fromEntries(response.headers.entries()), responseData, }) // Check if response contains expected data structure if (!responseData.data) { // If there's an error object in the response if (responseData.errors && responseData.errors.length > 0) { const error = responseData.errors[0] // Remove the square brackets from the error message const cleanedMessage = error.detail ? error.detail.replace(/\[(.*?)\]/, '$1') : '' throw new Error( `X API error: ${cleanedMessage || error.message || JSON.stringify(error)}` ) } throw new Error('Invalid response format from X API') } const userData = responseData.data const user = transformUser(userData) return { success: true, output: { user, }, } } catch (error) { logger.error('Error processing X API response', { error, status: response.status, }) throw error } }, outputs: { user: { type: 'object', description: 'X user profile information', 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/description', 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' }, }, }, }, }, }, }