create-message-kit 1.1.7-beta.2 → 1.1.7-beta.20

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,49 +1,39 @@
1
- import { HandlerContext, AbstractedMember } from "@xmtp/message-kit";
2
- import { getUserInfo } from "../lib/resolver.js";
1
+ import {
2
+ HandlerContext,
3
+ AbstractedMember,
4
+ SkillResponse,
5
+ } from "@xmtp/message-kit";
6
+ import { getUserInfo } from "@xmtp/message-kit";
3
7
 
4
- export async function handler(context: HandlerContext) {
8
+ export async function handler(context: HandlerContext): Promise<SkillResponse> {
5
9
  const {
6
10
  members,
7
- getMessageById,
8
- message: { content, sender, typeId },
11
+ message: {
12
+ content: {
13
+ reference,
14
+ reply,
15
+ text,
16
+ skill,
17
+ params: { amount, username },
18
+ },
19
+ sender,
20
+ },
9
21
  } = context;
10
- const msg = await getMessageById(content.reference);
11
- const replyReceiver = members?.find(
12
- (member) => member.inboxId === msg?.senderInboxId,
13
- );
14
- let amount: number = 0,
15
- receivers: AbstractedMember[] = [];
16
- // Handle different types of messages
17
- if (typeId === "reply" && replyReceiver) {
18
- const { content: reply } = content;
19
-
20
- if (reply.includes("degen")) {
21
- receivers = [replyReceiver];
22
- const match = reply.match(/(\d+)/);
23
- if (match)
24
- amount = parseInt(match[0]); // Extract amount from reply
25
- else amount = 10;
26
- }
27
- } else if (typeId === "text") {
28
- const { content: text, params } = content;
29
- if (text.startsWith("/tip") && params) {
30
- // Process text skills starting with "/tip"
31
- const {
32
- params: { amount: extractedAmount, username },
33
- } = content;
34
- amount = extractedAmount || 10; // Default amount if not specified
22
+ let receivers: AbstractedMember[] = [];
35
23
 
36
- receivers = await Promise.all(
37
- username.map((username: string) => getUserInfo(username)),
38
- );
39
- }
24
+ if (skill === "tip") {
25
+ receivers = await Promise.all(
26
+ username.map((username: string) => getUserInfo(username)),
27
+ );
40
28
  }
41
29
  if (!sender || receivers.length === 0 || amount === 0) {
42
30
  context.reply("Sender or receiver or amount not found.");
43
- return;
31
+ return {
32
+ code: 400,
33
+ message: "Sender or receiver or amount not found.",
34
+ };
44
35
  }
45
36
  const receiverAddresses = receivers.map((receiver) => receiver.address);
46
- // Process sending tokens to each receiver
47
37
 
48
38
  context.sendTo(
49
39
  `You received ${amount} tokens from ${sender.address}.`,
@@ -55,4 +45,8 @@ export async function handler(context: HandlerContext) {
55
45
  `You sent ${amount * receiverAddresses.length} tokens in total.`,
56
46
  [sender.address],
57
47
  );
48
+ return {
49
+ code: 200,
50
+ message: "Success",
51
+ };
58
52
  }
@@ -1,50 +1,69 @@
1
- import { HandlerContext } from "@xmtp/message-kit";
2
- import { getUserInfo } from "../lib/resolver.js";
1
+ import { getUserInfo, HandlerContext, SkillResponse } from "@xmtp/message-kit";
3
2
 
4
- // Main handler function for processing commands
5
- export async function handler(context: HandlerContext) {
3
+ // Main handler function for processing
4
+ export async function handler(context: HandlerContext): Promise<SkillResponse> {
6
5
  const {
7
6
  message: {
8
- content: { command, params },
7
+ content: { skill, params },
9
8
  },
10
9
  } = context;
11
10
  const baseUrl = "https://base-tx-frame.vercel.app/transaction";
12
11
 
13
- switch (command) {
12
+ switch (skill) {
14
13
  case "send":
15
- // Destructure and validate parameters for the send command
14
+ // Destructure and validate parameters for the send
16
15
  const { amount: amountSend, token: tokenSend, username } = params; // [!code hl] // [!code focus]
17
16
  let senderInfo = await getUserInfo(username);
18
17
  if (!amountSend || !tokenSend || !senderInfo) {
19
18
  context.reply(
20
19
  "Missing required parameters. Please provide amount, token, and username.",
21
20
  );
22
- return;
21
+ return {
22
+ code: 400,
23
+ message:
24
+ "Missing required parameters. Please provide amount, token, and username.",
25
+ };
23
26
  }
24
27
 
25
28
  let sendUrl = `${baseUrl}/?transaction_type=send&amount=${amountSend}&token=${tokenSend}&receiver=${senderInfo.address}`;
26
29
  context.send(`${sendUrl}`);
27
- break;
30
+ return {
31
+ code: 200,
32
+ message: `${sendUrl}`,
33
+ };
28
34
  case "swap":
29
- // Destructure and validate parameters for the swap command
35
+ // Destructure and validate parameters for the swap
30
36
  const { amount, token_from, token_to } = params; // [!code hl] // [!code focus]
31
37
 
32
38
  if (!amount || !token_from || !token_to) {
33
39
  context.reply(
34
40
  "Missing required parameters. Please provide amount, token_from, and token_to.",
35
41
  );
36
- return;
42
+ return {
43
+ code: 400,
44
+ message:
45
+ "Missing required parameters. Please provide amount, token_from, and token_to.",
46
+ };
37
47
  }
38
48
 
39
49
  let swapUrl = `${baseUrl}/?transaction_type=swap&token_from=${token_from}&token_to=${token_to}&amount=${amount}`;
40
50
  context.send(`${swapUrl}`);
41
- break;
51
+ return {
52
+ code: 200,
53
+ message: `${swapUrl}`,
54
+ };
42
55
  case "show": // [!code hl] // [!code focus]
43
56
  // Show the base URL without the transaction path
44
57
  context.reply(`${baseUrl.replace("/transaction", "")}`);
45
- break;
58
+ return {
59
+ code: 200,
60
+ message: `${baseUrl.replace("/transaction", "")}`,
61
+ };
46
62
  default:
47
- // Handle unknown commands
48
- context.reply("Unknown command. Use help to see all available commands.");
63
+ context.reply("Unknown skill. Use help to see all available skills.");
64
+ return {
65
+ code: 400,
66
+ message: "Unknown skill. Use help to see all available skills.",
67
+ };
49
68
  }
50
69
  }
@@ -1,37 +1,32 @@
1
1
  import { run, HandlerContext } from "@xmtp/message-kit";
2
+ import { textGeneration, processMultilineResponse } from "@xmtp/message-kit";
3
+ import { agent_prompt } from "./prompt.js";
4
+ import { getUserInfo } from "@xmtp/message-kit";
2
5
 
3
- // Main function to run the app
4
- run(
5
- async (context: HandlerContext) => {
6
- const {
7
- message: { typeId },
8
- group,
9
- } = context;
10
- switch (typeId) {
11
- case "reply":
12
- handleReply(context);
13
- break;
14
- }
15
- if (!group) {
16
- context.send("This is a group bot, add this address to a group");
17
- }
18
- },
19
- { attachments: true },
20
- );
21
- async function handleReply(context: HandlerContext) {
6
+ run(async (context: HandlerContext) => {
22
7
  const {
23
- v2client,
24
- getReplyChain,
25
- version,
26
8
  message: {
27
- content: { reference },
9
+ content: { text, params },
10
+ sender,
28
11
  },
12
+ group,
29
13
  } = context;
30
14
 
31
- const { chain, isSenderInChain } = await getReplyChain(
32
- reference,
33
- version,
34
- v2client.address,
35
- );
36
- //await context.skill(chain);
37
- }
15
+ try {
16
+ let userPrompt = params?.prompt ?? text;
17
+ const userInfo = await getUserInfo(sender.address);
18
+ if (!userInfo) {
19
+ console.log("User info not found");
20
+ return;
21
+ }
22
+ const { reply } = await textGeneration(
23
+ sender.address,
24
+ userPrompt,
25
+ await agent_prompt(userInfo),
26
+ );
27
+ await processMultilineResponse(sender.address, reply, context);
28
+ } catch (error) {
29
+ console.error("Error during OpenAI call:", error);
30
+ await context.send("An error occurred while processing your request.");
31
+ }
32
+ });
@@ -0,0 +1,25 @@
1
+ import { skills } from "./skills.js";
2
+ import {
3
+ UserInfo,
4
+ PROMPT_USER_CONTENT,
5
+ PROMPT_RULES,
6
+ PROMPT_SKILLS_AND_EXAMPLES,
7
+ PROMPT_REPLACE_VARIABLES,
8
+ } from "@xmtp/message-kit";
9
+
10
+ export async function agent_prompt(userInfo: UserInfo) {
11
+ let systemPrompt =
12
+ PROMPT_RULES +
13
+ PROMPT_USER_CONTENT(userInfo) +
14
+ PROMPT_SKILLS_AND_EXAMPLES(skills, "@bot");
15
+
16
+ // Replace the variables in the system prompt
17
+ systemPrompt = PROMPT_REPLACE_VARIABLES(
18
+ systemPrompt,
19
+ userInfo?.address ?? "",
20
+ userInfo,
21
+ "@bot",
22
+ );
23
+ console.log(systemPrompt);
24
+ return systemPrompt;
25
+ }
@@ -1,18 +1,17 @@
1
1
  import { handler as tipping } from "./handler/tipping.js";
2
- import { handler as agent } from "./handler/agent.js";
3
2
  import { handler as transaction } from "./handler/transaction.js";
4
3
  import { handler as games } from "./handler/game.js";
5
- import { handler as loyalty } from "./handler/loyalty.js";
6
- import { handler as groupHelp } from "./handler/group.js";
4
+ import { handler as help } from "./handler/helpers.js";
7
5
  import type { SkillGroup } from "@xmtp/message-kit";
8
6
 
9
7
  export const skills: SkillGroup[] = [
10
8
  {
11
- name: "Tipping",
12
- description: "Tip tokens via emoji, replies or command.",
9
+ name: "Group bot",
10
+ tag: "@bot",
11
+ description: "Group bot for tipping and transactions.",
13
12
  skills: [
14
13
  {
15
- command: "/tip [@usernames] [amount] [token]",
14
+ skill: "/tip [usernames] [amount] [token]",
16
15
  triggers: ["/tip"],
17
16
  examples: ["/tip @vitalik 10 usdc"],
18
17
  description: "Tip users in a specified token.",
@@ -29,15 +28,8 @@ export const skills: SkillGroup[] = [
29
28
  },
30
29
  },
31
30
  },
32
- ],
33
- },
34
-
35
- {
36
- name: "Transactions",
37
- description: "Multipurpose transaction frame built onbase.",
38
- skills: [
39
31
  {
40
- command: "/send [amount] [token] [username]",
32
+ skill: "/send [amount] [token] [username]",
41
33
  triggers: ["/send"],
42
34
  examples: ["/send 10 usdc @vitalik"],
43
35
  description:
@@ -60,7 +52,7 @@ export const skills: SkillGroup[] = [
60
52
  },
61
53
  },
62
54
  {
63
- command: "/swap [amount] [token_from] [token_to]",
55
+ skill: "/swap [amount] [token_from] [token_to]",
64
56
  triggers: ["/swap"],
65
57
  examples: ["/swap 10 usdc eth"],
66
58
  description: "Exchange one type of cryptocurrency for another.",
@@ -83,24 +75,19 @@ export const skills: SkillGroup[] = [
83
75
  },
84
76
  },
85
77
  {
86
- command: "/show",
78
+ skill: "/show",
87
79
  triggers: ["/show"],
88
80
  examples: ["/show"],
89
81
  handler: transaction,
90
82
  description: "Show the whole frame.",
91
83
  params: {},
92
84
  },
93
- ],
94
- },
95
- {
96
- name: "Games",
97
- description: "Provides various gaming experiences.",
98
- skills: [
99
85
  {
100
- command: "/game [game]",
86
+ skill: "/game [game]",
101
87
  triggers: ["/game", "🔎", "🔍"],
102
88
  handler: games,
103
89
  description: "Play a game.",
90
+ examples: ["/game wordle", "/game slot", "/game help"],
104
91
  params: {
105
92
  game: {
106
93
  default: "",
@@ -109,65 +96,19 @@ export const skills: SkillGroup[] = [
109
96
  },
110
97
  },
111
98
  },
112
- ],
113
- },
114
- {
115
- name: "Loyalty",
116
- description: "Manage group members and metadata.",
117
- skills: [
118
- {
119
- command: "/points",
120
- triggers: ["/points"],
121
- examples: ["/points"],
122
- handler: loyalty,
123
- description: "Check your points.",
124
- params: {},
125
- },
126
- {
127
- command: "/leaderboard",
128
- triggers: ["/leaderboard"],
129
- adminOnly: true,
130
- handler: loyalty,
131
- description: "Check the points of a user.",
132
- params: {},
133
- },
134
- ],
135
- },
136
- {
137
- name: "Agent",
138
- description: "Manage agent commands.",
139
- skills: [
140
- {
141
- command: "/agent [prompt]",
142
- triggers: ["/agent", "@agent", "@bot"],
143
- examples: ["/agent @vitalik"],
144
- handler: agent,
145
- description: "Manage agent commands.",
146
- params: {
147
- prompt: {
148
- default: "",
149
- type: "prompt",
150
- },
151
- },
152
- },
153
- ],
154
- },
155
- {
156
- name: "Help",
157
- description: "Get help with the bot.",
158
- skills: [
159
99
  {
160
- command: "/help",
100
+ skill: "/help",
161
101
  triggers: ["/help"],
162
102
  examples: ["/help"],
163
- handler: groupHelp,
103
+ handler: help,
164
104
  description: "Get help with the bot.",
165
105
  params: {},
166
106
  },
167
107
  {
168
- command: "/id",
108
+ skill: "/id",
169
109
  adminOnly: true,
170
- handler: groupHelp,
110
+ examples: ["/id"],
111
+ handler: help,
171
112
  triggers: ["/id"],
172
113
  description: "Get the group ID.",
173
114
  params: {},
@@ -1,22 +0,0 @@
1
- {
2
- "name": "agent",
3
- "private": true,
4
- "type": "module",
5
- "scripts": {
6
- "build": "tsc",
7
- "dev": "tsc -w & sleep 1 && nodemon --quiet dist/index.js",
8
- "start": "node dist/index.js"
9
- },
10
- "dependencies": {
11
- "@xmtp/message-kit": "workspace:*",
12
- "openai": "^4.65.0"
13
- },
14
- "devDependencies": {
15
- "@types/node": "^20.14.2",
16
- "nodemon": "^3.1.3",
17
- "typescript": "^5.4.5"
18
- },
19
- "engines": {
20
- "node": ">=20"
21
- }
22
- }
@@ -1,161 +0,0 @@
1
- import "dotenv/config";
2
- import type { SkillGroup } from "@xmtp/message-kit";
3
- import OpenAI from "openai";
4
- const openai = new OpenAI({
5
- apiKey: process.env.OPEN_AI_API_KEY,
6
- });
7
-
8
- type ChatHistoryEntry = { role: string; content: string };
9
- type ChatHistories = Record<string, ChatHistoryEntry[]>;
10
- // New ChatMemory class
11
- class ChatMemory {
12
- private histories: ChatHistories = {};
13
-
14
- getHistory(address: string): ChatHistoryEntry[] {
15
- return this.histories[address] || [];
16
- }
17
-
18
- addEntry(address: string, entry: ChatHistoryEntry) {
19
- if (!this.histories[address]) {
20
- this.histories[address] = [];
21
- }
22
- this.histories[address].push(entry);
23
- }
24
-
25
- initializeWithSystem(address: string, systemPrompt: string) {
26
- if (this.getHistory(address).length === 0) {
27
- this.addEntry(address, {
28
- role: "system",
29
- content: systemPrompt,
30
- });
31
- }
32
- }
33
-
34
- clear() {
35
- this.histories = {};
36
- }
37
- }
38
-
39
- // Create singleton instance
40
- export const chatMemory = new ChatMemory();
41
-
42
- export const clearMemory = () => {
43
- chatMemory.clear();
44
- };
45
-
46
- export const PROMPT_RULES = `You are a helpful and playful agent called {NAME} that lives inside a web3 messaging app called Converse.
47
- - You can respond with multiple messages if needed. Each message should be separated by a newline character.
48
- - You can trigger skills by only sending the command in a newline message.
49
- - Never announce actions without using a command separated by a newline character.
50
- - Dont answer in markdown format, just answer in plaintext.
51
- - Do not make guesses or assumptions
52
- - Only answer if the verified information is in the prompt.
53
- - Check that you are not missing a command
54
- - Focus only on helping users with operations detailed below.
55
- `;
56
-
57
- export function PROMPT_SKILLS_AND_EXAMPLES(skills: SkillGroup[], tag: string) {
58
- let foundSkills = skills.filter(
59
- (skill) => skill.tag == `@${tag.toLowerCase()}`,
60
- );
61
- if (!foundSkills.length || !foundSkills[0] || !foundSkills[0].skills)
62
- return "";
63
- let returnPrompt = `\nCommands:\n${foundSkills[0].skills
64
- .map((skill) => skill.command)
65
- .join("\n")}\n\nExamples:\n${foundSkills[0].skills
66
- .map((skill) => skill.examples)
67
- .join("\n")}`;
68
- return returnPrompt;
69
- }
70
-
71
- export async function textGeneration(
72
- memoryKey: string,
73
- userPrompt: string,
74
- systemPrompt: string,
75
- ) {
76
- if (!memoryKey) {
77
- clearMemory();
78
- }
79
- let messages = chatMemory.getHistory(memoryKey);
80
- chatMemory.initializeWithSystem(memoryKey, systemPrompt);
81
- if (messages.length === 0) {
82
- messages.push({
83
- role: "system",
84
- content: systemPrompt,
85
- });
86
- }
87
- messages.push({
88
- role: "user",
89
- content: userPrompt,
90
- });
91
- try {
92
- const response = await openai.chat.completions.create({
93
- model: "gpt-4o",
94
- messages: messages as any,
95
- });
96
- const reply = response.choices[0].message.content;
97
- messages.push({
98
- role: "assistant",
99
- content: reply || "No response from OpenAI.",
100
- });
101
- const cleanedReply = parseMarkdown(reply as string);
102
- chatMemory.addEntry(memoryKey, {
103
- role: "assistant",
104
- content: cleanedReply,
105
- });
106
- return { reply: cleanedReply, history: messages };
107
- } catch (error) {
108
- console.error("Failed to fetch from OpenAI:", error);
109
- throw error;
110
- }
111
- }
112
-
113
- export async function processMultilineResponse(
114
- memoryKey: string,
115
- reply: string,
116
- context: any,
117
- ) {
118
- if (!memoryKey) {
119
- clearMemory();
120
- }
121
- let messages = reply
122
- .split("\n")
123
- .map((message: string) => parseMarkdown(message))
124
- .filter((message): message is string => message.length > 0);
125
-
126
- console.log(messages);
127
- for (const message of messages) {
128
- if (message.startsWith("/")) {
129
- const response = await context.skill(message);
130
- if (response && typeof response.message === "string") {
131
- let msg = parseMarkdown(response.message);
132
- chatMemory.addEntry(memoryKey, {
133
- role: "system",
134
- content: msg,
135
- });
136
- await context.send(response.message);
137
- }
138
- } else {
139
- await context.send(message);
140
- }
141
- }
142
- }
143
- export function parseMarkdown(message: string) {
144
- let trimmedMessage = message;
145
- // Remove bold and underline markdown
146
- trimmedMessage = trimmedMessage?.replace(/(\*\*|__)(.*?)\1/g, "$2");
147
- // Remove markdown links, keeping only the URL
148
- trimmedMessage = trimmedMessage?.replace(/\[([^\]]+)\]\(([^)]+)\)/g, "$2");
149
- // Remove markdown headers
150
- trimmedMessage = trimmedMessage?.replace(/^#+\s*(.*)$/gm, "$1");
151
- // Remove inline code formatting
152
- trimmedMessage = trimmedMessage?.replace(/`([^`]+)`/g, "$1");
153
- // Remove single backticks at the start or end of the message
154
- trimmedMessage = trimmedMessage?.replace(/^`|`$/g, "");
155
- // Remove leading and trailing whitespace
156
- trimmedMessage = trimmedMessage?.replace(/^\s+|\s+$/g, "");
157
- // Remove any remaining leading or trailing whitespace
158
- trimmedMessage = trimmedMessage.trim();
159
-
160
- return trimmedMessage;
161
- }