feat(msteams): add MS Teams provider skeleton
- Add Microsoft 365 Agents SDK packages (@microsoft/agents-hosting, @microsoft/agents-hosting-express, @microsoft/agents-hosting-extensions-teams) - Add MSTeamsConfig type and zod schema - Create src/msteams/ provider with monitor, token, send, probe - Wire provider into gateway (server-providers.ts, server.ts) - Add msteams to all provider type unions (hooks, queue, cron, etc.) - Update implementation guide with new SDK and progress
This commit is contained in:
4
src/msteams/index.ts
Normal file
4
src/msteams/index.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
export { monitorMSTeamsProvider } from "./monitor.js";
|
||||
export { probeMSTeams } from "./probe.js";
|
||||
export { sendMessageMSTeams } from "./send.js";
|
||||
export { type MSTeamsCredentials, resolveMSTeamsCredentials } from "./token.js";
|
||||
111
src/msteams/monitor.ts
Normal file
111
src/msteams/monitor.ts
Normal file
@@ -0,0 +1,111 @@
|
||||
import type { ClawdbotConfig } from "../config/types.js";
|
||||
import { getChildLogger } from "../logging.js";
|
||||
import type { RuntimeEnv } from "../runtime.js";
|
||||
import { resolveMSTeamsCredentials } from "./token.js";
|
||||
|
||||
const log = getChildLogger({ name: "msteams:monitor" });
|
||||
|
||||
export type MonitorMSTeamsOpts = {
|
||||
cfg: ClawdbotConfig;
|
||||
runtime?: RuntimeEnv;
|
||||
abortSignal?: AbortSignal;
|
||||
};
|
||||
|
||||
export type MonitorMSTeamsResult = {
|
||||
app: unknown;
|
||||
shutdown: () => Promise<void>;
|
||||
};
|
||||
|
||||
export async function monitorMSTeamsProvider(
|
||||
opts: MonitorMSTeamsOpts,
|
||||
): Promise<MonitorMSTeamsResult> {
|
||||
const msteamsCfg = opts.cfg.msteams;
|
||||
if (!msteamsCfg?.enabled) {
|
||||
log.debug("msteams provider disabled");
|
||||
return { app: null, shutdown: async () => {} };
|
||||
}
|
||||
|
||||
const creds = resolveMSTeamsCredentials(msteamsCfg);
|
||||
if (!creds) {
|
||||
log.error("msteams credentials not configured");
|
||||
return { app: null, shutdown: async () => {} };
|
||||
}
|
||||
|
||||
const port = msteamsCfg.webhook?.port ?? 3978;
|
||||
const path = msteamsCfg.webhook?.path ?? "/msteams/messages";
|
||||
|
||||
log.info(`starting msteams provider on port ${port}${path}`);
|
||||
|
||||
// Dynamic import to avoid loading SDK when provider is disabled
|
||||
const agentsHosting = await import("@microsoft/agents-hosting");
|
||||
const { startServer } = await import("@microsoft/agents-hosting-express");
|
||||
|
||||
const { ActivityHandler } = agentsHosting;
|
||||
type TurnContext = InstanceType<typeof agentsHosting.TurnContext>;
|
||||
|
||||
// Create activity handler using fluent API
|
||||
const handler = new ActivityHandler()
|
||||
.onMessage(async (context: TurnContext, next: () => Promise<void>) => {
|
||||
const text = context.activity?.text?.trim() ?? "";
|
||||
const from = context.activity?.from;
|
||||
const conversation = context.activity?.conversation;
|
||||
|
||||
log.debug("received message", {
|
||||
text: text.slice(0, 100),
|
||||
from: from?.id,
|
||||
conversation: conversation?.id,
|
||||
});
|
||||
|
||||
// TODO: Implement full message handling
|
||||
// - Route to agent based on config
|
||||
// - Process commands
|
||||
// - Send reply via context.sendActivity()
|
||||
|
||||
// Echo for now as a test
|
||||
await context.sendActivity(`Received: ${text}`);
|
||||
await next();
|
||||
})
|
||||
.onMembersAdded(async (context: TurnContext, next: () => Promise<void>) => {
|
||||
const membersAdded = context.activity?.membersAdded ?? [];
|
||||
for (const member of membersAdded) {
|
||||
if (member.id !== context.activity?.recipient?.id) {
|
||||
log.debug("member added", { member: member.id });
|
||||
await context.sendActivity("Hello! I'm Clawdbot.");
|
||||
}
|
||||
}
|
||||
await next();
|
||||
});
|
||||
|
||||
// Auth configuration using the new SDK format
|
||||
const authConfig = {
|
||||
clientId: creds.appId,
|
||||
clientSecret: creds.appPassword,
|
||||
tenantId: creds.tenantId,
|
||||
};
|
||||
|
||||
// Set env vars that startServer reads (it uses loadAuthConfigFromEnv internally)
|
||||
process.env.clientId = creds.appId;
|
||||
process.env.clientSecret = creds.appPassword;
|
||||
process.env.tenantId = creds.tenantId;
|
||||
process.env.PORT = String(port);
|
||||
|
||||
// Start the server
|
||||
const expressApp = startServer(handler, authConfig);
|
||||
|
||||
log.info(`msteams provider started on port ${port}`);
|
||||
|
||||
const shutdown = async () => {
|
||||
log.info("shutting down msteams provider");
|
||||
// Express app doesn't have a direct close method
|
||||
// The server is managed by startServer internally
|
||||
};
|
||||
|
||||
// Handle abort signal
|
||||
if (opts.abortSignal) {
|
||||
opts.abortSignal.addEventListener("abort", () => {
|
||||
void shutdown();
|
||||
});
|
||||
}
|
||||
|
||||
return { app: expressApp, shutdown };
|
||||
}
|
||||
23
src/msteams/probe.ts
Normal file
23
src/msteams/probe.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
import type { MSTeamsConfig } from "../config/types.js";
|
||||
import { resolveMSTeamsCredentials } from "./token.js";
|
||||
|
||||
export type ProbeMSTeamsResult = {
|
||||
ok: boolean;
|
||||
error?: string;
|
||||
appId?: string;
|
||||
};
|
||||
|
||||
export async function probeMSTeams(
|
||||
cfg?: MSTeamsConfig,
|
||||
): Promise<ProbeMSTeamsResult> {
|
||||
const creds = resolveMSTeamsCredentials(cfg);
|
||||
if (!creds) {
|
||||
return {
|
||||
ok: false,
|
||||
error: "missing credentials (appId, appPassword, tenantId)",
|
||||
};
|
||||
}
|
||||
|
||||
// TODO: Validate credentials by attempting to get a token
|
||||
return { ok: true, appId: creds.appId };
|
||||
}
|
||||
25
src/msteams/send.ts
Normal file
25
src/msteams/send.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
import type { MSTeamsConfig } from "../config/types.js";
|
||||
import { getChildLogger } from "../logging.js";
|
||||
|
||||
const log = getChildLogger({ name: "msteams:send" });
|
||||
|
||||
export type SendMSTeamsMessageParams = {
|
||||
cfg: MSTeamsConfig;
|
||||
conversationId: string;
|
||||
text: string;
|
||||
serviceUrl: string;
|
||||
};
|
||||
|
||||
export type SendMSTeamsMessageResult = {
|
||||
ok: boolean;
|
||||
messageId?: string;
|
||||
error?: string;
|
||||
};
|
||||
|
||||
export async function sendMessageMSTeams(
|
||||
_params: SendMSTeamsMessageParams,
|
||||
): Promise<SendMSTeamsMessageResult> {
|
||||
// TODO: Implement using CloudAdapter.continueConversationAsync
|
||||
log.warn("sendMessageMSTeams not yet implemented");
|
||||
return { ok: false, error: "not implemented" };
|
||||
}
|
||||
23
src/msteams/token.ts
Normal file
23
src/msteams/token.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
import type { MSTeamsConfig } from "../config/types.js";
|
||||
|
||||
export type MSTeamsCredentials = {
|
||||
appId: string;
|
||||
appPassword: string;
|
||||
tenantId: string;
|
||||
};
|
||||
|
||||
export function resolveMSTeamsCredentials(
|
||||
cfg?: MSTeamsConfig,
|
||||
): MSTeamsCredentials | undefined {
|
||||
const appId = cfg?.appId?.trim() || process.env.MSTEAMS_APP_ID?.trim();
|
||||
const appPassword =
|
||||
cfg?.appPassword?.trim() || process.env.MSTEAMS_APP_PASSWORD?.trim();
|
||||
const tenantId =
|
||||
cfg?.tenantId?.trim() || process.env.MSTEAMS_TENANT_ID?.trim();
|
||||
|
||||
if (!appId || !appPassword || !tenantId) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return { appId, appPassword, tenantId };
|
||||
}
|
||||
Reference in New Issue
Block a user