Skip to content

Commit 588d2b5

Browse files
authored
Merge pull request #223 from mw2000/mw2000/twitter-toolkit
feat: Twitter toolkit implementation
2 parents c60b768 + 654b68d commit 588d2b5

File tree

19 files changed

+575
-6
lines changed

19 files changed

+575
-6
lines changed

next.config.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ const config = {
2222
experimental: {
2323
authInterrupts: true,
2424
},
25+
serverExternalPackages: ["twitter-api-v2"],
2526
};
2627

2728
export default config;

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@
3939
"@discordjs/rest": "^2.5.1",
4040
"@e2b/code-interpreter": "^1.5.1",
4141
"@hookform/resolvers": "^5.2.0",
42-
"@icons-pack/react-simple-icons": "^13.1.0",
42+
"@icons-pack/react-simple-icons": "^13.7.0",
4343
"@llm-ui/code": "^0.13.3",
4444
"@llm-ui/markdown": "^0.13.3",
4545
"@llm-ui/react": "^0.13.3",
@@ -113,6 +113,7 @@
113113
"strava": "^2.3.0",
114114
"superjson": "^2.2.1",
115115
"tailwind-merge": "^3.3.0",
116+
"twitter-api-v2": "^1.24.0",
116117
"usehooks-ts": "^3.1.1",
117118
"uuid": "^11.1.0",
118119
"vaul": "^1.1.2",

pnpm-lock.yaml

Lines changed: 13 additions & 5 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/toolkits/toolkits/client.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import { discordClientToolkit } from "./discord/client";
1717
import { stravaClientToolkit } from "./strava/client";
1818
import { spotifyClientToolkit } from "./spotify/client";
1919
import { videoClientToolkit } from "./video/client";
20+
import { twitterClientToolkit } from "./twitter/client";
2021

2122
export type ClientToolkits = {
2223
[K in Toolkits]: ClientToolkit<
@@ -38,6 +39,7 @@ export const clientToolkits: ClientToolkits = {
3839
[Toolkits.Strava]: stravaClientToolkit,
3940
[Toolkits.Spotify]: spotifyClientToolkit,
4041
[Toolkits.Video]: videoClientToolkit,
42+
[Toolkits.Twitter]: twitterClientToolkit,
4143
};
4244

4345
export function getClientToolkit<T extends Toolkits>(

src/toolkits/toolkits/server.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import { discordToolkitServer } from "./discord/server";
1111
import { stravaToolkitServer } from "./strava/server";
1212
import { spotifyToolkitServer } from "./spotify/server";
1313
import { videoToolkitServer } from "./video/server";
14+
import { twitterToolkitServer } from "./twitter/server";
1415
import {
1516
Toolkits,
1617
type ServerToolkitNames,
@@ -37,6 +38,7 @@ export const serverToolkits: ServerToolkits = {
3738
[Toolkits.Strava]: stravaToolkitServer,
3839
[Toolkits.Spotify]: spotifyToolkitServer,
3940
[Toolkits.Video]: videoToolkitServer,
41+
[Toolkits.Twitter]: twitterToolkitServer,
4042
};
4143

4244
export function getServerToolkit<T extends Toolkits>(

src/toolkits/toolkits/shared.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ import type { spotifyParameters } from "./spotify/base";
2222
import type { SpotifyTools } from "./spotify/tools";
2323
import type { VideoTools } from "./video/tools";
2424
import type { videoParameters } from "./video/base";
25+
import type { TwitterTools } from "./twitter/tools";
26+
import type { twitterParameters } from "./twitter/base";
2527

2628
export enum Toolkits {
2729
Exa = "exa",
@@ -36,6 +38,7 @@ export enum Toolkits {
3638
Strava = "strava",
3739
Spotify = "spotify",
3840
Video = "video",
41+
Twitter = "twitter",
3942
}
4043

4144
export type ServerToolkitNames = {
@@ -51,6 +54,7 @@ export type ServerToolkitNames = {
5154
[Toolkits.Strava]: StravaTools;
5255
[Toolkits.Spotify]: SpotifyTools;
5356
[Toolkits.Video]: VideoTools;
57+
[Toolkits.Twitter]: TwitterTools;
5458
};
5559

5660
export type ServerToolkitParameters = {
@@ -66,4 +70,5 @@ export type ServerToolkitParameters = {
6670
[Toolkits.Strava]: typeof stravaParameters.shape;
6771
[Toolkits.Spotify]: typeof spotifyParameters.shape;
6872
[Toolkits.Video]: typeof videoParameters.shape;
73+
[Toolkits.Twitter]: typeof twitterParameters.shape;
6974
};
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import type { ToolkitConfig } from "@/toolkits/types";
2+
import { z } from "zod";
3+
import { TwitterTools } from "./tools";
4+
import { getUserProfileTool, getLatestTweetsTool } from "./tools";
5+
6+
export const twitterParameters = z.object({});
7+
8+
export const baseTwitterToolkitConfig: ToolkitConfig<
9+
TwitterTools,
10+
typeof twitterParameters.shape
11+
> = {
12+
tools: {
13+
[TwitterTools.GetUserProfile]: getUserProfileTool,
14+
[TwitterTools.GetLatestTweets]: getLatestTweetsTool,
15+
},
16+
parameters: twitterParameters,
17+
};
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
import { SiX } from "@icons-pack/react-simple-icons";
2+
3+
import { createClientToolkit } from "@/toolkits/create-toolkit";
4+
5+
import { baseTwitterToolkitConfig } from "./base";
6+
import { TwitterTools } from "./tools";
7+
import {
8+
getUserProfileToolConfigClient,
9+
getLatestTweetsToolConfigClient,
10+
} from "./tools/client";
11+
12+
import { ToolkitGroups } from "@/toolkits/types";
13+
14+
import { TwitterWrapper } from "./wrapper";
15+
16+
export const twitterClientToolkit = createClientToolkit(
17+
baseTwitterToolkitConfig,
18+
{
19+
name: "Twitter",
20+
description: "Get Twitter user profiles and latest tweets",
21+
icon: SiX,
22+
form: null,
23+
Wrapper: TwitterWrapper,
24+
type: ToolkitGroups.DataSource,
25+
envVars: [
26+
{
27+
type: "all",
28+
keys: ["AUTH_TWITTER_ID", "AUTH_TWITTER_SECRET"],
29+
description: (
30+
<span>
31+
Get OAuth credentials from{" "}
32+
<a
33+
href="https://developer.twitter.com/en/portal/dashboard"
34+
target="_blank"
35+
rel="noopener noreferrer"
36+
className="text-blue-500 underline hover:text-blue-700"
37+
>
38+
Twitter Developer Portal
39+
</a>
40+
. You&apos;ll need to create an app and generate OAuth 1.0a
41+
credentials.
42+
</span>
43+
),
44+
},
45+
],
46+
},
47+
{
48+
[TwitterTools.GetUserProfile]: getUserProfileToolConfigClient,
49+
[TwitterTools.GetLatestTweets]: getLatestTweetsToolConfigClient,
50+
},
51+
);
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
import { createServerToolkit } from "@/toolkits/create-toolkit";
2+
import { baseTwitterToolkitConfig } from "./base";
3+
import {
4+
getUserProfileToolConfigServer,
5+
getLatestTweetsToolConfigServer,
6+
} from "./tools/server";
7+
import { TwitterTools } from "./tools";
8+
import { api } from "@/trpc/server";
9+
import { TwitterApi } from "twitter-api-v2";
10+
11+
export const twitterToolkitServer = createServerToolkit(
12+
baseTwitterToolkitConfig,
13+
`You have access to the Twitter toolkit for fetching user profiles and tweets. This toolkit provides:
14+
15+
- **Get User Profile**: Retrieve detailed information about a Twitter user by their username
16+
- **Get Latest Tweets**: Fetch the most recent tweets from a user (up to 100 tweets)
17+
18+
**Tool Sequencing Strategies:**
19+
1. **User Research**: Start with Get User Profile to understand a user's background, then use Get Latest Tweets to see their recent activity
20+
2. **Content Analysis**: Use Get Latest Tweets to analyze a user's recent posts and engagement patterns
21+
3. **Profile Verification**: Combine Get User Profile with Get Latest Tweets to verify user authenticity and activity
22+
23+
**Best Practices:**
24+
- Use usernames without the @ symbol (e.g., 'elonmusk' not '@elonmusk')
25+
- For tweet analysis, start with fewer results (10-20) to get a quick overview
26+
- Use the exclude_retweets and exclude_replies options to focus on original content
27+
- Consider the user's follower count and verification status when analyzing their influence`,
28+
async () => {
29+
const account = await api.accounts.getAccountByProvider("twitter");
30+
31+
if (!account) {
32+
throw new Error(
33+
"No Twitter account found. Please connect your Twitter account first.",
34+
);
35+
}
36+
37+
if (!account.access_token) {
38+
throw new Error(
39+
"Twitter access token not found. Please reconnect your Twitter account.",
40+
);
41+
}
42+
43+
// Create Twitter API client with user's access token
44+
const client = new TwitterApi(account.access_token);
45+
46+
return {
47+
[TwitterTools.GetUserProfile]: getUserProfileToolConfigServer(client),
48+
[TwitterTools.GetLatestTweets]: getLatestTweetsToolConfigServer(client),
49+
};
50+
},
51+
);
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
export * from "./profile/client";
2+
export * from "./tweets/client";

0 commit comments

Comments
 (0)