From 51bff8550086e4c54d8068ddd3fd000d362da9f8 Mon Sep 17 00:00:00 2001 From: Walter Luna Date: Fri, 14 Nov 2025 21:08:27 +0000 Subject: [PATCH 1/5] add full observability support --- nodejs/devin/sample-agent/.env.example | 8 +++ nodejs/devin/sample-agent/src/agent.ts | 59 +++++++++++++++++++- nodejs/devin/sample-agent/src/index.ts | 30 ++++++++-- nodejs/devin/sample-agent/src/token-cache.ts | 52 +++++++++++++++++ 4 files changed, 141 insertions(+), 8 deletions(-) create mode 100644 nodejs/devin/sample-agent/src/token-cache.ts diff --git a/nodejs/devin/sample-agent/.env.example b/nodejs/devin/sample-agent/.env.example index 4f7a1993..989b0fac 100644 --- a/nodejs/devin/sample-agent/.env.example +++ b/nodejs/devin/sample-agent/.env.example @@ -1,3 +1,11 @@ +PORT=3978 + +# Observability +ENABLE_OBSERVABILITY=true +ENABLE_A365_OBSERVABILITY=true +ENABLE_A365_OBSERVABILITY_EXPORTER=true +CLUSTER_CATEGORY='local'/'dev'/'test'/'preprod'/'firstrelease'/'prod'/'gov'/'high'/'dod'/'mooncake'/'ex'/'rx' + # Devin API Configuration DEVIN_BASE_URL=https://api.devin.ai/v1 DEVIN_API_KEY=your_devin_api_key_here diff --git a/nodejs/devin/sample-agent/src/agent.ts b/nodejs/devin/sample-agent/src/agent.ts index f9b73ac1..0a79f528 100644 --- a/nodejs/devin/sample-agent/src/agent.ts +++ b/nodejs/devin/sample-agent/src/agent.ts @@ -8,11 +8,14 @@ import { InferenceOperationType, InferenceScope, InvokeAgentScope, + ObservabilityManager, TenantDetails, } from "@microsoft/agents-a365-observability"; +import { ClusterCategory } from "@microsoft/agents-a365-runtime"; import { Activity, ActivityTypes } from "@microsoft/agents-activity"; import { AgentApplication, + AgentApplicationOptions, DefaultConversationState, TurnContext, TurnState, @@ -20,6 +23,7 @@ import { import { Stream } from "stream"; import { v4 as uuidv4 } from "uuid"; import { devinClient } from "./devin-client"; +import tokenCache from "./token-cache"; import { getAgentDetails, getTenantDetails } from "./utils"; interface ConversationState extends DefaultConversationState { @@ -31,9 +35,47 @@ export class A365Agent extends AgentApplication { isApplicationInstalled: boolean = false; agentName = "Devin Agent"; - constructor() { - super(); + constructor( + options?: Partial> | undefined + ) { + super(options); + const clusterCategory: ClusterCategory = + (process.env.CLUSTER_CATEGORY as ClusterCategory) || "dev"; + + // Initialize Observability SDK + const observabilitySDK = ObservabilityManager.configure((builder) => + builder + .withService("claude-travel-agent", "1.0.0") + .withTokenResolver(async (agentId, tenantId) => { + // Token resolver for authentication with Agent365 observability + console.log( + "šŸ”‘ Token resolver called for agent:", + agentId, + "tenant:", + tenantId + ); + + // Retrieve the cached agentic token + const cacheKey = this.createAgenticTokenCacheKey(agentId, tenantId); + const cachedToken = tokenCache.get(cacheKey); + + if (cachedToken) { + console.log("šŸ”‘ Token retrieved from cache successfully"); + return cachedToken; + } + + console.log( + "āš ļø No cached token found - token should be cached during agent invocation" + ); + return null; + }) + .withClusterCategory(clusterCategory) + ); + + // Start the observability SDK + observabilitySDK.start(); + // Handle messages this.onActivity( ActivityTypes.Message, async (context: TurnContext, state: ApplicationTurnState) => { @@ -81,6 +123,7 @@ export class A365Agent extends AgentApplication { } ); + // Handle installation activities this.onActivity( ActivityTypes.InstallationUpdate, async (context: TurnContext, state: TurnState) => { @@ -179,6 +222,18 @@ export class A365Agent extends AgentApplication { ); } } + + /** + * Create a cache key for the agentic token + */ + private createAgenticTokenCacheKey( + agentId: string, + tenantId: string + ): string { + return tenantId + ? `agentic-token-${agentId}-${tenantId}` + : `agentic-token-${agentId}`; + } } export const agentApplication = new A365Agent(); diff --git a/nodejs/devin/sample-agent/src/index.ts b/nodejs/devin/sample-agent/src/index.ts index a6c58093..52372c26 100644 --- a/nodejs/devin/sample-agent/src/index.ts +++ b/nodejs/devin/sample-agent/src/index.ts @@ -1,6 +1,7 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. +import { ObservabilityManager } from "@microsoft/agents-a365-observability"; import { AuthConfiguration, authorizeJWT, @@ -40,10 +41,27 @@ const server = app console.log("Server is shutting down..."); }); -process.on("SIGINT", () => { - console.log("Received SIGINT. Shutting down gracefully..."); - server.close(() => { - console.log("Server closed."); - process.exit(0); +process + .on("SIGINT", async () => { + console.log("\nšŸ›‘ Shutting down agent..."); + try { + server.close(); + await ObservabilityManager.shutdown(); + console.log("šŸ”­ Observability SDK shut down gracefully"); + process.exit(0); + } catch (err) { + console.error("Error during shutdown:", err); + process.exit(1); + } + }) + .on("SIGTERM", async () => { + console.log("\nšŸ›‘ Shutting down agent..."); + try { + await ObservabilityManager.shutdown(); + console.log("šŸ”­ Observability SDK shut down gracefully"); + process.exit(0); + } catch (err) { + console.error("Error during shutdown:", err); + process.exit(1); + } }); -}); diff --git a/nodejs/devin/sample-agent/src/token-cache.ts b/nodejs/devin/sample-agent/src/token-cache.ts new file mode 100644 index 00000000..9647da3b --- /dev/null +++ b/nodejs/devin/sample-agent/src/token-cache.ts @@ -0,0 +1,52 @@ +/** + * Simple in-memory token cache + * In production, use a more robust caching solution like Redis + */ +class TokenCache { + private readonly cache: Map; + + constructor() { + this.cache = new Map(); + } + + /** + * Store a token with key + */ + set(key: string, token: string) { + this.cache.set(key, token); + console.log(`šŸ” Token cached for key: ${key}`); + } + + /** + * Retrieve a token + */ + get(key: string) { + const entry = this.cache.get(key); + + if (!entry) { + console.log(`šŸ” Token cache miss for key: ${key}`); + return null; + } + + return entry; + } + + /** + * Check if a token exists + */ + has(key: string) { + return this.cache.has(key); + } + + /** + * Clear a token from cache + */ + delete(key: string) { + return this.cache.delete(key); + } +} + +// Create a singleton instance for the application +const tokenCache = new TokenCache(); + +export default tokenCache; From c9d470f2f90b7427e5e3372d1972370443167c22 Mon Sep 17 00:00:00 2001 From: Walter Luna Date: Fri, 14 Nov 2025 21:17:08 +0000 Subject: [PATCH 2/5] instantiate agent with memory and storage --- nodejs/devin/sample-agent/.env.example | 13 +++++++++++-- nodejs/devin/sample-agent/src/agent.ts | 6 +++++- 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/nodejs/devin/sample-agent/.env.example b/nodejs/devin/sample-agent/.env.example index 989b0fac..1f62a06a 100644 --- a/nodejs/devin/sample-agent/.env.example +++ b/nodejs/devin/sample-agent/.env.example @@ -1,4 +1,5 @@ PORT=3978 +POLLING_INTERVAL_SECONDS=10 # Polling interval in seconds (how often to check for Devin responses) # Observability ENABLE_OBSERVABILITY=true @@ -10,6 +11,14 @@ CLUSTER_CATEGORY='local'/'dev'/'test'/'preprod'/'firstrelease'/'prod'/'gov'/'hig DEVIN_BASE_URL=https://api.devin.ai/v1 DEVIN_API_KEY=your_devin_api_key_here -# Polling interval in seconds (how often to check for Devin responses) -POLLING_INTERVAL_SECONDS=10 +#Auth +connections__serviceConnection__settings__clientId=blueprint_id +connections__serviceConnection__settings__clientSecret=blueprint_secret +connections__serviceConnection__settings__tenantId=tenant_id +connectionsMap__0__connection=serviceConnection +connectionsMap__0__serviceUrl=* + +agentic_type=agentic +agentic_scopes=https://graph.microsoft.com/.default +agentic_connectionName=serviceConnection diff --git a/nodejs/devin/sample-agent/src/agent.ts b/nodejs/devin/sample-agent/src/agent.ts index 0a79f528..4669cef2 100644 --- a/nodejs/devin/sample-agent/src/agent.ts +++ b/nodejs/devin/sample-agent/src/agent.ts @@ -17,6 +17,7 @@ import { AgentApplication, AgentApplicationOptions, DefaultConversationState, + MemoryStorage, TurnContext, TurnState, } from "@microsoft/agents-hosting"; @@ -236,4 +237,7 @@ export class A365Agent extends AgentApplication { } } -export const agentApplication = new A365Agent(); +export const agentApplication = new A365Agent({ + storage: new MemoryStorage(), + authorization: { agentic: {} }, // Type and scopes set in .env +}); From 197e8ca60a940108dc725d913626302d5d22a5b8 Mon Sep 17 00:00:00 2001 From: Walter Luna Date: Fri, 14 Nov 2025 21:20:39 +0000 Subject: [PATCH 3/5] update package.json --- nodejs/devin/sample-agent/package.json | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/nodejs/devin/sample-agent/package.json b/nodejs/devin/sample-agent/package.json index 8b84888e..386c142c 100644 --- a/nodejs/devin/sample-agent/package.json +++ b/nodejs/devin/sample-agent/package.json @@ -5,9 +5,10 @@ "scripts": { "build": "tsc", "start": "node --env-file=.env dist/index.js", - "test-tool": "agentsplayground", - "install:clean": "npm run clean && npm install", - "clean": "rimraf dist node_modules package-lock.json" + "test-tool": "agentsplayground" + }, + "engines": { + "node": ">=24.0.0" }, "keywords": [], "license": "ISC", @@ -22,9 +23,6 @@ }, "devDependencies": { "@microsoft/m365agentsplayground": "^0.2.20", - "nodemon": "^3.1.10", - "rimraf": "^5.0.0", - "ts-node": "^10.9.2", "typescript": "^5.9.2" } } \ No newline at end of file From 6c27948aa93455e4d3f79def8cc2dbcae8b7909b Mon Sep 17 00:00:00 2001 From: Walter Luna Date: Fri, 14 Nov 2025 21:36:12 +0000 Subject: [PATCH 4/5] Add readme --- nodejs/devin/sample-agent/README.md | 65 ++++++++++++++++++++++++++++- 1 file changed, 64 insertions(+), 1 deletion(-) diff --git a/nodejs/devin/sample-agent/README.md b/nodejs/devin/sample-agent/README.md index 1ea1912b..c783e267 100644 --- a/nodejs/devin/sample-agent/README.md +++ b/nodejs/devin/sample-agent/README.md @@ -1 +1,64 @@ -TODO +# Sample Agent - Node.js Devin + +This directory contains a sample agent implementation using Node.js and Devin API. + +## Demonstrates + +This sample demonstrates how to build an agent using the Microsoft Agent 365 SDK with Node.js and Devin API. + +## Prerequisites + +- Node.js 18+ +- Devin API access +- Agents SDK + +## How to run this sample + +1. **Setup environment variables** + + ```bash + # Copy the template 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 run start + ``` + +5. **Start AgentsPlayground to chat with your agent** + ```bash + npm run test-tool + ``` + +The agent will start and be ready to receive requests through the configured hosting mechanism. + +## šŸ“š Related Documentation + +- [Devin API Documentation](https://docs.devin.ai/api-reference/overview) +- [Microsoft Agent 365 SDK](https://github.com/microsoft/Agents-for-js) +- [AgentsPlayground](https://learn.microsoft.com/en-us/microsoft-365/agents-sdk/test-with-toolkit-project?tabs=windows) + +## šŸ¤ Contributing + +1. Follow the existing code patterns and structure +2. Add comprehensive logging and error handling +3. Update documentation for new features +4. Test thoroughly with different authentication methods + +## šŸ“„ License + +This project is licensed under the MIT License - see the [LICENSE](../../../LICENSE.md) file for details. From 5709d07654529f7099b5fe2b3c1f3e165c02e11a Mon Sep 17 00:00:00 2001 From: Walter Luna Date: Fri, 14 Nov 2025 22:44:47 +0000 Subject: [PATCH 5/5] suggestions from code review --- nodejs/devin/sample-agent/.env.example | 7 +++---- nodejs/devin/sample-agent/README.md | 2 +- nodejs/devin/sample-agent/src/agent.ts | 2 +- nodejs/devin/sample-agent/src/index.ts | 1 + nodejs/devin/sample-agent/src/token-cache.ts | 15 +++++++++------ 5 files changed, 15 insertions(+), 12 deletions(-) diff --git a/nodejs/devin/sample-agent/.env.example b/nodejs/devin/sample-agent/.env.example index 1f62a06a..8f70d7b0 100644 --- a/nodejs/devin/sample-agent/.env.example +++ b/nodejs/devin/sample-agent/.env.example @@ -5,13 +5,12 @@ POLLING_INTERVAL_SECONDS=10 # Polling interval in seconds (how often to check fo ENABLE_OBSERVABILITY=true ENABLE_A365_OBSERVABILITY=true ENABLE_A365_OBSERVABILITY_EXPORTER=true -CLUSTER_CATEGORY='local'/'dev'/'test'/'preprod'/'firstrelease'/'prod'/'gov'/'high'/'dod'/'mooncake'/'ex'/'rx' - +CLUSTER_CATEGORY=dev # Options: 'local', 'dev', 'test', 'preprod', 'firstrelease', 'prod', 'gov', 'high', 'dod', 'mooncake', 'ex', 'rx' # Devin API Configuration DEVIN_BASE_URL=https://api.devin.ai/v1 -DEVIN_API_KEY=your_devin_api_key_here +DEVIN_API_KEY= -#Auth +# Auth connections__serviceConnection__settings__clientId=blueprint_id connections__serviceConnection__settings__clientSecret=blueprint_secret connections__serviceConnection__settings__tenantId=tenant_id diff --git a/nodejs/devin/sample-agent/README.md b/nodejs/devin/sample-agent/README.md index c783e267..95f9951f 100644 --- a/nodejs/devin/sample-agent/README.md +++ b/nodejs/devin/sample-agent/README.md @@ -8,7 +8,7 @@ This sample demonstrates how to build an agent using the Microsoft Agent 365 SDK ## Prerequisites -- Node.js 18+ +- Node.js 24+ - Devin API access - Agents SDK diff --git a/nodejs/devin/sample-agent/src/agent.ts b/nodejs/devin/sample-agent/src/agent.ts index 4669cef2..1874f2e2 100644 --- a/nodejs/devin/sample-agent/src/agent.ts +++ b/nodejs/devin/sample-agent/src/agent.ts @@ -48,7 +48,7 @@ export class A365Agent extends AgentApplication { builder .withService("claude-travel-agent", "1.0.0") .withTokenResolver(async (agentId, tenantId) => { - // Token resolver for authentication with Agent365 observability + // Token resolver for authentication with Agent 365 observability console.log( "šŸ”‘ Token resolver called for agent:", agentId, diff --git a/nodejs/devin/sample-agent/src/index.ts b/nodejs/devin/sample-agent/src/index.ts index 52372c26..6a780010 100644 --- a/nodejs/devin/sample-agent/src/index.ts +++ b/nodejs/devin/sample-agent/src/index.ts @@ -57,6 +57,7 @@ process .on("SIGTERM", async () => { console.log("\nšŸ›‘ Shutting down agent..."); try { + server.close(); await ObservabilityManager.shutdown(); console.log("šŸ”­ Observability SDK shut down gracefully"); process.exit(0); diff --git a/nodejs/devin/sample-agent/src/token-cache.ts b/nodejs/devin/sample-agent/src/token-cache.ts index 9647da3b..30785f90 100644 --- a/nodejs/devin/sample-agent/src/token-cache.ts +++ b/nodejs/devin/sample-agent/src/token-cache.ts @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + /** * Simple in-memory token cache * In production, use a more robust caching solution like Redis @@ -12,19 +15,19 @@ class TokenCache { /** * Store a token with key */ - set(key: string, token: string) { + set(key: string, token: string): void { this.cache.set(key, token); - console.log(`šŸ” Token cached for key: ${key}`); + console.log("šŸ” Token cached succesfully"); } /** * Retrieve a token */ - get(key: string) { + get(key: string): string | null { const entry = this.cache.get(key); if (!entry) { - console.log(`šŸ” Token cache miss for key: ${key}`); + console.log("šŸ” Token cache miss"); return null; } @@ -34,14 +37,14 @@ class TokenCache { /** * Check if a token exists */ - has(key: string) { + has(key: string): boolean { return this.cache.has(key); } /** * Clear a token from cache */ - delete(key: string) { + delete(key: string): boolean { return this.cache.delete(key); } }