diff --git a/nodejs/devin/sample-agent/.env.example b/nodejs/devin/sample-agent/.env.example index 4f7a1993..8f70d7b0 100644 --- a/nodejs/devin/sample-agent/.env.example +++ b/nodejs/devin/sample-agent/.env.example @@ -1,7 +1,23 @@ +PORT=3978 +POLLING_INTERVAL_SECONDS=10 # Polling interval in seconds (how often to check for Devin responses) + +# Observability +ENABLE_OBSERVABILITY=true +ENABLE_A365_OBSERVABILITY=true +ENABLE_A365_OBSERVABILITY_EXPORTER=true +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 +connections__serviceConnection__settings__clientId=blueprint_id +connections__serviceConnection__settings__clientSecret=blueprint_secret +connections__serviceConnection__settings__tenantId=tenant_id -# Polling interval in seconds (how often to check for Devin responses) -POLLING_INTERVAL_SECONDS=10 +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/README.md b/nodejs/devin/sample-agent/README.md index 1ea1912b..95f9951f 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 24+ +- 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. 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 diff --git a/nodejs/devin/sample-agent/src/agent.ts b/nodejs/devin/sample-agent/src/agent.ts index f9b73ac1..1874f2e2 100644 --- a/nodejs/devin/sample-agent/src/agent.ts +++ b/nodejs/devin/sample-agent/src/agent.ts @@ -8,18 +8,23 @@ 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, + MemoryStorage, TurnContext, TurnState, } from "@microsoft/agents-hosting"; 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 +36,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 Agent 365 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 +124,7 @@ export class A365Agent extends AgentApplication { } ); + // Handle installation activities this.onActivity( ActivityTypes.InstallationUpdate, async (context: TurnContext, state: TurnState) => { @@ -179,6 +223,21 @@ 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(); +export const agentApplication = new A365Agent({ + storage: new MemoryStorage(), + authorization: { agentic: {} }, // Type and scopes set in .env +}); diff --git a/nodejs/devin/sample-agent/src/index.ts b/nodejs/devin/sample-agent/src/index.ts index a6c58093..6a780010 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,28 @@ 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 { + 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); + } }); -}); 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..30785f90 --- /dev/null +++ b/nodejs/devin/sample-agent/src/token-cache.ts @@ -0,0 +1,55 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +/** + * 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): void { + this.cache.set(key, token); + console.log("šŸ” Token cached succesfully"); + } + + /** + * Retrieve a token + */ + get(key: string): string | null { + const entry = this.cache.get(key); + + if (!entry) { + console.log("šŸ” Token cache miss"); + return null; + } + + return entry; + } + + /** + * Check if a token exists + */ + has(key: string): boolean { + return this.cache.has(key); + } + + /** + * Clear a token from cache + */ + delete(key: string): boolean { + return this.cache.delete(key); + } +} + +// Create a singleton instance for the application +const tokenCache = new TokenCache(); + +export default tokenCache;