Skip to content

hahnfeld/teams-adapter

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

87 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

@hahnfeld/teams-adapter

Microsoft Teams adapter plugin for OpenACP — real-time streaming, rich message composition, slash commands.

Features

  • Streaming Message Composer — Single message per session with live-updating header (tool activity), body (streamed text), and footer (usage/completion)
  • Per-conversation Rate Limiter — Multi-window sliding rate limiter (6/1s, 7/2s, 55/30s, 1700/hr) with operation coalescing
  • Slash Commands — Full command suite: /new, /cancel, /agents, /menu, and more
  • Threads — Session threads within Teams channels
  • Permissions — Allow/Deny/Always Allow via Adaptive Card buttons
  • Output Modes — Low/Medium/High detail levels
  • File Sharing — Upload and share files via Microsoft Graph / OneDrive
  • Interactive Install Wizard — Guided setup with credential validation, auto-discovery, and app package generation
  • Auto-session Creation — First message automatically creates a session with the default agent

Prerequisites

  • OpenACP CLI >= 2026.0.0
  • Node.js 18+
  • An Azure Bot registration with the Microsoft Teams channel enabled
  • A Microsoft 365 tenant with a Teams team and channel (a Business Basic trial works for testing)

Installation

Option A: OpenACP plugin install (recommended)

openacp plugin install @hahnfeld/teams-adapter

This launches an interactive wizard that walks you through Azure Bot setup, credential validation, team/channel selection, and generates a Teams app package for sideloading.

Option B: Manual npm install

npm install @hahnfeld/teams-adapter
# or
pnpm add @hahnfeld/teams-adapter

Azure Bot Setup

Before configuring the adapter you need an Azure Bot registration. If you don't have one yet:

  1. Go to the Azure Bot creation page
  2. Fill in:
    • Bot handle: any unique name (e.g. openacp-bot)
    • Pricing tier: Free (F0) for testing
    • App type: "Single Tenant" for enterprise use
    • Creation type: "Create new Microsoft App ID"
  3. Click Create and wait for deployment
  4. Go to the Bot resource > Settings > Configuration
    • Copy the Microsoft App ID — this is your botAppId
  5. Click Manage Password > New client secret
    • Copy the secret value immediately (shown only once) — this is your botAppPassword
  6. Under Channels, add the Microsoft Teams channel

For full details see the Azure Bot Service docs.

Configuration

Interactive wizard

If you installed via openacp plugin install, the wizard runs automatically. To re-run it later:

openacp plugin configure @hahnfeld/teams-adapter

The wizard guides you through:

  1. Bot credential entry and real-time validation
  2. Tenant type selection (single-tenant enterprise vs. multi-tenant)
  3. Team and channel selection — auto-discovered via Graph API, or paste a Teams channel link
  4. Optional notification channel for session completions and errors
  5. Optional Graph API file sharing (OneDrive)
  6. Auto-generates a Teams app package (openacp-bot.zip) for sideloading

Adding the bot to Teams

After the wizard completes, you need to upload the app package to Teams:

  1. The wizard generates openacp-bot.zip — note the path it prints
  2. Open Microsoft Teams
  3. Go to Apps (left sidebar) > Manage your apps > Upload a custom app
  4. Select the openacp-bot.zip file
  5. Click Add to a team > select your team > Set up a bot

The bot will now appear in your team. You can @mention it in channels or DM it directly.

Manual configuration

Add the following to your openacp.yaml:

channels:
  teams:
    enabled: true
    botAppId: "${TEAMS_BOT_APP_ID}"
    botAppPassword: "${TEAMS_BOT_APP_PASSWORD}"
    tenantId: "${TEAMS_TENANT_ID}"
    teamId: "${TEAMS_TEAM_ID}"
    channelId: "${TEAMS_CHANNEL_ID}"
    allowedChannelIds:                                   # optional — defaults to [channelId]
      - "${TEAMS_CHANNEL_ID}"                            # additional channels can be added here
    botPort: 3978                                        # Bot Framework port (default)
    notificationChannelId: "${TEAMS_NOTIFICATION_CHANNEL_ID}"  # optional
    assistantThreadId: null  # auto-set after first run
    graphClientSecret: "${TEAMS_GRAPH_CLIENT_SECRET}"   # optional, for file sharing

Configuration reference

Field Type Required Description
enabled boolean Yes Enable the Teams adapter
botAppId string Yes Azure AD App ID for the bot
botAppPassword string Yes Bot client secret (proves bot identity to Azure Bot Service)
tenantId string Yes Microsoft tenant ID (GUID), or botframework.com for multi-tenant
teamId string Yes Default team ID (groupId GUID)
channelId string Yes Primary channel for sessions (e.g. 19:abc@thread.tacv2)
allowedChannelIds string[] No Channels the bot will respond in. Defaults to [channelId]. Add extra channels here to expand access.
botPort number No Bot Framework HTTP server port (default: 3978)
notificationChannelId string | null No Separate channel for notifications
assistantThreadId string | null No Thread for the assistant (auto-populated)
graphClientSecret string No Azure AD client secret for Graph API file sharing

Finding your Team and Channel IDs

From a channel link (easiest):

  1. Open Microsoft Teams
  2. Right-click the channel name > Get link to channel
  3. The link contains groupId (Team ID) and the channel path

Both teams.microsoft.com and teams.cloud.microsoft link formats are supported.

From the Azure/Graph API:

  • Team ID = the groupId parameter from the Teams URL
  • Channel ID = the encoded string like 19:xxx@thread.tacv2

Networking: Bot port vs API port

The Teams adapter runs its own HTTP server for Bot Framework webhook traffic. This is separate from the OpenACP API server:

Server Default Port Purpose
Bot Framework (this adapter) 3978 Receives messages from Azure Bot Service
OpenACP API 21420 REST API, SSE, web UI

Your tunnel must point to the bot port (3978), not the OpenACP API port. If you use OpenACP's built-in tunnel, it tunnels the API port — the Teams adapter requests its own separate tunnel on the bot port automatically.

The bot port is configurable via the botPort setting (default: 3978, the Bot Framework standard).

Messaging endpoint

After installation, set the bot's messaging endpoint in Azure:

  1. Azure Portal > Bot resource > Configuration > Messaging endpoint
  2. Set it to: https://<your-tunnel-url>/api/messages
  3. The URL must reach port 3978 (or your configured botPort) on the machine running OpenACP

Tunneling

The adapter automatically requests a tunnel on the bot port at startup if an OpenACP tunnel provider is available. The tunnel URL is logged on boot.

Recommended: @hahnfeld/devtunnel-provider — a tunnel provider plugin using Microsoft Dev Tunnels, which aligns with the Microsoft/Azure ecosystem:

openacp plugin install @hahnfeld/devtunnel-provider

Manual tunnel setup (if not using an OpenACP tunnel provider):

# Install Dev Tunnels CLI
brew install --cask devtunnel   # macOS

# Login and create a persistent tunnel
devtunnel user login
devtunnel create --allow-anonymous
devtunnel port create -p 3978

# Host it (use the same tunnel ID each time for a stable URL)
devtunnel host <tunnel-id> --allow-anonymous

Set the tunnel URL as the messaging endpoint in Azure: https://<id>.devtunnels.ms/api/messages

Important: Always use --allow-anonymous — Azure Bot Service cannot authenticate with Dev Tunnel auth.

Security

The bot uses a channel allowlist — it only responds in the configured channelId (and any additional channels listed in allowedChannelIds). Messages from other channels are silently ignored. This prevents the bot from responding in channels where it wasn't intentionally installed.

The bot also operates within your Azure AD tenant (single-tenant configuration), which prevents external access.

For enterprise deployments with many users, tenant-level auth alone is not sufficient — any user in your organization who can sideload a Teams app could create their own app manifest pointing to the same bot App ID and gain access to agent sessions, your configured workspace, and any tools available to the agent.

Recommended: Configure the built-in @openacp/security plugin to restrict which users can interact with the bot:

openacp plugin configure @openacp/security

Add your Teams user ID to the allowedUserIds list. The security plugin registers middleware on all incoming messages at the OpenACP core level, blocking unauthorized users before any adapter code runs. You can find your Teams user ID in the OpenACP logs when you send a message (the userId field).

Summary of layers:

Layer Protects against
allowedChannelIds Bot responding in wrong Teams channel
Single-tenant Azure AD Users from other organizations
@openacp/security plugin (allowedUserIds) Untrusted users in your tenant
Teams Admin Center (app permission policies) Users installing the sideloaded app
Tunnel access controls Unauthorized network access

Important: Without allowedUserIds configured, any authenticated user in your tenant can execute agent commands, create sessions, and access your configured workspace through the bot.

Personal chat (1:1 DMs)

By default the bot ignores personal DMs — only the configured channel(s) in allowedChannelIds are accepted. This is secure by default.

If you want to allow personal DMs, you need to add the DM conversation IDs to allowedChannelIds. Here's how to find a conversation ID:

  1. Enable debug logging in OpenACP (logLevel: debug)
  2. Send a message to the bot via personal DM in Teams
  3. Look in the OpenACP logs for the conversationId of the incoming message — it will look like 19:xxx@thread.tacv2 or a similar Teams thread ID format
  4. Add that ID to allowedChannelIds in your config:
channels:
  teams:
    channelId: "19:primary-channel@thread.tacv2"
    allowedChannelIds:
      - "19:primary-channel@thread.tacv2"
      - "19:your-personal-dm-id@thread.tacv2"   # add DM conversation IDs here

Note: Each personal DM between a user and the bot has its own unique conversation ID. You would need to add each user's DM thread ID individually.

Slash Commands

Command Description
/new [agent] Create a new agent session
/newchat New chat, same agent & workspace
/cancel Cancel the current session
/status Show session or global status
/sessions List all sessions
/agents List available agents
/install <name> Install an agent by name
/menu Show the action menu
/help Show help
/outputmode low|medium|high Set output detail level
/bypass Auto-approve permissions
/doctor Run system diagnostics
/handoff Generate terminal resume command
/restart Restart OpenACP
/respawn Restart the assistant session
/update Update to latest version
/settings Show configuration settings
/integrate Manage agent integrations
/clear Reset the assistant session
/tts [on|off] Toggle Text to Speech
/mode Switch session mode
/model Switch AI model
/thought Adjust thinking level

Uninstalling

openacp plugin uninstall @hahnfeld/teams-adapter --purge

The --purge flag removes all saved settings. After uninstalling, you may also want to:

  1. Remove the bot from your Teams team
  2. Delete the Azure Bot resource in the Azure Portal

Development

# Install dependencies
pnpm install

# Build
pnpm build

# Watch mode
pnpm dev

# Run tests
pnpm test

Architecture

Message Rendering Model

All agent output is consolidated into a single Teams message per turn, edited in place:

┌─────────────────────────────────────────────┐
│ **Session Name**                            │  ← TITLE (persistent, bold)
│ *🔧 Reading file src/adapter.ts...*         │  ← HEADER (ephemeral, italic)
│─────────────────────────────────────────────│
│                                             │
│ Here's what I found in the adapter...       │  ← BODY (streamed text + attachments)
│ The function handles three cases:           │
│                                             │
│─────────────────────────────────────────────│
│ *44k tokens · $0.14 · Task completed*       │  ← FOOTER (persistent, italic)
└─────────────────────────────────────────────┘
Message Type Zone Behavior
text Body Streamed, edited in place
thought Header Ephemeral, 💭 prefix, replaced by next header
tool_call Header Ephemeral, 🔧 prefix, replaced by next header
tool_update Header Ephemeral, 🔧 prefix, replaced by next header
usage Footer Persistent, dot-separated stats + "Task completed"
plan Separate Single message, updated in place per session
error Separate Standalone ❌ message
mode_change Separate ⚙️ prefix
config_update Separate ⚙️ prefix
model_update Separate ⚙️ prefix
system_message Separate ⚙️ prefix

Source Layout

src/
├── index.ts              # Plugin entry point & public exports
├── plugin.ts             # Plugin factory (install wizard, configure, setup/teardown)
├── adapter.ts            # TeamsAdapter — extends MessagingAdapter
├── message-composer.ts   # SessionMessage (title/header/body/footer zones)
├── rate-limiter.ts       # Per-conversation rate limiter with coalescing
├── app-package.ts        # Teams app manifest package generator
├── activity.ts           # Type re-exports from plugin-sdk
├── formatting.ts         # Text formatting helpers (tool summaries, plans, usage)
├── permissions.ts        # PermissionHandler (Adaptive Card buttons)
├── graph.ts              # GraphFileClient (OneDrive file sharing)
├── media.ts              # File download/upload utilities
├── conversation-store.ts # Conversation reference storage
├── send-utils.ts         # Message sending helpers (Teams SDK compat)
├── assistant.ts          # Assistant session spawning
├── validators.ts         # Credential & tenant validation, Teams link parsing
├── types.ts              # TeamsChannelConfig, TeamsPlatformData
└── commands/
    ├── index.ts           # Command router + SLASH_COMMANDS registry
    ├── new-session.ts     # /new, /newchat
    ├── session.ts         # /cancel, /status, /sessions, /handoff
    ├── admin.ts           # /bypass, /tts, /restart, /respawn, /update, /outputmode
    ├── menu.ts            # /menu, /help, /clear
    ├── agents.ts          # /agents, /install
    ├── doctor.ts          # /doctor
    ├── integrate.ts       # /integrate
    └── settings.ts        # /settings

Known Issues

  • openacp plugin configure does not work for npm-installed plugins. This is a bug in the OpenACP CLI where the configure command only resolves built-in plugins. To reconfigure the adapter after initial setup, re-run the install wizard:

    openacp plugin install @hahnfeld/teams-adapter

Tech Stack

License

MIT

About

No description, website, or topics provided.

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Contributors