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

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.
@@ -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
- }