@@ -24,6 +24,7 @@ import {
2424} from './ai.constants' ;
2525import { MemoryPersistenceService } from './memory/memory.persistence.service' ;
2626import type { MemoryWithSlackId } from '../shared/db/models/Memory' ;
27+ import { logError } from '../shared/logger/error-logging' ;
2728import { logger } from '../shared/logger/logger' ;
2829import { SlackService } from '../shared/services/slack/slack.service' ;
2930import { MuzzlePersistenceService } from '../muzzle/muzzle.persistence.service' ;
@@ -55,6 +56,8 @@ const extractAndParseOpenAiResponse = (response: OpenAI.Responses.Response): str
5556 return outputText ?. trim ( ) ;
5657} ;
5758
59+ const DEFAULT_IMAGE_DIR = path . join ( '/tmp' , 'mocker-images' ) ;
60+
5861export class AIService {
5962 redis = new AIPersistenceService ( ) ;
6063 openAi = new OpenAI ( {
@@ -110,27 +113,35 @@ export class AIService {
110113 }
111114 } )
112115 . catch ( async ( e ) => {
113- this . aiServiceLogger . error ( e ) ;
116+ logError ( this . aiServiceLogger , 'Failed to generate AI text response' , e , {
117+ userId,
118+ teamId,
119+ channelId,
120+ prompt : text ,
121+ } ) ;
114122 await this . redis . removeInflight ( userId , teamId ) ;
115123 await this . redis . decrementDailyRequests ( userId , teamId ) ;
116124 throw e ;
117125 } ) ;
118126 }
119127
120128 public async writeToDiskAndReturnUrl ( base64Image : string ) : Promise < string > {
121- const dir = process . env . IMAGE_DIR ? process . env . IMAGE_DIR : path . join ( __dirname , '../../../images' ) ;
129+ const dir = process . env . IMAGE_DIR ?? DEFAULT_IMAGE_DIR ;
122130 const filename = `${ uuidv4 ( ) } .png` ;
123131 const filePath = path . join ( dir , filename ) ;
124132 const base64Data = base64Image . replace ( / ^ d a t a : i m a g e \/ p n g ; b a s e 6 4 , / , '' ) ;
125- return new Promise ( ( resolve , reject ) =>
126- fs . writeFile ( filePath , base64Data , 'base64' , ( err ) => {
127- if ( err ) {
128- this . aiServiceLogger . error ( 'Error writing image to disk:' , err ) ;
129- reject ( err ) ;
130- }
131- resolve ( `https://muzzle.lol/${ filename } ` ) ;
132- } ) ,
133- ) ;
133+
134+ try {
135+ await fs . promises . mkdir ( dir , { recursive : true } ) ;
136+ await fs . promises . writeFile ( filePath , base64Data , 'base64' ) ;
137+ return `https://muzzle.lol/${ filename } ` ;
138+ } catch ( error ) {
139+ logError ( this . aiServiceLogger , 'Failed to write AI image to disk' , error , {
140+ imageDirectory : dir ,
141+ filePath,
142+ } ) ;
143+ throw error ;
144+ }
134145 }
135146
136147 public async redeployMoonbeam ( ) : Promise < void > {
@@ -191,8 +202,11 @@ export class AIService {
191202 if ( x ) {
192203 return this . writeToDiskAndReturnUrl ( x ) ;
193204 } else {
194- this . aiServiceLogger . error ( `No b64_json was returned for prompt: ${ REDPLOY_MOONBEAM_IMAGE_PROMPT } ` ) ;
195- throw new Error ( `No b64_json was returned for prompt: ${ REDPLOY_MOONBEAM_IMAGE_PROMPT } ` ) ;
205+ const error = new Error ( `No b64_json was returned for prompt: ${ REDPLOY_MOONBEAM_IMAGE_PROMPT } ` ) ;
206+ logError ( this . aiServiceLogger , 'Gemini redeploy image generation returned no image data' , error , {
207+ prompt : REDPLOY_MOONBEAM_IMAGE_PROMPT ,
208+ } ) ;
209+ throw error ;
196210 }
197211 } ) ;
198212
@@ -217,7 +231,7 @@ export class AIService {
217231 void this . webService . sendMessage ( '#muzzlefeedback' , 'Moonbeam has been deployed.' , blocks ) ;
218232 } )
219233 . catch ( ( e ) => {
220- this . aiServiceLogger . error ( e ) ;
234+ logError ( this . aiServiceLogger , 'Failed to redeploy Moonbeam assets' , e ) ;
221235 } ) ;
222236 }
223237
@@ -274,15 +288,26 @@ export class AIService {
274288 if ( x ) {
275289 return this . writeToDiskAndReturnUrl ( x ) ;
276290 } else {
277- this . aiServiceLogger . error ( `No b64_json was returned for prompt: ${ text } ` ) ;
278- throw new Error ( `No b64_json was returned for prompt: ${ text } ` ) ;
291+ const error = new Error ( `No b64_json was returned for prompt: ${ text } ` ) ;
292+ logError ( this . aiServiceLogger , 'Gemini image generation returned no image data' , error , {
293+ userId,
294+ teamId,
295+ channelId : channel ,
296+ prompt : text ,
297+ } ) ;
298+ throw error ;
279299 }
280300 } )
281301 . then ( ( imageUrl ) => {
282302 this . sendImage ( imageUrl , userId , teamId , channel , text ) ;
283303 } )
284304 . catch ( async ( e ) => {
285- this . aiServiceLogger . error ( e ) ;
305+ logError ( this . aiServiceLogger , 'Failed to generate AI image response' , e , {
306+ userId,
307+ teamId,
308+ channelId : channel ,
309+ prompt : text ,
310+ } ) ;
286311 await this . redis . removeInflight ( userId , teamId ) ;
287312 await this . redis . decrementDailyRequests ( userId , teamId ) ;
288313 throw e ;
@@ -296,7 +321,9 @@ export class AIService {
296321 return extractAndParseOpenAiResponse ( x ) ;
297322 } )
298323 . catch ( async ( e ) => {
299- this . aiServiceLogger . error ( e ) ;
324+ logError ( this . aiServiceLogger , 'Failed to generate corpo-speak response' , e , {
325+ prompt : text ,
326+ } ) ;
300327 throw e ;
301328 } ) ;
302329 }
@@ -370,15 +397,25 @@ export class AIService {
370397 } ) ;
371398
372399 this . webService . sendMessage ( request . channel_id , request . text , blocks ) . catch ( ( e ) => {
373- this . aiServiceLogger . error ( e ) ;
400+ logError ( this . aiServiceLogger , 'Failed to send prompt-with-history response to Slack' , e , {
401+ userId : request . user_id ,
402+ teamId : request . team_id ,
403+ channelId : request . channel_id ,
404+ prompt : request . text ,
405+ } ) ;
374406 void this . webService . sendMessage (
375407 request . user_id ,
376408 'Sorry, unable to send the requested text to Slack. You have been credited for your Moon Token. Perhaps you were trying to send in a private channel? If so, invite @MoonBeam and try again.' ,
377409 ) ;
378410 } ) ;
379411 } )
380412 . catch ( async ( e ) => {
381- this . aiServiceLogger . error ( e ) ;
413+ logError ( this . aiServiceLogger , 'Failed to process prompt with history' , e , {
414+ userId : request . user_id ,
415+ teamId : request . team_id ,
416+ channelId : request . channel_id ,
417+ prompt : request . text ,
418+ } ) ;
382419 await this . redis . removeInflight ( user_id , team_id ) ;
383420 await this . redis . decrementDailyRequests ( user_id , team_id ) ;
384421 throw e ;
@@ -420,7 +457,12 @@ export class AIService {
420457 this . webService
421458 . sendMessage ( channelId , result , [ { type : 'markdown' , text : result } ] )
422459 . then ( ( ) => this . redis . setHasParticipated ( teamId , channelId ) )
423- . catch ( ( e ) => this . aiServiceLogger . error ( 'Error sending AI Participation message:' , e ) ) ;
460+ . catch ( ( e ) =>
461+ logError ( this . aiServiceLogger , 'Failed to send AI participation message' , e , {
462+ teamId,
463+ channelId,
464+ } ) ,
465+ ) ;
424466
425467 // Fire-and-forget: extract memories from this conversation
426468 this . extractMemories ( teamId , channelId , history , result , participantSlackIds ) . catch ( ( e ) =>
@@ -429,7 +471,11 @@ export class AIService {
429471 }
430472 } )
431473 . catch ( async ( e ) => {
432- this . aiServiceLogger . error ( e ) ;
474+ logError ( this . aiServiceLogger , 'Failed to generate AI participation response' , e , {
475+ teamId,
476+ channelId,
477+ taggedMessage,
478+ } ) ;
433479 throw e ;
434480 } )
435481 . finally ( ( ) => {
@@ -655,7 +701,12 @@ export class AIService {
655701 } ,
656702 ] ;
657703 this . webService . sendMessage ( channel , text , blocks ) . catch ( ( e ) => {
658- this . aiServiceLogger . error ( e ) ;
704+ logError ( this . aiServiceLogger , 'Failed to send generated AI image to Slack' , e , {
705+ userId,
706+ teamId,
707+ channelId : channel ,
708+ prompt : text ,
709+ } ) ;
659710 void this . decrementDaiyRequests ( userId , teamId ) ;
660711 void this . webService . sendMessage (
661712 userId ,
@@ -686,7 +737,12 @@ export class AIService {
686737 } ) ;
687738
688739 this . webService . sendMessage ( channelId , text , blocks ) . catch ( ( e ) => {
689- this . aiServiceLogger . error ( e ) ;
740+ logError ( this . aiServiceLogger , 'Failed to send generated AI text to Slack' , e , {
741+ userId,
742+ teamId,
743+ channelId,
744+ prompt : query ,
745+ } ) ;
690746 void this . decrementDaiyRequests ( userId , teamId ) ;
691747 void this . webService . sendMessage (
692748 userId ,
0 commit comments