From 399f547848e3c6748138ed6d32bd7c1e5e888a81 Mon Sep 17 00:00:00 2001 From: BYND Date: Thu, 21 May 2026 18:58:54 +0300 Subject: [PATCH] Add Twitter tweet lookup tool --- src/toolkits/toolkits/twitter/base.ts | 3 +- src/toolkits/toolkits/twitter/client.tsx | 2 + src/toolkits/toolkits/twitter/server.ts | 7 +- src/toolkits/toolkits/twitter/tools/client.ts | 1 + src/toolkits/toolkits/twitter/tools/index.ts | 2 + src/toolkits/toolkits/twitter/tools/server.ts | 1 + .../toolkits/twitter/tools/tweet/base.ts | 16 ++++ .../toolkits/twitter/tools/tweet/client.tsx | 86 +++++++++++++++++++ .../toolkits/twitter/tools/tweet/server.ts | 40 +++++++++ 9 files changed, 156 insertions(+), 2 deletions(-) create mode 100644 src/toolkits/toolkits/twitter/tools/tweet/base.ts create mode 100644 src/toolkits/toolkits/twitter/tools/tweet/client.tsx create mode 100644 src/toolkits/toolkits/twitter/tools/tweet/server.ts diff --git a/src/toolkits/toolkits/twitter/base.ts b/src/toolkits/toolkits/twitter/base.ts index b5255d33..af3cd458 100644 --- a/src/toolkits/toolkits/twitter/base.ts +++ b/src/toolkits/toolkits/twitter/base.ts @@ -1,7 +1,7 @@ import type { ToolkitConfig } from "@/toolkits/types"; import { z } from "zod"; import { TwitterTools } from "./tools"; -import { getUserProfileTool, getLatestTweetsTool } from "./tools"; +import { getUserProfileTool, getLatestTweetsTool, getTweetTool } from "./tools"; export const twitterParameters = z.object({}); @@ -12,6 +12,7 @@ export const baseTwitterToolkitConfig: ToolkitConfig< tools: { [TwitterTools.GetUserProfile]: getUserProfileTool, [TwitterTools.GetLatestTweets]: getLatestTweetsTool, + [TwitterTools.GetTweet]: getTweetTool, }, parameters: twitterParameters, }; diff --git a/src/toolkits/toolkits/twitter/client.tsx b/src/toolkits/toolkits/twitter/client.tsx index 6fe25b46..f904d618 100644 --- a/src/toolkits/toolkits/twitter/client.tsx +++ b/src/toolkits/toolkits/twitter/client.tsx @@ -7,6 +7,7 @@ import { TwitterTools } from "./tools"; import { getUserProfileToolConfigClient, getLatestTweetsToolConfigClient, + getTweetToolConfigClient, } from "./tools/client"; import { ToolkitGroups } from "@/toolkits/types"; @@ -47,5 +48,6 @@ export const twitterClientToolkit = createClientToolkit( { [TwitterTools.GetUserProfile]: getUserProfileToolConfigClient, [TwitterTools.GetLatestTweets]: getLatestTweetsToolConfigClient, + [TwitterTools.GetTweet]: getTweetToolConfigClient, }, ); diff --git a/src/toolkits/toolkits/twitter/server.ts b/src/toolkits/toolkits/twitter/server.ts index a7bcff40..999d94fb 100644 --- a/src/toolkits/toolkits/twitter/server.ts +++ b/src/toolkits/toolkits/twitter/server.ts @@ -3,6 +3,7 @@ import { baseTwitterToolkitConfig } from "./base"; import { getUserProfileToolConfigServer, getLatestTweetsToolConfigServer, + getTweetToolConfigServer, } from "./tools/server"; import { TwitterTools } from "./tools"; import { api } from "@/trpc/server"; @@ -14,14 +15,17 @@ export const twitterToolkitServer = createServerToolkit( - **Get User Profile**: Retrieve detailed information about a Twitter user by their username - **Get Latest Tweets**: Fetch the most recent tweets from a user (up to 100 tweets) +- **Get Tweet**: Fetch one tweet by ID with author context and engagement metrics **Tool Sequencing Strategies:** 1. **User Research**: Start with Get User Profile to understand a user's background, then use Get Latest Tweets to see their recent activity 2. **Content Analysis**: Use Get Latest Tweets to analyze a user's recent posts and engagement patterns -3. **Profile Verification**: Combine Get User Profile with Get Latest Tweets to verify user authenticity and activity +3. **Tweet Lookup**: Use Get Tweet when the user provides a tweet URL or ID and needs details about that specific post +4. **Profile Verification**: Combine Get User Profile with Get Latest Tweets to verify user authenticity and activity **Best Practices:** - Use usernames without the @ symbol (e.g., 'elonmusk' not '@elonmusk') +- Extract the numeric ID from tweet URLs before calling Get Tweet - For tweet analysis, start with fewer results (10-20) to get a quick overview - Use the exclude_retweets and exclude_replies options to focus on original content - Consider the user's follower count and verification status when analyzing their influence`, @@ -46,6 +50,7 @@ export const twitterToolkitServer = createServerToolkit( return { [TwitterTools.GetUserProfile]: getUserProfileToolConfigServer(client), [TwitterTools.GetLatestTweets]: getLatestTweetsToolConfigServer(client), + [TwitterTools.GetTweet]: getTweetToolConfigServer(client), }; }, ); diff --git a/src/toolkits/toolkits/twitter/tools/client.ts b/src/toolkits/toolkits/twitter/tools/client.ts index a3188692..090af0a9 100644 --- a/src/toolkits/toolkits/twitter/tools/client.ts +++ b/src/toolkits/toolkits/twitter/tools/client.ts @@ -1,2 +1,3 @@ export * from "./profile/client"; export * from "./tweets/client"; +export * from "./tweet/client"; diff --git a/src/toolkits/toolkits/twitter/tools/index.ts b/src/toolkits/toolkits/twitter/tools/index.ts index 892f7b26..500a4c33 100644 --- a/src/toolkits/toolkits/twitter/tools/index.ts +++ b/src/toolkits/toolkits/twitter/tools/index.ts @@ -1,7 +1,9 @@ export enum TwitterTools { GetUserProfile = "get-user-profile", GetLatestTweets = "get-latest-tweets", + GetTweet = "get-tweet", } export * from "./profile/base"; export * from "./tweets/base"; +export * from "./tweet/base"; diff --git a/src/toolkits/toolkits/twitter/tools/server.ts b/src/toolkits/toolkits/twitter/tools/server.ts index a223f6b0..8a3b1516 100644 --- a/src/toolkits/toolkits/twitter/tools/server.ts +++ b/src/toolkits/toolkits/twitter/tools/server.ts @@ -1,2 +1,3 @@ export * from "./profile/server"; export * from "./tweets/server"; +export * from "./tweet/server"; diff --git a/src/toolkits/toolkits/twitter/tools/tweet/base.ts b/src/toolkits/toolkits/twitter/tools/tweet/base.ts new file mode 100644 index 00000000..29fed09f --- /dev/null +++ b/src/toolkits/toolkits/twitter/tools/tweet/base.ts @@ -0,0 +1,16 @@ +import { z } from "zod"; +import { createBaseTool } from "@/toolkits/create-tool"; + +import type { TweetV2SingleResult } from "twitter-api-v2"; + +export const getTweetTool = createBaseTool({ + description: "Get a single tweet by its tweet ID", + inputSchema: z.object({ + tweetId: z + .string() + .describe("Tweet ID from a Twitter/X post URL or copied tweet ID"), + }), + outputSchema: z.object({ + tweet: z.custom(), + }), +}); diff --git a/src/toolkits/toolkits/twitter/tools/tweet/client.tsx b/src/toolkits/toolkits/twitter/tools/tweet/client.tsx new file mode 100644 index 00000000..cdaaffb1 --- /dev/null +++ b/src/toolkits/toolkits/twitter/tools/tweet/client.tsx @@ -0,0 +1,86 @@ +import React from "react"; +import { createClientTool } from "@/toolkits/create-tool"; +import { Badge } from "@/components/ui/badge"; +import { Heart, MessageCircle, Repeat2, Search, Share } from "lucide-react"; + +import { getTweetTool } from "./base"; + +export const getTweetToolConfigClient = createClientTool(getTweetTool, { + CallComponent: ({ args }) => ( +
+ + Getting tweet {args.tweetId} +
+ ), + ResultComponent: ({ result: { tweet } }) => { + const author = tweet.includes?.users?.[0]; + + return ( +
+
+
+

+ {author?.name ?? "Tweet"} +

+ {author?.username && ( +

+ @{author.username} +

+ )} +
+ Tweet +
+ +

{tweet.data.text}

+ + {tweet.data.entities?.urls && tweet.data.entities.urls.length > 0 && ( +
+ {tweet.data.entities.urls.map((url, index) => ( + + {url.display_url} + + ))} +
+ )} + +
+ + + + +
+ + {tweet.data.created_at && ( +

+ {new Date(tweet.data.created_at).toLocaleString()} +

+ )} +
+ ); + }, +}); + +const Metric = ({ + icon: Icon, + value, +}: { + icon: React.ComponentType<{ className?: string }>; + value?: number; +}) => ( +
+ + {value?.toLocaleString() ?? "0"} +
+); diff --git a/src/toolkits/toolkits/twitter/tools/tweet/server.ts b/src/toolkits/toolkits/twitter/tools/tweet/server.ts new file mode 100644 index 00000000..ee82a14e --- /dev/null +++ b/src/toolkits/toolkits/twitter/tools/tweet/server.ts @@ -0,0 +1,40 @@ +import type { ServerToolConfig } from "@/toolkits/types"; +import type { getTweetTool } from "./base"; +import type { TwitterApi } from "twitter-api-v2"; + +export const getTweetToolConfigServer = ( + client: TwitterApi, +): ServerToolConfig< + typeof getTweetTool.inputSchema.shape, + typeof getTweetTool.outputSchema.shape +> => { + return { + callback: async ({ tweetId }) => { + const tweet = await client.v2.singleTweet(tweetId, { + "tweet.fields": [ + "author_id", + "conversation_id", + "created_at", + "entities", + "public_metrics", + "referenced_tweets", + ], + expansions: ["author_id"], + "user.fields": [ + "name", + "profile_image_url", + "username", + "verified_type", + ], + }); + + if (!tweet.data) { + throw new Error(`Tweet ${tweetId} not found`); + } + + return { + tweet, + }; + }, + }; +};