From ed11c4b14697ba5ed6327fe57e0016314873ba86 Mon Sep 17 00:00:00 2001 From: Dominik Bezic Date: Tue, 11 Nov 2025 14:01:43 +0000 Subject: [PATCH 1/2] Add Vercel AI SDK sample --- nodejs/vercel-sdk/sample-agent/.env.example | 30 +++++ nodejs/vercel-sdk/sample-agent/README.md | 43 +++++++ nodejs/vercel-sdk/sample-agent/package.json | 40 ++++++ .../sample-agent/preinstall-local-packages.js | 72 +++++++++++ nodejs/vercel-sdk/sample-agent/src/agent.ts | 62 +++++++++ nodejs/vercel-sdk/sample-agent/src/client.ts | 119 ++++++++++++++++++ nodejs/vercel-sdk/sample-agent/src/index.ts | 32 +++++ nodejs/vercel-sdk/sample-agent/tsconfig.json | 20 +++ 8 files changed, 418 insertions(+) create mode 100644 nodejs/vercel-sdk/sample-agent/.env.example create mode 100644 nodejs/vercel-sdk/sample-agent/README.md create mode 100644 nodejs/vercel-sdk/sample-agent/package.json create mode 100644 nodejs/vercel-sdk/sample-agent/preinstall-local-packages.js create mode 100644 nodejs/vercel-sdk/sample-agent/src/agent.ts create mode 100644 nodejs/vercel-sdk/sample-agent/src/client.ts create mode 100644 nodejs/vercel-sdk/sample-agent/src/index.ts create mode 100644 nodejs/vercel-sdk/sample-agent/tsconfig.json diff --git a/nodejs/vercel-sdk/sample-agent/.env.example b/nodejs/vercel-sdk/sample-agent/.env.example new file mode 100644 index 00000000..38a619dd --- /dev/null +++ b/nodejs/vercel-sdk/sample-agent/.env.example @@ -0,0 +1,30 @@ +# Anthropic Configuration +ANTHROPIC_API_KEY= + +# Environment Settings +NODE_ENV=development + +# Telemetry and Tracing Configuration +DEBUG=agents:* +AZURE_EXPERIMENTAL_ENABLE_ACTIVITY_SOURCE=true +AZURE_TRACING_GEN_AI_CONTENT_RECORDING_ENABLED=true +OPENAI_AGENTS_DISABLE_TRACING=false +OTEL_SDK_DISABLED=false +CONNECTION_STRING= + +# Use Agentic Authentication rather than OBO +USE_AGENTIC_AUTH=false + +# Service Connection Settings +connections__service_connection__settings__clientId= +connections__service_connection__settings__clientSecret= +connections__service_connection__settings__tenantId= + +# Set service connection as default +connectionsMap__0__serviceUrl=* +connectionsMap__0__connection=service_connection + +# AgenticAuthentication Options +agentic_type=agentic +agentic_altBlueprintConnectionName=service_connection +agentic_scopes=ea9ffc3e-8a23-4a7d-836d-234d7c7565c1/.default # Prod Agentic scope \ No newline at end of file diff --git a/nodejs/vercel-sdk/sample-agent/README.md b/nodejs/vercel-sdk/sample-agent/README.md new file mode 100644 index 00000000..9d6b5f5d --- /dev/null +++ b/nodejs/vercel-sdk/sample-agent/README.md @@ -0,0 +1,43 @@ +# Sample Agent - Node.js Vercel AI SDK + +This directory contains a sample agent implementation using Node.js and Vercel AI SDK. + +## Demonstrates + +This sample demonstrates how to build an agent using the Agent365 framework with Node.js and Vercel AI SDK. + +## Prerequisites + +- Node.js 18+ +- Vercel AI SDK +- Agents SDK + +## How to run this sample + +1. **Setup environment variables** + ```bash + # Copy the example environment file + cp .env.example .env + ``` + +2. **Install dependencies** + ```bash + npm install + ``` + +3. **Build the project** + ```bash + npm run build + ``` + +4. **Start the agent** + ```bash + npm start + ``` + +5. **Optionally, while testing you can run in dev mode** + ```bash + npm run dev + ``` + +The agent will start and be ready to receive requests through the configured hosting mechanism. diff --git a/nodejs/vercel-sdk/sample-agent/package.json b/nodejs/vercel-sdk/sample-agent/package.json new file mode 100644 index 00000000..3455b50c --- /dev/null +++ b/nodejs/vercel-sdk/sample-agent/package.json @@ -0,0 +1,40 @@ +{ + "name": "vercel-sdk-sample", + "version": "2025.11.6", + "description": "Sample agent integrating Vercel AI SDK Agents with Microsoft 365 Agents SDK and Agent365 SDK", + "main": "src/index.ts", + "scripts": { + "preinstall": "node preinstall-local-packages.js", + "start": "node dist/index.js", + "dev": "nodemon --watch src/*.ts --exec ts-node src/index.ts", + "test-tool": "agentsplayground", + "eval": "node --env-file .env src/evals/index.js", + "build": "tsc" + }, + "keywords": [ + "vercel-ai-sdk", + "microsoft-365", + "agent", + "ai" + ], + "license": "MIT", + "dependencies": { + "@ai-sdk/anthropic": "^2.0.31", + "@microsoft/agents-activity": "^1.1.0-alpha.85", + "@microsoft/agents-hosting": "^1.1.0-alpha.85", + "ai": "^5.0.72", + "dotenv": "^17.2.3", + "express": "^5.1.0", + "node-fetch": "^3.3.2", + "uuid": "^9.0.0" + }, + "devDependencies": { + "@azure/monitor-opentelemetry-exporter": "^1.0.0-beta.32", + "@babel/cli": "^7.28.3", + "@babel/core": "^7.28.4", + "@babel/preset-env": "^7.28.3", + "@microsoft/m365agentsplayground": "^0.2.16", + "nodemon": "^3.1.10", + "ts-node": "^10.9.2" + } +} diff --git a/nodejs/vercel-sdk/sample-agent/preinstall-local-packages.js b/nodejs/vercel-sdk/sample-agent/preinstall-local-packages.js new file mode 100644 index 00000000..7e913fd7 --- /dev/null +++ b/nodejs/vercel-sdk/sample-agent/preinstall-local-packages.js @@ -0,0 +1,72 @@ +#!/usr/bin/env node + +import { readdir } from 'fs/promises'; +import { join, dirname } from 'path'; +import { fileURLToPath } from 'url'; +import { execSync } from 'child_process'; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = dirname(__filename); + +// Look for *.tgz files two directories above +const tgzDir = join(__dirname, '../../'); + +// Define the installation order +const installOrder = [ + 'microsoft-agents-a365-runtime-', + 'microsoft-agents-a365-notifications-', + 'microsoft-agents-a365-observability-', + 'microsoft-agents-a365-tooling-', + 'microsoft-agents-a365-tooling-extensions-claude-' +]; + +async function findTgzFiles() { + try { + const files = await readdir(tgzDir); + return files.filter(file => file.endsWith('.tgz')); + } catch (error) { + console.log('No tgz directory found or no files to install'); + return []; + } +} + +function findFileForPattern(files, pattern) { + return files.find(file => file.startsWith(pattern)); +} + +async function installPackages() { + const tgzFiles = await findTgzFiles(); + + if (tgzFiles.length === 0) { + console.log('No .tgz files found in', tgzDir); + return; + } + + console.log('Found .tgz files:', tgzFiles); + + for (const pattern of installOrder) { + const file = findFileForPattern(tgzFiles, pattern); + if (file) { + const filePath = join(tgzDir, file); + console.log(`Installing ${file}...`); + try { + execSync(`npm install "${filePath}"`, { + stdio: 'inherit', + cwd: __dirname + }); + console.log(`✓ Successfully installed ${file}`); + } catch (error) { + console.error(`✗ Failed to install ${file}:`, error.message); + process.exit(1); + } + } else { + console.log(`No file found matching pattern: ${pattern}`); + } + } +} + +// Run the installation +installPackages().catch(error => { + console.error('Error during package installation:', error); + process.exit(1); +}); \ No newline at end of file diff --git a/nodejs/vercel-sdk/sample-agent/src/agent.ts b/nodejs/vercel-sdk/sample-agent/src/agent.ts new file mode 100644 index 00000000..365219ad --- /dev/null +++ b/nodejs/vercel-sdk/sample-agent/src/agent.ts @@ -0,0 +1,62 @@ +import { TurnState, AgentApplication, TurnContext, MemoryStorage } from '@microsoft/agents-hosting'; +import { ActivityTypes } from '@microsoft/agents-activity'; + +// Notification Imports +import '@microsoft/agents-a365-notifications'; +import { AgentNotificationActivity } from '@microsoft/agents-a365-notifications'; + +import { Client, getClient } from './client'; + +export class A365Agent extends AgentApplication { + agentName = "A365 Agent"; + + constructor() { + super({ + startTypingTimer: true, + storage: new MemoryStorage(), + authorization: { + agentic: { + type: 'agentic', + } // scopes set in the .env file... + } + }); + + // Route agent notifications + this.onAgentNotification("agents:*", async (context: TurnContext, state: TurnState, agentNotificationActivity: AgentNotificationActivity) => { + await this.handleAgentNotificationActivity(context, state, agentNotificationActivity); + }); + + this.onActivity(ActivityTypes.Message, async (context: TurnContext, state: TurnState) => { + await this.handleAgentMessageActivity(context, state); + }); + } + + /** + * Handles incoming user messages and sends responses. + */ + async handleAgentMessageActivity(turnContext: TurnContext, state: TurnState): Promise { + const userMessage = turnContext.activity.text?.trim() || ''; + + if (!userMessage) { + await turnContext.sendActivity('Please send me a message and I\'ll help you!'); + return; + } + + try { + const client: Client = await getClient(); + const response = await client.invokeAgentWithScope(userMessage); + await turnContext.sendActivity(response); + } catch (error) { + console.error('LLM query error:', error); + const err = error as any; + await turnContext.sendActivity(`Error: ${err.message || err}`); + } + } + + async handleAgentNotificationActivity(context: TurnContext, state: TurnState, agentNotificationActivity: AgentNotificationActivity) { + context.sendActivity("Recieved an AgentNotification!"); + /* your logic here... */ + } +} + +export const agentApplication = new A365Agent(); diff --git a/nodejs/vercel-sdk/sample-agent/src/client.ts b/nodejs/vercel-sdk/sample-agent/src/client.ts new file mode 100644 index 00000000..34647c03 --- /dev/null +++ b/nodejs/vercel-sdk/sample-agent/src/client.ts @@ -0,0 +1,119 @@ +import { generateText, Experimental_Agent as Agent } from "ai"; +import { anthropic } from '@ai-sdk/anthropic'; + + +// Observability Imports +import { + ObservabilityManager, + InferenceScope, + Builder, + InferenceOperationType, + AgentDetails, + TenantDetails, + InferenceDetails +} from '@microsoft/agents-a365-observability'; + +const modelName = 'claude-sonnet-4-20250514'; + +export interface Client { + invokeAgentWithScope(prompt: string): Promise; +} + +const sdk = ObservabilityManager.configure( + (builder: Builder) => + builder + .withService('Vercel SDK Sample Agent', '1.0.0') +); + +sdk.start(); + +/** + * Creates and configures a LangChain client with Agent365 MCP tools. + * + * This factory function initializes a LangChain React agent with access to + * Microsoft 365 tools through MCP (Model Context Protocol) servers. It handles + * tool discovery, authentication, and agent configuration. + * + * @param authorization - Agent365 authorization context for token acquisition + * @param turnContext - Bot Framework turn context for the current conversation + * @returns Promise - Configured LangChain client ready for agent interactions + * + * @example + * ```typescript + * const client = await getClient(authorization, turnContext); + * const response = await client.invokeAgent("Send an email to john@example.com"); + * ``` + */ +export async function getClient(): Promise { + // Create the model + const model = anthropic(modelName) + + // Create the agent + const agent = new Agent({ + model: model, + }); + + return new VercelAiClient(agent); +} + +/** + * VercelAiClient provides an interface to interact with LangChain agents. + * It creates a React agent with tools and exposes an invokeAgent method. + */ +class VercelAiClient implements Client { + private agent: Agent; + + constructor(agent: any) { + this.agent = agent; + } + + /** + * Sends a user message to the LangChain agent and returns the AI's response. + * Handles streaming results and error reporting. + * + * @param {string} userMessage - The message or prompt to send to the agent. + * @returns {Promise} The response from the agent, or an error message if the query fails. + */ + async invokeAgent(userMessage: string): Promise { + const { text: agentMessage } = await this.agent.generate({ + prompt: userMessage + }); + + if (!agentMessage) { + return "Sorry, I couldn't get a response from the agent :("; + } + + return agentMessage; + } + + async invokeAgentWithScope(prompt: string) { + const inferenceDetails: InferenceDetails = { + operationName: InferenceOperationType.CHAT, + model: modelName, + }; + + const agentDetails: AgentDetails = { + agentId: 'typescript-compliance-agent', + agentName: 'TypeScript Compliance Agent', + conversationId: 'conv-12345', + }; + + const tenantDetails: TenantDetails = { + tenantId: 'typescript-sample-tenant', + }; + + const scope = InferenceScope.start(inferenceDetails, agentDetails, tenantDetails); + + const response = await this.invokeAgent(prompt); + + // Record the inference response with token usage + scope?.recordOutputMessages([response]); + scope?.recordInputMessages([prompt]); + scope?.recordResponseId(`resp-${Date.now()}`); + scope?.recordInputTokens(45); + scope?.recordOutputTokens(78); + scope?.recordFinishReasons(['stop']); + + return response; + } +} diff --git a/nodejs/vercel-sdk/sample-agent/src/index.ts b/nodejs/vercel-sdk/sample-agent/src/index.ts new file mode 100644 index 00000000..85bd9eff --- /dev/null +++ b/nodejs/vercel-sdk/sample-agent/src/index.ts @@ -0,0 +1,32 @@ +// It is important to load environment variables before importing other modules +import { configDotenv } from 'dotenv'; + +configDotenv(); + +import { AuthConfiguration, authorizeJWT, CloudAdapter, Request } from '@microsoft/agents-hosting'; +import express, { Response } from 'express' +import { agentApplication } from './agent'; + +const authConfig: AuthConfiguration = {}; + +const server = express() +server.use(express.json()) +server.use(authorizeJWT(authConfig)) + +server.post('/api/messages', (req: Request, res: Response) => { + const adapter = agentApplication.adapter as CloudAdapter; + adapter.process(req, res, async (context) => { + await agentApplication.run(context) + }) +}) + +const port = process.env.PORT || 3978 +server.listen(port, async () => { + console.log(`\nServer listening to port ${port} for appId ${authConfig.clientId} debug ${process.env.DEBUG}`) +}).on('error', async (err) => { + console.error(err); + process.exit(1); +}).on('close', async () => { + console.log('Server closed'); + process.exit(0); +}); \ No newline at end of file diff --git a/nodejs/vercel-sdk/sample-agent/tsconfig.json b/nodejs/vercel-sdk/sample-agent/tsconfig.json new file mode 100644 index 00000000..6f07ceb7 --- /dev/null +++ b/nodejs/vercel-sdk/sample-agent/tsconfig.json @@ -0,0 +1,20 @@ +{ + "compilerOptions": { + "incremental": true, + "lib": ["ES2021"], + "target": "es2019", + "module": "node16", + "declaration": true, + "sourceMap": true, + "composite": true, + "strict": true, + "moduleResolution": "node16", + "esModuleInterop": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true, + "resolveJsonModule": true, + "rootDir": "src", + "outDir": "dist", + "tsBuildInfoFile": "dist/.tsbuildinfo" + } +} \ No newline at end of file From 980fabb1100ed54d5d9cfcae30da705b7ffdb515 Mon Sep 17 00:00:00 2001 From: Dominik Bezic Date: Tue, 11 Nov 2025 14:04:41 +0000 Subject: [PATCH 2/2] fix comments --- nodejs/vercel-sdk/sample-agent/src/client.ts | 22 ++++++++------------ 1 file changed, 9 insertions(+), 13 deletions(-) diff --git a/nodejs/vercel-sdk/sample-agent/src/client.ts b/nodejs/vercel-sdk/sample-agent/src/client.ts index 34647c03..7f694fde 100644 --- a/nodejs/vercel-sdk/sample-agent/src/client.ts +++ b/nodejs/vercel-sdk/sample-agent/src/client.ts @@ -1,4 +1,4 @@ -import { generateText, Experimental_Agent as Agent } from "ai"; +import { Experimental_Agent as Agent } from "ai"; import { anthropic } from '@ai-sdk/anthropic'; @@ -22,26 +22,22 @@ export interface Client { const sdk = ObservabilityManager.configure( (builder: Builder) => builder - .withService('Vercel SDK Sample Agent', '1.0.0') + .withService('Vercel AI SDK Sample Agent', '1.0.0') ); sdk.start(); /** - * Creates and configures a LangChain client with Agent365 MCP tools. + * Creates and configures a Vercel AI SDK client with anthropic model. * - * This factory function initializes a LangChain React agent with access to - * Microsoft 365 tools through MCP (Model Context Protocol) servers. It handles - * tool discovery, authentication, and agent configuration. + * This factory function initializes a Vercel AI SDK React agent with access to * - * @param authorization - Agent365 authorization context for token acquisition - * @param turnContext - Bot Framework turn context for the current conversation - * @returns Promise - Configured LangChain client ready for agent interactions + * @returns Promise - Configured Vercel AI SDK client ready for agent interactions * * @example * ```typescript - * const client = await getClient(authorization, turnContext); - * const response = await client.invokeAgent("Send an email to john@example.com"); + * const client = await getClient(); + * const response = await client.invokeAgent("Hello, how are you?"); * ``` */ export async function getClient(): Promise { @@ -57,7 +53,7 @@ export async function getClient(): Promise { } /** - * VercelAiClient provides an interface to interact with LangChain agents. + * VercelAiClient provides an interface to interact with Vercel AI SDK agents. * It creates a React agent with tools and exposes an invokeAgent method. */ class VercelAiClient implements Client { @@ -68,7 +64,7 @@ class VercelAiClient implements Client { } /** - * Sends a user message to the LangChain agent and returns the AI's response. + * Sends a user message to the Vercel AI SDK agent and returns the AI's response. * Handles streaming results and error reporting. * * @param {string} userMessage - The message or prompt to send to the agent.