diff --git a/nodejs/devin/sample-agent/package.json b/nodejs/devin/sample-agent/package.json index 426bb851..30e94e21 100644 --- a/nodejs/devin/sample-agent/package.json +++ b/nodejs/devin/sample-agent/package.json @@ -4,7 +4,7 @@ "main": "index.js", "scripts": { "build": "tsc", - "start": "node --env-file=.env dist/index.js", + "start": "node dist/index.js", "test-tool": "agentsplayground" }, "engines": { @@ -23,6 +23,7 @@ }, "devDependencies": { "@microsoft/m365agentsplayground": "^0.2.20", + "@types/express": "^5.0.6", "typescript": "^5.9.2" } } diff --git a/nodejs/devin/sample-agent/src/agent.ts b/nodejs/devin/sample-agent/src/agent.ts index 346db544..8e7324b7 100644 --- a/nodejs/devin/sample-agent/src/agent.ts +++ b/nodejs/devin/sample-agent/src/agent.ts @@ -8,7 +8,9 @@ import { InferenceOperationType, InferenceScope, InvokeAgentScope, + InvokeAgentScopeDetails, ObservabilityManager, + Request, TenantDetails, } from "@microsoft/agents-a365-observability"; import { ClusterCategory } from "@microsoft/agents-a365-runtime"; @@ -93,15 +95,21 @@ export class A365Agent extends AgentApplication { const baggageScope = new BaggageBuilder() .tenantId(tenantDetails.tenantId) .agentId(invokeAgentDetails.agentId) - .correlationId(uuidv4()) .agentName(invokeAgentDetails.agentName) .conversationId(context.activity.conversation?.id) .build(); await baggageScope.run(async () => { + const request: Request = { + conversationId: context.activity.conversation?.id, + sessionId: context.activity.conversation?.id, + content: context.activity.text || undefined, + }; + const invokeScopeDetails: InvokeAgentScopeDetails = {}; const invokeAgentScope = InvokeAgentScope.start( - invokeAgentDetails, - tenantDetails + request, + invokeScopeDetails, + { ...invokeAgentDetails, tenantId: tenantDetails.tenantId } ); await invokeAgentScope.withActiveSpanAsync(async () => { @@ -181,6 +189,7 @@ export class A365Agent extends AgentApplication { // Each sendActivity call produces a discrete Teams message. // NOTE: For Teams agentic identities, streaming is buffered into a single message by the SDK; // use sendActivity for any messages that must arrive immediately. + await turnContext.sendActivity('Got it — working on it…'); // Typing indicator loop — refreshes the "..." animation every ~4s for long-running operations. @@ -197,50 +206,62 @@ export class A365Agent extends AgentApplication { startTypingLoop(); + let inferenceScope!: ReturnType; try { const inferenceDetails: InferenceDetails = { operationName: InferenceOperationType.CHAT, model: "claude-3-7-sonnet-20250219", providerName: "cognition-ai", inputTokens: Math.ceil(userMessage.length / 4), // Rough estimate - responseId: `resp-${Date.now()}`, outputTokens: 0, // Will be updated after response finishReasons: undefined, }; - const inferenceScope = InferenceScope.start( + inferenceScope = InferenceScope.start( + { conversationId: turnContext.activity.conversation?.id }, inferenceDetails, - agentDetails, - tenantDetails + { ...agentDetails, tenantId: agentDetails.tenantId || tenantDetails.tenantId } ); inferenceScope.recordInputMessages([userMessage]); + const chunks: string[] = []; + let streamErrorMessage: string | undefined; let totalResponseLength = 0; const responseStream = new Stream() - .on("data", async (chunk) => { - totalResponseLength += (chunk as string).length; - invokeAgentScope.recordOutputMessages([`LLM Response: ${chunk}`]); - inferenceScope.recordOutputMessages([`LLM Response: ${chunk}`]); - await turnContext.sendActivity(chunk); + .on("data", (chunk) => { + const text = chunk as string; + totalResponseLength += text.length; + chunks.push(text); + invokeAgentScope.recordOutputMessages([`LLM Response: ${text}`]); + inferenceScope.recordOutputMessages([`LLM Response: ${text}`]); }) - .on("error", async (error) => { + .on("error", (error) => { + streamErrorMessage = String(error); invokeAgentScope.recordOutputMessages([`Streaming error: ${error}`]); inferenceScope.recordOutputMessages([`Streaming error: ${error}`]); - await turnContext.sendActivity(error); }) .on("close", () => { - inferenceScope.recordOutputTokens(Math.ceil(totalResponseLength / 4)); // Rough estimate + inferenceScope.recordOutputTokens(Math.ceil(totalResponseLength / 4)); inferenceScope.recordFinishReasons(["stop"]); }); await devinClient.invokeAgent(userMessage, responseStream); + stopTypingLoop(); + if (streamErrorMessage) { + await turnContext.sendActivity("There was an error processing your request, please try again."); + } else if (chunks.length > 0) { + await turnContext.sendActivity(chunks.join("\n\n")); + } else { + await turnContext.sendActivity("Devin did not return a response. Please try again."); + } } catch (error) { + stopTypingLoop(); invokeAgentScope.recordOutputMessages([`LLM error: ${error}`]); await turnContext.sendActivity( "There was an error processing your request" ); } finally { - stopTypingLoop(); + inferenceScope?.dispose(); } } diff --git a/nodejs/devin/sample-agent/src/devin-client.ts b/nodejs/devin/sample-agent/src/devin-client.ts index aea6d1f3..c14222bb 100644 --- a/nodejs/devin/sample-agent/src/devin-client.ts +++ b/nodejs/devin/sample-agent/src/devin-client.ts @@ -146,7 +146,8 @@ export class DevinClient implements Client { const messageContent = String(latestMessage?.message); responseStream.emit("data", messageContent); sentMessages.add(latestMessage.event_id); - console.debug(`emit data event with content: ${messageContent}}`); + console.debug(`emit data event with content: ${messageContent}`); + break; } } } diff --git a/nodejs/devin/sample-agent/src/index.ts b/nodejs/devin/sample-agent/src/index.ts index 6a780010..f0c98e77 100644 --- a/nodejs/devin/sample-agent/src/index.ts +++ b/nodejs/devin/sample-agent/src/index.ts @@ -33,7 +33,7 @@ const server = app `\nServer listening to port ${port} for appId ${authConfig.clientId} debug ${process.env.DEBUG}` ); }) - .on("error", async (err) => { + .on("error", async (err: Error) => { console.error(err); process.exit(1); }) diff --git a/nodejs/devin/sample-agent/src/utils.ts b/nodejs/devin/sample-agent/src/utils.ts index 1ba74519..9793329e 100644 --- a/nodejs/devin/sample-agent/src/utils.ts +++ b/nodejs/devin/sample-agent/src/utils.ts @@ -2,14 +2,13 @@ // Licensed under the MIT License. import { - ExecutionType, - InvokeAgentDetails, + AgentDetails, TenantDetails, } from "@microsoft/agents-a365-observability"; import { TurnContext } from "@microsoft/agents-hosting"; // Helper functions to extract agent and tenant details from context -export function getAgentDetails(context: TurnContext): InvokeAgentDetails { +export function getAgentDetails(context: TurnContext): AgentDetails { // Extract agent ID from activity recipient - use agenticAppId (camelCase, not underscore) const agentId = (context.activity.recipient as any)?.agenticAppId || @@ -30,12 +29,6 @@ export function getAgentDetails(context: TurnContext): InvokeAgentDetails { (context.activity.recipient as any)?.name || process.env.AGENT_NAME || "Devin Agent Sample", - conversationId: context.activity.conversation?.id, - request: { - content: context.activity.text || "Unknown text", - executionType: ExecutionType.HumanToAgent, - sessionId: context.activity.conversation?.id, - }, }; } diff --git a/nodejs/devin/sample-agent/tsconfig.json b/nodejs/devin/sample-agent/tsconfig.json index 0e188450..57c32c76 100644 --- a/nodejs/devin/sample-agent/tsconfig.json +++ b/nodejs/devin/sample-agent/tsconfig.json @@ -16,5 +16,6 @@ "rootDir": "src", "outDir": "dist", "tsBuildInfoFile": "dist/.tsbuildinfo" - } + }, + "exclude": ["publish", "dist", "node_modules"] } \ No newline at end of file