Skip to content
Open
Show file tree
Hide file tree
Changes from 11 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 10 additions & 8 deletions packages/agents-activity/src/activity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ import { MessageReaction, messageReactionZodSchema } from './messageReaction'
import { TextFormatTypes, textFormatTypesZodSchema } from './textFormatTypes'
import { TextHighlight, textHighlightZodSchema } from './textHighlight'
import { RoleTypes } from './conversation/roleTypes'
import { Errors } from './errorHelper'
import { ExceptionHelper } from './exceptionHelper'

/**
* Zod schema for validating an Activity object.
Expand Down Expand Up @@ -316,13 +318,13 @@ export class Activity {
*/
constructor (t: ActivityTypes | string) {
if (t === undefined) {
throw new Error('Invalid ActivityType: undefined')
throw ExceptionHelper.generateException(Error, Errors.InvalidActivityTypeUndefined)
}
if (t === null) {
throw new Error('Invalid ActivityType: null')
throw ExceptionHelper.generateException(Error, Errors.InvalidActivityTypeNull)
}
if ((typeof t === 'string') && (t.length === 0)) {
throw new Error('Invalid ActivityType: empty string')
throw ExceptionHelper.generateException(Error, Errors.InvalidActivityTypeEmptyString)
}

this.type = t
Expand Down Expand Up @@ -382,7 +384,7 @@ export class Activity {

// if they passed in a value but the channel is blank, this is invalid
if (value && !channel) {
throw new Error(`Invalid channelId ${value}. Found subChannel but no main channel.`)
throw ExceptionHelper.generateException(Error, Errors.InvalidChannelIdSubChannelOnly, undefined, { channelId: value })
}
this._channelId = channel
if (subChannel) {
Expand Down Expand Up @@ -418,7 +420,7 @@ export class Activity {
*/
set channelIdSubChannel (value) {
if (!this._channelId) {
throw new Error('Primary channel must be set before setting subChannel')
throw ExceptionHelper.generateException(Error, Errors.PrimaryChannelNotSet)
}
this.channelId = `${this._channelId}${value ? `:${value}` : ''}`
}
Expand Down Expand Up @@ -467,13 +469,13 @@ export class Activity {
*/
public getConversationReference (): ConversationReference {
if (this.recipient === null || this.recipient === undefined) {
throw new Error('Activity Recipient undefined')
throw ExceptionHelper.generateException(Error, Errors.ActivityRecipientUndefined)
}
if (this.conversation === null || this.conversation === undefined) {
throw new Error('Activity Conversation undefined')
throw ExceptionHelper.generateException(Error, Errors.ActivityConversationUndefined)
}
if (this.channelId === null || this.channelId === undefined) {
throw new Error('Activity ChannelId undefined')
throw ExceptionHelper.generateException(Error, Errors.ActivityChannelIdUndefined)
}

return {
Expand Down
93 changes: 93 additions & 0 deletions packages/agents-activity/src/errorHelper.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

import { AgentErrorDefinition } from './exceptionHelper'

/**
* Error definitions for the Activity system.
* This contains localized error codes for the Activity subsystem of the AgentSDK.
*
* Each error definition includes an error code (starting from -110000), a description, and a help link
* pointing to an AKA link to get help for the given error.
*
* Usage example:
* ```
* throw ExceptionHelper.generateException(
* Error,
* Errors.InvalidActivityTypeUndefined
* );
* ```
*/
export const Errors: { [key: string]: AgentErrorDefinition } = {
/**
* Error thrown when ActivityType is undefined.
*/
InvalidActivityTypeUndefined: {
code: -110000,
description: 'Invalid ActivityType: undefined',
helplink: 'https://aka.ms/M365AgentsErrorCodes/#{errorCode}'
},

/**
* Error thrown when ActivityType is null.
*/
InvalidActivityTypeNull: {
code: -110001,
description: 'Invalid ActivityType: null',
helplink: 'https://aka.ms/M365AgentsErrorCodes/#{errorCode}'
},

/**
* Error thrown when ActivityType is an empty string.
*/
InvalidActivityTypeEmptyString: {
code: -110002,
description: 'Invalid ActivityType: empty string',
helplink: 'https://aka.ms/M365AgentsErrorCodes/#{errorCode}'
},

/**
* Error thrown when channelId contains a subChannel but no main channel.
*/
InvalidChannelIdSubChannelOnly: {
code: -110003,
description: 'Invalid channelId {channelId}. Found subChannel but no main channel.',
helplink: 'https://aka.ms/M365AgentsErrorCodes/#{errorCode}'
},

/**
* Error thrown when attempting to set subChannel before setting primary channel.
*/
PrimaryChannelNotSet: {
code: -110004,
description: 'Primary channel must be set before setting subChannel',
helplink: 'https://aka.ms/M365AgentsErrorCodes/#{errorCode}'
},

/**
* Error thrown when Activity Recipient is undefined.
*/
ActivityRecipientUndefined: {
code: -110005,
description: 'Activity Recipient undefined',
helplink: 'https://aka.ms/M365AgentsErrorCodes/#{errorCode}'
},

/**
* Error thrown when Activity Conversation is undefined.
*/
ActivityConversationUndefined: {
code: -110006,
description: 'Activity Conversation undefined',
helplink: 'https://aka.ms/M365AgentsErrorCodes/#{errorCode}'
},

/**
* Error thrown when Activity ChannelId is undefined.
*/
ActivityChannelIdUndefined: {
code: -110007,
description: 'Activity ChannelId undefined',
helplink: 'https://aka.ms/M365AgentsErrorCodes/#{errorCode}'
}
}
1 change: 1 addition & 0 deletions packages/agents-activity/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,3 +50,4 @@ export { ActivityTreatments } from './activityTreatments'
export { debug, Logger } from './logger'

export { AgentErrorDefinition, AgentError, ExceptionHelper } from './exceptionHelper'
export { Errors } from './errorHelper'
81 changes: 81 additions & 0 deletions packages/agents-activity/test/errorHelper.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
import assert from 'assert'
import { describe, it } from 'node:test'
import { AgentErrorDefinition } from '../src/exceptionHelper'
import { Errors } from '../src/errorHelper'

describe('Activity Errors tests', () => {
it('should have InvalidActivityTypeUndefined error definition', () => {
const error = Errors.InvalidActivityTypeUndefined

assert.strictEqual(error.code, -110000)
assert.strictEqual(error.description, 'Invalid ActivityType: undefined')
assert.strictEqual(error.helplink, 'https://aka.ms/M365AgentsErrorCodes/#{errorCode}')
})

it('should have InvalidActivityTypeNull error definition', () => {
const error = Errors.InvalidActivityTypeNull

assert.strictEqual(error.code, -110001)
assert.strictEqual(error.description, 'Invalid ActivityType: null')
assert.strictEqual(error.helplink, 'https://aka.ms/M365AgentsErrorCodes/#{errorCode}')
})

it('should have InvalidActivityTypeEmptyString error definition', () => {
const error = Errors.InvalidActivityTypeEmptyString

assert.strictEqual(error.code, -110002)
assert.strictEqual(error.description, 'Invalid ActivityType: empty string')
assert.strictEqual(error.helplink, 'https://aka.ms/M365AgentsErrorCodes/#{errorCode}')
})

it('should have all error codes in the correct range', () => {
const errorDefinitions = Object.values(Errors).filter(
val => val && typeof val === 'object' && 'code' in val && 'description' in val && 'helplink' in val
) as AgentErrorDefinition[]

// All error codes should be negative and in the range -110000 to -110007
errorDefinitions.forEach(errorDef => {
assert.ok(errorDef.code < 0, `Error code ${errorDef.code} should be negative`)
assert.ok(errorDef.code >= -110007, `Error code ${errorDef.code} should be >= -110007`)
assert.ok(errorDef.code <= -110000, `Error code ${errorDef.code} should be <= -110000`)
})
})

it('should have unique error codes', () => {
const errorDefinitions = Object.values(Errors).filter(
val => val && typeof val === 'object' && 'code' in val && 'description' in val && 'helplink' in val
) as AgentErrorDefinition[]

const codes = errorDefinitions.map(e => e.code)
const uniqueCodes = new Set(codes)

assert.strictEqual(codes.length, uniqueCodes.size, 'All error codes should be unique')
})

it('should have help links with tokenized format', () => {
const errorDefinitions = Object.values(Errors).filter(
val => val && typeof val === 'object' && 'code' in val && 'description' in val && 'helplink' in val
) as AgentErrorDefinition[]

errorDefinitions.forEach(errorDef => {
assert.ok(
errorDef.helplink.includes('{errorCode}'),
`Help link should contain {errorCode} token: ${errorDef.helplink}`
)
assert.ok(
errorDef.helplink.startsWith('https://aka.ms/M365AgentsErrorCodes/#'),
`Help link should start with correct URL: ${errorDef.helplink}`
)
})
})

it('should have non-empty descriptions', () => {
const errorDefinitions = Object.values(Errors).filter(
val => val && typeof val === 'object' && 'code' in val && 'description' in val && 'helplink' in val
) as AgentErrorDefinition[]

errorDefinitions.forEach(errorDef => {
assert.ok(errorDef.description.length > 0, 'Description should not be empty')
})
})
})
Original file line number Diff line number Diff line change
Expand Up @@ -362,7 +362,7 @@ export class CosmosDbPartitionedStorage implements Storage {
errorObj,
{
maxDepth: maxDepthAllowed.toString(),
additionalMessage: additionalMessage
additionalMessage
}
)
} else if (obj && typeof obj === 'object') {
Expand Down
9 changes: 5 additions & 4 deletions packages/agents-hosting/src/activityHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
*/
import { debug } from '@microsoft/agents-activity/logger'
import { TurnContext } from './turnContext'
import { Activity, ActivityTypes, Channels } from '@microsoft/agents-activity'
import { Activity, ActivityTypes, Channels, ExceptionHelper } from '@microsoft/agents-activity'
import { StatusCodes } from './statusCodes'
import { InvokeResponse } from './invoke/invokeResponse'
import { InvokeException } from './invoke/invokeException'
Expand All @@ -13,6 +13,7 @@ import { SearchInvokeValue } from './invoke/searchInvokeValue'
import { SearchInvokeResponse } from './invoke/searchInvokeResponse'
import { AdaptiveCardInvokeResponse } from './invoke/adaptiveCardInvokeResponse'
import { tokenResponseEventName } from './tokenResponseEventName'
import { Errors } from './errorHelper'

/** Symbol key for invoke response */
export const INVOKE_RESPONSE_KEY = Symbol('invokeResponse')
Expand Down Expand Up @@ -254,9 +255,9 @@ export class ActivityHandler {
* @throws Error if context is missing, activity is missing, or activity type is missing
*/
async run (context: TurnContext): Promise<void> {
if (!context) throw new Error('Missing TurnContext parameter')
if (!context.activity) throw new Error('TurnContext does not include an activity')
if (!context.activity.type) throw new Error('Activity is missing its type')
if (!context) throw ExceptionHelper.generateException(Error, Errors.MissingTurnContext)
if (!context.activity) throw ExceptionHelper.generateException(Error, Errors.TurnContextMissingActivity)
if (!context.activity.type) throw ExceptionHelper.generateException(Error, Errors.ActivityMissingType)

await this.onTurnActivity(context)
}
Expand Down
9 changes: 5 additions & 4 deletions packages/agents-hosting/src/agent-client/agentClient.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import { AuthConfiguration, MsalTokenProvider } from '../auth'
import { Activity, ConversationReference, RoleTypes } from '@microsoft/agents-activity'
import { Activity, ConversationReference, RoleTypes, ExceptionHelper } from '@microsoft/agents-activity'
import { v4 } from 'uuid'
import { debug } from '@microsoft/agents-activity/logger'
import { ConversationState } from '../state'
import { TurnContext } from '../turnContext'
import { Errors } from '../errorHelper'

const logger = debug('agents:agent-client')

Expand Down Expand Up @@ -115,7 +116,7 @@ export class AgentClient {
})
if (!response.ok) {
await conversationDataAccessor.delete(context, { channelId: activityCopy.channelId!, conversationId: activityCopy.conversation!.id })
throw new Error(`Failed to post activity to agent: ${response.statusText}`)
throw ExceptionHelper.generateException(Error, Errors.FailedToPostActivityToAgent, undefined, { statusText: response.statusText })
}
return response.statusText
}
Expand All @@ -139,10 +140,10 @@ export class AgentClient {
serviceUrl: process.env[`${agentName}_serviceUrl`]!
}
} else {
throw new Error(`Missing agent client config for agent ${agentName}`)
throw ExceptionHelper.generateException(Error, Errors.MissingAgentClientConfig, undefined, { agentName })
}
} else {
throw new Error('Agent name is required')
throw ExceptionHelper.generateException(Error, Errors.AgentNameRequired)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,9 @@
* Licensed under the MIT License.
*/

import { ExceptionHelper, activityZodSchema, AdaptiveCardInvokeAction, adaptiveCardInvokeActionZodSchema } from '@microsoft/agents-activity'
import { Errors } from '../../errorHelper'
import { z } from 'zod'
import { activityZodSchema, AdaptiveCardInvokeAction, adaptiveCardInvokeActionZodSchema } from '@microsoft/agents-activity'
// import { MessagingExtensionQuery, messagingExtensionQueryZodSchema } from '../messageExtension/messagingExtensionQuery'
import { adaptiveCardsSearchParamsZodSchema } from './adaptiveCardsSearchParams'

Expand Down Expand Up @@ -76,7 +77,7 @@ export function parseValueActionExecuteSelector (value: unknown): ValueAction |
})
const safeParsedValue = actionValueExecuteSelector.passthrough().safeParse(value)
if (!safeParsedValue.success) {
throw new Error(`Invalid action value: ${safeParsedValue.error}`)
throw ExceptionHelper.generateException(Error, Errors.InvalidActionValue, undefined, { error: JSON.stringify(safeParsedValue.error) })
}
const parsedValue = safeParsedValue.data
return {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,9 @@
* Licensed under the MIT License.
*/

import { Activity, ActivityTypes } from '@microsoft/agents-activity'
import { Activity, ActivityTypes, ExceptionHelper } from '@microsoft/agents-activity'
import { AdaptiveCardInvokeResponse, AgentApplication, CardFactory, INVOKE_RESPONSE_KEY, InvokeResponse, MessageFactory, RouteSelector, TurnContext, TurnState } from '../../'
import { Errors } from '../../errorHelper'
import { AdaptiveCardActionExecuteResponseType } from './adaptiveCardActionExecuteResponseType'
import { parseAdaptiveCardInvokeAction, parseValueActionExecuteSelector, parseValueDataset, parseValueSearchQuery } from './activityValueParsers'
import { AdaptiveCardsSearchParams } from './adaptiveCardsSearchParams'
Expand Down Expand Up @@ -117,7 +118,7 @@ export class AdaptiveCardsActions<TState extends TurnState> {
a?.name !== ACTION_INVOKE_NAME ||
(invokeAction?.action.type !== ACTION_EXECUTE_TYPE)
) {
throw new Error(`Unexpected AdaptiveCards.actionExecute() triggered for activity type: ${invokeAction?.action.type}`
throw ExceptionHelper.generateException(Error, Errors.UnexpectedActionExecute, undefined, { activityType: invokeAction?.action.type || 'unknown' }
)
}

Expand Down Expand Up @@ -196,7 +197,7 @@ export class AdaptiveCardsActions<TState extends TurnState> {
this._app.addRoute(selector, async (context, state) => {
const a = context?.activity
if (a?.type !== ActivityTypes.Message || a?.text || typeof a?.value !== 'object') {
throw new Error(`Unexpected AdaptiveCards.actionSubmit() triggered for activity type: ${a?.type}`)
throw ExceptionHelper.generateException(Error, Errors.UnexpectedActionSubmit, undefined, { activityType: a?.type })
}

await handler(context, state as TState, (parseAdaptiveCardInvokeAction(a.value)) as TData ?? {} as TData)
Expand Down Expand Up @@ -227,7 +228,7 @@ export class AdaptiveCardsActions<TState extends TurnState> {
async (context, state) => {
const a = context?.activity
if (a?.type !== 'invoke' || a?.name !== SEARCH_INVOKE_NAME) {
throw new Error(`Unexpected AdaptiveCards.search() triggered for activity type: ${a?.type}`)
throw ExceptionHelper.generateException(Error, Errors.UnexpectedSearch, undefined, { activityType: a?.type })
}

const parsedQuery = parseValueSearchQuery(a.value)
Expand Down
Loading