UNPKG

@microsoft/agents-hosting

Version:

Microsoft 365 Agents SDK for JavaScript

362 lines 14.2 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.TurnContext = exports.AgentCallbackHandlerKey = void 0; /** * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. */ const activityHandler_1 = require("./activityHandler"); const agents_activity_1 = require("@microsoft/agents-activity"); const turnContextStateCollection_1 = require("./turnContextStateCollection"); const streamingResponse_1 = require("./app/streaming/streamingResponse"); /** * Key for the agent callback handler in TurnState collection. */ exports.AgentCallbackHandlerKey = 'agentCallbackHandler'; /** * Represents the context for a single turn in a conversation between a user and an agent. * * @remarks * TurnContext is a central concept in the Agents framework - it contains: * - The incoming activity that started the turn * - Access to the adapter that can be used to send responses * - A state collection for storing information during the turn * - Methods for sending, updating, and deleting activities * - Middleware hooks for intercepting activity operations * * The TurnContext object is created by the adapter when an activity is received * and is passed to the agent's logic to process the turn. It maintains information * about the conversation and provides methods to send responses. * * This class follows the builder pattern for registering middleware handlers. */ class TurnContext { constructor(adapterOrContext, request) { this._respondedRef = { responded: false }; this._turnState = new turnContextStateCollection_1.TurnContextStateCollection(); this._onSendActivities = []; this._onUpdateActivity = []; this._onDeleteActivity = []; this._turn = 'turn'; this._locale = 'locale'; /** * A list of reply activities that are buffered until the end of the turn. * * This is primarily used with the 'expectReplies' delivery mode where all * activities during a turn are collected and returned as a single response. */ this.bufferedReplyActivities = []; if (adapterOrContext instanceof TurnContext) { adapterOrContext.copyTo(this); } else { this._adapter = adapterOrContext; this._activity = request; } this._streamingResponse = new streamingResponse_1.StreamingResponse(this); } /** * Sends a trace activity for debugging purposes. * * Trace activities are typically used for debugging and are only visible in * channels that support them, like the Bot Framework Emulator. * * @param name The name/category of the trace * @param value The value/data to include in the trace * @param valueType Optional type name for the value * @param label Optional descriptive label for the trace * @returns A promise that resolves to the resource response or undefined */ async sendTraceActivity(name, value, valueType, label) { const traceActivityObj = { type: agents_activity_1.ActivityTypes.Trace, timestamp: new Date().toISOString(), name, value, valueType, label }; const traceActivity = agents_activity_1.Activity.fromObject(traceActivityObj); return await this.sendActivity(traceActivity); } /** * Sends an activity to the sender of the incoming activity. * * This is the primary method used to respond to the user. It automatically * addresses the response to the correct conversation and recipient using * information from the incoming activity. * * @param activityOrText The activity to send or a string for a simple message * @param speak Optional text to be spoken by the agent * @param inputHint Optional input hint to indicate if the agent is expecting input * @returns A promise that resolves to the resource response or undefined */ async sendActivity(activityOrText, speak, inputHint) { let activityObject; if (typeof activityOrText === 'string') { activityObject = { type: agents_activity_1.ActivityTypes.Message, text: activityOrText, inputHint: inputHint || agents_activity_1.InputHints.AcceptingInput }; if (speak) { activityObject = { ...activityObject, speak }; } } else { activityObject = activityOrText; } const activity = agents_activity_1.Activity.fromObject(activityObject); const responses = (await this.sendActivities([activity])) || []; return responses[0]; } /** * Sends multiple activities to the sender of the incoming activity. * * This method applies conversation references to each activity and * emits them through the middleware chain before sending them to * the adapter. * * @param activities The array of activities to send * @returns A promise that resolves to an array of resource responses */ async sendActivities(activities) { let sentNonTraceActivity = false; const ref = this.activity.getConversationReference(); const output = activities.map((activity) => { const result = activity.applyConversationReference(ref); if (!result.type) { result.type = agents_activity_1.ActivityTypes.Message; } if (result.type === agents_activity_1.ActivityTypes.InvokeResponse) { this.turnState.set(activityHandler_1.INVOKE_RESPONSE_KEY, activity); } if (result.type !== agents_activity_1.ActivityTypes.Trace) { sentNonTraceActivity = true; } if (result.id) { delete result.id; } return result; }); return await this.emit(this._onSendActivities, output, async () => { if (this.activity.deliveryMode === agents_activity_1.DeliveryModes.ExpectReplies) { const responses = []; output.forEach((a) => { this.bufferedReplyActivities.push(a); if (a.type === agents_activity_1.ActivityTypes.InvokeResponse) { this.turnState.set(activityHandler_1.INVOKE_RESPONSE_KEY, a); } responses.push({ id: '' }); }); if (sentNonTraceActivity) { this.responded = true; } return responses; } else { const responses = await this.adapter.sendActivities(this, output); for (let index = 0; index < (responses === null || responses === void 0 ? void 0 : responses.length); index++) { const activity = output[index]; activity.id = responses[index].id; } if (sentNonTraceActivity) { this.responded = true; } return responses; } }); } /** * Updates an existing activity in the conversation. * * This can be used to edit previously sent activities, for example to * update the content of an adaptive card or change a message. * * @param activity The activity to update with its ID specified * @returns A promise that resolves when the activity has been updated */ async updateActivity(activity) { const ref = this.activity.getConversationReference(); const a = activity.applyConversationReference(ref); return await this.emit(this._onUpdateActivity, a, async () => await this.adapter.updateActivity(this, a).then(() => { })); } /** * Deletes an activity from the conversation. * * @param idOrReference The ID of the activity to delete or a conversation reference * @returns A promise that resolves when the activity has been deleted */ async deleteActivity(idOrReference) { let reference; if (typeof idOrReference === 'string') { reference = this.activity.getConversationReference(); reference.activityId = idOrReference; } else { reference = idOrReference; } return await this.emit(this._onDeleteActivity, reference, async () => await this.adapter.deleteActivity(this, reference)); } /** * Uploads an attachment to the conversation. * * @param conversationId The ID of the conversation * @param attachmentData The attachment data to upload * @returns A promise that resolves to the resource response */ async uploadAttachment(conversationId, attachmentData) { return await this.adapter.uploadAttachment(conversationId, attachmentData); } /** * Gets information about an attachment. * * @param attachmentId The ID of the attachment * @returns A promise that resolves to the attachment information */ async getAttachmentInfo(attachmentId) { return await this.adapter.getAttachmentInfo(attachmentId); } /** * Gets the content of an attachment. * * @param attachmentId The ID of the attachment * @param viewId The view to get * @returns A promise that resolves to a readable stream of the attachment content */ async getAttachment(attachmentId, viewId) { return await this.adapter.getAttachment(attachmentId, viewId); } /** * Registers a handler for intercepting and processing activities being sent. * * This method follows a middleware pattern, allowing multiple handlers to * be chained together. Handlers can modify activities or inject new ones. * * @param handler The handler to register * @returns The current TurnContext instance for chaining */ onSendActivities(handler) { this._onSendActivities.push(handler); return this; } /** * Registers a handler for intercepting activity updates. * * @param handler The handler to register * @returns The current TurnContext instance for chaining */ onUpdateActivity(handler) { this._onUpdateActivity.push(handler); return this; } /** * Registers a handler for intercepting activity deletions. * * @param handler The handler to register * @returns The current TurnContext instance for chaining */ onDeleteActivity(handler) { this._onDeleteActivity.push(handler); return this; } /** * Copies the properties of this TurnContext to another TurnContext. * * Used internally when cloning contexts. * * @param context The context to copy to * @protected */ copyTo(context) { ['_adapter', '_activity', '_respondedRef', '_services', '_onSendActivities', '_onUpdateActivity', '_onDeleteActivity'].forEach((prop) => (context[prop] = this[prop])); } /** * Gets the adapter that created this context. * * The adapter is responsible for sending and receiving activities * to and from the user's channel. */ get adapter() { return this._adapter; } /** * Gets the incoming activity that started this turn. * * This is the activity that was received from the user or channel * and triggered the creation of this context. */ get activity() { return this._activity; } /** * Gets or sets whether the turn has sent a response to the user. * * This is used to track whether the agent has responded to the user's * activity. Once set to true, it cannot be set back to false. */ get responded() { return this._respondedRef.responded; } set responded(value) { if (!value) { throw new Error("TurnContext: cannot set 'responded' to a value of 'false'."); } this._respondedRef.responded = true; } /** * Gets or sets the locale for the turn. * * The locale affects language-dependent operations like * formatting dates or numbers. */ get locale() { const turnObj = this._turnState.get(this._turn); if (turnObj && typeof turnObj[this._locale] === 'string') { return turnObj[this._locale]; } return undefined; } set locale(value) { let turnObj = this._turnState.get(this._turn); if (turnObj) { turnObj[this._locale] = value; } else { turnObj = { [this._locale]: value }; this._turnState.set(this._turn, turnObj); } } /** * Gets the turn state collection for storing data during the turn. * * The turn state collection provides a dictionary-like interface * for storing arbitrary data that needs to be accessible during * the processing of the current turn. */ get turnState() { return this._turnState; } get streamingResponse() { return this._streamingResponse; } /** * Emits events to registered middleware handlers. * * This internal method implements the middleware pattern, allowing * handlers to be chained together with each having the option to * short-circuit the chain. * * @param handlers Array of handlers to execute * @param arg The argument to pass to each handler * @param next The function to execute at the end of the middleware chain * @returns A promise that resolves to the result from the handlers or next function * @private */ async emit(handlers, arg, next) { const runHandlers = async ([handler, ...remaining]) => { try { return handler ? await handler(this, arg, async () => await runHandlers(remaining)) : await Promise.resolve(next()); } catch (err) { return await Promise.reject(err); } }; return await runHandlers(handlers); } } exports.TurnContext = TurnContext; //# sourceMappingURL=turnContext.js.map