create-message-kit 1.1.8-beta.9 → 1.1.9

Sign up to get free protection for your applications and to get access to all the features.
package/index.js CHANGED
@@ -108,6 +108,7 @@ async function gatherProjectInfo() {
108
108
  { value: "gpt", label: "GPT" },
109
109
  { value: "agent", label: "Agent" },
110
110
  { value: "group", label: "Group" },
111
+ { value: "ens", label: "Ens Agent Pro" },
111
112
  ];
112
113
 
113
114
  const templateType = await select({
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-message-kit",
3
- "version": "1.1.8-beta.9",
3
+ "version": "1.1.9",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "main": "index.js",
@@ -0,0 +1,2 @@
1
+ KEY= # the private key of the wallet
2
+ OPEN_AI_API_KEY= # sk-proj-...
@@ -0,0 +1,90 @@
1
+ import { run, HandlerContext } from "@xmtp/message-kit";
2
+ import { ChatOpenAI } from "@langchain/openai";
3
+ import { createOpenAIFunctionsAgent, AgentExecutor } from "langchain/agents";
4
+ import { tools } from "./skills.js";
5
+ import { systemPrompt } from "./prompt.js";
6
+ import { ChatPromptTemplate } from "@langchain/core/prompts";
7
+
8
+ // Initialize OpenAI chat model
9
+ const model = new ChatOpenAI({
10
+ temperature: 0.7,
11
+ modelName: "gpt-4o",
12
+ openAIApiKey: process.env.OPEN_AI_API_KEY,
13
+ });
14
+
15
+ // Create the prompt template with required variables
16
+ const prompt = ChatPromptTemplate.fromMessages([
17
+ ["system", systemPrompt],
18
+ ]).partial({
19
+ tools: tools.map((tool) => `${tool.name}: ${tool.description}`).join("\n"),
20
+ tool_names: tools.map((tool) => tool.name).join(", "),
21
+ });
22
+
23
+ // Initialize chat history storage
24
+ const chatHistory = new Map<string, { role: string; content: string }[]>();
25
+
26
+ // Initialize the agent and executor
27
+ const initializeAgent = async () => {
28
+ const agent = await createOpenAIFunctionsAgent({
29
+ llm: model,
30
+ tools: tools as any,
31
+ prompt: await prompt,
32
+ });
33
+
34
+ return new AgentExecutor({
35
+ agent,
36
+ tools: tools as any,
37
+ returnIntermediateSteps: false,
38
+ verbose: true, // Set to true for checking the agent's thought process
39
+ });
40
+ };
41
+
42
+ // Create executor instance
43
+ const executor = await initializeAgent();
44
+
45
+ run(
46
+ async (context: HandlerContext) => {
47
+ const {
48
+ message: {
49
+ content: { text },
50
+ sender,
51
+ },
52
+ } = context;
53
+
54
+ console.log("Received message:", text);
55
+
56
+ // Get or initialize chat history for this sender
57
+ if (!chatHistory.has(sender.address)) {
58
+ chatHistory.set(sender.address, []);
59
+ }
60
+ const userHistory: any = chatHistory.get(sender.address)!;
61
+
62
+ // Add user message to history
63
+ userHistory.push({ role: "user", content: context.message.content.text });
64
+
65
+ try {
66
+ // Execute agent with user's message and chat history
67
+ const result = await executor.invoke({
68
+ input: text,
69
+ chat_history: userHistory,
70
+ });
71
+
72
+ console.log("Agent response:", result.output);
73
+ const output = result.output.replace(/\*/g, "");
74
+
75
+ // Add assistant's response to history
76
+ userHistory.push({ role: "assistant", content: output });
77
+
78
+ await context.send(output);
79
+ } catch (error) {
80
+ console.error("Error:", error);
81
+ // Add error message to history
82
+ userHistory.push({
83
+ role: "assistant",
84
+ content: "An error occurred while processing your request.",
85
+ });
86
+ await context.send("An error occurred while processing your request.");
87
+ }
88
+ },
89
+ { skills: [] },
90
+ );
@@ -0,0 +1,19 @@
1
+ export const systemPrompt = `You are a helpful AI assistant that can use various tools to help users.
2
+
3
+ You have access to the following tools:
4
+ {tools}
5
+
6
+ Tool Names: {tool_names}
7
+
8
+ Instructions:
9
+ 1. DO NOT respond in markdown. ALWAYS respond in plain text.
10
+ 2. Use tools when appropriate
11
+ 3. Be friendly and helpful
12
+ 4. Ask for clarification if you don't understand the user's request
13
+ 5. For tipping, always confirm the amount with the user once
14
+
15
+ Previous conversation history:
16
+ {chat_history}
17
+
18
+ User Input: {input}
19
+ {agent_scratchpad}`;
@@ -0,0 +1,233 @@
1
+ import { DynamicStructuredTool } from "langchain/tools";
2
+ import { getUserInfo } from "@xmtp/message-kit";
3
+ import { clearMemory } from "@xmtp/message-kit";
4
+ import { isAddress } from "viem";
5
+ import { z } from "zod";
6
+
7
+ const frameUrl = "https://ens.steer.fun/";
8
+ const ensUrl = "https://app.ens.domains/";
9
+ const txpayUrl = "https://txpay.vercel.app";
10
+ const converseUrl = "https://converse.xyz/profile/";
11
+
12
+ // Add interface for Converse API response
13
+ interface ConverseProfile {
14
+ address: string;
15
+ avatar?: string;
16
+ formattedName?: string;
17
+ name?: string;
18
+ onXmtp: boolean;
19
+ }
20
+
21
+ // Add function to check XMTP status
22
+ async function checkXMTPStatus(address: string): Promise<boolean> {
23
+ try {
24
+ const response = await fetch(converseUrl + address, {
25
+ method: "POST",
26
+ headers: {
27
+ "Content-Type": "application/json",
28
+ Accept: "application/json",
29
+ },
30
+ body: JSON.stringify({ address }),
31
+ });
32
+
33
+ if (!response.ok) {
34
+ console.error(`Failed to check XMTP status: ${response.status}`);
35
+ return false;
36
+ }
37
+
38
+ const data = (await response.json()) as ConverseProfile;
39
+ return data.onXmtp;
40
+ } catch (error) {
41
+ console.error("Error checking XMTP status:", error);
42
+ return false;
43
+ }
44
+ }
45
+
46
+ const generateCoolAlternatives = (domain: string) => {
47
+ const suffixes = ["lfg", "cool", "degen", "moon", "base", "gm"];
48
+ const alternatives = [];
49
+ for (let i = 0; i < 5; i++) {
50
+ const randomPosition = Math.random() < 0.5;
51
+ const baseDomain = domain.replace(/\.eth$/, ""); // Remove any existing .eth suffix
52
+ alternatives.push(
53
+ randomPosition
54
+ ? `${suffixes[i]}${baseDomain}.eth`
55
+ : `${baseDomain}${suffixes[i]}.eth`,
56
+ );
57
+ }
58
+
59
+ return alternatives
60
+ .map(
61
+ (alternative: string, index: number) => `${index + 1}. ${alternative} ✨`,
62
+ )
63
+ .join("\n");
64
+ };
65
+
66
+ // Export tools array with all tools
67
+ export const tools = [
68
+ new DynamicStructuredTool({
69
+ name: "reset_ens_conversation",
70
+ description: "Reset the ENS conversation and clear memory",
71
+ schema: z.object({}),
72
+ func: async () => {
73
+ clearMemory();
74
+ return "Conversation reset successfully.";
75
+ },
76
+ }),
77
+
78
+ new DynamicStructuredTool({
79
+ name: "renew_ens_domain",
80
+ description:
81
+ "Generate renewal URL for an ENS domain. Only works if sender owns the domain",
82
+ schema: z.object({
83
+ domain: z.string().describe("The ENS domain to renew"),
84
+ }),
85
+ func: async ({ domain }) => {
86
+ const data = await getUserInfo(domain);
87
+ if (!data?.address) {
88
+ return "Domain not found or not registered.";
89
+ }
90
+ return `${frameUrl}frames/manage?name=${domain}`;
91
+ },
92
+ }),
93
+
94
+ new DynamicStructuredTool({
95
+ name: "register_ens_domain",
96
+ description: "Get URL to register a new ENS domain",
97
+ schema: z.object({
98
+ domain: z.string().describe("The ENS domain to register"),
99
+ }),
100
+ func: async ({ domain }) => {
101
+ if (!domain) return "Please provide a domain name";
102
+ return `${ensUrl}${domain}`;
103
+ },
104
+ }),
105
+
106
+ new DynamicStructuredTool({
107
+ name: "get_ens_info",
108
+ description:
109
+ "Get detailed information about an ENS domain including owner, avatar, description, etc",
110
+ schema: z.object({
111
+ domain: z.string().describe("The ENS domain to get information about"),
112
+ }),
113
+ func: async ({ domain }) => {
114
+ const data = await getUserInfo(domain);
115
+ if (!data?.ensDomain) {
116
+ return "Domain not found.";
117
+ }
118
+
119
+ const formattedData = {
120
+ Address: data?.address,
121
+ "Avatar URL": data?.ensInfo?.avatar,
122
+ Description: data?.ensInfo?.description,
123
+ ENS: data?.ensDomain,
124
+ "Primary ENS": data?.ensInfo?.ens_primary,
125
+ GitHub: data?.ensInfo?.github,
126
+ Resolver: data?.ensInfo?.resolverAddress,
127
+ Twitter: data?.ensInfo?.twitter,
128
+ URL: `${ensUrl}${domain}`,
129
+ };
130
+
131
+ let message = "Domain information:\n\n";
132
+ for (const [key, value] of Object.entries(formattedData)) {
133
+ if (value) {
134
+ message += `${key}: ${value}\n`;
135
+ }
136
+ }
137
+ message +=
138
+ "\nWould you like to tip the domain owner for getting there first? 🤣";
139
+
140
+ // Check XMTP status if we have an address
141
+ if (data.address) {
142
+ const isOnXMTP = await checkXMTPStatus(data.address);
143
+ if (isOnXMTP) {
144
+ message += `\n\nAh, this domain is on XMTP! You can message it directly: https://converse.xyz/dm/${domain}`;
145
+ }
146
+ }
147
+
148
+ return message;
149
+ },
150
+ }),
151
+
152
+ new DynamicStructuredTool({
153
+ name: "check_ens_availability",
154
+ description: "Check if an ENS domain is available for registration",
155
+ schema: z.object({
156
+ domain: z
157
+ .string()
158
+ .transform((str) => str.replace(/^["'](.+)["']$/, "$1")) // Remove quotes if present
159
+ .transform((str) => str.toLowerCase())
160
+ .describe("The ENS domain to check availability for"),
161
+ }),
162
+ func: async ({ domain }) => {
163
+ if (!domain) return "Please provide a domain name to check.";
164
+
165
+ if (domain.includes(".") && !domain.endsWith(".eth")) {
166
+ return "Invalid ENS domain. Only .eth domains are supported.";
167
+ }
168
+
169
+ if (!domain.includes(".")) {
170
+ domain = `${domain}.eth`;
171
+ }
172
+
173
+ const data = await getUserInfo(domain);
174
+ if (!data?.address) {
175
+ return `Looks like ${domain} is available! Here you can register it: ${ensUrl}${domain} or would you like to see some cool alternatives?`;
176
+ } else {
177
+ const alternatives = generateCoolAlternatives(domain);
178
+ return `Looks like ${domain} is already registered!\n\nHere are some cool alternatives:\n${alternatives}`;
179
+ }
180
+ },
181
+ }),
182
+
183
+ new DynamicStructuredTool({
184
+ name: "get_ens_alternatives",
185
+ description: "Generate cool alternative names for an ENS domain",
186
+ schema: z.object({
187
+ domain: z
188
+ .string()
189
+ .describe("The ENS domain to generate alternatives for"),
190
+ }),
191
+ func: async ({ domain }) => {
192
+ if (!domain) return "Please provide a domain name.";
193
+ return `What about these cool alternatives?\n\n${generateCoolAlternatives(domain)}`;
194
+ },
195
+ }),
196
+
197
+ new DynamicStructuredTool({
198
+ name: "get_ens_tip_url",
199
+ description:
200
+ "Generate a URL to tip an ENS domain owner in USDC. Works with both ENS domains and Ethereum addresses.",
201
+ schema: z.object({
202
+ addressOrDomain: z
203
+ .string()
204
+ .describe("The ENS domain or Ethereum address to tip"),
205
+ amount: z
206
+ .number()
207
+ .optional()
208
+ .default(1)
209
+ .describe("The amount of USDC to tip"),
210
+ }),
211
+ func: async ({ addressOrDomain, amount }) => {
212
+ if (!addressOrDomain) {
213
+ return "Please provide an address or ENS domain to tip.";
214
+ }
215
+
216
+ let address: string | undefined;
217
+
218
+ if (isAddress(addressOrDomain)) {
219
+ address = addressOrDomain;
220
+ } else {
221
+ const data = await getUserInfo(addressOrDomain);
222
+ address = data?.address;
223
+ }
224
+
225
+ if (!address) {
226
+ return "Could not resolve address for tipping. Please provide a valid ENS domain or Ethereum address.";
227
+ }
228
+
229
+ let sendUrl = `${txpayUrl}/?&amount=${amount}&token=USDC&receiver=${address}`;
230
+ return sendUrl;
231
+ },
232
+ }),
233
+ ];