Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion src/toolkits/toolkits/twitter/base.ts
Original file line number Diff line number Diff line change
@@ -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({});

Expand All @@ -12,6 +12,7 @@ export const baseTwitterToolkitConfig: ToolkitConfig<
tools: {
[TwitterTools.GetUserProfile]: getUserProfileTool,
[TwitterTools.GetLatestTweets]: getLatestTweetsTool,
[TwitterTools.GetTweet]: getTweetTool,
},
parameters: twitterParameters,
};
2 changes: 2 additions & 0 deletions src/toolkits/toolkits/twitter/client.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { TwitterTools } from "./tools";
import {
getUserProfileToolConfigClient,
getLatestTweetsToolConfigClient,
getTweetToolConfigClient,
} from "./tools/client";

import { ToolkitGroups } from "@/toolkits/types";
Expand Down Expand Up @@ -47,5 +48,6 @@ export const twitterClientToolkit = createClientToolkit(
{
[TwitterTools.GetUserProfile]: getUserProfileToolConfigClient,
[TwitterTools.GetLatestTweets]: getLatestTweetsToolConfigClient,
[TwitterTools.GetTweet]: getTweetToolConfigClient,
},
);
7 changes: 6 additions & 1 deletion src/toolkits/toolkits/twitter/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { baseTwitterToolkitConfig } from "./base";
import {
getUserProfileToolConfigServer,
getLatestTweetsToolConfigServer,
getTweetToolConfigServer,
} from "./tools/server";
import { TwitterTools } from "./tools";
import { api } from "@/trpc/server";
Expand All @@ -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`,
Expand All @@ -46,6 +50,7 @@ export const twitterToolkitServer = createServerToolkit(
return {
[TwitterTools.GetUserProfile]: getUserProfileToolConfigServer(client),
[TwitterTools.GetLatestTweets]: getLatestTweetsToolConfigServer(client),
[TwitterTools.GetTweet]: getTweetToolConfigServer(client),
};
},
);
1 change: 1 addition & 0 deletions src/toolkits/toolkits/twitter/tools/client.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
export * from "./profile/client";
export * from "./tweets/client";
export * from "./tweet/client";
2 changes: 2 additions & 0 deletions src/toolkits/toolkits/twitter/tools/index.ts
Original file line number Diff line number Diff line change
@@ -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";
1 change: 1 addition & 0 deletions src/toolkits/toolkits/twitter/tools/server.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
export * from "./profile/server";
export * from "./tweets/server";
export * from "./tweet/server";
16 changes: 16 additions & 0 deletions src/toolkits/toolkits/twitter/tools/tweet/base.ts
Original file line number Diff line number Diff line change
@@ -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<TweetV2SingleResult>(),
}),
});
86 changes: 86 additions & 0 deletions src/toolkits/toolkits/twitter/tools/tweet/client.tsx
Original file line number Diff line number Diff line change
@@ -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 }) => (
<div className="flex items-center gap-2 text-sm">
<Search className="h-4 w-4" />
Getting tweet <span className="font-mono">{args.tweetId}</span>
</div>
),
ResultComponent: ({ result: { tweet } }) => {
const author = tweet.includes?.users?.[0];

return (
<div className="space-y-3">
<div className="flex items-center justify-between gap-3">
<div className="min-w-0">
<h3 className="truncate font-semibold">
{author?.name ?? "Tweet"}
</h3>
{author?.username && (
<p className="text-muted-foreground text-sm">
@{author.username}
</p>
)}
</div>
<Badge variant="secondary">Tweet</Badge>
</div>

<p className="text-sm whitespace-pre-wrap">{tweet.data.text}</p>

{tweet.data.entities?.urls && tweet.data.entities.urls.length > 0 && (
<div className="space-y-1">
{tweet.data.entities.urls.map((url, index) => (
<a
key={index}
href={url.expanded_url}
target="_blank"
rel="noopener noreferrer"
className="block truncate text-sm text-blue-500 hover:underline"
>
{url.display_url}
</a>
))}
</div>
)}

<div className="text-muted-foreground flex flex-wrap items-center gap-4 text-sm">
<Metric
icon={MessageCircle}
value={tweet.data.public_metrics?.reply_count}
/>
<Metric
icon={Repeat2}
value={tweet.data.public_metrics?.retweet_count}
/>
<Metric icon={Heart} value={tweet.data.public_metrics?.like_count} />
<Metric icon={Share} value={tweet.data.public_metrics?.quote_count} />
</div>

{tweet.data.created_at && (
<p className="text-muted-foreground text-xs">
{new Date(tweet.data.created_at).toLocaleString()}
</p>
)}
</div>
);
},
});

const Metric = ({
icon: Icon,
value,
}: {
icon: React.ComponentType<{ className?: string }>;
value?: number;
}) => (
<div className="flex items-center gap-1">
<Icon className="h-4 w-4" />
{value?.toLocaleString() ?? "0"}
</div>
);
40 changes: 40 additions & 0 deletions src/toolkits/toolkits/twitter/tools/tweet/server.ts
Original file line number Diff line number Diff line change
@@ -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,
};
},
};
};