-
Notifications
You must be signed in to change notification settings - Fork 1
Add desktop API chat adapter package #26
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,3 @@ | ||
| dist | ||
| node_modules | ||
| coverage |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,70 @@ | ||
| # Beeper Desktop API Chat Adapter | ||
|
|
||
| Chat SDK adapter for Beeper Desktop API built on top of `@beeper/desktop-api`. | ||
|
|
||
| ## Install | ||
|
|
||
| ```bash | ||
| yarn add @beeper/chat-adapter-desktop-api | ||
| ``` | ||
|
|
||
| ## Usage | ||
|
|
||
| ```ts | ||
| import { Chat } from "chat"; | ||
| import { createMemoryState } from "@chat-adapter/state-memory"; | ||
| import { createBeeperDesktopAPIAdapter } from "@beeper/chat-adapter-desktop-api"; | ||
|
|
||
| const adapter = createBeeperDesktopAPIAdapter({ | ||
| accessToken: process.env.BEEPER_DESKTOP_API_ACCESS_TOKEN!, | ||
| botUserID: process.env.BEEPER_DESKTOP_API_BOT_USER_ID, | ||
| defaultAccountID: process.env.BEEPER_DESKTOP_API_DEFAULT_ACCOUNT_ID, | ||
| }); | ||
|
|
||
| const bot = new Chat({ | ||
| userName: "bot", | ||
| adapters: { beeperDesktopAPI: adapter }, | ||
| state: createMemoryState(), | ||
| }); | ||
|
|
||
| await bot.initialize(); | ||
| console.log(adapter.getDesktopAPIServerInfo()); | ||
| await adapter.startEventsListener(["*"]); | ||
| ``` | ||
|
|
||
| ## Implemented easy-win features | ||
|
|
||
| - `openDM(userID)` with either: | ||
| - scoped input `<accountID>:<participantID>`, or | ||
| - `defaultAccountID` in adapter config | ||
| - `fetchMessage(threadID, messageID)` | ||
| - `fetchChannelInfo(channelId)` | ||
| - `listThreads(channelId, { cursor, limit })` | ||
| - auto reconnect for WS listener (`wsReconnectDelayMs`, default `3000`) | ||
| - auto server version detection (`/v1/info` first, then response headers fallback) | ||
|
|
||
| ## Logger Example | ||
|
|
||
| A runnable logger example is included in `examples/logger`. | ||
|
|
||
| Run it: | ||
|
|
||
| ```bash | ||
| BEEPER_DESKTOP_API_ACCESS_TOKEN=... yarn example:logger | ||
| ``` | ||
|
|
||
| If you run the example file directly: | ||
|
|
||
| ```bash | ||
| yarn build | ||
| cd examples/logger | ||
| BEEPER_DESKTOP_API_ACCESS_TOKEN=... node index.mjs | ||
| ``` | ||
|
|
||
| Optional env vars: | ||
|
|
||
| - `BEEPER_DESKTOP_API_BASE_URL` (default `http://localhost:23373`) | ||
| - `BEEPER_DESKTOP_API_BOT_USER_ID` | ||
| - `BEEPER_DESKTOP_API_BOT_NAME` | ||
| - `BEEPER_DESKTOP_API_CHAT_IDS` (comma-separated, default `*`) | ||
| - `BEEPER_DESKTOP_API_DEFAULT_ACCOUNT_ID` | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,84 @@ | ||
| import { Chat } from "chat"; | ||
| import { createMemoryState } from "@chat-adapter/state-memory"; | ||
| import { createBeeperDesktopAPIAdapter } from "../../dist/index.js"; | ||
|
|
||
| const accessToken = process.env.BEEPER_DESKTOP_API_ACCESS_TOKEN; | ||
| if (!accessToken) { | ||
| throw new Error("Missing BEEPER_DESKTOP_API_ACCESS_TOKEN"); | ||
| } | ||
|
|
||
| const baseUrl = process.env.BEEPER_DESKTOP_API_BASE_URL ?? "http://localhost:23373"; | ||
| const botUserID = process.env.BEEPER_DESKTOP_API_BOT_USER_ID; | ||
| const defaultAccountID = process.env.BEEPER_DESKTOP_API_DEFAULT_ACCOUNT_ID; | ||
| const chatIDs = (process.env.BEEPER_DESKTOP_API_CHAT_IDS ?? "*") | ||
| .split(",") | ||
| .map((value) => value.trim()) | ||
| .filter(Boolean); | ||
|
|
||
| const adapter = createBeeperDesktopAPIAdapter({ | ||
| baseUrl, | ||
| accessToken, | ||
| botUserID, | ||
| defaultAccountID, | ||
| autoConnectEvents: false, | ||
| userName: process.env.BEEPER_DESKTOP_API_BOT_NAME ?? "bot", | ||
| }); | ||
|
|
||
| const bot = new Chat({ | ||
| userName: process.env.BEEPER_DESKTOP_API_BOT_NAME ?? "bot", | ||
| adapters: { | ||
| beeperDesktopAPI: adapter, | ||
| }, | ||
| state: createMemoryState(), | ||
| logger: "info", | ||
| }); | ||
|
|
||
| bot.onNewMention(async (thread, message) => { | ||
| console.log("[mention]", { | ||
| threadId: thread.id, | ||
| messageId: message.id, | ||
| sender: message.author.userName, | ||
| text: message.text, | ||
| }); | ||
| await thread.subscribe(); | ||
| }); | ||
|
|
||
| bot.onNewMessage(/[\s\S]*/u, async (thread, message) => { | ||
| console.log("[new-message]", { | ||
| threadId: thread.id, | ||
| messageId: message.id, | ||
| sender: message.author.userName, | ||
| isMention: message.isMention, | ||
| text: message.text, | ||
| }); | ||
| }); | ||
|
|
||
| bot.onSubscribedMessage(async (thread, message) => { | ||
| console.log("[subscribed-message]", { | ||
| threadId: thread.id, | ||
| messageId: message.id, | ||
| sender: message.author.userName, | ||
| text: message.text, | ||
| }); | ||
| }); | ||
|
|
||
| bot.onReaction(async (event) => { | ||
| console.log("[reaction]", { | ||
| threadId: event.threadId, | ||
| messageId: event.messageId, | ||
| added: event.added, | ||
| emoji: event.rawEmoji, | ||
| by: event.user.userName, | ||
| }); | ||
| }); | ||
|
|
||
| await bot.initialize(); | ||
| console.log("BeeperDesktopAPI server info", adapter.getDesktopAPIServerInfo()); | ||
| await adapter.startEventsListener(chatIDs); | ||
|
|
||
| console.log("BeeperDesktopAPI logger is running", { | ||
| baseUrl, | ||
| chatIDs, | ||
| }); | ||
|
|
||
| await new Promise(() => {}); | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,63 @@ | ||
| { | ||
| "name": "@beeper/chat-adapter-desktop-api", | ||
| "version": "4.5.0", | ||
| "description": "Beeper Desktop API adapter for Chat SDK", | ||
| "type": "module", | ||
| "main": "./dist/index.js", | ||
| "module": "./dist/index.js", | ||
| "types": "./dist/index.d.ts", | ||
| "exports": { | ||
| ".": { | ||
| "types": "./dist/index.d.ts", | ||
| "import": "./dist/index.js" | ||
| } | ||
| }, | ||
| "files": [ | ||
| "dist" | ||
| ], | ||
| "author": "Beeper Desktop <help@beeper.com>", | ||
| "packageManager": "yarn@1.22.22", | ||
| "private": false, | ||
| "scripts": { | ||
| "build": "yarn clean && tsup && node ./scripts/prepare-dist.mjs", | ||
| "dev": "tsup --watch", | ||
| "test": "vitest run --coverage", | ||
| "test:watch": "vitest", | ||
| "typecheck": "tsc --noEmit", | ||
| "clean": "rm -rf dist", | ||
| "example:logger": "yarn build && node examples/logger/index.mjs" | ||
| }, | ||
| "dependencies": { | ||
| "@beeper/desktop-api": "^4.2.3", | ||
| "@chat-adapter/shared": "^4.13.4", | ||
| "chat": "^4.13.4", | ||
| "ws": "^8.18.3" | ||
| }, | ||
| "devDependencies": { | ||
| "@vitest/coverage-v8": "^2.1.8", | ||
| "@chat-adapter/state-memory": "^4.13.4", | ||
| "@types/node": "^22.10.2", | ||
| "@types/ws": "^8.18.1", | ||
| "tsup": "^8.3.5", | ||
| "typescript": "^5.7.2", | ||
| "vitest": "^2.1.8" | ||
| }, | ||
| "repository": { | ||
| "type": "git", | ||
| "url": "git+https://github.com/beeper/desktop-api-js.git", | ||
| "directory": "packages/chat-adapter" | ||
| }, | ||
| "homepage": "https://github.com/beeper/desktop-api-js/tree/main/packages/chat-adapter#readme", | ||
| "bugs": { | ||
| "url": "https://github.com/beeper/desktop-api-js/issues" | ||
| }, | ||
| "publishConfig": { | ||
| "access": "public" | ||
| }, | ||
| "keywords": [ | ||
| "chat", | ||
| "beeper-desktop-api", | ||
| "adapter" | ||
| ], | ||
| "license": "MIT" | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,54 @@ | ||
| import { cpSync, existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs'; | ||
| import path from 'node:path'; | ||
| import { fileURLToPath } from 'node:url'; | ||
|
|
||
| const __dirname = path.dirname(fileURLToPath(import.meta.url)); | ||
| const packageDir = path.join(__dirname, '..'); | ||
| const distDir = path.join(packageDir, 'dist'); | ||
| const packageJsonPath = path.join(packageDir, 'package.json'); | ||
|
|
||
| if (!existsSync(distDir)) { | ||
| throw new Error(`Expected dist directory at ${distDir}`); | ||
| } | ||
|
|
||
| const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf8')); | ||
|
|
||
| const mapPath = (value) => { | ||
| if (typeof value !== 'string') return value; | ||
| return value.replace(/^\.\/dist\//, './'); | ||
| }; | ||
|
|
||
| const mapExports = (value) => { | ||
| if (typeof value === 'string') return mapPath(value); | ||
| if (Array.isArray(value)) return value.map(mapExports); | ||
| if (value && typeof value === 'object') { | ||
| return Object.fromEntries(Object.entries(value).map(([key, inner]) => [key, mapExports(inner)])); | ||
| } | ||
| return value; | ||
| }; | ||
|
|
||
| const publishPackage = { | ||
| name: packageJson.name, | ||
| version: packageJson.version, | ||
| description: packageJson.description, | ||
| type: packageJson.type, | ||
| main: mapPath(packageJson.main), | ||
| module: mapPath(packageJson.module), | ||
| types: mapPath(packageJson.types), | ||
| exports: mapExports(packageJson.exports), | ||
| author: packageJson.author, | ||
| repository: packageJson.repository, | ||
| homepage: packageJson.homepage, | ||
| bugs: packageJson.bugs, | ||
| publishConfig: packageJson.publishConfig, | ||
| keywords: packageJson.keywords, | ||
| license: packageJson.license, | ||
| dependencies: packageJson.dependencies, | ||
| }; | ||
|
|
||
| writeFileSync(path.join(distDir, 'package.json'), JSON.stringify(publishPackage, null, 2) + '\n'); | ||
|
|
||
| mkdirSync(path.join(distDir, 'examples', 'logger'), { recursive: true }); | ||
| cpSync(path.join(packageDir, 'README.md'), path.join(distDir, 'README.md')); | ||
| cpSync(path.join(packageDir, 'examples', 'logger', 'index.mjs'), path.join(distDir, 'examples', 'logger', 'index.mjs')); | ||
| cpSync(path.join(packageDir, '..', '..', 'LICENSE'), path.join(distDir, 'LICENSE')); |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,14 @@ | ||
| declare module '@beeper/desktop-api' { | ||
| export default class BeeperDesktop { | ||
| constructor(options: { accessToken: string; baseURL: string }); | ||
|
|
||
| messages: any; | ||
| accounts: any; | ||
| chats: any; | ||
|
|
||
| get<T = unknown>(path: string, options?: unknown): any; | ||
| put(path: string, options?: unknown): Promise<unknown>; | ||
| post(path: string, options?: unknown): Promise<unknown>; | ||
| delete(path: string, options?: unknown): Promise<unknown>; | ||
| } | ||
|
Comment on lines
+1
to
+13
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🏁 Script executed: #!/bin/bash
set -euo pipefail
python - <<'PY'
import io, json, pathlib, tarfile, urllib.request
meta = json.load(urllib.request.urlopen('https://registry.npmjs.org/%40beeper%2Fdesktop-api'))
latest = meta['dist-tags']['latest']
version = meta['versions'][latest]
print('latest:', latest)
print('types field:', version.get('types') or version.get('typings'))
tarball = version['dist']['tarball']
data = urllib.request.urlopen(tarball).read()
with tarfile.open(fileobj=io.BytesIO(data), mode='r:gz') as tar:
dts = sorted(name for name in tar.getnames() if name.endswith('.d.ts'))
print('published .d.ts sample:')
for name in dts[:20]:
print(name)
print('\nlocal any-typed members:')
for line in pathlib.Path('packages/chat-adapter/src/beeper-desktop-api.d.ts').read_text().splitlines():
if ': any' in line or '): any' in line:
print(line)
PYRepository: beeper/desktop-api-js Length of output: 2646 🏁 Script executed: #!/bin/bash
# Check if the file exists and read its content
echo "=== Checking if the file exists ==="
if [ -f "packages/chat-adapter/src/beeper-desktop-api.d.ts" ]; then
echo "File exists. Content:"
cat -n "packages/chat-adapter/src/beeper-desktop-api.d.ts"
else
echo "File not found. Listing packages/chat-adapter/src directory:"
ls -la packages/chat-adapter/src/ 2>/dev/null || echo "Directory does not exist"
fi
echo -e "\n=== Checking for `@beeper/desktop-api` in package.json ==="
find packages -name "package.json" -type f | while read pkgjson; do
if grep -q "@beeper/desktop-api" "$pkgjson"; then
echo "Found in: $pkgjson"
grep "@beeper/desktop-api" "$pkgjson"
fi
doneRepository: beeper/desktop-api-js Length of output: 1018 🏁 Script executed: #!/bin/bash
# Search for imports/usage of `@beeper/desktop-api` in the codebase
echo "=== Searching for imports of `@beeper/desktop-api` ==="
rg "from\s+['\"]@beeper/desktop-api" --type ts --type tsx --type js --type jsx -A 2Repository: beeper/desktop-api-js Length of output: 162 🏁 Script executed: #!/bin/bash
# Check if there are any other type declaration files or references
echo "=== Looking for references to BeeperDesktop class ==="
rg "BeeperDesktop" --type ts --type tsx --type js --type jsx -A 1 -B 1 | head -50Repository: beeper/desktop-api-js Length of output: 163 🏁 Script executed: #!/bin/bash
# Try simpler search without file type specifiers
echo "=== Searching for imports of `@beeper/desktop-api` ==="
rg "from.*@beeper/desktop-api|import.*@beeper/desktop-api" -A 2
echo -e "\n=== Searching for BeeperDesktop usage ==="
rg "BeeperDesktop" -A 1 -B 1Repository: beeper/desktop-api-js Length of output: 47448 🏁 Script executed: #!/bin/bash
# Check if there are other SDK type files in node_modules or dist
echo "=== Checking for actual SDK types in dist/node_modules ==="
find . -path ./node_modules -prune -o -name "@beeper" -type d -print 2>/dev/null | head -20
echo -e "\n=== Looking at package.json to understand the dev setup ==="
cat packages/chat-adapter/package.json | grep -A 5 -B 5 "@beeper/desktop-api"Repository: beeper/desktop-api-js Length of output: 486 🏁 Script executed: #!/bin/bash
# Verify the actual SDK typing by checking src/client.ts constructor and exports
echo "=== Actual SDK constructor from src/client.ts ==="
grep -A 10 "constructor(" src/client.ts | head -20
echo -e "\n=== SDK exports typed resources ==="
grep -E "BeeperDesktop\.(Accounts|Chats|Messages|Assets)" src/client.ts | head -10
echo -e "\n=== Verify get method signature in SDK ==="
rg "^\s+get<" src/ -A 2 | head -15Repository: beeper/desktop-api-js Length of output: 979 🏁 Script executed: #!/bin/bash
# Check how the shim actually impacts type usage in chat-adapter
echo "=== How chat-adapter uses BeeperDesktop from the shim ==="
grep -A 5 "new BeeperDesktop" packages/chat-adapter/src/index.ts
echo -e "\n=== Type annotations in chat-adapter that rely on SDK types ==="
grep "BeeperDesktopAPI" packages/chat-adapter/src/index.ts | head -5Repository: beeper/desktop-api-js Length of output: 680 Remove this ambient module declaration and use the SDK's published types. The official Either remove the shim and rely on the SDK's published declarations, or use module augmentation to extend only the missing pieces instead of redeclaring the whole module. 🤖 Prompt for AI Agents |
||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The quickstart omits packages required by the sample.
The snippet imports
chatand@chat-adapter/state-memory, but the install section only adds@beeper/chat-adapter-desktop-api.@chat-adapter/state-memoryis only adevDependencyin this package, so the README cannot work as written for consumers. Either list the extra installs here or rewrite the sample around packages the reader is explicitly told to add.📦 Suggested README fix
Also applies to: 14-16
🤖 Prompt for AI Agents