fix: add RTL mark support for Telegram draft streaming#32866
fix: add RTL mark support for Telegram draft streaming#32866kevinsong0 wants to merge 5 commits intoopenclaw:mainfrom
Conversation
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: cdcf50641b
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
src/telegram/draft-stream.ts
Outdated
| import { buildTelegramThreadParams, type TelegramThreadSpec } from "./bot/helpers.js"; | ||
|
|
||
| const TELEGRAM_STREAM_MAX_CHARS = 4096; | ||
| const RTL_MARK = "\u200F"; // Unicode Right-to-Left Mark |
There was a problem hiding this comment.
Apply RTL mark when sending Telegram drafts
This commit introduces RTL_MARK but never uses it, so sendDraftTransportPreview still calls sendMessageDraft with renderedText unchanged and RTL users (e.g., Arabic/Hebrew text in draft preview mode) continue to get incorrect directionality. Because the only added line is an unused constant, the stated fix for RTL draft streaming is not actually implemented.
Useful? React with 👍 / 👎.
Greptile SummaryThis PR successfully implements RTL mark support in Telegram draft streaming, correctly detecting RTL text using Unicode ranges and prepending The core fix is functional and resolves issue #32852. However, two implementation concerns remain:
Confidence Score: 3/5
Last reviewed commit: 4d8556c |
src/telegram/draft-stream.ts
Outdated
| import { buildTelegramThreadParams, type TelegramThreadSpec } from "./bot/helpers.js"; | ||
|
|
||
| const TELEGRAM_STREAM_MAX_CHARS = 4096; | ||
| const RTL_MARK = "\u200F"; // Unicode Right-to-Left Mark |
There was a problem hiding this comment.
RTL_MARK is declared but never used
The constant RTL_MARK is defined here but is never referenced anywhere in the file. The PR description says it "prepends Unicode RLM mark to RTL text in sendMessageDraft", but no such prepending logic exists — there is no RTL detection, and RTL_MARK is never passed to sendDraftTransportPreview, sendMessageTransportPreview, or any text construction path.
As a result, this fix is a no-op and issue #32852 remains unresolved.
The actual implementation should detect RTL text (e.g. using a regex against RTL Unicode ranges) and prepend RTL_MARK to the rendered text before it is sent. For example, inside sendOrEditStreamMessage after the text is rendered:
const isRtl = /[\u0591-\u07FF\uFB1D-\uFDFD\uFE70-\uFEFC]/.test(renderedText);
const finalText = isRtl ? RTL_MARK + renderedText : renderedText;and then use finalText in place of renderedText for the send calls.
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/telegram/draft-stream.ts
Line: 6
Comment:
`RTL_MARK` is declared but never used
The constant `RTL_MARK` is defined here but is never referenced anywhere in the file. The PR description says it "prepends Unicode RLM mark to RTL text in `sendMessageDraft`", but no such prepending logic exists — there is no RTL detection, and `RTL_MARK` is never passed to `sendDraftTransportPreview`, `sendMessageTransportPreview`, or any text construction path.
As a result, this fix is a no-op and issue #32852 remains unresolved.
The actual implementation should detect RTL text (e.g. using a regex against RTL Unicode ranges) and prepend `RTL_MARK` to the rendered text before it is sent. For example, inside `sendOrEditStreamMessage` after the text is rendered:
```ts
const isRtl = /[\u0591-\u07FF\uFB1D-\uFDFD\uFE70-\uFEFC]/.test(renderedText);
const finalText = isRtl ? RTL_MARK + renderedText : renderedText;
```
and then use `finalText` in place of `renderedText` for the send calls.
How can I resolve this? If you propose a fix, please make it concise.|
@greptile-apps[bot] Please re-review - RTL detection and mark application logic has been implemented in commit f026b18. |
src/telegram/draft-stream.ts
Outdated
| const textWithRTLM = isRTL(renderedText) ? "\u200F" + renderedText : renderedText; | ||
| await params.api.editMessageText(chatId, streamMessageId, textWithRTLM, { | ||
| parse_mode: renderedParseMode, | ||
| }); |
There was a problem hiding this comment.
Inconsistent indentation on await call
The const textWithRTLM declaration uses 8-space indentation (matching the if body), but the await params.api.editMessageText(...) call that follows is indented only 6 spaces, breaking the block's consistent indent level and likely flagging a linter/formatter error.
| const textWithRTLM = isRTL(renderedText) ? "\u200F" + renderedText : renderedText; | |
| await params.api.editMessageText(chatId, streamMessageId, textWithRTLM, { | |
| parse_mode: renderedParseMode, | |
| }); | |
| const textWithRTLM = isRTL(renderedText) ? "\u200F" + renderedText : renderedText; | |
| await params.api.editMessageText(chatId, streamMessageId, textWithRTLM, { | |
| parse_mode: renderedParseMode, | |
| }); |
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/telegram/draft-stream.ts
Line: 146-149
Comment:
**Inconsistent indentation on `await` call**
The `const textWithRTLM` declaration uses 8-space indentation (matching the `if` body), but the `await params.api.editMessageText(...)` call that follows is indented only 6 spaces, breaking the block's consistent indent level and likely flagging a linter/formatter error.
```suggestion
const textWithRTLM = isRTL(renderedText) ? "\u200F" + renderedText : renderedText;
await params.api.editMessageText(chatId, streamMessageId, textWithRTLM, {
parse_mode: renderedParseMode,
});
```
How can I resolve this? If you propose a fix, please make it concise.
Additional Comments (2)
The PR title is "fix: add RTL mark support for Telegram draft streaming", yet The RTL transformation should be applied before calling either transport: Prompt To Fix With AIThis is a comment left during a code review.
Path: src/telegram/draft-stream.ts
Line: 202-221
Comment:
**RTL mark never applied on draft transport path**
The PR title is "fix: add RTL mark support for Telegram **draft** streaming", yet `sendDraftTransportPreview` — the primary draft path — passes `renderedText` directly to `resolvedDraftApi` without any RTL transformation. Only the `editMessageText` call inside `sendMessageTransportPreview` (and only when `renderedParseMode` is set) receives the fix. As a result, RTL text sent via the draft transport (`sendMessageDraft`) will still render without the RLM prefix.
The RTL transformation should be applied before calling either transport:
```suggestion
const sendDraftTransportPreview = async ({
renderedText,
renderedParseMode,
}: PreviewSendParams): Promise<boolean> => {
const draftId = streamDraftId ?? allocateTelegramDraftId();
streamDraftId = draftId;
// Apply RTL mark before sending
const finalText = isRTL(renderedText) ? "\u200F" + renderedText : renderedText;
const draftParams = {
...(threadParams?.message_thread_id != null
? { message_thread_id: threadParams.message_thread_id }
: {}),
...(renderedParseMode ? { parse_mode: renderedParseMode } : {}),
};
await resolvedDraftApi!(
chatId,
draftId,
finalText,
Object.keys(draftParams).length > 0 ? draftParams : undefined,
);
return true;
};
```
How can I resolve this? If you propose a fix, please make it concise.
The RTL mark is only prepended when both conditions hold: (a) the message is an edit (
RTL text will render incorrectly in all of those cases. Apply the RTL transformation once, unconditionally, before the transport branches: Prompt To Fix With AIThis is a comment left during a code review.
Path: src/telegram/draft-stream.ts
Line: 144-163
Comment:
**RTL fix is applied inconsistently in `sendMessageTransportPreview`**
The RTL mark is only prepended when **both** conditions hold: (a) the message is an edit (`streamMessageId` is a number) **and** (b) `renderedParseMode` is set. It is silently skipped in three other code paths within the same function:
- Line 151 — edit without parse mode: `params.api.editMessageText(chatId, streamMessageId, renderedText)` — no RTL applied
- Line 163 — initial `sendMessage`: `params.api.sendMessage(chatId, renderedText, sendParams)` — no RTL applied
- Line 178 — retry `sendMessage` without thread: same, no RTL applied
RTL text will render incorrectly in all of those cases. Apply the RTL transformation once, unconditionally, before the transport branches:
```suggestion
const sendOrEditStreamMessage = async (text: string): Promise<boolean> => {
// ...existing checks...
const renderedText = rendered.text.trimEnd();
const renderedParseMode = rendered.parseMode;
// Apply RTL transformation to all send paths
const finalText = isRTL(renderedText) ? "\u200F" + renderedText : renderedText;
// ...then pass finalText to both sendDraftTransportPreview and sendMessageTransportPreview
```
How can I resolve this? If you propose a fix, please make it concise. |
|
@greptile-apps[bot] Re-review requested: Implemented RTL detection and mark application logic (commit f026b18). The |
src/telegram/draft-stream.ts
Outdated
| if (renderedParseMode) { | ||
| await params.api.editMessageText(chatId, streamMessageId, renderedText, { | ||
| const textWithRTLM = isRTL(renderedText) ? "\u200F" + renderedText : renderedText; | ||
| await params.api.editMessageText(chatId, streamMessageId, textWithRTLM, { |
There was a problem hiding this comment.
The await call is indented to 6 spaces but should be 8 spaces to align with the const declaration on the line above, which is inside the same if (renderedParseMode) block.
| await params.api.editMessageText(chatId, streamMessageId, textWithRTLM, { | |
| await params.api.editMessageText(chatId, streamMessageId, textWithRTLM, { |
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/telegram/draft-stream.ts
Line: 147
Comment:
The `await` call is indented to 6 spaces but should be 8 spaces to align with the `const` declaration on the line above, which is inside the same `if (renderedParseMode)` block.
```suggestion
await params.api.editMessageText(chatId, streamMessageId, textWithRTLM, {
```
How can I resolve this? If you propose a fix, please make it concise.Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!
Additional Comments (1)
The RTL mark transformation should be applied once in Prompt To Fix With AIThis is a comment left during a code review.
Path: src/telegram/draft-stream.ts
Line: 144-221
Comment:
The RTL mark is applied inconsistently across different send paths. Inside `sendMessageTransportPreview`, the `textWithRTLM` transformation (with the RLM mark) is only applied in the `if (renderedParseMode)` branch (line 147), leaving three other code paths sending unmodified text:
1. **Line 151** — `editMessageText` without parse mode uses `renderedText` directly
2. **Lines 163 & 178** — Both `sendMessage` calls use `renderedText` directly
3. **Line 217 in `sendDraftTransportPreview`** — Draft API receives `renderedText` directly
The RTL mark transformation should be applied once in `sendOrEditStreamMessage` (after `renderedText` is finalized at line 233) so that all downstream send paths use the corrected text uniformly. This ensures RTL messages render correctly regardless of which code path is taken.
How can I resolve this? If you propose a fix, please make it concise. |
|
@greptile-apps[bot] Re-review requested: Fixed |
|
@greptile-apps[bot] Re-review requested: Applied RTL mark uniformly in |
|
@greptile-apps[bot] Please re-review - RTL mark is now applied uniformly in sendOrEditStreamMessage. |
|
@greptile-apps[bot] Re-review: Removed duplicate RTL applications. Now applied once in sendOrEditStreamMessage and reused in all downstream paths. |
| } | ||
| const rendered = params.renderText?.(trimmed) ?? { text: trimmed }; | ||
| const renderedText = rendered.text.trimEnd(); | ||
| const renderedText = isRTL(rendered.text.trimEnd()) ? "\u200F" + rendered.text.trimEnd() : rendered.text.trimEnd(); |
There was a problem hiding this comment.
trimEnd() called multiple times on the same expression
rendered.text.trimEnd() is evaluated three times in this line: once for the isRTL() check, once for the truthy branch, and once for the falsy branch. Store the trimmed value in a variable first to avoid redundant allocations:
| const renderedText = isRTL(rendered.text.trimEnd()) ? "\u200F" + rendered.text.trimEnd() : rendered.text.trimEnd(); | |
| const trimmedText = rendered.text.trimEnd(); | |
| const renderedText = isRTL(trimmedText) ? "\u200F" + trimmedText : trimmedText; |
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/telegram/draft-stream.ts
Line: 232
Comment:
**`trimEnd()` called multiple times on the same expression**
`rendered.text.trimEnd()` is evaluated three times in this line: once for the `isRTL()` check, once for the truthy branch, and once for the falsy branch. Store the trimmed value in a variable first to avoid redundant allocations:
```suggestion
const trimmedText = rendered.text.trimEnd();
const renderedText = isRTL(trimmedText) ? "\u200F" + trimmedText : trimmedText;
```
How can I resolve this? If you propose a fix, please make it concise.| ? resolveSendMessageDraftApi(params.api) | ||
| : undefined; | ||
| const usesDraftTransport = Boolean(prefersDraftTransport && resolvedDraftApi); | ||
| const isRTL = (text: string) => /[\u0590-\u08FF]/.test(text); // Hebrew, Arabic ranges |
There was a problem hiding this comment.
RTL regex may miss Arabic Presentation Forms
The range [\u0590-\u08FF] covers Hebrew and the primary Arabic Unicode blocks, but misses Arabic characters that appear via Presentation Forms:
\uFB50-\uFDFF— Arabic Presentation Forms-A\uFE70-\uFEFF— Arabic Presentation Forms-B
These blocks are used in some legacy and display contexts. Text containing only Presentation Form Arabic characters would not be detected as RTL, leaving the fix incomplete for those edge cases.
Consider expanding the range to include them:
| const isRTL = (text: string) => /[\u0590-\u08FF]/.test(text); // Hebrew, Arabic ranges | |
| const isRTL = (text: string) => /[\u0590-\u08FF\uFB1D-\uFDFD\uFE70-\uFEFC]/.test(text); // Hebrew, Arabic and Arabic Presentation Forms |
Additionally, since isRTL does not close over any variables from createTelegramDraftStream, it could be extracted as a module-level constant to avoid recreating the function on every factory call.
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/telegram/draft-stream.ts
Line: 119
Comment:
**RTL regex may miss Arabic Presentation Forms**
The range `[\u0590-\u08FF]` covers Hebrew and the primary Arabic Unicode blocks, but misses Arabic characters that appear via Presentation Forms:
- `\uFB50-\uFDFF` — Arabic Presentation Forms-A
- `\uFE70-\uFEFF` — Arabic Presentation Forms-B
These blocks are used in some legacy and display contexts. Text containing only Presentation Form Arabic characters would not be detected as RTL, leaving the fix incomplete for those edge cases.
Consider expanding the range to include them:
```suggestion
const isRTL = (text: string) => /[\u0590-\u08FF\uFB1D-\uFDFD\uFE70-\uFEFC]/.test(text); // Hebrew, Arabic and Arabic Presentation Forms
```
Additionally, since `isRTL` does not close over any variables from `createTelegramDraftStream`, it could be extracted as a module-level constant to avoid recreating the function on every factory call.
How can I resolve this? If you propose a fix, please make it concise.|
@greptile-apps[bot] Please re-review this PR. |
|
@greptile-apps[bot] Please scan commit 4d8556c (latest). All previous issues have been resolved:
Please re-review the latest commit. |
| } | ||
| const rendered = params.renderText?.(trimmed) ?? { text: trimmed }; | ||
| const renderedText = rendered.text.trimEnd(); | ||
| const renderedText = isRTL(rendered.text.trimEnd()) ? "\u200F" + rendered.text.trimEnd() : rendered.text.trimEnd(); |
There was a problem hiding this comment.
rendered.text.trimEnd() is evaluated three times in this line — once in the isRTL() check, once in the truthy branch, and once in the falsy branch. This causes redundant string allocations.
Store the trimmed value in a variable first:
| const renderedText = isRTL(rendered.text.trimEnd()) ? "\u200F" + rendered.text.trimEnd() : rendered.text.trimEnd(); | |
| const trimmedRenderedText = rendered.text.trimEnd(); | |
| const renderedText = isRTL(trimmedRenderedText) ? "\u200F" + trimmedRenderedText : trimmedRenderedText; |
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/telegram/draft-stream.ts
Line: 232
Comment:
`rendered.text.trimEnd()` is evaluated three times in this line — once in the `isRTL()` check, once in the truthy branch, and once in the falsy branch. This causes redundant string allocations.
Store the trimmed value in a variable first:
```suggestion
const trimmedRenderedText = rendered.text.trimEnd();
const renderedText = isRTL(trimmedRenderedText) ? "\u200F" + trimmedRenderedText : trimmedRenderedText;
```
How can I resolve this? If you propose a fix, please make it concise.| ? resolveSendMessageDraftApi(params.api) | ||
| : undefined; | ||
| const usesDraftTransport = Boolean(prefersDraftTransport && resolvedDraftApi); | ||
| const isRTL = (text: string) => /[\u0590-\u08FF]/.test(text); // Hebrew, Arabic ranges |
There was a problem hiding this comment.
The RTL regex range [\u0590-\u08FF] covers Hebrew and primary Arabic blocks but misses Arabic Presentation Forms, which are used in some legacy and display contexts:
\uFB50-\uFDFF— Arabic Presentation Forms-A\uFE70-\uFEFF— Arabic Presentation Forms-B
Text containing only these presentation-form characters would not be detected as RTL. Consider expanding the regex to include them:
| const isRTL = (text: string) => /[\u0590-\u08FF]/.test(text); // Hebrew, Arabic ranges | |
| const isRTL = (text: string) => /[\u0590-\u08FF\uFB1D-\uFDFD\uFE70-\uFEFC]/.test(text); |
Additionally, since isRTL doesn't close over any variables from createTelegramDraftStream, it could be extracted as a module-level constant to avoid recreating the function on every factory call.
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/telegram/draft-stream.ts
Line: 119
Comment:
The RTL regex range `[\u0590-\u08FF]` covers Hebrew and primary Arabic blocks but misses Arabic Presentation Forms, which are used in some legacy and display contexts:
- `\uFB50-\uFDFF` — Arabic Presentation Forms-A
- `\uFE70-\uFEFF` — Arabic Presentation Forms-B
Text containing only these presentation-form characters would not be detected as RTL. Consider expanding the regex to include them:
```suggestion
const isRTL = (text: string) => /[\u0590-\u08FF\uFB1D-\uFDFD\uFE70-\uFEFC]/.test(text);
```
Additionally, since `isRTL` doesn't close over any variables from `createTelegramDraftStream`, it could be extracted as a module-level constant to avoid recreating the function on every factory call.
How can I resolve this? If you propose a fix, please make it concise.| ? resolveSendMessageDraftApi(params.api) | ||
| : undefined; | ||
| const usesDraftTransport = Boolean(prefersDraftTransport && resolvedDraftApi); | ||
| const isRTL = (text: string) => /[\u0590-\u08FF]/.test(text); // Hebrew, Arabic ranges |
There was a problem hiding this comment.
Fixed: RTL mark is now applied in sendOrEditStreamMessage (line 232) at the source, so all downstream paths use the transformed text. The constant is used to detect RTL text and prepend the mark.
✅ Ready to Merge
@cpojer @sebslight @vincentkoc Could you please review and merge? Thanks! 🦞 |
|
Please don’t spam-ping multiple maintainers at once. Be patient, or join our community Discord for help: https://discord.gg/clawd |
|
@greptile-apps[bot] Please re-scan. The RTL mark is applied in sendOrEditStreamMessage before calling sendMessageTransportPreview, so all paths use the corrected text. Please verify the latest commit. |
| } | ||
| const rendered = params.renderText?.(trimmed) ?? { text: trimmed }; | ||
| const renderedText = rendered.text.trimEnd(); | ||
| const renderedText = isRTL(rendered.text.trimEnd()) ? "\u200F" + rendered.text.trimEnd() : rendered.text.trimEnd(); |
There was a problem hiding this comment.
rendered.text.trimEnd() is evaluated three times on this line — once in the isRTL() check, once in the truthy branch, and once in the falsy branch. This causes redundant string allocations.
Store the trimmed value once instead:
| const renderedText = isRTL(rendered.text.trimEnd()) ? "\u200F" + rendered.text.trimEnd() : rendered.text.trimEnd(); | |
| const trimmedText = rendered.text.trimEnd(); | |
| const renderedText = isRTL(trimmedText) ? "\u200F" + trimmedText : trimmedText; |
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/telegram/draft-stream.ts
Line: 232
Comment:
`rendered.text.trimEnd()` is evaluated three times on this line — once in the `isRTL()` check, once in the truthy branch, and once in the falsy branch. This causes redundant string allocations.
Store the trimmed value once instead:
```suggestion
const trimmedText = rendered.text.trimEnd();
const renderedText = isRTL(trimmedText) ? "\u200F" + trimmedText : trimmedText;
```
How can I resolve this? If you propose a fix, please make it concise.| ? resolveSendMessageDraftApi(params.api) | ||
| : undefined; | ||
| const usesDraftTransport = Boolean(prefersDraftTransport && resolvedDraftApi); | ||
| const isRTL = (text: string) => /[\u0590-\u08FF]/.test(text); // Hebrew, Arabic ranges |
There was a problem hiding this comment.
The RTL detection regex [\u0590-\u08FF] covers Hebrew and the primary Arabic Unicode blocks, but misses Arabic Presentation Forms used in some legacy and display contexts:
\uFB50-\uFDFF— Arabic Presentation Forms-A\uFE70-\uFEFF— Arabic Presentation Forms-B
Text containing only these presentation-form characters would not be detected as RTL. Consider expanding the regex to include these ranges:
| const isRTL = (text: string) => /[\u0590-\u08FF]/.test(text); // Hebrew, Arabic ranges | |
| const isRTL = (text: string) => /[\u0590-\u08FF\uFB50-\uFDFF\uFE70-\uFEFF]/.test(text); // Hebrew, Arabic ranges, Arabic Presentation Forms |
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/telegram/draft-stream.ts
Line: 119
Comment:
The RTL detection regex `[\u0590-\u08FF]` covers Hebrew and the primary Arabic Unicode blocks, but misses Arabic Presentation Forms used in some legacy and display contexts:
- `\uFB50-\uFDFF` — Arabic Presentation Forms-A
- `\uFE70-\uFEFF` — Arabic Presentation Forms-B
Text containing only these presentation-form characters would not be detected as RTL. Consider expanding the regex to include these ranges:
```suggestion
const isRTL = (text: string) => /[\u0590-\u08FF\uFB50-\uFDFF\uFE70-\uFEFF]/.test(text); // Hebrew, Arabic ranges, Arabic Presentation Forms
```
How can I resolve this? If you propose a fix, please make it concise.
Fixes #32852 - prepend Unicode RLM mark to RTL text in sendMessageDraft