1
1
import { SupabaseClient , createClient } from "@supabase/supabase-js" ;
2
- import { BgentRuntime , Message } from "bgent" ;
2
+ import { BgentRuntime , Content , Message , State , composeContext , embeddingZeroVector , messageHandlerTemplate , parseJSONObjectFromText } from "bgent" ;
3
3
import { UUID } from 'crypto' ;
4
4
import dotenv from "dotenv" ;
5
5
import { Readable } from "stream" ;
6
6
import getUuid from 'uuid-by-string' ;
7
- import { AudioMonitor } from "./audioMonitor" ;
8
- import DiscordClient from "./discordClient" ;
9
- import { textToSpeech } from "./elevenlabs" ;
10
- import { speechToText } from "./speechtotext" ;
7
+ import { AudioMonitor } from "./audioMonitor.ts " ;
8
+ import DiscordClient from "./discordClient.ts " ;
9
+ import { textToSpeech } from "./elevenlabs.ts " ;
10
+ import { speechToText } from "./speechtotext.ts " ;
11
11
import { BaseGuildVoiceChannel } from "discord.js" ;
12
12
13
13
enum ResponseType {
@@ -29,6 +29,138 @@ enum ResponseType {
29
29
RESPONSE_AUDIO = 3
30
30
}
31
31
32
+
33
+ /**
34
+ * Handle an incoming message, processing it and returning a response.
35
+ * @param message The message to handle.
36
+ * @param state The state of the agent.
37
+ * @returns The response to the message.
38
+ */
39
+ async function handleMessage (
40
+ runtime : BgentRuntime ,
41
+ message : Message ,
42
+ state ?: State
43
+ ) {
44
+ const _saveRequestMessage = async ( message : Message , state : State ) => {
45
+ const { content : senderContent , /* senderId, userIds, room_id */ } = message
46
+
47
+ // we run evaluation here since some evals could be modulo based, and we should run on every message
48
+ if ( ( senderContent as Content ) . content ) {
49
+ const { data : data2 , error } = await runtime . supabase . from ( 'messages' ) . select ( '*' ) . eq ( 'user_id' , message . senderId )
50
+ . eq ( 'room_id' , room_id )
51
+ . order ( 'created_at' , { ascending : false } )
52
+
53
+ if ( error ) {
54
+ console . log ( 'error' , error )
55
+ // TODO: dont need this recall
56
+ } else if ( data2 . length > 0 && data2 [ 0 ] . content === message . content ) {
57
+ console . log ( 'already saved' , data2 )
58
+ } else {
59
+ await runtime . messageManager . createMemory ( {
60
+ user_ids : [ message . senderId , message . agentId , ...message . userIds ] ,
61
+ user_id : senderId ! ,
62
+ content : senderContent ,
63
+ room_id,
64
+ embedding : embeddingZeroVector
65
+ } )
66
+ }
67
+ await runtime . evaluate ( message , state )
68
+ }
69
+ }
70
+
71
+ await _saveRequestMessage ( message , state as State )
72
+ // if (!state) {
73
+ state = ( await runtime . composeState ( message ) ) as State
74
+ // }
75
+
76
+ const context = composeContext ( {
77
+ state,
78
+ template : messageHandlerTemplate
79
+ } )
80
+
81
+ if ( runtime . debugMode ) {
82
+ console . log ( context , 'Response Context' )
83
+ }
84
+
85
+ let responseContent : Content | null = null
86
+ const { senderId, room_id, userIds : user_ids , agentId } = message
87
+
88
+ for ( let triesLeft = 3 ; triesLeft > 0 ; triesLeft -- ) {
89
+ console . log ( context )
90
+ const response = await runtime . completion ( {
91
+ context,
92
+ stop : [ ]
93
+ } )
94
+
95
+ runtime . supabase
96
+ . from ( 'logs' )
97
+ . insert ( {
98
+ body : { message, context, response } ,
99
+ user_id : senderId ,
100
+ room_id,
101
+ user_ids : user_ids ! ,
102
+ agent_id : agentId ! ,
103
+ type : 'main_completion'
104
+ } )
105
+ . then ( ( { error } ) => {
106
+ if ( error ) {
107
+ console . error ( 'error' , error )
108
+ }
109
+ } )
110
+
111
+ const parsedResponse = parseJSONObjectFromText (
112
+ response
113
+ ) as unknown as Content
114
+
115
+ if (
116
+ ( parsedResponse . user as string ) ?. includes (
117
+ ( state as State ) . agentName as string
118
+ )
119
+ ) {
120
+ responseContent = {
121
+ content : parsedResponse . content ,
122
+ action : parsedResponse . action
123
+ }
124
+ break
125
+ }
126
+ }
127
+
128
+ if ( ! responseContent ) {
129
+ responseContent = {
130
+ content : '' ,
131
+ action : 'IGNORE'
132
+ }
133
+ }
134
+
135
+ const _saveResponseMessage = async (
136
+ message : Message ,
137
+ state : State ,
138
+ responseContent : Content
139
+ ) => {
140
+ const { agentId, userIds, room_id } = message
141
+
142
+ responseContent . content = responseContent . content ?. trim ( )
143
+
144
+ if ( responseContent . content ) {
145
+ await runtime . messageManager . createMemory ( {
146
+ user_ids : userIds ! ,
147
+ user_id : agentId ! ,
148
+ content : responseContent ,
149
+ room_id,
150
+ embedding : embeddingZeroVector
151
+ } )
152
+ await runtime . evaluate ( message , { ...state , responseContent } )
153
+ } else {
154
+ console . warn ( 'Empty response, skipping' )
155
+ }
156
+ }
157
+
158
+ await _saveResponseMessage ( message , state , responseContent )
159
+ await runtime . processActions ( message , responseContent )
160
+
161
+ return responseContent
162
+ }
163
+
32
164
// Add this function to fetch the bot's name
33
165
async function fetchBotName ( botToken : string ) {
34
166
const url = 'https://discord.com/api/v10/users/@me' ;
@@ -159,13 +291,6 @@ const supabase = createClient(
159
291
160
292
const discordClient = new DiscordClient ( ) ;
161
293
162
- const runtime = new BgentRuntime ( {
163
- supabase,
164
- token : process . env . OPENAI_API_KEY ,
165
- serverUrl : 'https://api.openai.com/v1' ,
166
- evaluators : [ ] ,
167
- actions : [ ] ,
168
- } ) ;
169
294
170
295
/**
171
296
* Listens on an audio stream and responds with an audio stream.
@@ -198,9 +323,9 @@ async function respondToSpokenAudio(userId: string, userName: string, channelId:
198
323
const sstService = speechToText ;
199
324
const text = await sstService ( inputBuffer ) ;
200
325
if ( requestedResponseType == ResponseType . SPOKEN_TEXT ) {
201
- return Readable . from ( text ) ;
326
+ return Readable . from ( text as string ) ;
202
327
} else {
203
- return await respondToText ( userId , userName , channelId , text , requestedResponseType ) ;
328
+ return await respondToText ( userId , userName , channelId , text as string , requestedResponseType ) ;
204
329
}
205
330
}
206
331
/**
@@ -214,7 +339,7 @@ async function respondToText(userId: string, userName: string, channelId: string
214
339
215
340
const userIdUUID = getUuid ( userId ) as UUID ;
216
341
217
- const agentId = getUuid ( process . env . DISCORD_APPLICATION_ID ) as UUID ;
342
+ const agentId = getUuid ( process . env . DISCORD_APPLICATION_ID as string ) as UUID ;
218
343
219
344
await ensureUserExists ( supabase , agentId , null , process . env . DISCORD_TOKEN ) ;
220
345
await ensureUserExists ( supabase , userIdUUID , userName ) ;
@@ -230,7 +355,16 @@ async function respondToText(userId: string, userName: string, channelId: string
230
355
room_id,
231
356
} as unknown as Message ;
232
357
233
- const response = await runtime . handleMessage ( message )
358
+
359
+ const runtime = new BgentRuntime ( {
360
+ supabase,
361
+ token : process . env . OPENAI_API_KEY as string ,
362
+ serverUrl : 'https://api.openai.com/v1' ,
363
+ evaluators : [ ] ,
364
+ actions : [ ] ,
365
+ } ) ;
366
+
367
+ const response = await handleMessage ( runtime , message )
234
368
235
369
if ( requestedResponseType == ResponseType . RESPONSE_TEXT ) {
236
370
return Readable . from ( response . content ) ;
0 commit comments