Skip to content

Add desktop API chat adapter package#26

Open
batuhan wants to merge 1 commit intomainfrom
feat/packages-chat-adapter
Open

Add desktop API chat adapter package#26
batuhan wants to merge 1 commit intomainfrom
feat/packages-chat-adapter

Conversation

@batuhan
Copy link
Member

@batuhan batuhan commented Mar 8, 2026

Summary

  • add @beeper/chat-adapter-desktop-api under packages/chat-adapter
  • depend on the latest published @beeper/desktop-api release in both source and packaged dist metadata
  • make package verification scripts runnable by adding the Vitest coverage provider and allowing an empty test set for now

Testing

  • yarn typecheck
  • yarn build
  • yarn test

@coderabbitai
Copy link

coderabbitai bot commented Mar 8, 2026

📝 Walkthrough

Summary by CodeRabbit

Release Notes

  • New Features

    • Introduced Beeper Desktop API Chat Adapter package with WebSocket-based real-time messaging, support for message operations (post, edit, delete), reaction management, thread fetching, DM handling, and multi-account support.
  • Documentation

    • Added comprehensive README with installation, configuration, and usage examples.
    • Added example logger script demonstrating adapter initialization and event handling.

Walkthrough

This pull request introduces the @beeper/chat-adapter-desktop-api package, a new chat adapter implementation that bridges the Beeper Desktop API with WebSocket-based event streaming. It includes adapter initialization, message/reaction management, thread operations, and server metadata detection.

Changes

Cohort / File(s) Summary
Configuration & Build
package.json, tsconfig.json, tsup.config.ts, vitest.config.ts
Package manifest with dependencies, TypeScript/build configuration (tsup with declaration generation), and Vitest configuration with coverage setup.
Documentation & Configuration
.gitignore, README.md
Standard ignores for build output and comprehensive documentation covering installation, usage, features, and examples.
Source Implementation
src/index.ts, src/types.ts, src/beeper-desktop-api.d.ts
Core adapter implementation with WebSocket lifecycle management, message/reaction handling, thread operations, server detection; comprehensive TypeScript types and Beeper Desktop API declarations.
Build & Examples
scripts/prepare-dist.mjs, examples/logger/index.mjs
Distribution preparation script that sanitizes package.json and copies assets; example logger demonstrating adapter instantiation and event handler registration.

Sequence Diagram(s)

sequenceDiagram
    participant Client as Client Code
    participant Adapter as BeeperDesktopAPIAdapter
    participant WS as WebSocket
    participant API as Beeper Desktop API
    participant Chat as Chat Interface

    Client->>Adapter: initialize()
    Adapter->>API: GET /v1/info
    API-->>Adapter: server info + headers
    Adapter->>Adapter: detectServerInfo()
    Adapter-->>Client: initialized

    Client->>Adapter: startEventsListener(chatIds)
    Adapter->>WS: connect(wsEventsUrl)
    WS-->>Adapter: connected

    Adapter->>WS: subscribe({chatIds})
    WS-->>Adapter: subscription ack

    loop Event Processing
        WS->>Adapter: message.upserted event
        Adapter->>Adapter: process payload
        Adapter->>Adapter: toChatMessage(beeperMsg)
        Adapter->>Adapter: handle reactions
        Adapter->>Chat: emit("newMessage", chatMsg)
        Chat-->>Client: message received
    end

    WS->>Adapter: reaction.added event
    Adapter->>Adapter: update reaction state
    Adapter->>Chat: emit("reaction", reactionEvent)

    Client->>Adapter: postMessage({text, threadId})
    Adapter->>API: POST /v1/chat/.../messages
    API-->>Adapter: message created
    Adapter-->>Client: message posted
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title 'Add desktop API chat adapter package' accurately and clearly describes the main change—introducing a new chat adapter package for Beeper Desktop API.
Description check ✅ Passed The description is directly related to the changeset, detailing the new package addition, dependency declarations, and script updates with testing steps.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/packages-chat-adapter

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

🧹 Nitpick comments (5)
packages/chat-adapter/src/types.ts (1)

5-6: Pick one canonical bot user key in the public config.

Exposing both botUserID and botUserId makes the API ambiguous: callers can provide both with different values, and the type does not say which one wins. If the alias is needed for compatibility, deprecate one spelling and normalize/document precedence in the implementation instead of keeping both as first-class options.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/chat-adapter/src/types.ts` around lines 5 - 6, The public config
exposes two ambiguous keys botUserID and botUserId; pick a single canonical key
(choose one spelling, e.g., botUserId) in the types.ts declaration and remove
the duplicate to avoid ambiguity, then update the runtime normalization logic
(e.g., the config parsing/initialization path that reads external config) to
accept the deprecated alias if present by mapping botUserID -> botUserId (or
vice versa) and explicitly document the precedence (alias value is used only if
the canonical key is missing). Ensure any references to both symbols in code,
tests, and docs are updated to use the canonical name and add a short
deprecation comment where the alias mapping is handled.
packages/chat-adapter/src/index.ts (4)

703-711: Redundant property in factory function.

userName: config.userName on line 709 is redundant since config is already spread on line 707. The spread already includes userName if present.

♻️ Suggested cleanup
 export function createBeeperDesktopAPIAdapter(
   config: BeeperDesktopAPIAdapterConfig,
 ): BeeperDesktopAPIAdapter {
   return new BeeperDesktopAPIAdapter({
     ...config,
     logger: new ConsoleLogger("info", "beeper-desktop-api-adapter"),
-    userName: config.userName,
   });
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/chat-adapter/src/index.ts` around lines 703 - 711, The factory
function createBeeperDesktopAPIAdapter spreads config into the new
BeeperDesktopAPIAdapter but then redundantly sets userName: config.userName;
remove the redundant property so the returned object simply spreads ...config
(and still sets logger) to avoid duplication and keep behavior unchanged—update
the createBeeperDesktopAPIAdapter function to eliminate the explicit userName
assignment.

113-138: Consider adding a connection timeout for WebSocket.

The WebSocket connection has no timeout configured, which means if the server is unresponsive, the connection attempt could hang indefinitely. Consider adding a connection timeout or using the handshakeTimeout option.

💡 Suggested improvement
     const wsURL = this.wsEventsUrl ?? `${this.baseUrl.replace(/^http/, "ws")}/v1/ws`;
     this.ws = new WebSocket(wsURL, {
       headers: {
         Authorization: `Bearer ${this.accessToken}`,
       },
+      handshakeTimeout: 10000, // 10 second connection timeout
     });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/chat-adapter/src/index.ts` around lines 113 - 138, The WebSocket
connection created in the constructor (this.ws = new WebSocket(wsURL, { ... }))
has no connection timeout and can hang; update the WebSocket instantiation to
include a handshake timeout (e.g., pass handshakeTimeout) and handle timeout
errors by listening for the "error" or "close" events to trigger
scheduleReconnect (respecting manualStopWS and autoConnectEvents) and set
this.ws = null; ensure the timeout value is configurable or reasonable and that
logging via this.logger.error/ warn records timeout-specific info so
handleWSEvent, scheduleReconnect, manualStopWS and autoConnectEvents flow
remains correct.

175-180: Consider making forEveryone configurable.

The forEveryone: false value is hardcoded. Depending on use cases, callers may want to delete messages for everyone. Consider adding an optional parameter.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/chat-adapter/src/index.ts` around lines 175 - 180, deleteMessage
currently hardcodes forEveryone: false which prevents callers from deleting
messages for all participants; change the deleteMessage signature to accept an
optional parameter (e.g., options? or forEveryone?: boolean) with a default of
false, update callers and tests as needed, and pass that value into the request
body instead of the literal false so the function (and methods that call
decodeThreadId and this.client.delete) can request deletion for everyone when
desired.

420-429: Consider exponential backoff for reconnection.

The reconnection logic uses a fixed delay regardless of how many times it has failed. For improved resilience, consider implementing exponential backoff with a maximum delay cap.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/chat-adapter/src/index.ts` around lines 420 - 429, scheduleReconnect
currently always uses wsReconnectDelayMs fixed delay; change it to exponential
backoff by adding a retry state (e.g., reconnectAttempts or
currentReconnectDelay) and a max cap (e.g., maxWsReconnectDelayMs). Compute
delay = min(wsReconnectDelayMs * 2 ** reconnectAttempts, maxWsReconnectDelayMs)
when scheduling, increment reconnectAttempts after scheduling, and keep existing
guard on reconnectTimer. In the startEventsListener success path reset
reconnectAttempts/currentReconnectDelay to initial value so future failures
start from the base delay; preserve existing symbols (scheduleReconnect,
reconnectTimer, wsReconnectDelayMs, startEventsListener, wsSubscriptions,
logger.error) when implementing.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/chat-adapter/README.md`:
- Line 8: The quickstart install instruction only adds
`@beeper/chat-adapter-desktop-api` but the sample imports "chat" and
"@chat-adapter/state-memory"; update the README to either include the missing
install(s) (e.g., add yarn add for "chat" and "@chat-adapter/state-memory") or
change the example imports to only use packages you explicitly instruct the
reader to install; reference the sample import names ("chat",
"@chat-adapter/state-memory") and the existing install line ("yarn add
`@beeper/chat-adapter-desktop-api`") so the consumer instructions and sample code
match.

In `@packages/chat-adapter/src/beeper-desktop-api.d.ts`:
- Around line 1-13: The ambient module declaration for '@beeper/desktop-api'
collapses SDK types to any and forces a required baseURL; remove this file so
the project uses the SDK's published types, or replace it with a minimal module
augmentation that only fixes what the SDK misses (e.g., make constructor option
baseURL optional and augment types for BeeperDesktop.messages/accounts/chats and
get<T>() to return APIPromise<T>), targeting the module name
'@beeper/desktop-api' and the BeeperDesktop class and get<T>() signature so
TypeScript picks up the real APIPromise<Rsp> and typed resources instead of any.

In `@packages/chat-adapter/src/index.ts`:
- Line 52: reactionStateByMessage is an unbounded Map that will grow
indefinitely; replace it with a bounded cache (e.g., an LRU cache like lru-cache
or a custom bounded Map with eviction) or add TTL+periodic cleanup so entries
are pruned when capacity or age limits are exceeded. Change the field
declaration (reactionStateByMessage) to the cache instance and update all places
that call .get/.set/.has to use the cache API (or wrap accessors) so evictions
happen automatically; apply the same bounded-cache/TTL cleanup approach to any
other long-lived per-message state maps in this file to prevent memory leaks.
- Around line 225-239: The catch block in the method that calls
this.client.get(`/v1/chats/${encodeURIComponent(chatID)}/messages/${encodeURIComponent(messageID)}`)
is swallowing all errors; update the catch to capture the error (e.g., catch
(err)) and emit a log via the existing logger or processLogger including context
(chatID, messageID, accountID) before proceeding to the fallback
this.client.messages.search call, then continue returning
toChatMessage(found...) as before.
- Around line 331-340: The decodeThreadId function mishandles chat IDs
containing additional colons because it uses a plain split(":"), which truncates
the chatID; update decodeThreadId to only treat the first two colon separations
as prefix and accountID and treat the rest as the chatID (e.g., split into
parts, take parts[0] as prefix, parts[1] as accountID, and join the remaining
parts with ":" for chatID), then validate prefix === "beeperDesktopAPI" and that
accountID and chatID exist, and finally return accountID and chatID after
decodeURIComponent; reference decodeThreadId (and ensure compatibility with
encodeThreadId which uses encodeURIComponent).

---

Nitpick comments:
In `@packages/chat-adapter/src/index.ts`:
- Around line 703-711: The factory function createBeeperDesktopAPIAdapter
spreads config into the new BeeperDesktopAPIAdapter but then redundantly sets
userName: config.userName; remove the redundant property so the returned object
simply spreads ...config (and still sets logger) to avoid duplication and keep
behavior unchanged—update the createBeeperDesktopAPIAdapter function to
eliminate the explicit userName assignment.
- Around line 113-138: The WebSocket connection created in the constructor
(this.ws = new WebSocket(wsURL, { ... })) has no connection timeout and can
hang; update the WebSocket instantiation to include a handshake timeout (e.g.,
pass handshakeTimeout) and handle timeout errors by listening for the "error" or
"close" events to trigger scheduleReconnect (respecting manualStopWS and
autoConnectEvents) and set this.ws = null; ensure the timeout value is
configurable or reasonable and that logging via this.logger.error/ warn records
timeout-specific info so handleWSEvent, scheduleReconnect, manualStopWS and
autoConnectEvents flow remains correct.
- Around line 175-180: deleteMessage currently hardcodes forEveryone: false
which prevents callers from deleting messages for all participants; change the
deleteMessage signature to accept an optional parameter (e.g., options? or
forEveryone?: boolean) with a default of false, update callers and tests as
needed, and pass that value into the request body instead of the literal false
so the function (and methods that call decodeThreadId and this.client.delete)
can request deletion for everyone when desired.
- Around line 420-429: scheduleReconnect currently always uses
wsReconnectDelayMs fixed delay; change it to exponential backoff by adding a
retry state (e.g., reconnectAttempts or currentReconnectDelay) and a max cap
(e.g., maxWsReconnectDelayMs). Compute delay = min(wsReconnectDelayMs * 2 **
reconnectAttempts, maxWsReconnectDelayMs) when scheduling, increment
reconnectAttempts after scheduling, and keep existing guard on reconnectTimer.
In the startEventsListener success path reset
reconnectAttempts/currentReconnectDelay to initial value so future failures
start from the base delay; preserve existing symbols (scheduleReconnect,
reconnectTimer, wsReconnectDelayMs, startEventsListener, wsSubscriptions,
logger.error) when implementing.

In `@packages/chat-adapter/src/types.ts`:
- Around line 5-6: The public config exposes two ambiguous keys botUserID and
botUserId; pick a single canonical key (choose one spelling, e.g., botUserId) in
the types.ts declaration and remove the duplicate to avoid ambiguity, then
update the runtime normalization logic (e.g., the config parsing/initialization
path that reads external config) to accept the deprecated alias if present by
mapping botUserID -> botUserId (or vice versa) and explicitly document the
precedence (alias value is used only if the canonical key is missing). Ensure
any references to both symbols in code, tests, and docs are updated to use the
canonical name and add a short deprecation comment where the alias mapping is
handled.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: dbf9e16d-49e0-455f-890c-9a58ea4f8930

📥 Commits

Reviewing files that changed from the base of the PR and between 7d3347e and e6e0b30.

⛔ Files ignored due to path filters (1)
  • packages/chat-adapter/yarn.lock is excluded by !**/yarn.lock, !**/*.lock
📒 Files selected for processing (11)
  • packages/chat-adapter/.gitignore
  • packages/chat-adapter/README.md
  • packages/chat-adapter/examples/logger/index.mjs
  • packages/chat-adapter/package.json
  • packages/chat-adapter/scripts/prepare-dist.mjs
  • packages/chat-adapter/src/beeper-desktop-api.d.ts
  • packages/chat-adapter/src/index.ts
  • packages/chat-adapter/src/types.ts
  • packages/chat-adapter/tsconfig.json
  • packages/chat-adapter/tsup.config.ts
  • packages/chat-adapter/vitest.config.ts
📜 Review details
🧰 Additional context used
🧬 Code graph analysis (3)
packages/chat-adapter/scripts/prepare-dist.mjs (1)
scripts/utils/make-dist-package-json.cjs (1)
  • value (5-5)
packages/chat-adapter/src/types.ts (1)
packages/chat-adapter/src/index.ts (3)
  • BeeperDesktopAPIAdapterConfig (714-714)
  • BeeperDesktopAPIServerInfo (715-715)
  • BeeperDesktopAPIThreadID (716-716)
packages/chat-adapter/examples/logger/index.mjs (1)
packages/chat-adapter/src/index.ts (1)
  • createBeeperDesktopAPIAdapter (703-711)
🪛 ESLint
packages/chat-adapter/tsup.config.ts

[error] 1-1: Replace "tsup" with 'tsup'

(prettier/prettier)


[error] 4-4: Replace "src/index.ts" with 'src/index.ts'

(prettier/prettier)


[error] 5-5: Replace "esm" with 'esm'

(prettier/prettier)


[error] 9-9: Replace "ws" with 'ws'

(prettier/prettier)

packages/chat-adapter/src/index.ts

[error] 1-1: Replace "@beeper/desktop-api" with '@beeper/desktop-api'

(prettier/prettier)


[error] 2-2: Replace "@chat-adapter/shared" with '@chat-adapter/shared'

(prettier/prettier)


[error] 19-19: Replace "chat" with 'chat'

(prettier/prettier)


[error] 20-20: Replace "chat" with 'chat'

(prettier/prettier)


[error] 21-21: Replace "ws" with 'ws'

(prettier/prettier)


[error] 28-28: Replace "./types" with './types'

(prettier/prettier)


[error] 31-31: Replace "beeperDesktopAPI" with 'beeperDesktopAPI'

(prettier/prettier)


[error] 49-49: Replace "*" with '*'

(prettier/prettier)


[error] 53-53: Replace "unknown" with 'unknown'

(prettier/prettier)


[error] 56-56: Replace "http://localhost:23373").replace(/\/+$/,·"" with 'http://localhost:23373').replace(/\/+$/,·''

(prettier/prettier)


[error] 63-63: Replace "bot" with 'bot'

(prettier/prettier)


[error] 74-74: Replace "BeeperDesktopAPI·adapter·initialized" with 'BeeperDesktopAPI·adapter·initialized'

(prettier/prettier)


[error] 88-88: Replace "BeeperDesktopAPI·uses·WebSocket·events·(/v1/ws),·not·inbound·webhooks.·Call·startEventsListener()." with 'BeeperDesktopAPI·uses·WebSocket·events·(/v1/ws),·not·inbound·webhooks.·Call·startEventsListener().'

(prettier/prettier)


[error] 97-97: Replace "*" with '*'

(prettier/prettier)


[error] 99-99: Replace "Chat·instance·not·initialized" with 'Chat·instance·not·initialized'

(prettier/prettier)


[error] 112-112: Replace "ws" with 'ws'

(prettier/prettier)


[error] 119-119: Replace "open" with 'open'

(prettier/prettier)


[error] 120-120: Replace "BeeperDesktopAPI·WS·connected" with 'BeeperDesktopAPI·WS·connected'

(prettier/prettier)


[error] 121-121: Replace "subscriptions.set" with 'subscriptions.set'

(prettier/prettier)


[error] 124-124: Replace "message" with 'message'

(prettier/prettier)


[error] 128-128: Replace "error" with 'error'

(prettier/prettier)


[error] 129-129: Replace "BeeperDesktopAPI·WS·error" with 'BeeperDesktopAPI·WS·error'

(prettier/prettier)


[error] 132-132: Replace "close" with 'close'

(prettier/prettier)


[error] 133-133: Replace "BeeperDesktopAPI·WS·disconnected" with 'BeeperDesktopAPI·WS·disconnected'

(prettier/prettier)


[error] 164-164: Replace threadID:·string,·messageID:·string,·message:·AdapterPostableMessage with ⏎····threadID:·string,⏎····messageID:·string,⏎····message:·AdapterPostableMessage,⏎··

(prettier/prettier)


[error] 168-168: Replace ``/v1/chats/${encodeURIComponent(chatID)}/messages/${encodeURIComponent(messageID)}, with `⏎······`/v1/chats/${encodeURIComponent(chatID)}/messages/${encodeURIComponent(messageID)}`,⏎·····`

(prettier/prettier)


[error] 169-169: Insert ··

(prettier/prettier)


[error] 170-170: Replace } with ··},⏎····

(prettier/prettier)


[error] 177-177: Replace ``/v1/chats/${encodeURIComponent(chatID)}/messages/${encodeURIComponent(messageID)}, with `⏎······`/v1/chats/${encodeURIComponent(chatID)}/messages/${encodeURIComponent(messageID)}`,⏎·····`

(prettier/prettier)


[error] 178-178: Insert ··

(prettier/prettier)


[error] 179-179: Replace } with ··},⏎····

(prettier/prettier)


[error] 184-184: Replace ``/v1/chats/${encodeURIComponent(chatID)}/messages/${encodeURIComponent(messageID)}/reactions, with `⏎······`/v1/chats/${encodeURIComponent(chatID)}/messages/${encodeURIComponent(messageID)}/reactions`,⏎·····`

(prettier/prettier)


[error] 185-185: Insert ··

(prettier/prettier)


[error] 186-186: Replace } with ··},⏎····

(prettier/prettier)


[error] 191-191: Replace ``/v1/chats/${encodeURIComponent(chatID)}/messages/${encodeURIComponent(messageID)}/reactions, with `⏎······`/v1/chats/${encodeURIComponent(chatID)}/messages/${encodeURIComponent(messageID)}/reactions`,⏎·····`

(prettier/prettier)


[error] 192-192: Insert ··

(prettier/prettier)


[error] 193-193: Replace } with ··},⏎····

(prettier/prettier)


[error] 205-205: Replace "backward" with 'backward'

(prettier/prettier)


[error] 211-211: Replace "forward"·?·"after"·:·"before" with 'forward'·?·'after'·:·'before'

(prettier/prettier)


[error] 217-217: Replace ·this.toChatMessage(item·as·unknown·as·BeeperDesktopAPIMessage,·accountID,·threadID) with ⏎··········this.toChatMessage(item·as·unknown·as·BeeperDesktopAPIMessage,·accountID,·threadID),⏎········

(prettier/prettier)


[error] 218-218: Replace "forward"·?·(data.newestCursor·??·undefined)·:·(data.oldestCursor·??·undefined) with 'forward'·?·data.newestCursor·??·undefined·:·data.oldestCursor·??·undefined

(prettier/prettier)


[error] 249-249: Replace "single" with 'single'

(prettier/prettier)


[error] 275-275: Replace channelId:·string,·options:·ListThreadsOptions·=·{} with ⏎····channelId:·string,⏎····options:·ListThreadsOptions·=·{},⏎··

(prettier/prettier)


[error] 280-280: Replace "before" with 'before'

(prettier/prettier)


[error] 283-283: Replace "any" with 'any'

(prettier/prettier)


[error] 294-294: Replace "unknown" with 'unknown'

(prettier/prettier)


[error] 295-295: Replace "unknown" with 'unknown'

(prettier/prettier)


[error] 297-297: Replace "" with ''

(prettier/prettier)


[error] 322-322: Replace "single" with 'single'

(prettier/prettier)


[error] 328-328: Replace platformData.chatID with ⏎······platformData.chatID,⏎····

(prettier/prettier)


[error] 332-332: Replace ":" with ':'

(prettier/prettier)


[error] 333-333: Replace "beeperDesktopAPI" with 'beeperDesktopAPI'

(prettier/prettier)


[error] 362-362: Replace "Ignoring·invalid·BeeperDesktopAPI·WS·payload" with 'Ignoring·invalid·BeeperDesktopAPI·WS·payload'

(prettier/prettier)


[error] 366-366: Replace "message.upserted" with 'message.upserted'

(prettier/prettier)


[error] 383-383: Replace "reaction.added"·||·event.type·===·"reaction.removed" with 'reaction.added'·||·event.type·===·'reaction.removed'

(prettier/prettier)


[error] 397-397: Replace "reaction.added" with 'reaction.added'

(prettier/prettier)


[error] 406-406: Replace "reaction.added" with 'reaction.added'

(prettier/prettier)


[error] 411-411: Replace "unknown" with 'unknown'

(prettier/prettier)


[error] 425-425: Replace "BeeperDesktopAPI·WS·reconnect·failed" with 'BeeperDesktopAPI·WS·reconnect·failed'

(prettier/prettier)


[error] 432-432: Replace ":" with ':'

(prettier/prettier)


[error] 433-433: Replace "beeperDesktopAPI" with 'beeperDesktopAPI'

(prettier/prettier)


[error] 440-440: Replace ":" with ':'

(prettier/prettier)


[error] 466-466: Replace "/v1/info" with '/v1/info'

(prettier/prettier)


[error] 469-469: Replace "info-endpoint" with 'info-endpoint'

(prettier/prettier)


[error] 470-470: Replace "version",·"desktopVersion",·"appVersion" with 'version',·'desktopVersion',·'appVersion'

(prettier/prettier)


[error] 471-471: Replace "apiVersion",·"desktopAPIVersion",·"specVersion" with 'apiVersion',·'desktopAPIVersion',·'specVersion'

(prettier/prettier)


[error] 483-483: Replace "headers" with 'headers'

(prettier/prettier)


[error] 487-487: Replace "unknown" with 'unknown'

(prettier/prettier)


[error] 494-494: Replace "string" with 'string'

(prettier/prettier)


[error] 500-505: Replace ⏎······"x-beeper-desktop-api-version",⏎······"x-beeper-desktop-version",⏎······"x-api-version",⏎······"server",⏎···· with 'x-beeper-desktop-api-version',·'x-beeper-desktop-version',·'x-api-version',·'server'

(prettier/prettier)


[error] 518-518: Replace "messageID",·"messageId" with 'messageID',·'messageId'

(prettier/prettier)


[error] 519-519: Replace "reactionKey",·"reaction",·"description" with 'reactionKey',·'reaction',·'description'

(prettier/prettier)


[error] 520-520: Replace "participantID",·"senderID",·"userID"])·??·"unknown" with 'participantID',·'senderID',·'userID'])·??·'unknown'

(prettier/prettier)


[error] 528-528: Replace "string" with 'string'

(prettier/prettier)


[error] 534-534: Replace "string" with 'string'

(prettier/prettier)


[error] 542-542: Replace "string" with 'string'

(prettier/prettier)


[error] 564-564: Replace "" with ''

(prettier/prettier)


[error] 566-566: Replace "unknown" with 'unknown'

(prettier/prettier)


[error] 595-595: Replace "unknown" with 'unknown'

(prettier/prettier)


[error] 604-604: Replace "unknown",·reactionKey·=·""]·=·identity.split("::" with 'unknown',·reactionKey·=·'']·=·identity.split('::'

(prettier/prettier)


[error] 618-618: Replace "unknown" with 'unknown'

(prettier/prettier)


[error] 644-644: Replace message:·BeeperDesktopAPIMessage,·accountID:·string,·threadID:·string with ⏎····message:·BeeperDesktopAPIMessage,⏎····accountID:·string,⏎····threadID:·string,⏎··

(prettier/prettier)


[error] 645-645: Replace "" with ''

(prettier/prettier)


[error] 647-649: Replace ·this.botUserID⏎······?·mentions.includes(this.botUserID)·||·mentions.includes("@room")⏎····· with ⏎······this.botUserID·?·mentions.includes(this.botUserID)·||·mentions.includes('@room')

(prettier/prettier)


[error] 661-661: Replace "unknown" with 'unknown'

(prettier/prettier)


[error] 677-677: Insert ,

(prettier/prettier)


[error] 681-681: Replace "image"·|·"video"·|·"audio"·|·"file" with 'image'·|·'video'·|·'audio'·|·'file'

(prettier/prettier)


[error] 682-682: Replace "file" with 'file'

(prettier/prettier)


[error] 684-684: Replace "IMAGE"·||·normalized·===·"STICKER"·||·normalized·===·"IMG")·return·"image" with 'IMAGE'·||·normalized·===·'STICKER'·||·normalized·===·'IMG')·return·'image'

(prettier/prettier)


[error] 685-685: Replace "VIDEO")·return·"video" with 'VIDEO')·return·'video'

(prettier/prettier)


[error] 686-686: Replace "AUDIO"·||·normalized·===·"VOICE")·return·"audio" with 'AUDIO'·||·normalized·===·'VOICE')·return·'audio'

(prettier/prettier)


[error] 687-687: Replace "file" with 'file'

(prettier/prettier)


[error] 694-694: Replace "string" with 'string'

(prettier/prettier)


[error] 695-695: Replace "raw" with 'raw'

(prettier/prettier)


[error] 696-696: Replace "markdown" with 'markdown'

(prettier/prettier)


[error] 697-697: Replace "ast" with 'ast'

(prettier/prettier)


[error] 699-699: Replace "" with ''

(prettier/prettier)


[error] 708-708: Replace "info",·"beeper-desktop-api-adapter" with 'info',·'beeper-desktop-api-adapter'

(prettier/prettier)


[error] 717-717: Replace "./types" with './types'

(prettier/prettier)

packages/chat-adapter/scripts/prepare-dist.mjs

[error] 53-53: Replace path.join(packageDir,·'examples',·'logger',·'index.mjs'),·path.join(distDir,·'examples',·'logger',·'index.mjs') with ⏎··path.join(packageDir,·'examples',·'logger',·'index.mjs'),⏎··path.join(distDir,·'examples',·'logger',·'index.mjs'),⏎

(prettier/prettier)

packages/chat-adapter/src/types.ts

[error] 14-14: Replace "info-endpoint"·|·"headers"·|·"unknown" with 'info-endpoint'·|·'headers'·|·'unknown'

(prettier/prettier)


[error] 54-54: Replace "ready" with 'ready'

(prettier/prettier)


[error] 55-55: Replace "subscriptions.updated" with 'subscriptions.updated'

(prettier/prettier)


[error] 56-56: Replace "error" with 'error'

(prettier/prettier)


[error] 57-57: Replace "chat.upserted" with 'chat.upserted'

(prettier/prettier)


[error] 58-58: Replace "chat.deleted" with 'chat.deleted'

(prettier/prettier)


[error] 59-59: Replace "message.upserted" with 'message.upserted'

(prettier/prettier)


[error] 60-60: Replace "message.deleted" with 'message.deleted'

(prettier/prettier)


[error] 61-61: Replace "reaction.added" with 'reaction.added'

(prettier/prettier)


[error] 62-62: Replace "reaction.removed" with 'reaction.removed'

(prettier/prettier)

packages/chat-adapter/vitest.config.ts

[error] 1-1: Replace "vitest/config" with 'vitest/config'

(prettier/prettier)


[error] 6-6: Replace "node" with 'node'

(prettier/prettier)


[error] 9-9: Replace "v8" with 'v8'

(prettier/prettier)


[error] 10-10: Replace "text",·"json-summary" with 'text',·'json-summary'

(prettier/prettier)


[error] 11-11: Replace "src/**/*.ts" with 'src/**/*.ts'

(prettier/prettier)


[error] 12-12: Replace "src/**/*.test.ts" with 'src/**/*.test.ts'

(prettier/prettier)

packages/chat-adapter/examples/logger/index.mjs

[error] 1-1: Replace "chat" with 'chat'

(prettier/prettier)


[error] 2-2: Replace "@chat-adapter/state-memory" with '@chat-adapter/state-memory'

(prettier/prettier)


[error] 3-3: Replace "../../dist/index.js" with '../../dist/index.js'

(prettier/prettier)


[error] 7-7: Replace "Missing·BEEPER_DESKTOP_API_ACCESS_TOKEN" with 'Missing·BEEPER_DESKTOP_API_ACCESS_TOKEN'

(prettier/prettier)


[error] 10-10: Replace "http://localhost:23373" with 'http://localhost:23373'

(prettier/prettier)


[error] 13-13: Replace "*" with '*'

(prettier/prettier)


[error] 14-14: Replace "," with ','

(prettier/prettier)


[error] 24-24: Replace "bot" with 'bot'

(prettier/prettier)


[error] 28-28: Replace "bot" with 'bot'

(prettier/prettier)


[error] 33-33: Replace "info" with 'info'

(prettier/prettier)


[error] 37-37: Replace "[mention]" with '[mention]'

(prettier/prettier)


[error] 47-47: Replace "[new-message]" with '[new-message]'

(prettier/prettier)


[error] 57-57: Replace "[subscribed-message]" with '[subscribed-message]'

(prettier/prettier)


[error] 66-66: Replace "[reaction]" with '[reaction]'

(prettier/prettier)


[error] 76-76: Replace "BeeperDesktopAPI·server·info" with 'BeeperDesktopAPI·server·info'

(prettier/prettier)


[error] 79-79: Replace "BeeperDesktopAPI·logger·is·running" with 'BeeperDesktopAPI·logger·is·running'

(prettier/prettier)

🪛 GitHub Check: lint
packages/chat-adapter/examples/logger/index.mjs

[failure] 33-33:
Replace "info" with 'info'


[failure] 28-28:
Replace "bot" with 'bot'


[failure] 24-24:
Replace "bot" with 'bot'


[failure] 14-14:
Replace "," with ','


[failure] 13-13:
Replace "*" with '*'


[failure] 10-10:
Replace "http://localhost:23373" with 'http://localhost:23373'


[failure] 7-7:
Replace "Missing·BEEPER_DESKTOP_API_ACCESS_TOKEN" with 'Missing·BEEPER_DESKTOP_API_ACCESS_TOKEN'


[failure] 3-3:
Replace "../../dist/index.js" with '../../dist/index.js'


[failure] 2-2:
Replace "@chat-adapter/state-memory" with '@chat-adapter/state-memory'


[failure] 1-1:
Replace "chat" with 'chat'

🔇 Additional comments (9)
packages/chat-adapter/package.json (1)

30-34: The npm registry shows the latest published version of @beeper/desktop-api is 0.1.4, not 4.4.1 as stated in the review. The declared version ^4.2.3 in package.json is valid. The suggestion to bump to ^4.4.1 is not supported by the actual latest published release.

			> Likely an incorrect or invalid review comment.
packages/chat-adapter/src/index.ts (3)

30-84: LGTM!

The adapter initialization is well-structured with sensible defaults, proper logger injection, and flexible configuration handling (including both botUserID and botUserId variants for convenience).


355-418: LGTM!

The WebSocket event handling correctly processes message upserts with reaction diffing and explicit reaction events. The guard clauses properly validate required fields before processing.


644-679: LGTM!

The message conversion logic correctly maps all fields including author information, attachments with type normalization, and mention detection.

packages/chat-adapter/examples/logger/index.mjs (2)

5-34: LGTM!

The configuration handling correctly validates the required access token, provides sensible defaults, and properly parses the comma-separated chat IDs.


36-84: LGTM!

The example demonstrates a complete integration flow: registering event handlers, initializing the bot, starting the events listener, and keeping the process alive. The handlers appropriately log events for debugging purposes.

packages/chat-adapter/scripts/prepare-dist.mjs (3)

1-12: LGTM!

The script correctly sets up paths using ESM patterns and validates the dist directory existence before proceeding.


49-54: LGTM!

The file operations correctly write the transformed package.json and copy necessary assets to the dist directory. Using synchronous operations is appropriate for a build script.


30-47: No action needed. The source package.json does not contain peerDependencies, so the publishPackage object correctly excludes them.

## Install

```bash
yarn add @beeper/chat-adapter-desktop-api
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

The quickstart omits packages required by the sample.

The snippet imports chat and @chat-adapter/state-memory, but the install section only adds @beeper/chat-adapter-desktop-api. @chat-adapter/state-memory is only a devDependency in 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
-yarn add `@beeper/chat-adapter-desktop-api`
+yarn add `@beeper/chat-adapter-desktop-api` chat `@chat-adapter/state-memory`

Also applies to: 14-16

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/chat-adapter/README.md` at line 8, The quickstart install
instruction only adds `@beeper/chat-adapter-desktop-api` but the sample imports
"chat" and "@chat-adapter/state-memory"; update the README to either include the
missing install(s) (e.g., add yarn add for "chat" and
"@chat-adapter/state-memory") or change the example imports to only use packages
you explicitly instruct the reader to install; reference the sample import names
("chat", "@chat-adapter/state-memory") and the existing install line ("yarn add
`@beeper/chat-adapter-desktop-api`") so the consumer instructions and sample code
match.

Comment on lines +1 to +13
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>;
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 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)
PY

Repository: 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
done

Repository: 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 2

Repository: 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 -50

Repository: 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 1

Repository: 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 -15

Repository: 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 -5

Repository: beeper/desktop-api-js

Length of output: 680


Remove this ambient module declaration and use the SDK's published types.

The official @beeper/desktop-api SDK has fully typed APIs: get<Rsp>() returns APIPromise<Rsp>, not any; and provides typed resources like BeeperDesktop.Accounts, BeeperDesktop.Chats, etc. This shim collapses all of that to any and incorrectly requires baseURL in the constructor (the actual SDK makes it optional with environment variable fallback). This defeats TypeScript's type checking—for instance, client.get<BeeperDesktopAPIMessage>(...) returns any instead of APIPromise<BeeperDesktopAPIMessage>.

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
Verify each finding against the current code and only fix it if needed.

In `@packages/chat-adapter/src/beeper-desktop-api.d.ts` around lines 1 - 13, The
ambient module declaration for '@beeper/desktop-api' collapses SDK types to any
and forces a required baseURL; remove this file so the project uses the SDK's
published types, or replace it with a minimal module augmentation that only
fixes what the SDK misses (e.g., make constructor option baseURL optional and
augment types for BeeperDesktop.messages/accounts/chats and get<T>() to return
APIPromise<T>), targeting the module name '@beeper/desktop-api' and the
BeeperDesktop class and get<T>() signature so TypeScript picks up the real
APIPromise<Rsp> and typed resources instead of any.

private wsSubscriptions: string[] = ["*"];
private reconnectTimer: NodeJS.Timeout | null = null;
private manualStopWS = false;
private reactionStateByMessage = new Map<string, Set<string>>();
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Potential memory leak: reactionStateByMessage grows unbounded.

The reactionStateByMessage Map stores reaction state for every processed message but is never pruned. For long-running adapter instances, this will grow indefinitely, potentially causing memory exhaustion.

Consider implementing an LRU cache or periodic cleanup of old entries.

💡 Suggested approach using a bounded cache
+  private readonly maxReactionCacheSize = 10000;

   private processReactionDiffFromMessageUpsert(message: BeeperDesktopAPIMessage): void {
     // ... existing logic ...
     
     this.reactionStateByMessage.set(messageKey, next);
+    
+    // Prune oldest entries if cache exceeds limit
+    if (this.reactionStateByMessage.size > this.maxReactionCacheSize) {
+      const keysToDelete = Array.from(this.reactionStateByMessage.keys())
+        .slice(0, this.reactionStateByMessage.size - this.maxReactionCacheSize);
+      for (const key of keysToDelete) {
+        this.reactionStateByMessage.delete(key);
+      }
+    }
   }

Also applies to: 549-626

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/chat-adapter/src/index.ts` at line 52, reactionStateByMessage is an
unbounded Map that will grow indefinitely; replace it with a bounded cache
(e.g., an LRU cache like lru-cache or a custom bounded Map with eviction) or add
TTL+periodic cleanup so entries are pruned when capacity or age limits are
exceeded. Change the field declaration (reactionStateByMessage) to the cache
instance and update all places that call .get/.set/.has to use the cache API (or
wrap accessors) so evictions happen automatically; apply the same
bounded-cache/TTL cleanup approach to any other long-lived per-message state
maps in this file to prevent memory leaks.

Comment on lines +225 to +239
try {
const message = await this.client.get<BeeperDesktopAPIMessage>(
`/v1/chats/${encodeURIComponent(chatID)}/messages/${encodeURIComponent(messageID)}`,
);
return this.toChatMessage(message, accountID, threadID);
} catch {
const data = await this.client.messages.search({
chatIDs: [chatID],
limit: 50,
});
const found = data.items.find((item: any) => item.id === messageID);
if (!found) return null;
return this.toChatMessage(found as unknown as BeeperDesktopAPIMessage, accountID, threadID);
}
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Silent error handling may mask issues.

The bare catch block on line 230 swallows all errors from the direct message fetch, including potential network issues or authentication failures. Consider logging the error before falling back to search.

🔧 Proposed fix
     try {
       const message = await this.client.get<BeeperDesktopAPIMessage>(
         `/v1/chats/${encodeURIComponent(chatID)}/messages/${encodeURIComponent(messageID)}`,
       );
       return this.toChatMessage(message, accountID, threadID);
-    } catch {
+    } catch (error) {
+      this.logger.debug("Direct message fetch failed, falling back to search", { messageID, error: String(error) });
       const data = await this.client.messages.search({
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
try {
const message = await this.client.get<BeeperDesktopAPIMessage>(
`/v1/chats/${encodeURIComponent(chatID)}/messages/${encodeURIComponent(messageID)}`,
);
return this.toChatMessage(message, accountID, threadID);
} catch {
const data = await this.client.messages.search({
chatIDs: [chatID],
limit: 50,
});
const found = data.items.find((item: any) => item.id === messageID);
if (!found) return null;
return this.toChatMessage(found as unknown as BeeperDesktopAPIMessage, accountID, threadID);
}
}
try {
const message = await this.client.get<BeeperDesktopAPIMessage>(
`/v1/chats/${encodeURIComponent(chatID)}/messages/${encodeURIComponent(messageID)}`,
);
return this.toChatMessage(message, accountID, threadID);
} catch (error) {
this.logger.debug("Direct message fetch failed, falling back to search", { messageID, error: String(error) });
const data = await this.client.messages.search({
chatIDs: [chatID],
limit: 50,
});
const found = data.items.find((item: any) => item.id === messageID);
if (!found) return null;
return this.toChatMessage(found as unknown as BeeperDesktopAPIMessage, accountID, threadID);
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/chat-adapter/src/index.ts` around lines 225 - 239, The catch block
in the method that calls
this.client.get(`/v1/chats/${encodeURIComponent(chatID)}/messages/${encodeURIComponent(messageID)}`)
is swallowing all errors; update the catch to capture the error (e.g., catch
(err)) and emit a log via the existing logger or processLogger including context
(chatID, messageID, accountID) before proceeding to the fallback
this.client.messages.search call, then continue returning
toChatMessage(found...) as before.

Comment on lines +331 to +340
decodeThreadId(threadID: string): BeeperDesktopAPIThreadID {
const [prefix, accountID, chatID] = threadID.split(":");
if (!(prefix === "beeperDesktopAPI" && accountID && chatID)) {
throw new Error(`Invalid BeeperDesktopAPI thread ID: ${threadID}`);
}
return {
accountID: decodeURIComponent(accountID),
chatID: decodeURIComponent(chatID),
};
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Bug: decodeThreadId doesn't handle colons in chatID correctly.

The split(":") on line 332 will incorrectly truncate chatID if it contains colon characters. For example, "beeperDesktopAPI:acc1:chat%3Awith%3Acolons" would only capture "chat%3Awith%3Acolons" as chatID before decoding, but if chatID was encoded as "beeperDesktopAPI:acc1:chat:id" (without encoding), only "chat" would be captured.

Since encodeThreadId uses encodeURIComponent, colons should be encoded, but to be defensive against malformed IDs:

🔧 Safer implementation using limited split
   decodeThreadId(threadID: string): BeeperDesktopAPIThreadID {
-    const [prefix, accountID, chatID] = threadID.split(":");
+    const parts = threadID.split(":");
+    const prefix = parts[0];
+    const accountID = parts[1];
+    const chatID = parts.slice(2).join(":");
     if (!(prefix === "beeperDesktopAPI" && accountID && chatID)) {
       throw new Error(`Invalid BeeperDesktopAPI thread ID: ${threadID}`);
     }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
decodeThreadId(threadID: string): BeeperDesktopAPIThreadID {
const [prefix, accountID, chatID] = threadID.split(":");
if (!(prefix === "beeperDesktopAPI" && accountID && chatID)) {
throw new Error(`Invalid BeeperDesktopAPI thread ID: ${threadID}`);
}
return {
accountID: decodeURIComponent(accountID),
chatID: decodeURIComponent(chatID),
};
}
decodeThreadId(threadID: string): BeeperDesktopAPIThreadID {
const parts = threadID.split(":");
const prefix = parts[0];
const accountID = parts[1];
const chatID = parts.slice(2).join(":");
if (!(prefix === "beeperDesktopAPI" && accountID && chatID)) {
throw new Error(`Invalid BeeperDesktopAPI thread ID: ${threadID}`);
}
return {
accountID: decodeURIComponent(accountID),
chatID: decodeURIComponent(chatID),
};
}
🧰 Tools
🪛 ESLint

[error] 332-332: Replace ":" with ':'

(prettier/prettier)


[error] 333-333: Replace "beeperDesktopAPI" with 'beeperDesktopAPI'

(prettier/prettier)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/chat-adapter/src/index.ts` around lines 331 - 340, The
decodeThreadId function mishandles chat IDs containing additional colons because
it uses a plain split(":"), which truncates the chatID; update decodeThreadId to
only treat the first two colon separations as prefix and accountID and treat the
rest as the chatID (e.g., split into parts, take parts[0] as prefix, parts[1] as
accountID, and join the remaining parts with ":" for chatID), then validate
prefix === "beeperDesktopAPI" and that accountID and chatID exist, and finally
return accountID and chatID after decodeURIComponent; reference decodeThreadId
(and ensure compatibility with encodeThreadId which uses encodeURIComponent).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

1 participant