create-message-kit 1.0.16 → 1.0.17

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-message-kit",
3
- "version": "1.0.16",
3
+ "version": "1.0.17",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "main": "index.js",
@@ -1,52 +1,39 @@
1
1
  import { HandlerContext } from "@xmtp/message-kit";
2
- import { Client } from "@xmtp/xmtp-js";
3
- import { getUserInfo, getInfoCache, isOnXMTP } from "../lib/resolver.js";
4
- import { textGeneration, responseParser } from "../lib/openai.js";
2
+ import { getUserInfo, clearInfoCache, isOnXMTP } from "../lib/resolver.js";
3
+ import { textGeneration } from "../lib/openai.js";
4
+ import { processResponseWithSkill } from "../lib/openai.js";
5
+ import { isAddress } from "viem";
5
6
  import { ens_agent_prompt } from "../prompt.js";
6
- import type {
7
- ensDomain,
8
- converseUsername,
9
- tipAddress,
10
- chatHistories,
11
- tipDomain,
12
- } from "../lib/types.js";
13
- import { frameUrl, ensUrl, baseTxUrl, InfoCache } from "../lib/types.js";
14
-
15
- let tipAddress: tipAddress = {};
16
- let tipDomain: tipDomain = {};
17
- let ensDomain: ensDomain = {};
18
- let infoCache: InfoCache = {};
19
- let converseUsername: converseUsername = {};
20
- let chatHistories: chatHistories = {};
21
-
22
- // URL for the send transaction
7
+ import { frameUrl, ensUrl, baseTxUrl } from "../index.js";
8
+ import { clearChatHistories } from "../lib/openai.js";
9
+
23
10
  export async function handleEns(context: HandlerContext) {
24
11
  const {
25
12
  message: {
26
13
  content: { command, params, sender },
27
14
  },
28
15
  } = context;
29
-
30
- if (command == "renew") {
16
+ if (command == "reset") {
17
+ clear();
18
+ return { code: 200, message: "Conversation reset." };
19
+ } else if (command == "renew") {
31
20
  // Destructure and validate parameters for the ens command
32
21
  const { domain } = params;
33
22
  // Check if the user holds the domain
34
23
  if (!domain) {
35
- context.reply("Missing required parameters. Please provide domain.");
36
- return;
24
+ return {
25
+ code: 400,
26
+ message: "Missing required parameters. Please provide domain.",
27
+ };
37
28
  }
38
29
 
39
- const { infoCache: retrievedInfoCache } = await getInfoCache(
40
- domain,
41
- infoCache,
42
- );
43
- infoCache = retrievedInfoCache;
44
- let data = infoCache[domain].info;
30
+ const data = await getUserInfo(domain);
45
31
 
46
- if (data?.address !== sender?.address) {
32
+ if (!data || data?.address !== sender?.address) {
47
33
  return {
48
34
  code: 403,
49
- message: "You do not hold this domain. Only the owner can renew it.",
35
+ message:
36
+ "Looks like this domain is not registered to you. Only the owner can renew it.",
50
37
  };
51
38
  }
52
39
 
@@ -69,22 +56,23 @@ export async function handleEns(context: HandlerContext) {
69
56
  } else if (command == "info") {
70
57
  const { domain } = params;
71
58
 
72
- const { infoCache: retrievedInfoCache } = await getInfoCache(
73
- domain,
74
- infoCache,
75
- );
76
- infoCache = retrievedInfoCache;
77
- let data = infoCache[domain].info;
59
+ const data = await getUserInfo(domain);
60
+ if (!data) {
61
+ return {
62
+ code: 404,
63
+ message: "Domain not found.",
64
+ };
65
+ }
78
66
 
79
67
  const formattedData = {
80
68
  Address: data?.address,
81
- "Avatar URL": data?.avatar_url,
82
- Description: data?.description,
83
- ENS: data?.ens,
84
- "Primary ENS": data?.ens_primary,
85
- GitHub: data?.github,
86
- Resolver: data?.resolverAddress,
87
- Twitter: data?.twitter,
69
+ "Avatar URL": data?.ensInfo?.avatar,
70
+ Description: data?.ensInfo?.description,
71
+ ENS: data?.ensDomain,
72
+ "Primary ENS": data?.ensInfo?.ens_primary,
73
+ GitHub: data?.ensInfo?.github,
74
+ Resolver: data?.ensInfo?.resolverAddress,
75
+ Twitter: data?.ensInfo?.twitter,
88
76
  URL: `${ensUrl}${domain}`,
89
77
  };
90
78
 
@@ -96,23 +84,20 @@ export async function handleEns(context: HandlerContext) {
96
84
  }
97
85
  message += `\n\nWould you like to tip the domain owner for getting there first 🤣?`;
98
86
  message = message.trim();
99
- if (await isOnXMTP(context.v2client, data?.ens, data?.address)) {
100
- context.send(
87
+ if (
88
+ await isOnXMTP(
89
+ context.v2client,
90
+ data?.ensInfo?.ens,
91
+ data?.ensInfo?.address,
92
+ )
93
+ ) {
94
+ await context.send(
101
95
  `Ah, this domains is in XMTP, you can message it directly: https://converse.xyz/dm/${domain}`,
102
96
  );
103
97
  }
104
98
  return { code: 200, message };
105
99
  } else if (command == "check") {
106
- console.log(params);
107
- const { domain, cool_alternatives } = params;
108
-
109
- const cool_alternativesFormat = cool_alternatives
110
- ?.split(",")
111
- .map(
112
- (alternative: string, index: number) =>
113
- `${index + 1}. ${alternative} ✨`,
114
- )
115
- .join("\n");
100
+ const { domain } = params;
116
101
 
117
102
  if (!domain) {
118
103
  return {
@@ -120,82 +105,46 @@ export async function handleEns(context: HandlerContext) {
120
105
  message: "Please provide a domain name to check.",
121
106
  };
122
107
  }
123
- const { infoCache: retrievedInfoCache } = await getInfoCache(
124
- domain,
125
- infoCache,
126
- );
127
- infoCache = retrievedInfoCache;
128
- let data = infoCache?.[domain]?.info;
108
+
109
+ const data = await getUserInfo(domain);
129
110
  if (!data?.address) {
130
- let message = `Looks like ${domain} is available! Do you want to register it? ${ensUrl}${domain}`;
111
+ let message = `Looks like ${domain} is available! Do you want to register it? ${ensUrl}${domain} or would you like to see some cool alternatives?`;
131
112
  return {
132
113
  code: 200,
133
114
  message,
134
115
  };
135
116
  } else {
136
- let message = `Looks like ${domain} is already registered! What about these cool alternatives?\n\n${cool_alternativesFormat}`;
117
+ let message = `Looks like ${domain} is already registered!`;
118
+ await context.skill("/cool " + domain);
137
119
  return {
138
120
  code: 404,
139
121
  message,
140
122
  };
141
123
  }
142
124
  } else if (command == "tip") {
143
- // Destructure and validate parameters for the send command
144
125
  const { address } = params;
145
-
146
- const { infoCache: retrievedInfoCache } = await getInfoCache(
147
- address,
148
- infoCache,
149
- );
150
- infoCache = retrievedInfoCache;
151
- let data = infoCache[address].info;
152
-
153
- tipAddress[sender.address] = data?.address;
154
- tipDomain[sender.address] = data?.ens;
155
-
156
- if (!address || !tipAddress[sender.address]) {
157
- context.reply("Missing required parameters. Please provide address.");
158
- return;
126
+ if (!address) {
127
+ return {
128
+ code: 400,
129
+ message: "Please provide an address to tip.",
130
+ };
159
131
  }
160
- let txUrl = `${baseTxUrl}/transaction/?transaction_type=send&buttonName=Tip%20${tipDomain[sender.address]}&amount=1&token=USDC&receiver=${tipAddress[sender.address]}`;
161
- // Generate URL for the send transaction
162
- context.send(`Here is the url to send the tip:\n${txUrl}`);
132
+ const data = await getUserInfo(address);
133
+ let txUrl = `${baseTxUrl}/transaction/?transaction_type=send&buttonName=Tip%20${data?.ensDomain ?? ""}&amount=1&token=USDC&receiver=${
134
+ isAddress(address) ? address : data?.address
135
+ }`;
136
+ console.log(txUrl);
137
+ return {
138
+ code: 200,
139
+ message: txUrl,
140
+ };
163
141
  } else if (command == "cool") {
164
- return;
165
- }
166
- }
167
-
168
- export async function clearChatHistory() {
169
- chatHistories = {};
170
- }
171
-
172
- async function processResponseWithIntent(
173
- reply: string,
174
- context: any,
175
- senderAddress: string,
176
- ) {
177
- let messages = reply
178
- .split("\n")
179
- .map((message: string) => responseParser(message))
180
- .filter((message): message is string => message.length > 0);
181
-
182
- console.log(messages);
183
- for (const message of messages) {
184
- if (message.startsWith("/")) {
185
- const response = await context.intent(message);
186
- if (response && response.message) {
187
- let msg = responseParser(response.message);
188
-
189
- chatHistories[senderAddress].push({
190
- role: "system",
191
- content: msg,
192
- });
193
-
194
- await context.send(response.message);
195
- }
196
- } else {
197
- await context.send(message);
198
- }
142
+ const { domain } = params;
143
+ //What about these cool alternatives?\
144
+ return {
145
+ code: 200,
146
+ message: `${generateCoolAlternatives(domain)}`,
147
+ };
199
148
  }
200
149
  }
201
150
 
@@ -215,33 +164,48 @@ export async function ensAgent(context: HandlerContext) {
215
164
 
216
165
  try {
217
166
  let userPrompt = params?.prompt ?? content;
218
- const { converseUsername: newConverseUsername, ensDomain: newEnsDomain } =
219
- await getUserInfo(
220
- sender.address,
221
- ensDomain[sender.address],
222
- converseUsername[sender.address],
223
- );
224
-
225
- ensDomain[sender.address] = newEnsDomain;
226
- converseUsername[sender.address] = newConverseUsername;
227
- let txUrl = `${baseTxUrl}/transaction/?transaction_type=send&buttonName=Tip%20${tipDomain[sender.address]}&amount=1&token=USDC&receiver=${tipAddress[sender.address]}`;
167
+ const userInfo = await getUserInfo(sender.address);
168
+ if (!userInfo) {
169
+ console.log("User info not found");
170
+ return;
171
+ }
172
+ const { ensDomain, converseUsername } = userInfo;
228
173
 
229
- const { reply, history } = await textGeneration(
174
+ const { reply } = await textGeneration(
175
+ sender.address,
230
176
  userPrompt,
231
- await ens_agent_prompt(
232
- sender.address,
233
- ensDomain[sender.address],
234
- converseUsername[sender.address],
235
- tipAddress[sender.address],
236
- txUrl,
237
- ),
238
- chatHistories[sender.address],
177
+ await ens_agent_prompt(sender.address, ensDomain, converseUsername),
178
+ group !== undefined,
239
179
  );
240
- if (!group) chatHistories[sender.address] = history; // Update chat history for the user
241
-
242
- await processResponseWithIntent(reply, context, sender.address);
180
+ await processResponseWithSkill(sender.address, reply, context);
243
181
  } catch (error) {
244
182
  console.error("Error during OpenAI call:", error);
245
183
  await context.send("An error occurred while processing your request.");
246
184
  }
247
185
  }
186
+
187
+ export const generateCoolAlternatives = (domain: string) => {
188
+ const suffixes = ["lfg", "cool", "degen", "moon", "base", "gm"];
189
+ const alternatives = [];
190
+ for (let i = 0; i < 5; i++) {
191
+ const randomPosition = Math.random() < 0.5;
192
+ const baseDomain = domain.replace(/\.eth$/, ""); // Remove any existing .eth suffix
193
+ alternatives.push(
194
+ randomPosition
195
+ ? `${suffixes[i]}${baseDomain}.eth`
196
+ : `${baseDomain}${suffixes[i]}.eth`,
197
+ );
198
+ }
199
+
200
+ const cool_alternativesFormat = alternatives
201
+ .map(
202
+ (alternative: string, index: number) => `${index + 1}. ${alternative} ✨`,
203
+ )
204
+ .join("\n");
205
+ return cool_alternativesFormat;
206
+ };
207
+
208
+ export async function clear() {
209
+ clearChatHistories();
210
+ clearInfoCache();
211
+ }
@@ -1,6 +1,14 @@
1
1
  import { run, HandlerContext } from "@xmtp/message-kit";
2
2
  import { ensAgent } from "./handler/ens.js";
3
3
 
4
+ export const frameUrl = "https://ens.steer.fun/";
5
+ export const ensUrl = "https://app.ens.domains/";
6
+ export const baseTxUrl = "https://base-tx-frame.vercel.app";
7
+
4
8
  run(async (context: HandlerContext) => {
9
+ const { group, message } = context;
10
+ /*All the commands are handled through the commands file*/
11
+ /* If its just text, it will be handled by the ensAgent*/
12
+ /* If its a group message, it will be handled by the groupAgent*/
5
13
  await ensAgent(context);
6
14
  });
@@ -6,12 +6,17 @@ const openai = new OpenAI({
6
6
  apiKey: process.env.OPEN_AI_API_KEY,
7
7
  });
8
8
 
9
+ export type ChatHistoryEntry = { role: string; content: string };
10
+ export type ChatHistories = Record<string, ChatHistoryEntry[]>;
11
+
12
+ let chatHistories: ChatHistories = {};
9
13
  export async function textGeneration(
14
+ address: string,
10
15
  userPrompt: string,
11
16
  systemPrompt: string,
12
- chatHistory?: any[],
17
+ isGroup: boolean = false,
13
18
  ) {
14
- let messages = chatHistory ? [...chatHistory] : []; // Start with existing chat history
19
+ let messages = chatHistories[address] || [];
15
20
  if (messages.length === 0) {
16
21
  messages.push({
17
22
  role: "system",
@@ -33,7 +38,7 @@ export async function textGeneration(
33
38
  content: reply || "No response from OpenAI.",
34
39
  });
35
40
  const cleanedReply = responseParser(reply as string);
36
-
41
+ if (!isGroup) chatHistories[address] = messages;
37
42
  return { reply: cleanedReply, history: messages };
38
43
  } catch (error) {
39
44
  console.error("Failed to fetch from OpenAI:", error);
@@ -78,6 +83,35 @@ export async function vision(imageData: Uint8Array, systemPrompt: string) {
78
83
  }
79
84
  }
80
85
 
86
+ export async function processResponseWithSkill(
87
+ address: string,
88
+ reply: string,
89
+ context: any,
90
+ ) {
91
+ let messages = reply
92
+ .split("\n")
93
+ .map((message: string) => responseParser(message))
94
+ .filter((message): message is string => message.length > 0);
95
+
96
+ console.log(messages);
97
+ for (const message of messages) {
98
+ if (message.startsWith("/")) {
99
+ const response = await context.skill(message);
100
+ if (response && response.message) {
101
+ let msg = responseParser(response.message);
102
+
103
+ chatHistories[address].push({
104
+ role: "system",
105
+ content: msg,
106
+ });
107
+
108
+ await context.send(response.message);
109
+ }
110
+ } else {
111
+ await context.send(message);
112
+ }
113
+ }
114
+ }
81
115
  export function responseParser(message: string) {
82
116
  let trimmedMessage = message;
83
117
  // Remove bold and underline markdown
@@ -94,32 +128,10 @@ export function responseParser(message: string) {
94
128
  trimmedMessage = trimmedMessage?.replace(/^\s+|\s+$/g, "");
95
129
  // Remove any remaining leading or trailing whitespace
96
130
  trimmedMessage = trimmedMessage.trim();
97
- return trimmedMessage;
98
- }
99
-
100
- // UNTESTED, recursive response parser
101
- export function responseParser2(message: string | string[]): string | string[] {
102
- // If message is an array, process each item individually
103
- if (Array.isArray(message)) {
104
- return message
105
- .map((item) => responseParser(item))
106
- .flat() // Flatten nested arrays
107
- .filter((item: string) => item.length > 0)
108
- .filter((item: string) => item !== "`");
109
- }
110
- let trimmedMessage = message;
111
- // Remove bold and underline markdown
112
- trimmedMessage = trimmedMessage?.replace(/(\*\*|__)(.*?)\1/g, "$2");
113
- // Remove markdown links, keeping only the URL
114
- trimmedMessage = trimmedMessage?.replace(/\[([^\]]+)\]\(([^)]+)\)/g, "$2");
115
- // Remove markdown headers
116
- trimmedMessage = trimmedMessage?.replace(/^#+\s*(.*)$/gm, "$1");
117
- // Remove inline code formatting
118
- trimmedMessage = trimmedMessage?.replace(/(`{1,3})(.*?)\1/g, "$2");
119
- // Remove leading and trailing whitespace
120
- trimmedMessage = trimmedMessage?.replace(/`/g, ""); // Remove single backticks
121
- // Remove any remaining leading or trailing whitespace
122
- trimmedMessage = trimmedMessage?.trim();
123
131
 
124
132
  return trimmedMessage;
125
133
  }
134
+
135
+ export const clearChatHistories = () => {
136
+ chatHistories = {};
137
+ };
@@ -1,76 +1,120 @@
1
- import type { EnsData } from "./types.js";
2
- import { endpointURL } from "./types.js";
3
1
  import { Client } from "@xmtp/xmtp-js";
4
- import { InfoCache } from "./types.js";
2
+ import { isAddress } from "viem";
5
3
 
6
- export async function getUserInfo(
7
- address: string,
8
- ensDomain: string | undefined,
9
- converseUsername: string | undefined,
10
- ) {
11
- if (!ensDomain) {
12
- const response = await fetch(`https://ensdata.net/${address}`);
13
- const data: EnsData = (await response.json()) as EnsData;
14
- ensDomain = data?.ens;
4
+ export const converseEndpointURL =
5
+ "https://converse-website-git-endpoit-ephemerahq.vercel.app";
6
+ //export const converseEndpointURL = "http://localhost:3000";
7
+
8
+ export type InfoCache = Map<string, UserInfo>;
9
+ export type ConverseProfile = {
10
+ address: string | null;
11
+ onXmtp: boolean;
12
+ avatar: string | null;
13
+ formattedName: string | null;
14
+ name: string | null;
15
+ };
16
+ export type UserInfo = {
17
+ ensDomain?: string | undefined;
18
+ address?: string | undefined;
19
+ converseUsername?: string | undefined;
20
+ ensInfo?: EnsData | undefined;
21
+ avatar?: string | undefined;
22
+ };
23
+ export interface EnsData {
24
+ address?: string;
25
+ avatar?: string;
26
+ avatar_small?: string;
27
+ converse?: string;
28
+ avatar_url?: string;
29
+ contentHash?: string;
30
+ description?: string;
31
+ ens?: string;
32
+ ens_primary?: string;
33
+ github?: string;
34
+ resolverAddress?: string;
35
+ twitter?: string;
36
+ url?: string;
37
+ wallets?: {
38
+ eth?: string;
39
+ };
40
+ }
41
+
42
+ let infoCache: InfoCache = new Map();
43
+
44
+ export const clearInfoCache = () => {
45
+ infoCache.clear();
46
+ };
47
+ export const getUserInfo = async (
48
+ key: string,
49
+ clientAddress?: string,
50
+ ): Promise<UserInfo | null> => {
51
+ let data: UserInfo = infoCache.get(key) || {
52
+ ensDomain: undefined,
53
+ address: undefined,
54
+ converseUsername: undefined,
55
+ ensInfo: undefined,
56
+ };
57
+ //console.log("Getting user info", key, clientAddress);
58
+ if (isAddress(clientAddress || "")) {
59
+ data.address = clientAddress;
60
+ } else if (isAddress(key || "")) {
61
+ data.address = key;
62
+ } else if (key.includes(".eth")) {
63
+ data.ensDomain = key;
64
+ } else if (key == "@user" || key == "@me" || key == "@bot") {
65
+ data.address = clientAddress;
66
+ data.ensDomain = key.replace("@", "") + ".eth";
67
+ data.converseUsername = key.replace("@", "");
68
+ } else if (key == "@alix") {
69
+ data.address = "0x3a044b218BaE80E5b9E16609443A192129A67BeA";
70
+ data.converseUsername = "alix";
71
+ } else if (key == "@bo") {
72
+ data.address = "0xbc3246461ab5e1682baE48fa95172CDf0689201a";
73
+ data.converseUsername = "bo";
74
+ } else {
75
+ data.converseUsername = key;
76
+ }
77
+
78
+ let keyToUse = data.address || data.ensDomain || data.converseUsername;
79
+ let cacheData = keyToUse && infoCache.get(keyToUse);
80
+ if (cacheData) {
81
+ //console.log("Getting user info", keyToUse, cacheData);
82
+ return cacheData;
83
+ } else {
84
+ //console.log("Getting user info", keyToUse, data);
15
85
  }
16
- if (!converseUsername) {
17
- const response = await fetch(`${endpointURL}/profile/${address}`, {
86
+
87
+ if (keyToUse?.includes(".eth")) {
88
+ const response = await fetch(`https://ensdata.net/${keyToUse}`);
89
+ const ensData: EnsData = (await response.json()) as EnsData;
90
+ //console.log("Ens data", ensData);
91
+ if (ensData) {
92
+ data.ensInfo = ensData;
93
+ data.ensDomain = ensData?.ens;
94
+ data.address = ensData?.address;
95
+ }
96
+ } else if (keyToUse) {
97
+ keyToUse = keyToUse.replace("@", "");
98
+ const response = await fetch(`${converseEndpointURL}/profile/${keyToUse}`, {
18
99
  method: "POST",
19
100
  headers: {
20
101
  "Content-Type": "application/json",
21
102
  Accept: "application/json",
22
103
  },
23
- body: JSON.stringify({ address: address }),
104
+ body: JSON.stringify({
105
+ peer: keyToUse,
106
+ }),
24
107
  });
25
- const data = (await response.json()) as { name: string };
26
- converseUsername = data?.name;
27
- converseUsername = converseUsername.replace(".converse.xyz", "");
108
+ const converseData = (await response.json()) as ConverseProfile;
109
+ if (process.env.MSG_LOG)
110
+ console.log("Converse data", keyToUse, converseData);
111
+ data.converseUsername =
112
+ converseData?.formattedName || converseData?.name || undefined;
113
+ data.address = converseData?.address || undefined;
114
+ data.avatar = converseData?.avatar || undefined;
28
115
  }
29
- console.log("User info fetched for", {
30
- address,
31
- converseUsername,
32
- ensDomain,
33
- });
34
- return { converseUsername: converseUsername, ensDomain: ensDomain };
35
- }
36
-
37
- export const getInfoCache = async (
38
- key: string, // This can be either domain or address
39
- infoCache: InfoCache,
40
- ): Promise<{ domain: string; info: EnsData; infoCache: InfoCache }> => {
41
- if (infoCache[key]) {
42
- let data = {
43
- domain: key,
44
- info: infoCache[key].info,
45
- infoCache: infoCache,
46
- };
47
- return data;
48
- }
49
- try {
50
- const response = await fetch(`https://ensdata.net/${key}`);
51
- const data: EnsData = (await response.json()) as EnsData;
52
-
53
- // Assuming the data contains both domain and address
54
- const domain = data?.ens;
55
- const address = data?.address;
56
-
57
- // Store data in cache by both domain and address
58
- if (domain) infoCache[domain as string] = { info: data };
59
- if (address) infoCache[address as string] = { info: data };
60
-
61
- return { info: data, infoCache: infoCache, domain: domain as string };
62
- } catch (error) {
63
- console.error(error);
64
- return { info: {}, infoCache: infoCache, domain: "" };
65
- }
66
- };
67
- export const generateCoolAlternatives = (domain: string) => {
68
- const suffixes = ["lfg", "cool", "degen", "moon", "base", "gm"];
69
- const alternatives = suffixes.map((suffix) => {
70
- const randomPosition = Math.random() < 0.5;
71
- return randomPosition ? `${suffix}${domain}.eth` : `${domain}${suffix}.eth`;
72
- });
73
- return alternatives.join(",");
116
+ if (data.address) infoCache.set(data.address, data);
117
+ return data;
74
118
  };
75
119
  export const isOnXMTP = async (
76
120
  client: Client,