Skip to content

Extract shared bridge core helpers#3432

Draft
Hmbown wants to merge 2 commits into
mainfrom
feature/bridge-core-consolidation
Draft

Extract shared bridge core helpers#3432
Hmbown wants to merge 2 commits into
mainfrom
feature/bridge-core-consolidation

Conversation

@Hmbown

@Hmbown Hmbown commented Jun 23, 2026

Copy link
Copy Markdown
Owner

Goal

Consolidate duplicated bridge logic from the Telegram, Feishu, WeCom, and Weixin Node integrations into a shared integrations/bridge-core package while keeping each transport's protocol-specific behavior local.

Changes

  • Adds integrations/bridge-core with shared helpers for:
    • env parsing (envFirst, parseList, parseBool, parseEnvText)
    • placeholder/clean env checks
    • text-content parsing
    • group-prefix filtering with bridge-specific direct-chat options
    • command parsing and common command action mapping
    • preserved chat state, message splitting, runtime error compaction, and active-turn detection
  • Adds a shared configurable ThreadStore for JSON thread-map persistence:
    • message dedupe windows for Telegram/Feishu/Weixin
    • optional Telegram action-token cache
    • optional private chmod behavior for WeCom state files
  • Replaces duplicated helper/store implementations across:
    • integrations/telegram-bridge/src/lib.mjs
    • integrations/telegram-bridge/src/index.mjs
    • integrations/feishu-bridge/src/lib.mjs
    • integrations/feishu-bridge/src/index.mjs
    • integrations/wecom-bridge/src/lib.mjs
    • integrations/weixin-bridge/src/lib.mjs
    • integrations/weixin-bridge/src/index.mjs
  • Keeps transport-specific behavior local: Telegram bot mentions/buttons/callbacks, Feishu reply/topic state, WeCom SDK replies, and Weixin iLink QR/login/sync-buffer lifecycle.
  • Adds Weixin bridge tests and bridge-core ThreadStore coverage.

Verification

npm --prefix integrations/bridge-core run check && npm --prefix integrations/bridge-core test
npm --prefix integrations/telegram-bridge run check && npm --prefix integrations/telegram-bridge test
npm --prefix integrations/feishu-bridge run check && npm --prefix integrations/feishu-bridge test
npm --prefix integrations/wecom-bridge run check && npm --prefix integrations/wecom-bridge test
npm --prefix integrations/weixin-bridge run check && npm --prefix integrations/weixin-bridge test

Results:

  • bridge-core: 6 passed
  • telegram-bridge: 26 passed
  • feishu-bridge: 19 passed
  • wecom-bridge: 2 passed
  • weixin-bridge: 3 passed

Risks / Follow-ups

  • Runtime HTTP/SSE helpers and shared runtime command operations are still duplicated and should be extracted in later slices.
  • No transport SDK/polling/WebSocket/iLink lifecycle code moved in this PR.
  • Telegram's richer callback UI remains intentionally local behind shared action-token storage.

Add integrations/bridge-core with shared pure helpers for env parsing, command parsing/action mapping, message splitting, runtime error compaction, active-turn detection, and preserved chat state.

Re-export or wrap those helpers from Telegram, Feishu, WeCom, and Weixin bridge libs while keeping transport-specific identity, validation, keyboards, and protocol handling local. Add Weixin lib tests so its helper behavior is guarded.

@greptile-apps greptile-apps Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Your free trial has ended. If you'd like to continue receiving code reviews, you can add a payment method here.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Code Review

This pull request extracts shared helper functions from various chat bridges (Feishu, Telegram, WeCom, and Weixin) into a new centralized package, @codewhale/bridge-core, to reduce code duplication and improve maintainability. Unit tests have also been added to verify the shared functionality. The review feedback highlights three key areas for improvement: adding a length check in parseEnvText to prevent incorrectly stripping single-character quotes, using optional chaining on state in activeTurnBlock to avoid potential runtime errors when state is null, and restoring the original Telegram bridge behavior by adding 'channel' to directChatTypes in stripGroupPrefix.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment on lines +31 to +36
if (
(value.startsWith('"') && value.endsWith('"')) ||
(value.startsWith("'") && value.endsWith("'"))
) {
value = value.slice(1, -1);
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

In parseEnvText, if the value is a single quote or double quote character (e.g., " or '), the current logic will strip it to an empty string because startsWith and endsWith both match the same single character. Adding a length check ensures that only valid quoted strings (with at least an opening and a closing quote) are stripped.

Suggested change
if (
(value.startsWith('"') && value.endsWith('"')) ||
(value.startsWith("'") && value.endsWith("'"))
) {
value = value.slice(1, -1);
}
if (
value.length >= 2 &&
((value.startsWith('"') && value.endsWith('"')) ||
(value.startsWith("'") && value.endsWith("'")))
) {
value = value.slice(1, -1);
}

Comment on lines +190 to +193
turnId: runningTurn.id || state.activeTurnId || "",
message: `Thread already has active turn ${
runningTurn.id || state.activeTurnId || "(unknown)"
}. Wait for it to finish or send /interrupt.`

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

If state is explicitly passed as null to activeTurnBlock, accessing state.activeTurnId will throw a TypeError. Using optional chaining (state?.activeTurnId) prevents potential runtime crashes.

Suggested change
turnId: runningTurn.id || state.activeTurnId || "",
message: `Thread already has active turn ${
runningTurn.id || state.activeTurnId || "(unknown)"
}. Wait for it to finish or send /interrupt.`
turnId: runningTurn.id || state?.activeTurnId || "",
message: `Thread already has active turn ${
runningTurn.id || state?.activeTurnId || "(unknown)"
}. Wait for it to finish or send /interrupt.`

Comment on lines 77 to +83
export function stripGroupPrefix(text, { chatType, requirePrefix, prefix }) {
const trimmed = String(text || "").trim();
if (!trimmed) return { accepted: false, text: "" };
if (!requirePrefix || !isGroupChat(chatType)) {
return { accepted: true, text: trimmed };
}
const marker = prefix || "/cw";
if (trimmed === marker) return { accepted: true, text: "/help" };
if (trimmed.startsWith(`${marker} `)) {
return { accepted: true, text: trimmed.slice(marker.length).trim() };
}
return { accepted: false, text: "" };
return coreStripGroupPrefix(text, {
chatType,
requirePrefix,
prefix: prefix || "/cw",
directChatTypes: ["private"]
});

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

In Telegram, chatType can be "private", "group", "supergroup", or "channel". The original implementation accepted messages without a prefix if they were not group chats (i.e., private or channel). By specifying only "private" in directChatTypes, the new implementation will incorrectly require a prefix for channel posts when requirePrefix is enabled. Adding "channel" to directChatTypes preserves the original behavior.

Suggested change
export function stripGroupPrefix(text, { chatType, requirePrefix, prefix }) {
const trimmed = String(text || "").trim();
if (!trimmed) return { accepted: false, text: "" };
if (!requirePrefix || !isGroupChat(chatType)) {
return { accepted: true, text: trimmed };
}
const marker = prefix || "/cw";
if (trimmed === marker) return { accepted: true, text: "/help" };
if (trimmed.startsWith(`${marker} `)) {
return { accepted: true, text: trimmed.slice(marker.length).trim() };
}
return { accepted: false, text: "" };
return coreStripGroupPrefix(text, {
chatType,
requirePrefix,
prefix: prefix || "/cw",
directChatTypes: ["private"]
});
export function stripGroupPrefix(text, { chatType, requirePrefix, prefix }) {
return coreStripGroupPrefix(text, {
chatType,
requirePrefix,
prefix: prefix || "/cw",
directChatTypes: ["private", "channel"]
});
}

Move the duplicated JSON thread-map store into bridge-core with bridge-specific options for message dedupe, Telegram action tokens, and WeCom private state-file modes. Keep thin local subclasses where startup-order tests and bridge exports expect ThreadStore names.

Verification:

- npm --prefix integrations/bridge-core run check && npm --prefix integrations/bridge-core test

- npm --prefix integrations/telegram-bridge run check && npm --prefix integrations/telegram-bridge test

- npm --prefix integrations/feishu-bridge run check && npm --prefix integrations/feishu-bridge test

- npm --prefix integrations/wecom-bridge run check && npm --prefix integrations/wecom-bridge test

- npm --prefix integrations/weixin-bridge run check && npm --prefix integrations/weixin-bridge test

@greptile-apps greptile-apps Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Your free trial has ended. If you'd like to continue receiving code reviews, you can add a payment method here.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant