docs: add channel integration guide
All checks were successful
Build and Push Docker Image / build (pull_request) Successful in 48s

This commit is contained in:
2026-03-12 14:44:01 +01:00
parent d9e3baaa36
commit 2ed59c0334
4 changed files with 236 additions and 0 deletions

View File

@@ -16,6 +16,7 @@ You can also keep one-shot mode (`RUN_MODE=single`) for script usage.
- Gateway internals: `docs/gateway.md`
- Web UI internals: `docs/web-ui.md`
- Channel integrations (Web UI, Slack, Matrix, custom): `docs/channels.md`
---
@@ -135,3 +136,4 @@ The gateway is transport-agnostic. For each upstream client, map its thread iden
Then post messages to `/v1/chat` or `/v1/chat/stream` with that `conversationId`.
This keeps one agent session per thread across transports.
For detailed setup guidance per transport, see `docs/channels.md`.

View File

@@ -2,3 +2,4 @@
- [Gateway: how it works](./gateway.md)
- [Web UI: how it works](./web-ui.md)
- [Channels: Web UI, Slack, Matrix, custom adapters](./channels.md)

231
docs/channels.md Normal file
View File

@@ -0,0 +1,231 @@
# Channels: Web UI, Slack, Matrix, and custom adapters
This document explains how **channels** work in the gateway and how to integrate transports like Web UI, Slack, Matrix, or your own chat source.
---
## What is a "channel" in this project?
In this gateway, a channel is not a separate server object you provision.
A channel is simply an upstream conversation context (for example: a Slack thread, a Matrix room thread, or a browser chat tab) that maps to one stable `conversationId`.
The gateway uses that `conversationId` to keep one long-lived agent session.
---
## How channels are represented internally
You have two integration options:
1. **Direct chat endpoints**
- `POST /v1/chat`
- `POST /v1/chat/stream`
- You provide `conversationId` directly.
2. **Adapter endpoints** (recommended for Slack/Matrix/etc.)
- `POST /v1/adapters/chat`
- `POST /v1/adapters/chat/stream`
- You provide channel/thread fields; the gateway derives `conversationId` for you.
Adapter ID format:
- `conversationId = source:workspaceId:channelId:threadId`
- `adapterKey = source:workspaceId:channelId:threadId:userId`
Field behavior on adapter routes:
- `source` (optional, default: `generic`)
- `workspaceId` (optional, default: `default`)
- `channelId` (**required**)
- `threadId` (optional, default: `root`)
- `userId` (optional, default: `anonymous`)
Important constraints:
- segment values must be strings
- `channelId` must not be empty
- segment values must not contain `:`
If your upstream IDs contain `:` (common in Matrix), sanitize/encode them before sending to adapter routes.
---
## Do I need to "create" a channel first?
Usually: **no**.
A conversation/session is auto-created on first message. You can optionally pre-create one via:
- `POST /v1/conversations`
But for most channel integrations, you just start sending messages to chat endpoints with stable IDs.
---
## Requirements checklist (all channels)
1. Gateway is running (`RUN_MODE=gateway`)
2. Model/provider credentials are configured (`PROVIDER`, `MODEL`, `API_KEY` or provider-specific key)
3. Your adapter/client can reach the gateway URL
4. If `GATEWAY_AUTH_TOKEN` is set, send `Authorization: Bearer <token>`
5. For browser apps on another origin, set `GATEWAY_CORS_ORIGIN`
6. If you want restart-safe sessions, set `SESSION_PERSIST=true`
---
## Web UI channel
### Built-in UI
The built-in UI is available at `GET /` when:
- `GATEWAY_ENABLE_WEB_UI=true`
How it works:
- sends messages to `/v1/chat/stream`
- stores `conversationId` in browser local storage
- "New session" clears local `conversationId` and starts a new thread
### Custom web app
If you build your own web frontend, call `/v1/chat` or `/v1/chat/stream` and choose your own ID format, e.g.:
- `web:<userId>:<chatId>`
Example:
```json
{
"conversationId": "web:user-42:chat-main",
"message": "Summarize the last response"
}
```
---
## Slack channel integration
There is no built-in Slack bot in this repository; you run a small Slack adapter service.
Typical adapter needs:
- Slack bot token + app configuration (events/interactions)
- webhook/event receiver in your adapter
- code that forwards messages to gateway and posts replies back to Slack
Recommended mapping from Slack event payload:
- `source`: `"slack"`
- `workspaceId`: Slack team/workspace ID (e.g. `T123`)
- `channelId`: Slack channel ID (e.g. `C456`) **required**
- `threadId`: Slack `thread_ts` (or fallback strategy)
- `userId`: Slack user ID (e.g. `U789`)
Example request to gateway:
```bash
curl -N -X POST http://localhost:8787/v1/adapters/chat/stream \
-H 'content-type: application/json' \
-d '{
"source": "slack",
"workspaceId": "T123",
"channelId": "C456",
"threadId": "1712233.991",
"userId": "U789",
"message": "Please summarize this thread"
}'
```
Threading strategy tip:
- use a stable `threadId` per Slack thread to keep context isolated per thread
- if you omit `threadId`, gateway uses `root` (all messages for that channel collapse into one thread context)
---
## Matrix channel integration
There is no built-in Matrix bot in this repository; use a Matrix bot/bridge process as adapter.
Typical adapter needs:
- Matrix bot account + access token
- event listener for room messages
- logic to send assistant output back into room/thread
Recommended mapping:
- `source`: `"matrix"`
- `workspaceId`: homeserver or deployment identifier (optional but useful)
- `channelId`: Matrix `room_id` (**required**, encoded/sanitized for adapter route)
- `threadId`: Matrix thread root event ID (encoded/sanitized, or omit for `root`)
- `userId`: Matrix sender ID (encoded/sanitized)
Because adapter segments cannot contain `:`, Matrix IDs should be encoded in the adapter, e.g.:
```ts
const encodeSegment = (value: string) => encodeURIComponent(value);
```
Example payload (encoded values):
```json
{
"source": "matrix",
"workspaceId": "matrix.org",
"channelId": "%21roomid%3Amatrix.org",
"threadId": "%24rootEventId%3Amatrix.org",
"userId": "%40alice%3Amatrix.org",
"message": "What did we decide in this thread?"
}
```
Alternative: call `/v1/chat` directly and provide your own `conversationId` format if you do not want per-segment adapter normalization.
---
## Custom channel template (Discord, Telegram, email, etc.)
For any source, map your upstream identifiers into adapter fields and keep that mapping stable.
Minimal payload:
```json
{
"channelId": "your-channel-id",
"message": "Hello"
}
```
Full payload template:
```json
{
"source": "custom",
"workspaceId": "tenant-a",
"channelId": "channel-123",
"threadId": "thread-456",
"userId": "user-789",
"message": "Hello"
}
```
---
## Best practices
- Keep `threadId` stable per thread (do not generate random values per message)
- Include `userId` for audit/telemetry (`adapterKey`) even though it does not change `conversationId`
- Avoid `:` in any adapter segment value
- Use streaming endpoints for better UX (`/stream`)
- Persist sessions in production (`SESSION_PERSIST=true`)
- Add retry/backoff logic in your adapter for network failures
---
## Related docs
- Gateway internals/API: `docs/gateway.md`
- Built-in browser UI: `docs/web-ui.md`

View File

@@ -139,6 +139,8 @@ Adapter request fields (`source`, `workspaceId`, `channelId`, `threadId`, `userI
`channelId` is required. `:` is not allowed inside segment values.
For practical setup patterns per transport (Web UI, Slack, Matrix, custom), see `docs/channels.md`.
---
## Streaming (SSE) behavior