Files
oh-my-pi-telegram/lib/inbound.ts
Evandro Fonseca Junior d1d04001d0
Release / release (push) Failing after 5s
Validate / validate (push) Failing after 5m21s
chore: fork @llblab/[email protected] as @evandrodevbr/oh-my-pi-telegram
Fork of upstream llblab/[email protected].0 (SHA afe09c5) repackaged as a
drop-in extension for can1357/oh-my-pi (omp >= 16.5.2).

Changes vs upstream:
- Rename package: @llblab/pi-telegram -> @evandrodevbr/oh-my-pi-telegram
- Bump version: 0.22.0 -> 0.22.0-evandro.1
- Update repository/homepage/bugs URLs to evandrodevbr
- Add omp field in package.json (displayName + compatibleWith)
- README: prepend 'Oh My Pi (omp) Compatibility' section with install
  commands, verified-components table, upstream lineage, and rationale
- CHANGELOG: add '0.22.0-evandro.1' entry under Unreleased
- docs/: rename @llblab/pi-telegram imports to @evandrodevbr/oh-my-pi-telegram
- tests/public-api.test.ts: rename package import paths

Gates re-run on the fork:
- npm run typecheck  -> tsc --noEmit (success)
- npm test           -> 1229 pass / 0 fail / 1 platform-only skip
- npm run audit      -> found 0 vulnerabilities
- npm run pack:check -> evandrodevbr-oh-my-pi-telegram-0.22.0-evandro.1.tgz

Verified:
- omp install --link ./pi-telegram -> Linked OK
- omp plugin list -> @evandrodevbr/[email protected].0-evandro.1

Upstream lineage: badlogic/pi-telegram -> llblab/pi-telegram (0.22.0) -> evandrodevbr/oh-my-pi-telegram (this fork)
2026-07-15 14:18:16 -03:00

829 lines
25 KiB
TypeScript

/**
* Telegram inbound handler pipeline
* Zones: telegram inbound, command templates, prompt preparation
* Owns MIME/type matching, command-template execution, fallback handling, and prompt injection before prompt enqueueing
*/
import { readFile } from "node:fs/promises";
import { basename } from "node:path";
import {
buildCommandTemplateInvocation,
expandCommandTemplateConfigs,
normalizeCommandTemplateConfig,
substituteCommandTemplateToken,
type CommandTemplateConfig,
type CommandTemplateObjectConfig,
} from "./command-templates.ts";
import { getTelegramVoiceTranscriptionProviders } from "./voice.ts";
const DEFAULT_INBOUND_HANDLER_TIMEOUT_MS = 120_000;
const INBOUND_HANDLER_REGISTRY_KEY = "__piTelegramInboundHandlers__";
const MAX_INBOUND_HANDLER_OUTPUT_LENGTH = 12_000;
const MAX_INBOUND_HANDLER_FAILURE_STREAM_LENGTH = 4_000;
type TelegramInboundCommandTemplateConfig =
| string
| CommandTemplateObjectConfig;
export interface TelegramInboundHandlerConfig {
match?: string | string[];
mime?: string | string[];
type?: string | string[];
template?: string | TelegramInboundCommandTemplateConfig[];
args?: string[];
defaults?: Record<string, unknown>;
timeout?: number | string;
}
export interface TelegramInboundHandlerFile {
path: string;
fileName?: string;
mimeType?: string;
kind?: string;
isImage?: boolean;
}
export interface TelegramInboundHandlerOutput {
file: TelegramInboundHandlerFile;
output: string;
handler: TelegramInboundHandlerConfig;
}
export interface TelegramInboundHandlerProcessResult<
TFile extends TelegramInboundHandlerFile = TelegramInboundHandlerFile,
> {
rawText: string;
promptFiles: TFile[];
handlerOutputs: string[];
handledFiles: TelegramInboundHandlerOutput[];
}
export interface TelegramInboundHandlerExecOptions {
cwd?: string;
timeout?: number;
signal?: AbortSignal;
stdin?: string;
retry?: number;
}
export interface TelegramInboundHandlerExecResult {
stdout: string;
stderr: string;
code: number;
killed: boolean;
}
export interface TelegramInboundHandlerRuntimeContext {
cwd: string;
}
export interface TelegramInboundHandlerRuntimeDeps<TContext> {
getHandlers: () => TelegramInboundHandlerConfig[] | undefined;
execCommand: (
command: string,
args: string[],
options?: TelegramInboundHandlerExecOptions,
) => Promise<TelegramInboundHandlerExecResult>;
getCwd: (ctx: TContext) => string;
recordRuntimeEvent?: (
category: string,
error: unknown,
details?: Record<string, unknown>,
) => void;
}
export interface TelegramInboundHandlerRuntime<TContext> {
process: <TFile extends TelegramInboundHandlerFile>(
files: TFile[],
rawText: string,
ctx: TContext,
) => Promise<TelegramInboundHandlerProcessResult<TFile>>;
}
export type TelegramInboundProgrammaticHandlerResult =
| string
| { text: string }
| undefined;
export interface TelegramInboundProgrammaticHandlerInput {
kind: string;
text?: string;
file?: TelegramInboundHandlerFile;
mimeType?: string;
}
export type TelegramInboundProgrammaticHandler = (
input: TelegramInboundProgrammaticHandlerInput,
options?: { cwd?: string },
) => Promise<TelegramInboundProgrammaticHandlerResult>;
export interface TelegramInboundHandlerRegistry {
handlers: Map<string, TelegramInboundProgrammaticHandler[]>;
}
interface InboundHandlerInvocation {
command: string;
args: string[];
}
const BUILT_IN_TEXT_ATTACHMENT_MAX_BYTES = 1_000_000;
function getOrCreateInboundHandlerRegistry(): TelegramInboundHandlerRegistry {
const existing = (globalThis as Record<string, unknown>)[
INBOUND_HANDLER_REGISTRY_KEY
];
if (
existing &&
typeof existing === "object" &&
existing !== null &&
"handlers" in existing &&
existing.handlers instanceof Map
) {
return existing as TelegramInboundHandlerRegistry;
}
const registry: TelegramInboundHandlerRegistry = {
handlers: new Map(),
};
(globalThis as Record<string, unknown>)[INBOUND_HANDLER_REGISTRY_KEY] =
registry;
return registry;
}
export function registerTelegramInboundHandler(
kind: string,
handler: TelegramInboundProgrammaticHandler,
): () => void {
const normalizedKind = kind.trim() || "*";
const registry = getOrCreateInboundHandlerRegistry();
const list = registry.handlers.get(normalizedKind) ?? [];
list.push(handler);
registry.handlers.set(normalizedKind, list);
return () => {
const updated = registry.handlers.get(normalizedKind) ?? [];
const index = updated.indexOf(handler);
if (index !== -1) {
updated.splice(index, 1);
registry.handlers.set(normalizedKind, updated);
}
};
}
export function getTelegramInboundProgrammaticHandlers(
kind: string,
): TelegramInboundProgrammaticHandler[] {
const registry = getOrCreateInboundHandlerRegistry();
return [
...(registry.handlers.get(kind) ?? []),
...(kind === "*" ? [] : (registry.handlers.get("*") ?? [])),
];
}
export function hasTelegramInboundHandler(kind?: string): boolean {
const registry = getOrCreateInboundHandlerRegistry();
if (kind !== undefined) return (registry.handlers.get(kind)?.length ?? 0) > 0;
return Array.from(registry.handlers.values()).some((list) => list.length > 0);
}
export function clearTelegramInboundHandlers(): void {
getOrCreateInboundHandlerRegistry().handlers.clear();
}
function truncateTelegramInboundText(text: string, maxLength: number): string {
if (text.length <= maxLength) return text;
return `${text.slice(0, maxLength).trimEnd()}… [truncated ${text.length - maxLength} chars]`;
}
function truncateTelegramInboundOutput(text: string): string {
return truncateTelegramInboundText(text, MAX_INBOUND_HANDLER_OUTPUT_LENGTH);
}
function truncateTelegramInboundFailureStream(text: string): string {
return truncateTelegramInboundText(
text.trimEnd(),
MAX_INBOUND_HANDLER_FAILURE_STREAM_LENGTH,
);
}
function normalizeInboundProgrammaticHandlerText(
result: TelegramInboundProgrammaticHandlerResult,
): string | undefined {
const text = typeof result === "string" ? result : result?.text;
const normalized = text?.trim();
return normalized ? truncateTelegramInboundOutput(normalized) : undefined;
}
function normalizeStringList(value: string | string[] | undefined): string[] {
if (Array.isArray(value)) {
return value
.map(String)
.map((item) => item.trim())
.filter(Boolean);
}
if (typeof value === "string" && value.trim()) return [value.trim()];
return [];
}
function matchesWildcard(pattern: string, value: string | undefined): boolean {
if (!value) return false;
const normalizedPattern = pattern.toLowerCase();
const normalizedValue = value.toLowerCase();
if (normalizedPattern === "*") return true;
const escaped = normalizedPattern
.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")
.replace(/\\\*/g, ".*");
return new RegExp(`^${escaped}$`).test(normalizedValue);
}
function handlerHasSelectors(handler: TelegramInboundHandlerConfig): boolean {
return (
normalizeStringList(handler.match).length > 0 ||
normalizeStringList(handler.mime).length > 0 ||
normalizeStringList(handler.type).length > 0
);
}
function matchesAnyPattern(
patterns: string[],
value: string | undefined,
): boolean {
return patterns.some((pattern) => matchesWildcard(pattern, value));
}
function isTelegramTextMimeType(mimeType: string | undefined): boolean {
return matchesWildcard("text/*", mimeType);
}
export function telegramInboundHandlerMatchesFile(
handler: TelegramInboundHandlerConfig,
file: TelegramInboundHandlerFile,
): boolean {
if (!handlerHasSelectors(handler)) return true;
const matchPatterns = normalizeStringList(handler.match);
const mimePatterns = normalizeStringList(handler.mime);
const typePatterns = normalizeStringList(handler.type);
if (matchesAnyPattern(mimePatterns, file.mimeType)) return true;
if (matchesAnyPattern(typePatterns, file.kind)) return true;
if (matchesAnyPattern(matchPatterns, file.mimeType)) return true;
return matchesAnyPattern(matchPatterns, file.kind);
}
export function findTelegramInboundHandlers(
handlers: TelegramInboundHandlerConfig[] | undefined,
file: TelegramInboundHandlerFile,
): TelegramInboundHandlerConfig[] {
if (!Array.isArray(handlers)) return [];
return handlers.filter(
(handler) =>
!!handler &&
typeof handler === "object" &&
telegramInboundHandlerMatchesFile(handler, file),
);
}
export function findTelegramInboundHandler(
handlers: TelegramInboundHandlerConfig[] | undefined,
file: TelegramInboundHandlerFile,
): TelegramInboundHandlerConfig | undefined {
return findTelegramInboundHandlers(handlers, file)[0];
}
function hasInboundFilePlaceholder(value: string): boolean {
return /\{file\}/.test(value);
}
function getTelegramInboundHandlerTemplateValues(
file: TelegramInboundHandlerFile,
text = "",
): Record<string, string> {
return {
file: file.path,
mime: file.mimeType ?? "",
text,
type: file.kind ?? "",
};
}
function buildTelegramInboundTemplateInvocation(
handler: CommandTemplateConfig,
file: TelegramInboundHandlerFile,
cwd: string,
appendFileIfMissing = true,
): InboundHandlerInvocation {
const values = getTelegramInboundHandlerTemplateValues(file);
const templateConfig = normalizeCommandTemplateConfig(handler);
const hadFilePlaceholder =
typeof templateConfig.template === "string"
? hasInboundFilePlaceholder(templateConfig.template)
: false;
const invocation = buildCommandTemplateInvocation(handler, values, cwd, {
emptyMessage: "Inbound handler template is empty",
missingLabel: "inbound handler template",
});
if (appendFileIfMissing && !hadFilePlaceholder)
invocation.args.push(file.path);
return invocation;
}
export function buildTelegramInboundHandlerInvocation(
handler: CommandTemplateConfig,
file: TelegramInboundHandlerFile,
cwd: string,
appendFileIfMissing = true,
): InboundHandlerInvocation {
const { template } = normalizeCommandTemplateConfig(handler);
if (!template) throw new Error("Inbound handler template is required");
return buildTelegramInboundTemplateInvocation(
handler,
file,
cwd,
appendFileIfMissing,
);
}
function resolveTelegramInboundNumericControlField(
value: number | string | undefined,
values: Record<string, unknown>,
label: string,
): number | undefined {
if (value === undefined) return undefined;
const resolved =
typeof value === "string"
? substituteCommandTemplateToken(value, values, label)
: value;
if (resolved === "") return undefined;
const numeric = Number(resolved);
if (!Number.isFinite(numeric) || numeric < 0)
throw new Error(`Command template ${label} must be a non-negative number.`);
return numeric;
}
function getTelegramInboundHandlerConfiguredTimeout(
handler: TelegramInboundCommandTemplateConfig,
): number | undefined {
const timeout = typeof handler === "string" ? undefined : handler.timeout;
return resolveTelegramInboundNumericControlField(timeout, {}, "timeout");
}
function getTelegramInboundHandlerTimeout(
handler: TelegramInboundCommandTemplateConfig,
): number {
return (
getTelegramInboundHandlerConfiguredTimeout(handler) ??
DEFAULT_INBOUND_HANDLER_TIMEOUT_MS
);
}
function getRemainingTelegramInboundTimeout(
timeout: number,
startedAt: number,
): number {
return Math.max(1, timeout - (Date.now() - startedAt));
}
function getTelegramInboundInitialCompositionStepTimeout(
handler: TelegramInboundHandlerConfig,
step: TelegramInboundCommandTemplateConfig,
): number {
const timeout = getTelegramInboundHandlerTimeout(handler);
const stepTimeout = getTelegramInboundHandlerConfiguredTimeout(step);
return stepTimeout === undefined ? timeout : Math.min(stepTimeout, timeout);
}
function getTelegramInboundCompositionStepTimeout(
handler: TelegramInboundHandlerConfig,
step: TelegramInboundCommandTemplateConfig,
startedAt: number,
): number {
const remaining = getRemainingTelegramInboundTimeout(
getTelegramInboundHandlerTimeout(handler),
startedAt,
);
const stepTimeout = getTelegramInboundHandlerConfiguredTimeout(step);
return stepTimeout === undefined
? remaining
: Math.min(stepTimeout, remaining);
}
function getTelegramInboundHandlerKind(
handler: TelegramInboundHandlerConfig,
): string {
if (Array.isArray(handler.template)) return "composition";
if (handler.template) return "template";
return "unknown";
}
function formatTelegramInboundHandlerFailure(
result: TelegramInboundHandlerExecResult,
): string {
const parts = [
`Inbound handler exited with code ${result.code}${result.killed ? " (killed)" : ""}`,
];
if (result.stderr.trim())
parts.push(`stderr:\n${truncateTelegramInboundFailureStream(result.stderr)}`);
if (result.stdout.trim())
parts.push(`stdout:\n${truncateTelegramInboundFailureStream(result.stdout)}`);
return parts.join("\n\n");
}
async function executeTelegramInboundHandlerInvocation(
handler: TelegramInboundCommandTemplateConfig,
file: TelegramInboundHandlerFile,
cwd: string,
deps: Pick<TelegramInboundHandlerRuntimeDeps<unknown>, "execCommand">,
appendFileIfMissing = true,
timeout = getTelegramInboundHandlerTimeout(handler),
stdin?: string,
): Promise<string> {
const invocation = buildTelegramInboundHandlerInvocation(
handler,
file,
cwd,
appendFileIfMissing,
);
const result = await deps.execCommand(invocation.command, invocation.args, {
cwd,
timeout,
...(typeof handler === "object" && handler.retry !== undefined
? { retry: resolveTelegramInboundNumericControlField(handler.retry, {}, "retry") }
: {}),
...(stdin !== undefined ? { stdin } : {}),
});
if (result.code !== 0)
throw new Error(formatTelegramInboundHandlerFailure(result));
return truncateTelegramInboundOutput(result.stdout);
}
function getTelegramInboundHandlerCompositionSteps(
handler: TelegramInboundHandlerConfig,
): TelegramInboundCommandTemplateConfig[] {
if (Array.isArray(handler.template)) {
return expandCommandTemplateConfigs(
handler,
) as TelegramInboundCommandTemplateConfig[];
}
return [];
}
function getTelegramTextHandlerFile(): TelegramInboundHandlerFile {
return {
path: "",
fileName: "message.txt",
mimeType: "text/plain",
kind: "text",
isImage: false,
};
}
function findTelegramTextHandlers(
handlers: TelegramInboundHandlerConfig[] | undefined,
): TelegramInboundHandlerConfig[] {
if (!Array.isArray(handlers)) return [];
const textFile = getTelegramTextHandlerFile();
return handlers.filter(
(handler) =>
!!handler &&
typeof handler === "object" &&
handlerHasSelectors(handler) &&
telegramInboundHandlerMatchesFile(handler, textFile),
);
}
function buildTelegramTextHandlerInvocation(
handler: CommandTemplateConfig,
text: string,
cwd: string,
): InboundHandlerInvocation {
const values = getTelegramInboundHandlerTemplateValues(
getTelegramTextHandlerFile(),
text,
);
const { template } = normalizeCommandTemplateConfig(handler);
if (!template) throw new Error("Text handler template is required");
return buildCommandTemplateInvocation(handler, values, cwd, {
emptyMessage: "Text handler template is empty",
missingLabel: "text handler template",
});
}
async function executeTelegramTextHandlerInvocation(
handler: TelegramInboundCommandTemplateConfig,
text: string,
cwd: string,
deps: Pick<TelegramInboundHandlerRuntimeDeps<unknown>, "execCommand">,
timeout = getTelegramInboundHandlerTimeout(handler),
): Promise<string> {
const invocation = buildTelegramTextHandlerInvocation(handler, text, cwd);
const result = await deps.execCommand(invocation.command, invocation.args, {
cwd,
timeout,
stdin: text,
...(typeof handler === "object" && handler.retry !== undefined
? { retry: resolveTelegramInboundNumericControlField(handler.retry, {}, "retry") }
: {}),
});
if (result.code !== 0)
throw new Error(formatTelegramInboundHandlerFailure(result));
return truncateTelegramInboundOutput(result.stdout);
}
async function executeTelegramTextHandler(
handler: TelegramInboundHandlerConfig,
text: string,
cwd: string,
deps: Pick<TelegramInboundHandlerRuntimeDeps<unknown>, "execCommand">,
): Promise<string> {
const steps = getTelegramInboundHandlerCompositionSteps(handler);
if (steps.length === 0) {
return (
await executeTelegramTextHandlerInvocation(handler, text, cwd, deps)
).trim();
}
const startedAt = Date.now();
let output = text;
for (const [index, step] of steps.entries()) {
try {
output = await executeTelegramTextHandlerInvocation(
step,
output,
cwd,
deps,
index === 0
? getTelegramInboundInitialCompositionStepTimeout(handler, step)
: getTelegramInboundCompositionStepTimeout(handler, step, startedAt),
);
} catch (error) {
if (typeof step === "object" && step.failure === "root") throw error;
output = "";
}
if (index > 0 && !output) output = text;
}
return output.trim();
}
async function processTelegramTextHandlers(options: {
rawText: string;
handlers?: TelegramInboundHandlerConfig[];
cwd: string;
execCommand: TelegramInboundHandlerRuntimeDeps<unknown>["execCommand"];
recordRuntimeEvent?: TelegramInboundHandlerRuntimeDeps<unknown>["recordRuntimeEvent"];
}): Promise<string> {
if (!options.rawText) return options.rawText;
let text = options.rawText;
for (const handler of findTelegramTextHandlers(options.handlers)) {
try {
const output = await executeTelegramTextHandler(
handler,
text,
options.cwd,
options,
);
if (output) text = output;
} catch (error) {
options.recordRuntimeEvent?.("inbound-text-handler", error, {
handler: getTelegramInboundHandlerKind(handler),
});
}
}
for (const handler of getTelegramInboundProgrammaticHandlers("text")) {
try {
const output = normalizeInboundProgrammaticHandlerText(
await handler(
{ kind: "text", text, mimeType: "text/plain" },
{ cwd: options.cwd },
),
);
if (output) text = output;
} catch (error) {
options.recordRuntimeEvent?.("inbound-programmatic-handler", error, {
kind: "text",
});
}
}
return text;
}
function isTelegramVoiceLikeFile(file: TelegramInboundHandlerFile): boolean {
return (
file.kind === "voice" ||
file.kind === "audio" ||
matchesWildcard("audio/*", file.mimeType)
);
}
async function processTelegramFileWithProgrammaticHandlers(
file: TelegramInboundHandlerFile,
options: {
cwd: string;
recordRuntimeEvent?: TelegramInboundHandlerRuntimeDeps<unknown>["recordRuntimeEvent"];
},
): Promise<string | undefined> {
const kind = file.kind || "*";
for (const handler of getTelegramInboundProgrammaticHandlers(kind)) {
try {
const output = normalizeInboundProgrammaticHandlerText(
await handler(
{
kind,
file,
mimeType: file.mimeType,
},
{ cwd: options.cwd },
),
);
if (output) return output;
} catch (error) {
options.recordRuntimeEvent?.("inbound-programmatic-handler", error, {
fileName: file.fileName || basename(file.path),
kind,
});
}
}
return undefined;
}
async function transcribeTelegramVoiceFileWithProviders(
file: TelegramInboundHandlerFile,
options: {
recordRuntimeEvent?: TelegramInboundHandlerRuntimeDeps<unknown>["recordRuntimeEvent"];
},
): Promise<string | undefined> {
if (!isTelegramVoiceLikeFile(file)) return undefined;
for (const provider of getTelegramVoiceTranscriptionProviders()) {
try {
const result = await provider(file, {});
const text = typeof result === "string" ? result : result?.text;
if (text?.trim()) return truncateTelegramInboundOutput(text.trim());
} catch (error) {
options.recordRuntimeEvent?.("voice-transcription-provider", error, {
fileName: file.fileName || basename(file.path),
});
}
}
return undefined;
}
async function readBuiltInTelegramTextAttachment(
file: TelegramInboundHandlerFile,
): Promise<string | undefined> {
if (!isTelegramTextMimeType(file.mimeType)) return undefined;
const content = await readFile(file.path, "utf8");
const normalized = content.trim();
if (
!normalized ||
Buffer.byteLength(normalized, "utf8") > BUILT_IN_TEXT_ATTACHMENT_MAX_BYTES
) {
return undefined;
}
const name = file.fileName || basename(file.path);
return truncateTelegramInboundOutput(`[${name}]\n${normalized}`);
}
async function executeTelegramInboundHandler(
handler: TelegramInboundHandlerConfig,
file: TelegramInboundHandlerFile,
cwd: string,
deps: Pick<TelegramInboundHandlerRuntimeDeps<unknown>, "execCommand">,
): Promise<string> {
const steps = getTelegramInboundHandlerCompositionSteps(handler);
if (steps.length === 0) {
const output = await executeTelegramInboundHandlerInvocation(
handler,
file,
cwd,
deps,
);
return output.trim();
}
const startedAt = Date.now();
let output = "";
for (const [index, step] of steps.entries()) {
try {
output = await executeTelegramInboundHandlerInvocation(
step,
file,
cwd,
deps,
false,
index === 0
? getTelegramInboundInitialCompositionStepTimeout(handler, step)
: getTelegramInboundCompositionStepTimeout(handler, step, startedAt),
index === 0 ? undefined : output,
);
} catch (error) {
if (typeof step === "object" && step.failure === "root") throw error;
output = "";
}
}
return output.trim();
}
export async function processTelegramInboundHandlers<
TFile extends TelegramInboundHandlerFile,
>(options: {
files: TFile[];
rawText: string;
handlers?: TelegramInboundHandlerConfig[];
cwd: string;
execCommand: TelegramInboundHandlerRuntimeDeps<unknown>["execCommand"];
recordRuntimeEvent?: TelegramInboundHandlerRuntimeDeps<unknown>["recordRuntimeEvent"];
}): Promise<TelegramInboundHandlerProcessResult<TFile>> {
const rawText = await processTelegramTextHandlers({
rawText: options.rawText,
handlers: options.handlers,
cwd: options.cwd,
execCommand: options.execCommand,
recordRuntimeEvent: options.recordRuntimeEvent,
});
const promptFiles: TFile[] = [...options.files];
const outputs: TelegramInboundHandlerOutput[] = [];
for (const file of options.files) {
let hasOutput = false;
const handlers = findTelegramInboundHandlers(options.handlers, file);
for (const handler of handlers) {
try {
const output = await executeTelegramInboundHandler(
handler,
file,
options.cwd,
options,
);
if (output) {
outputs.push({ file, output, handler });
hasOutput = true;
}
break;
} catch (error) {
options.recordRuntimeEvent?.("inbound-handler", error, {
fileName: file.fileName || basename(file.path),
handler: getTelegramInboundHandlerKind(handler),
});
}
}
if (!hasOutput) {
try {
const output = await processTelegramFileWithProgrammaticHandlers(file, {
cwd: options.cwd,
recordRuntimeEvent: options.recordRuntimeEvent,
});
if (output) {
outputs.push({ file, output, handler: { type: "programmatic" } });
hasOutput = true;
}
} catch (error) {
options.recordRuntimeEvent?.("inbound-programmatic-handler", error, {
fileName: file.fileName || basename(file.path),
});
}
}
if (!hasOutput) {
try {
const output = await transcribeTelegramVoiceFileWithProviders(file, {
recordRuntimeEvent: options.recordRuntimeEvent,
});
if (output) {
outputs.push({ file, output, handler: { type: "voice-provider" } });
hasOutput = true;
}
} catch (error) {
options.recordRuntimeEvent?.("voice-transcription-provider", error, {
fileName: file.fileName || basename(file.path),
});
}
}
if (!hasOutput) {
try {
const output = await readBuiltInTelegramTextAttachment(file);
if (output) outputs.push({ file, output, handler: { type: "text" } });
} catch (error) {
options.recordRuntimeEvent?.("inbound-handler", error, {
fileName: file.fileName || basename(file.path),
handler: "built-in-text",
});
}
}
}
return {
rawText,
promptFiles,
handlerOutputs: outputs.map((output) => output.output),
handledFiles: outputs,
};
}
export function createTelegramInboundHandlerRuntime<TContext>(
deps: TelegramInboundHandlerRuntimeDeps<TContext>,
): TelegramInboundHandlerRuntime<TContext> {
return {
process: (files, rawText, ctx) =>
processTelegramInboundHandlers({
files,
rawText,
handlers: deps.getHandlers(),
cwd: deps.getCwd(ctx),
execCommand: deps.execCommand,
recordRuntimeEvent: deps.recordRuntimeEvent,
}),
};
}