create-message-kit 1.0.21 → 1.1.5-beta.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,42 +1,28 @@
1
1
  import { skills } from "./skills.js";
2
- import type { UserInfo } from "./lib/resolver.js";
2
+ import { UserInfo, PROMPT_USER_CONTENT } from "./lib/resolver.js";
3
+ import { PROMPT_RULES, PROMPT_SKILLS_AND_EXAMPLES } from "./lib/gpt.js";
3
4
 
4
- export async function ens_agent_prompt(userInfo: UserInfo) {
5
- let { address, ensDomain, converseUsername } = userInfo;
5
+ export async function agent_prompt(userInfo: UserInfo) {
6
+ let { address, ensDomain, converseUsername, preferredName } = userInfo;
6
7
 
7
- const systemPrompt = `You are a helpful and playful agent called @ens that lives inside a web3 messaging app called Converse.
8
- - You can respond with multiple messages if needed. Each message should be separated by a newline character.
9
- - You can trigger commands by only sending the command in a newline message.
10
- - Never announce actions without using a command separated by a newline character.
11
- - Only provide answers based on verified information.
12
- - Dont answer in markdown format, just answer in plaintext.
13
- - Do not make guesses or assumptions
14
- - CHECK that you are not missing a command
8
+ //Update the name of the agent with predefined prompt
9
+ let systemPrompt = PROMPT_RULES.replace("{NAME}", skills?.[0]?.tag ?? "@ens");
15
10
 
16
- User context:
17
- - Users address is: ${address}
18
- ${ensDomain != undefined ? `- User ENS domain is: ${ensDomain}` : ""}
19
- ${converseUsername != undefined ? `- Converse username is: ${converseUsername}` : ""}
11
+ //Add user context to the prompt
12
+ systemPrompt += PROMPT_USER_CONTENT(userInfo);
20
13
 
21
- ## Task
22
- - Start by fetch their domain from or Convese username
23
- - Call the user by their name or domain, in case they have one
24
- - Ask for a name (if they don't have one) so you can suggest domains.
25
-
26
- Commands:
27
- ${skills.map((skill) => skill.skills.map((s) => s.command).join("\n")).join("\n")}
28
-
29
- Examples:
30
- ${skills.map((skill) => skill.skills.map((s) => s.example).join("\n")).join("\n")}
14
+ //Add skills and examples to the prompt
15
+ systemPrompt += PROMPT_SKILLS_AND_EXAMPLES(skills);
31
16
 
17
+ systemPrompt += `
32
18
 
33
19
  ## Example responses:
34
20
 
35
21
  1. Check if the user does not have a ENS domain
36
- Hey ${converseUsername}! it looks like you don't have a ENS domain yet! \n\Let me start by checking your Converse username with the .eth suffix\n/check ${converseUsername}.eth
22
+ Hey ${preferredName}! it looks like you don't have a ENS domain yet! \n\Let me start by checking your Converse username with the .eth suffix\n/check ${converseUsername}.eth
37
23
 
38
24
  2. If the user has a ENS domain
39
- Hello ${ensDomain} ! I'll help you get your ENS domain.\n Let's start by checking your ENS domain ${ensDomain}. Give me a moment.\n/check ${ensDomain}
25
+ Hello ${preferredName} ! I'll help you get your ENS domain.\n Let's start by checking your ENS domain ${ensDomain}. Give me a moment.\n/check ${ensDomain}
40
26
 
41
27
  3. Check if the ENS domain is available
42
28
  Hello! I'll help you get your domain.\n Let's start by checking your ENS domain ${ensDomain}. Give me a moment.\n/check ${ensDomain}
@@ -62,8 +48,9 @@ ${skills.map((skill) => skill.skills.map((s) => s.example).join("\n")).join("\n"
62
48
  10. If the user wants cool suggestions about a domain, use the command "/cool [domain]"
63
49
  Here are some cool suggestions for your domain.\n/cool ${ensDomain}
64
50
 
65
- ## Most common bug
66
- Some times you will say something like: "Looks like vitalik.eth is registered! What about these cool alternatives?"
51
+ ## Most common bugs
52
+
53
+ 1. Some times you will say something like: "Looks like vitalik.eth is registered! What about these cool alternatives?"
67
54
  But you forgot to add the command at the end of the message.
68
55
  You should have said something like: "Looks like vitalik.eth is registered! What about these cool alternatives?\n/cool vitalik.eth
69
56
  `;
@@ -1,11 +1,10 @@
1
- import { handleEns, ensAgent } from "./handler/ens.js";
1
+ import { handleEns } from "./handler/ens.js";
2
2
  import type { SkillGroup } from "@xmtp/message-kit";
3
3
 
4
4
  export const skills: SkillGroup[] = [
5
5
  {
6
6
  name: "Ens Domain Bot",
7
7
  tag: "@ens",
8
- tagHandler: ensAgent,
9
8
  description: "Register ENS domains.",
10
9
  skills: [
11
10
  {
@@ -14,20 +13,33 @@ export const skills: SkillGroup[] = [
14
13
  handler: handleEns,
15
14
  description:
16
15
  "Register a new ENS domain. Returns a URL to complete the registration process.",
17
- example: "/register vitalik.eth",
16
+ examples: ["/register vitalik.eth"],
18
17
  params: {
19
18
  domain: {
20
19
  type: "string",
21
20
  },
22
21
  },
23
22
  },
23
+ {
24
+ command: "/exists",
25
+ adminOnly: true,
26
+ examples: ["/exists"],
27
+ handler: handleEns,
28
+ triggers: ["/exists"],
29
+ description: "Check if an address is onboarded.",
30
+ params: {
31
+ address: {
32
+ type: "address",
33
+ },
34
+ },
35
+ },
24
36
  {
25
37
  command: "/info [domain]",
26
38
  triggers: ["/info"],
27
39
  handler: handleEns,
28
40
  description:
29
41
  "Get detailed information about an ENS domain including owner, expiry date, and resolver.",
30
- example: "/info nick.eth",
42
+ examples: ["/info nick.eth"],
31
43
  params: {
32
44
  domain: {
33
45
  type: "string",
@@ -40,7 +52,7 @@ export const skills: SkillGroup[] = [
40
52
  handler: handleEns,
41
53
  description:
42
54
  "Extend the registration period of your ENS domain. Returns a URL to complete the renewal.",
43
- example: "/renew fabri.base.eth",
55
+ examples: ["/renew fabri.base.eth"],
44
56
  params: {
45
57
  domain: {
46
58
  type: "string",
@@ -48,22 +60,21 @@ export const skills: SkillGroup[] = [
48
60
  },
49
61
  },
50
62
  {
51
- command: "/check [domain] [cool_alternatives]",
63
+ command: "/check [domain]",
52
64
  triggers: ["/check"],
53
65
  handler: handleEns,
66
+ examples: ["/check vitalik.eth", "/check fabri.base.eth"],
54
67
  description: "Check if a domain is available.",
55
68
  params: {
56
69
  domain: {
57
70
  type: "string",
58
71
  },
59
- cool_alternatives: {
60
- type: "quoted",
61
- },
62
72
  },
63
73
  },
64
74
  {
65
75
  command: "/cool [domain]",
66
76
  triggers: ["/cool"],
77
+ examples: ["/cool vitalik.eth"],
67
78
  handler: handleEns,
68
79
  description: "Get cool alternatives for a .eth domain.",
69
80
  params: {
@@ -75,6 +86,7 @@ export const skills: SkillGroup[] = [
75
86
  {
76
87
  command: "/reset",
77
88
  triggers: ["/reset"],
89
+ examples: ["/reset"],
78
90
  handler: handleEns,
79
91
  description: "Reset the conversation.",
80
92
  params: {},
@@ -84,6 +96,7 @@ export const skills: SkillGroup[] = [
84
96
  description: "Show a URL for tipping a domain owner.",
85
97
  triggers: ["/tip"],
86
98
  handler: handleEns,
99
+ examples: ["/tip 0x1234567890123456789012345678901234567890"],
87
100
  params: {
88
101
  address: {
89
102
  type: "string",
@@ -1,9 +1,16 @@
1
1
  import { run, HandlerContext } from "@xmtp/message-kit";
2
2
 
3
- run(async (context: HandlerContext) => {
4
- // Get the message and the address from the sender
5
- const { content, sender } = context.message;
3
+ run(
4
+ async (context: HandlerContext) => {
5
+ // Get the message and the address from the sender
6
+ const { content, sender } = context.message;
6
7
 
7
- // To reply, just call `reply` on the HandlerContext
8
- await context.send(`gm`);
9
- });
8
+ // To reply, just call `reply` on the HandlerContext
9
+ await context.send(`gm`);
10
+ },
11
+ {
12
+ client: {
13
+ logging: "debug",
14
+ },
15
+ },
16
+ );
@@ -1,5 +1,5 @@
1
1
  import { HandlerContext, AbstractedMember } from "@xmtp/message-kit";
2
- import { textGeneration } from "../lib/openai.js";
2
+ import { textGeneration } from "../lib/gpt.js";
3
3
 
4
4
  export async function handler(context: HandlerContext) {
5
5
  if (!process?.env?.OPEN_AI_API_KEY) {
@@ -12,6 +12,7 @@ export async function handler(context: HandlerContext) {
12
12
  sender,
13
13
  content: { content, params },
14
14
  },
15
+ skill,
15
16
  } = context;
16
17
 
17
18
  const systemPrompt = generateSystemPrompt(context);
@@ -23,7 +24,7 @@ export async function handler(context: HandlerContext) {
23
24
  userPrompt,
24
25
  systemPrompt,
25
26
  );
26
- context.skill(reply);
27
+ skill(reply);
27
28
  } catch (error) {
28
29
  console.error("Error during OpenAI call:", error);
29
30
  await context.reply("An error occurred while processing your request.");
@@ -59,7 +60,7 @@ function generateSystemPrompt(context: HandlerContext) {
59
60
  Important:
60
61
  - If a user asks jokes, make jokes about web3 devs\n
61
62
  - If the user asks about performing an action and you can think of a command that would help, answer directly with the command and nothing else.
62
- - Populate the command with the correct or random values. Always return commands with real values only, using usernames with @ and excluding addresses.\n
63
+ - Populate the command with the correct or random values. Always return skills with real values only, using usernames with @ and excluding addresses.\n
63
64
  - If the user asks a question or makes a statement that does not clearly map to a command, respond with helpful information or a clarification question.\n
64
65
  - If the user is grateful, respond asking for a tip in a playful manner.
65
66
  `;
@@ -34,7 +34,7 @@ export async function handler(context: HandlerContext) {
34
34
  context.send("Available games: \n/game wordle\n/game slot");
35
35
  break;
36
36
  default:
37
- // Inform the user about unrecognized commands and provide available options
37
+ // Inform the user about unrecognized skills and provide available options
38
38
  context.send(
39
39
  "Command not recognized. Available games: wordle, slot, or help.",
40
40
  );
@@ -1,5 +1,6 @@
1
1
  import { HandlerContext } from "@xmtp/message-kit";
2
- import { vision, textGeneration } from "../lib/openai.js";
2
+ import { textGeneration } from "../lib/gpt.js";
3
+ import { vision } from "../lib/vision.js";
3
4
  import { getUserInfo } from "../lib/resolver.js";
4
5
 
5
6
  export async function handler(context: HandlerContext) {
@@ -9,7 +10,7 @@ export async function handler(context: HandlerContext) {
9
10
  }
10
11
  const {
11
12
  members,
12
- skills,
13
+ skill,
13
14
  message: {
14
15
  typeId,
15
16
  content: { attachment },
@@ -52,16 +53,11 @@ export async function handler(context: HandlerContext) {
52
53
  `;
53
54
 
54
55
  //I want the reply to be an array of messages so the bot feels like is sending multuple ones
55
- const { reply } = await textGeneration(
56
- sender.address,
57
- response,
58
- prompt,
59
- true,
60
- );
56
+ const { reply } = await textGeneration(sender.address, response, prompt);
61
57
  let splitMessages = JSON.parse(reply);
62
58
  for (const message of splitMessages) {
63
59
  let msg = message as string;
64
- if (msg.startsWith("/")) await context.skill(msg);
60
+ if (msg.startsWith("/")) await skill(msg);
65
61
  else await context.send(msg);
66
62
  }
67
63
  }
@@ -7,7 +7,6 @@ export async function handler(context: HandlerContext) {
7
7
  getMessageById,
8
8
  message: { content, sender, typeId },
9
9
  } = context;
10
- console.log(sender);
11
10
  const msg = await getMessageById(content.reference);
12
11
  const replyReceiver = members?.find(
13
12
  (member) => member.inboxId === msg?.senderInboxId,
@@ -28,7 +27,7 @@ export async function handler(context: HandlerContext) {
28
27
  } else if (typeId === "text") {
29
28
  const { content: text, params } = content;
30
29
  if (text.startsWith("/tip") && params) {
31
- // Process text commands starting with "/tip"
30
+ // Process text skills starting with "/tip"
32
31
  const {
33
32
  params: { amount: extractedAmount, username },
34
33
  } = content;
@@ -43,7 +42,6 @@ export async function handler(context: HandlerContext) {
43
42
  context.reply("Sender or receiver or amount not found.");
44
43
  return;
45
44
  }
46
- console.log(receivers);
47
45
  const receiverAddresses = receivers.map((receiver) => receiver.address);
48
46
  // Process sending tokens to each receiver
49
47
 
@@ -21,7 +21,6 @@ export async function handler(context: HandlerContext) {
21
21
  );
22
22
  return;
23
23
  }
24
- let name = senderInfo.converseUsername || senderInfo.address;
25
24
 
26
25
  let sendUrl = `${baseUrl}/?transaction_type=send&amount=${amountSend}&token=${tokenSend}&receiver=${senderInfo.address}`;
27
26
  context.send(`${sendUrl}`);
@@ -52,6 +52,6 @@ export async function helpHandler(context: HandlerContext) {
52
52
  ?.flatMap((app) => app.skills)
53
53
  .map((skill) => `${skill.command} - ${skill.description}`)
54
54
  .join("\n") +
55
- "\nUse these commands to interact with specific apps.";
55
+ "\nUse these skills to interact with specific apps.";
56
56
  context.send(intro);
57
57
  }
@@ -0,0 +1,161 @@
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
+ }
@@ -1,6 +1,6 @@
1
1
  import dotenv from "dotenv";
2
2
  dotenv.config();
3
-
3
+ import type { SkillGroup } from "@xmtp/message-kit";
4
4
  import OpenAI from "openai";
5
5
  const openai = new OpenAI({
6
6
  apiKey: process.env.OPEN_AI_API_KEY,
@@ -9,13 +9,91 @@ const openai = new OpenAI({
9
9
  export type ChatHistoryEntry = { role: string; content: string };
10
10
  export type ChatHistories = Record<string, ChatHistoryEntry[]>;
11
11
 
12
+ // New ChatMemory class
13
+ class ChatMemory {
14
+ private histories: ChatHistories = {};
15
+
16
+ getHistory(address: string): ChatHistoryEntry[] {
17
+ return this.histories[address] || [];
18
+ }
19
+
20
+ addEntry(address: string, entry: ChatHistoryEntry) {
21
+ if (!this.histories[address]) {
22
+ this.histories[address] = [];
23
+ }
24
+ this.histories[address].push(entry);
25
+ }
26
+
27
+ initializeWithSystem(address: string, systemPrompt: string) {
28
+ if (this.getHistory(address).length === 0) {
29
+ this.addEntry(address, {
30
+ role: "system",
31
+ content: systemPrompt,
32
+ });
33
+ }
34
+ }
35
+
36
+ clear() {
37
+ this.histories = {};
38
+ }
39
+ }
40
+
41
+ export const clearMemory = () => {
42
+ chatHistories = {};
43
+ };
44
+
45
+ // Create singleton instance
46
+ export const chatMemory = new ChatMemory();
47
+
12
48
  let chatHistories: ChatHistories = {};
49
+ export const PROMPT_RULES = `You are a helpful and playful agent called {NAME} that lives inside a web3 messaging app called Converse.
50
+ - You can respond with multiple messages if needed. Each message should be separated by a newline character.
51
+ - You can trigger skills by only sending the command in a newline message.
52
+ - Never announce actions without using a command separated by a newline character.
53
+ - Dont answer in markdown format, just answer in plaintext.
54
+ - Do not make guesses or assumptions
55
+ - Only answer if the verified information is in the prompt.
56
+ - Check that you are not missing a command
57
+ - Focus only on helping users with operations detailed below.
58
+ `;
59
+
60
+ export const PROMPT_SKILLS_AND_EXAMPLES = (skills: SkillGroup[]) => `
61
+ Commands:
62
+ ${skills
63
+ .map((skill) => skill.skills.map((s) => s.command).join("\n"))
64
+ .join("\n")}
65
+
66
+ Examples:
67
+ ${skills
68
+ .map((skill) => skill.skills.map((s) => s.examples).join("\n"))
69
+ .join("\n")}
70
+ `;
71
+
72
+ export async function agentResponse(
73
+ sender: { address: string },
74
+ userPrompt: string,
75
+ systemPrompt: string,
76
+ context: any,
77
+ ) {
78
+ try {
79
+ const { reply } = await textGeneration(
80
+ sender.address,
81
+ userPrompt,
82
+ systemPrompt,
83
+ );
84
+ await processMultilineResponse(sender.address, reply, context);
85
+ } catch (error) {
86
+ console.error("Error during OpenAI call:", error);
87
+ await context.reply("An error occurred while processing your request.");
88
+ }
89
+ }
13
90
  export async function textGeneration(
14
91
  address: string,
15
92
  userPrompt: string,
16
93
  systemPrompt: string,
17
94
  ) {
18
- let messages = chatHistories[address] || [];
95
+ let messages = chatMemory.getHistory(address);
96
+ chatMemory.initializeWithSystem(address, systemPrompt);
19
97
  if (messages.length === 0) {
20
98
  messages.push({
21
99
  role: "system",
@@ -36,8 +114,11 @@ export async function textGeneration(
36
114
  role: "assistant",
37
115
  content: reply || "No response from OpenAI.",
38
116
  });
39
- const cleanedReply = responseParser(reply as string);
40
- chatHistories[address] = messages;
117
+ const cleanedReply = parseMarkdown(reply as string);
118
+ chatMemory.addEntry(address, {
119
+ role: "assistant",
120
+ content: cleanedReply,
121
+ });
41
122
  return { reply: cleanedReply, history: messages };
42
123
  } catch (error) {
43
124
  console.error("Failed to fetch from OpenAI:", error);
@@ -45,68 +126,26 @@ export async function textGeneration(
45
126
  }
46
127
  }
47
128
 
48
- // New method to interpret an image
49
- export async function vision(imageData: Uint8Array, systemPrompt: string) {
50
- const base64Image = Buffer.from(imageData).toString("base64");
51
- const dataUrl = `data:image/jpeg;base64,${base64Image}`;
52
-
53
- // Create a new thread for each vision request
54
- const visionMessages = [
55
- {
56
- role: "system",
57
- content: systemPrompt,
58
- },
59
- {
60
- role: "user",
61
- content: [
62
- { type: "text", text: systemPrompt },
63
- {
64
- type: "image_url",
65
- image_url: {
66
- url: dataUrl,
67
- },
68
- },
69
- ],
70
- },
71
- ];
72
-
73
- try {
74
- const response = await openai.chat.completions.create({
75
- model: "gpt-4o",
76
- messages: visionMessages as any,
77
- });
78
- return response.choices[0].message.content;
79
- } catch (error) {
80
- console.error("Failed to interpret image with OpenAI:", error);
81
- throw error;
82
- }
83
- }
84
-
85
- export async function processResponseWithSkill(
129
+ export async function processMultilineResponse(
86
130
  address: string,
87
131
  reply: string,
88
132
  context: any,
89
133
  ) {
90
134
  let messages = reply
91
135
  .split("\n")
92
- .map((message: string) => responseParser(message))
136
+ .map((message: string) => parseMarkdown(message))
93
137
  .filter((message): message is string => message.length > 0);
94
138
 
95
139
  console.log(messages);
96
140
  for (const message of messages) {
97
141
  if (message.startsWith("/")) {
98
142
  const response = await context.skill(message);
99
- if (response && response.message) {
100
- let msg = responseParser(response.message);
101
-
102
- if (!chatHistories[address]) {
103
- chatHistories[address] = [];
104
- }
105
- chatHistories[address].push({
143
+ if (response && typeof response.message === "string") {
144
+ let msg = parseMarkdown(response.message);
145
+ chatMemory.addEntry(address, {
106
146
  role: "system",
107
147
  content: msg,
108
148
  });
109
-
110
149
  await context.send(response.message);
111
150
  }
112
151
  } else {
@@ -114,7 +153,7 @@ export async function processResponseWithSkill(
114
153
  }
115
154
  }
116
155
  }
117
- export function responseParser(message: string) {
156
+ export function parseMarkdown(message: string) {
118
157
  let trimmedMessage = message;
119
158
  // Remove bold and underline markdown
120
159
  trimmedMessage = trimmedMessage?.replace(/(\*\*|__)(.*?)\1/g, "$2");
@@ -133,7 +172,3 @@ export function responseParser(message: string) {
133
172
 
134
173
  return trimmedMessage;
135
174
  }
136
-
137
- export const clearChatHistories = () => {
138
- chatHistories = {};
139
- };