@microsoft/agents-hosting
Version:
Microsoft 365 Agents SDK for JavaScript
152 lines (127 loc) • 4.93 kB
text/typescript
import { TurnContext } from '../turnContext'
import { ResourceResponse } from '../connector-client'
import { Middleware } from '../middlewareSet'
import { TranscriptLogger } from './transcriptLogger'
import { Activity, ActivityEventNames, ActivityTypes, ConversationReference, RoleTypes } from '@microsoft/agents-activity'
import { debug } from '@microsoft/agents-activity/logger'
const appLogger = debug('agents:rest-client')
/**
* Middleware for logging agent conversations to a transcript logger.
*/
export class TranscriptLoggerMiddleware implements Middleware {
private logger: TranscriptLogger
/**
* Creates a new instance of the TranscriptLoggerMiddleware class.
* @param logger The transcript logger to use.
* @throws Will throw an error if the logger is not provided.
*/
constructor (logger: TranscriptLogger) {
if (!logger) {
throw new Error('TranscriptLoggerMiddleware requires a TranscriptLogger instance.')
}
this.logger = logger
}
/**
* Called each time the agent processes a turn.
* @param context The context object for the turn.
* @param next The next middleware or handler to call.
*/
async onTurn (context: TurnContext, next: () => Promise<void>): Promise<void> {
const transcript: Activity[] = []
if (context.activity && context.activity.from) {
if (!context.activity.from.role) {
context.activity.from.role = RoleTypes.User
}
this.logActivity(transcript, this.cloneActivity(context.activity))
}
context.onSendActivities(
async (ctx: TurnContext, activities: Partial<Activity>[], next: () => Promise<ResourceResponse[]>) => {
const responses = await next()
activities.forEach((activity, index) => {
const clonedActivity = this.cloneActivity(activity)
clonedActivity.id = responses && responses[index] ? responses[index].id : clonedActivity.id
if (!clonedActivity.id) {
const prefix = `g_${Math.random().toString(36).slice(2, 8)}`
if (clonedActivity.timestamp) {
clonedActivity.id = `${prefix}${new Date(clonedActivity.timestamp).getTime().toString()}`
} else {
clonedActivity.id = `${prefix}${new Date().getTime().toString()}`
}
}
this.logActivity(transcript, clonedActivity)
})
return responses
}
)
context.onUpdateActivity(async (ctx: TurnContext, activity: Partial<Activity>, next: () => Promise<void>) => {
const response: void = await next()
const updateActivity = this.cloneActivity(activity)
updateActivity.type = ActivityTypes.MessageUpdate
this.logActivity(transcript, updateActivity)
return response
})
context.onDeleteActivity(
async (ctx: TurnContext, reference: Partial<ConversationReference>, next: () => Promise<void>) => {
await next()
// const deleteActivity = TurnContext.applyConversationReference(
// {
// type: ActivityTypes.MessageDelete,
// id: reference.activityId,
// },
// reference,
// false,
// );
// this.logActivity(transcript, this.cloneActivity(deleteActivity));
}
)
await next()
while (transcript.length) {
try {
const activity = transcript.shift()
if (activity) {
const maybePromise = this.logger.logActivity(activity)
if (maybePromise instanceof Promise) {
maybePromise.catch((err) => {
this.transcriptLoggerErrorHandler(err)
})
}
}
} catch (err) {
this.transcriptLoggerErrorHandler(err)
}
}
}
/**
* Logs an activity to the transcript.
* @param transcript The transcript array to log the activity to.
* @param activity The activity to log.
*/
private logActivity (transcript: Activity[], activity: Activity): void {
if (!activity.timestamp) {
activity.timestamp = new Date()
}
if (!(activity.type === ActivityTypes.Event && activity.name === ActivityEventNames.ContinueConversation)) {
transcript.push(activity)
}
}
/**
* Creates a deep copy of an activity.
* @param activity The activity to clone.
* @returns A deep copy of the activity.
*/
private cloneActivity (activity: Partial<Activity>): Activity {
return Object.assign(<Activity>{}, activity)
}
/**
* Handles errors that occur during logging.
* @param err The error that occurred.
*/
private transcriptLoggerErrorHandler (err: Error | any): void {
if (err instanceof Error) {
appLogger.error(`TranscriptLoggerMiddleware logActivity failed: "${err.message}"`)
appLogger.error(JSON.stringify(err.stack))
} else {
appLogger.error(`TranscriptLoggerMiddleware logActivity failed: "${JSON.stringify(err)}"`)
}
}
}