openclaw/extensions/telegram/src/reasoning-lane-coordinator.ts
scoootscooob e5bca0832f
refactor: move Telegram channel implementation to extensions/ (#45635)
* refactor: move Telegram channel implementation to extensions/telegram/src/

Move all Telegram channel code (123 files + 10 bot/ files + 8 channel plugin
files) from src/telegram/ and src/channels/plugins/*/telegram.ts to
extensions/telegram/src/. Leave thin re-export shims at original locations so
cross-cutting src/ imports continue to resolve.

- Fix all relative import paths in moved files (../X/ -> ../../../src/X/)
- Fix vi.mock paths in 60 test files
- Fix inline typeof import() expressions
- Update tsconfig.plugin-sdk.dts.json rootDir to "." for cross-directory DTS
- Update write-plugin-sdk-entry-dts.ts for new rootDir structure
- Move channel plugin files with correct path remapping

* fix: support keyed telegram send deps

* fix: sync telegram extension copies with latest main

* fix: correct import paths and remove misplaced files in telegram extension

* fix: sync outbound-adapter with main (add sendTelegramPayloadMessages) and fix delivery.test import path
2026-03-14 02:50:17 -07:00

137 lines
3.6 KiB
TypeScript

import { formatReasoningMessage } from "../../../src/agents/pi-embedded-utils.js";
import type { ReplyPayload } from "../../../src/auto-reply/types.js";
import { findCodeRegions, isInsideCode } from "../../../src/shared/text/code-regions.js";
import { stripReasoningTagsFromText } from "../../../src/shared/text/reasoning-tags.js";
const REASONING_MESSAGE_PREFIX = "Reasoning:\n";
const REASONING_TAG_PREFIXES = [
"<think",
"<thinking",
"<thought",
"<antthinking",
"</think",
"</thinking",
"</thought",
"</antthinking",
];
const THINKING_TAG_RE = /<\s*(\/?)\s*(?:think(?:ing)?|thought|antthinking)\b[^<>]*>/gi;
function extractThinkingFromTaggedStreamOutsideCode(text: string): string {
if (!text) {
return "";
}
const codeRegions = findCodeRegions(text);
let result = "";
let lastIndex = 0;
let inThinking = false;
THINKING_TAG_RE.lastIndex = 0;
for (const match of text.matchAll(THINKING_TAG_RE)) {
const idx = match.index ?? 0;
if (isInsideCode(idx, codeRegions)) {
continue;
}
if (inThinking) {
result += text.slice(lastIndex, idx);
}
const isClose = match[1] === "/";
inThinking = !isClose;
lastIndex = idx + match[0].length;
}
if (inThinking) {
result += text.slice(lastIndex);
}
return result.trim();
}
function isPartialReasoningTagPrefix(text: string): boolean {
const trimmed = text.trimStart().toLowerCase();
if (!trimmed.startsWith("<")) {
return false;
}
if (trimmed.includes(">")) {
return false;
}
return REASONING_TAG_PREFIXES.some((prefix) => prefix.startsWith(trimmed));
}
export type TelegramReasoningSplit = {
reasoningText?: string;
answerText?: string;
};
export function splitTelegramReasoningText(text?: string): TelegramReasoningSplit {
if (typeof text !== "string") {
return {};
}
const trimmed = text.trim();
if (isPartialReasoningTagPrefix(trimmed)) {
return {};
}
if (
trimmed.startsWith(REASONING_MESSAGE_PREFIX) &&
trimmed.length > REASONING_MESSAGE_PREFIX.length
) {
return { reasoningText: trimmed };
}
const taggedReasoning = extractThinkingFromTaggedStreamOutsideCode(text);
const strippedAnswer = stripReasoningTagsFromText(text, { mode: "strict", trim: "both" });
if (!taggedReasoning && strippedAnswer === text) {
return { answerText: text };
}
const reasoningText = taggedReasoning ? formatReasoningMessage(taggedReasoning) : undefined;
const answerText = strippedAnswer || undefined;
return { reasoningText, answerText };
}
export type BufferedFinalAnswer = {
payload: ReplyPayload;
text: string;
};
export function createTelegramReasoningStepState() {
let reasoningStatus: "none" | "hinted" | "delivered" = "none";
let bufferedFinalAnswer: BufferedFinalAnswer | undefined;
const noteReasoningHint = () => {
if (reasoningStatus === "none") {
reasoningStatus = "hinted";
}
};
const noteReasoningDelivered = () => {
reasoningStatus = "delivered";
};
const shouldBufferFinalAnswer = () => {
return reasoningStatus === "hinted" && !bufferedFinalAnswer;
};
const bufferFinalAnswer = (value: BufferedFinalAnswer) => {
bufferedFinalAnswer = value;
};
const takeBufferedFinalAnswer = (): BufferedFinalAnswer | undefined => {
const value = bufferedFinalAnswer;
bufferedFinalAnswer = undefined;
return value;
};
const resetForNextStep = () => {
reasoningStatus = "none";
bufferedFinalAnswer = undefined;
};
return {
noteReasoningHint,
noteReasoningDelivered,
shouldBufferFinalAnswer,
bufferFinalAnswer,
takeBufferedFinalAnswer,
resetForNextStep,
};
}