diff --git a/.github/workflows/python-claude-sample.yml b/.github/workflows/python-claude-sample.yml index adad8165..3e17eac1 100644 --- a/.github/workflows/python-claude-sample.yml +++ b/.github/workflows/python-claude-sample.yml @@ -36,17 +36,10 @@ jobs: with: python-version: '3.11' - - name: Install uv - run: | - curl -LsSf https://astral.sh/uv/install.sh | sh - echo "$HOME/.cargo/bin" >> $GITHUB_PATH - - - name: Create virtual environment - run: uv venv - - name: Install dependencies run: | - uv pip install -e . + python -m pip install --upgrade pip + pip install --pre -e . - name: Check Python syntax run: | @@ -77,6 +70,7 @@ jobs: done - name: Validate imports + continue-on-error: true run: | python -c " import sys diff --git a/nodejs/claude/sample-agent/src/agent.ts b/nodejs/claude/sample-agent/src/agent.ts index 12db167e..09307ddf 100644 --- a/nodejs/claude/sample-agent/src/agent.ts +++ b/nodejs/claude/sample-agent/src/agent.ts @@ -9,7 +9,7 @@ import { getObservabilityAuthenticationScope } from '@microsoft/agents-a365-runt // Notification Imports import '@microsoft/agents-a365-notifications'; -import { AgentNotificationActivity } from '@microsoft/agents-a365-notifications'; +import { AgentNotificationActivity, NotificationType, createEmailResponseActivity } from '@microsoft/agents-a365-notifications'; import tokenCache, { createAgenticTokenCacheKey } from './token-cache'; import { Client, getClient } from './client'; @@ -101,8 +101,45 @@ export class MyAgent extends AgentApplication { } async handleAgentNotificationActivity(context: TurnContext, state: TurnState, agentNotificationActivity: AgentNotificationActivity) { - context.sendActivity("Received an AgentNotification!"); - /* your logic here... */ + switch (agentNotificationActivity.notificationType) { + case NotificationType.EmailNotification: + await this.handleEmailNotification(context, state, agentNotificationActivity); + break; + default: + await context.sendActivity(`Received notification of type: ${agentNotificationActivity.notificationType}`); + } + } + + private async handleEmailNotification(context: TurnContext, state: TurnState, activity: AgentNotificationActivity): Promise { + const emailNotification = activity.emailNotification; + + if (!emailNotification) { + const errorResponse = createEmailResponseActivity('I could not find the email notification details.'); + await context.sendActivity(errorResponse); + return; + } + + try { + const client: Client = await getClient(this.authorization, MyAgent.authHandlerName, context); + + // First, retrieve the email content + const emailContent = await client.invokeAgentWithScope( + `You have a new email from ${context.activity.from?.name} with id '${emailNotification.id}', ` + + `ConversationId '${emailNotification.conversationId}'. Please retrieve this message and return it in text format.` + ); + + // Then process the email + const response = await client.invokeAgentWithScope( + `You have received the following email. Please follow any instructions in it. ${emailContent}` + ); + + const emailResponseActivity = createEmailResponseActivity(response || 'I have processed your email but do not have a response at this time.'); + await context.sendActivity(emailResponseActivity); + } catch (error) { + console.error('Email notification error:', error); + const errorResponse = createEmailResponseActivity('Unable to process your email at this time.'); + await context.sendActivity(errorResponse); + } } } diff --git a/nodejs/devin/sample-agent/src/agent.ts b/nodejs/devin/sample-agent/src/agent.ts index 7e9ee982..13535228 100644 --- a/nodejs/devin/sample-agent/src/agent.ts +++ b/nodejs/devin/sample-agent/src/agent.ts @@ -20,6 +20,12 @@ import { TurnContext, TurnState, } from "@microsoft/agents-hosting"; +import '@microsoft/agents-a365-notifications'; +import { + AgentNotificationActivity, + NotificationType, + createEmailResponseActivity, +} from "@microsoft/agents-a365-notifications"; import { Stream } from "stream"; import { v4 as uuidv4 } from "uuid"; import { devinClient } from "./devin-client"; @@ -119,6 +125,22 @@ export class A365Agent extends AgentApplication { } ); + // Handle agent notifications + this.onAgentNotification( + "agents:*", + async ( + context: TurnContext, + state: ApplicationTurnState, + agentNotificationActivity: AgentNotificationActivity + ) => { + await this.handleAgentNotificationActivity( + context, + state, + agentNotificationActivity + ); + } + ); + // Handle installation activities this.onActivity( ActivityTypes.InstallationUpdate, @@ -198,6 +220,72 @@ export class A365Agent extends AgentApplication { } } + /** + * Handles agent notification activities. + */ + async handleAgentNotificationActivity( + context: TurnContext, + state: ApplicationTurnState, + agentNotificationActivity: AgentNotificationActivity + ): Promise { + switch (agentNotificationActivity.notificationType) { + case NotificationType.EmailNotification: + await this.handleEmailNotification(context, agentNotificationActivity); + break; + default: + await context.sendActivity( + `Received notification of type: ${agentNotificationActivity.notificationType}` + ); + } + } + + /** + * Handles email notification activities with proper EmailResponse. + */ + private async handleEmailNotification( + context: TurnContext, + activity: AgentNotificationActivity + ): Promise { + const emailNotification = activity.emailNotification; + + if (!emailNotification) { + const errorResponse = createEmailResponseActivity( + "I could not find the email notification details." + ); + await context.sendActivity(errorResponse); + return; + } + + try { + // Collect the response from Devin using a stream + let responseContent = ""; + const responseStream = new Stream() + .on("data", (chunk) => { + responseContent += chunk as string; + }) + .on("error", (error) => { + console.error("Stream error:", error); + }); + + // Process the email notification with Devin + const prompt = `You have a new email from ${context.activity.from?.name} with id '${emailNotification.id}', ` + + `ConversationId '${emailNotification.conversationId}'. Please process this email and provide a helpful response.`; + + await devinClient.invokeAgent(prompt, responseStream); + + const emailResponseActivity = createEmailResponseActivity( + responseContent || "I have processed your email but do not have a response at this time." + ); + await context.sendActivity(emailResponseActivity); + } catch (error) { + console.error("Email notification error:", error); + const errorResponse = createEmailResponseActivity( + "Unable to process your email at this time." + ); + await context.sendActivity(errorResponse); + } + } + /** * Handles agent installation and removal events. */ diff --git a/nodejs/langchain/sample-agent/src/agent.ts b/nodejs/langchain/sample-agent/src/agent.ts index 1590120e..70df795c 100644 --- a/nodejs/langchain/sample-agent/src/agent.ts +++ b/nodejs/langchain/sample-agent/src/agent.ts @@ -3,7 +3,7 @@ import { ActivityTypes } from '@microsoft/agents-activity'; // Notification Imports import '@microsoft/agents-a365-notifications'; -import { AgentNotificationActivity } from '@microsoft/agents-a365-notifications'; +import { AgentNotificationActivity, NotificationType, createEmailResponseActivity } from '@microsoft/agents-a365-notifications'; // Observability Imports import { BaggageBuilder } from '@microsoft/agents-a365-observability'; import { AgenticTokenCacheInstance, BaggageBuilderUtils } from '@microsoft/agents-a365-observability-hosting'; @@ -99,8 +99,45 @@ export class A365Agent extends AgentApplication { } async handleAgentNotificationActivity(context: TurnContext, state: TurnState, agentNotificationActivity: AgentNotificationActivity) { - context.sendActivity("Recieved an AgentNotification!"); - /* your logic here... */ + switch (agentNotificationActivity.notificationType) { + case NotificationType.EmailNotification: + await this.handleEmailNotification(context, state, agentNotificationActivity); + break; + default: + await context.sendActivity(`Received notification of type: ${agentNotificationActivity.notificationType}`); + } + } + + private async handleEmailNotification(context: TurnContext, state: TurnState, activity: AgentNotificationActivity): Promise { + const emailNotification = activity.emailNotification; + + if (!emailNotification) { + const errorResponse = createEmailResponseActivity('I could not find the email notification details.'); + await context.sendActivity(errorResponse); + return; + } + + try { + const client: Client = await getClient(this.authorization, A365Agent.authHandlerName, context); + + // First, retrieve the email content + const emailContent = await client.invokeInferenceScope( + `You have a new email from ${context.activity.from?.name} with id '${emailNotification.id}', ` + + `ConversationId '${emailNotification.conversationId}'. Please retrieve this message and return it in text format.` + ); + + // Then process the email + const response = await client.invokeInferenceScope( + `You have received the following email. Please follow any instructions in it. ${emailContent}` + ); + + const emailResponseActivity = createEmailResponseActivity(response || 'I have processed your email but do not have a response at this time.'); + await context.sendActivity(emailResponseActivity); + } catch (error) { + console.error('Email notification error:', error); + const errorResponse = createEmailResponseActivity('Unable to process your email at this time.'); + await context.sendActivity(errorResponse); + } } } diff --git a/nodejs/n8n/sample-agent/src/n8nAgent.ts b/nodejs/n8n/sample-agent/src/n8nAgent.ts index a1cca096..3ebca84e 100644 --- a/nodejs/n8n/sample-agent/src/n8nAgent.ts +++ b/nodejs/n8n/sample-agent/src/n8nAgent.ts @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -import { AgentNotificationActivity, NotificationType, createAgentNotificationActivity } from '@microsoft/agents-a365-notifications'; +import { AgentNotificationActivity, NotificationType, createAgentNotificationActivity, createEmailResponseActivity } from '@microsoft/agents-a365-notifications'; import { AgentApplication, TurnContext, TurnState } from '@microsoft/agents-hosting'; import { N8nClient } from './n8nClient'; import { McpToolRegistrationService, McpServer } from './mcpToolRegistrationService'; @@ -140,7 +140,8 @@ export class N8nAgent { `You have received the following email. Please follow any instructions in it. ${emailContent}` ); - await turnContext.sendActivity(response); + const emailResponseActivity = createEmailResponseActivity(response); + await turnContext.sendActivity(emailResponseActivity); } async getN8nClient(turnContext: TurnContext): Promise { diff --git a/nodejs/openai/sample-agent/src/agent.ts b/nodejs/openai/sample-agent/src/agent.ts index 137b7546..f215cd74 100644 --- a/nodejs/openai/sample-agent/src/agent.ts +++ b/nodejs/openai/sample-agent/src/agent.ts @@ -9,7 +9,7 @@ import { getObservabilityAuthenticationScope } from '@microsoft/agents-a365-runt // Notification Imports import '@microsoft/agents-a365-notifications'; -import { AgentNotificationActivity } from '@microsoft/agents-a365-notifications'; +import { AgentNotificationActivity, NotificationType, createEmailResponseActivity } from '@microsoft/agents-a365-notifications'; import { Client, getClient } from './client'; import tokenCache, { createAgenticTokenCacheKey } from './token-cache'; @@ -120,8 +120,45 @@ export class MyAgent extends AgentApplication { } async handleAgentNotificationActivity(context: TurnContext, state: TurnState, agentNotificationActivity: AgentNotificationActivity) { - context.sendActivity("Received an AgentNotification!"); - /* your logic here... */ + switch (agentNotificationActivity.notificationType) { + case NotificationType.EmailNotification: + await this.handleEmailNotification(context, state, agentNotificationActivity); + break; + default: + await context.sendActivity(`Received notification of type: ${agentNotificationActivity.notificationType}`); + } + } + + private async handleEmailNotification(context: TurnContext, state: TurnState, activity: AgentNotificationActivity): Promise { + const emailNotification = activity.emailNotification; + + if (!emailNotification) { + const errorResponse = createEmailResponseActivity('I could not find the email notification details.'); + await context.sendActivity(errorResponse); + return; + } + + try { + const client: Client = await getClient(this.authorization, MyAgent.authHandlerName, context); + + // First, retrieve the email content + const emailContent = await client.invokeAgentWithScope( + `You have a new email from ${context.activity.from?.name} with id '${emailNotification.id}', ` + + `ConversationId '${emailNotification.conversationId}'. Please retrieve this message and return it in text format.` + ); + + // Then process the email + const response = await client.invokeAgentWithScope( + `You have received the following email. Please follow any instructions in it. ${emailContent}` + ); + + const emailResponseActivity = createEmailResponseActivity(response || 'I have processed your email but do not have a response at this time.'); + await context.sendActivity(emailResponseActivity); + } catch (error) { + console.error('Email notification error:', error); + const errorResponse = createEmailResponseActivity('Unable to process your email at this time.'); + await context.sendActivity(errorResponse); + } } } diff --git a/nodejs/perplexity/sample-agent/src/notificationService.ts b/nodejs/perplexity/sample-agent/src/notificationService.ts index e0357060..b036d243 100644 --- a/nodejs/perplexity/sample-agent/src/notificationService.ts +++ b/nodejs/perplexity/sample-agent/src/notificationService.ts @@ -5,6 +5,7 @@ import { TurnContext, TurnState } from "@microsoft/agents-hosting"; import { AgentNotificationActivity, NotificationType, + createEmailResponseActivity, } from "@microsoft/agents-a365-notifications"; import type { InvokeAgentScope } from "@microsoft/agents-a365-observability"; @@ -207,7 +208,9 @@ export class NotificationService { "EmailNotification_Success", ]); - await stream.sendFinal(response); + // Send email response with proper EmailResponse entity + const emailResponseActivity = createEmailResponseActivity(response); + await turnContext.sendActivity(emailResponseActivity); } /* ------------------------------------------------------------------ diff --git a/nodejs/vercel-sdk/sample-agent/src/agent.ts b/nodejs/vercel-sdk/sample-agent/src/agent.ts index bbe69cc0..80b0fe7c 100644 --- a/nodejs/vercel-sdk/sample-agent/src/agent.ts +++ b/nodejs/vercel-sdk/sample-agent/src/agent.ts @@ -3,7 +3,7 @@ import { ActivityTypes } from '@microsoft/agents-activity'; // Notification Imports import '@microsoft/agents-a365-notifications'; -import { AgentNotificationActivity } from '@microsoft/agents-a365-notifications'; +import { AgentNotificationActivity, NotificationType, createEmailResponseActivity } from '@microsoft/agents-a365-notifications'; import { Client, getClient } from './client'; @@ -55,8 +55,45 @@ export class A365Agent extends AgentApplication { } async handleAgentNotificationActivity(context: TurnContext, state: TurnState, agentNotificationActivity: AgentNotificationActivity) { - context.sendActivity("Recieved an AgentNotification!"); - /* your logic here... */ + switch (agentNotificationActivity.notificationType) { + case NotificationType.EmailNotification: + await this.handleEmailNotification(context, state, agentNotificationActivity); + break; + default: + await context.sendActivity(`Received notification of type: ${agentNotificationActivity.notificationType}`); + } + } + + private async handleEmailNotification(context: TurnContext, state: TurnState, activity: AgentNotificationActivity): Promise { + const emailNotification = activity.emailNotification; + + if (!emailNotification) { + const errorResponse = createEmailResponseActivity('I could not find the email notification details.'); + await context.sendActivity(errorResponse); + return; + } + + try { + const client: Client = await getClient(); + + // First, retrieve the email content + const emailContent = await client.invokeAgentWithScope( + `You have a new email from ${context.activity.from?.name} with id '${emailNotification.id}', ` + + `ConversationId '${emailNotification.conversationId}'. Please retrieve this message and return it in text format.` + ); + + // Then process the email + const response = await client.invokeAgentWithScope( + `You have received the following email. Please follow any instructions in it. ${emailContent}` + ); + + const emailResponseActivity = createEmailResponseActivity(response || 'I have processed your email but do not have a response at this time.'); + await context.sendActivity(emailResponseActivity); + } catch (error) { + console.error('Email notification error:', error); + const errorResponse = createEmailResponseActivity('Unable to process your email at this time.'); + await context.sendActivity(errorResponse); + } } } diff --git a/python/agent-framework/sample-agent/host_agent_server.py b/python/agent-framework/sample-agent/host_agent_server.py index 5efe11f8..b6f4353c 100644 --- a/python/agent-framework/sample-agent/host_agent_server.py +++ b/python/agent-framework/sample-agent/host_agent_server.py @@ -211,12 +211,8 @@ async def on_notification( ) if notification_activity.notification_type == NotificationTypes.EMAIL_NOTIFICATION: - responseActivity = Activity(type=ActivityTypes.message) - if responseActivity.entities is None: - responseActivity.entities = [] - responseActivity.entities.append(EmailResponse(response)) - - await context.send_activity(responseActivity) + response_activity = EmailResponse.create_email_response_activity(response) + await context.send_activity(response_activity) return await context.send_activity(response) diff --git a/python/claude/sample-agent/ToolingManifest.json b/python/claude/sample-agent/ToolingManifest.json new file mode 100644 index 00000000..e842561c --- /dev/null +++ b/python/claude/sample-agent/ToolingManifest.json @@ -0,0 +1,11 @@ +{ + "mcpServers": [ + { + "mcpServerName": "mcp_MailTools", + "mcpServerUniqueName": "mcp_MailTools", + "url": "https://agent365.svc.cloud.microsoft/agents/servers/mcp_MailTools", + "scope": "McpServers.Mail.All", + "audience": "ea9ffc3e-8a23-4a7d-836d-234d7c7565c1" + } + ] +} \ No newline at end of file diff --git a/python/claude/sample-agent/host_agent_server.py b/python/claude/sample-agent/host_agent_server.py index 3c881631..fb7b4a06 100644 --- a/python/claude/sample-agent/host_agent_server.py +++ b/python/claude/sample-agent/host_agent_server.py @@ -51,6 +51,7 @@ AgentNotificationActivity, ChannelId, ) +from microsoft_agents_a365.notifications import EmailResponse, NotificationTypes # Observability imports (optional) try: @@ -250,7 +251,13 @@ async def _handle_notification_with_agent( notification_activity, self.agent_app.auth, context ) - # Send the response + # For email notifications, wrap response in EmailResponse entity + if notification_activity.notification_type == NotificationTypes.EMAIL_NOTIFICATION: + response_activity = EmailResponse.create_email_response_activity(response) + await context.send_activity(response_activity) + return + + # Send the response for other notification types await context.send_activity(response) async def _validate_agent_and_setup_context(self, context: TurnContext):