refactor: unify typing callbacks
This commit is contained in:
27
src/channels/typing.ts
Normal file
27
src/channels/typing.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
export type TypingCallbacks = {
|
||||
onReplyStart: () => Promise<void>;
|
||||
onIdle?: () => void;
|
||||
};
|
||||
|
||||
export function createTypingCallbacks(params: {
|
||||
start: () => Promise<void>;
|
||||
stop?: () => Promise<void>;
|
||||
onStartError: (err: unknown) => void;
|
||||
onStopError?: (err: unknown) => void;
|
||||
}): TypingCallbacks {
|
||||
const onReplyStart = async () => {
|
||||
try {
|
||||
await params.start();
|
||||
} catch (err) {
|
||||
params.onStartError(err);
|
||||
}
|
||||
};
|
||||
|
||||
const onIdle = params.stop
|
||||
? () => {
|
||||
void params.stop().catch((err) => (params.onStopError ?? params.onStartError)(err));
|
||||
}
|
||||
: undefined;
|
||||
|
||||
return { onReplyStart, onIdle };
|
||||
}
|
||||
@@ -12,6 +12,7 @@ import {
|
||||
removeAckReactionAfterReply,
|
||||
shouldAckReaction as shouldAckReactionGate,
|
||||
} from "../../channels/ack-reactions.js";
|
||||
import { createTypingCallbacks } from "../../channels/typing.js";
|
||||
import {
|
||||
formatInboundEnvelope,
|
||||
formatThreadStarterEnvelope,
|
||||
@@ -350,7 +351,12 @@ export async function processDiscordMessage(ctx: DiscordMessagePreflightContext)
|
||||
onError: (err, info) => {
|
||||
runtime.error?.(danger(`discord ${info.kind} reply failed: ${String(err)}`));
|
||||
},
|
||||
onReplyStart: () => sendTyping({ client, channelId: typingChannelId }),
|
||||
onReplyStart: createTypingCallbacks({
|
||||
start: () => sendTyping({ client, channelId: typingChannelId }),
|
||||
onStartError: (err) => {
|
||||
logVerbose(`discord typing cue failed for channel ${typingChannelId}: ${String(err)}`);
|
||||
},
|
||||
}).onReplyStart,
|
||||
});
|
||||
|
||||
const { queuedFinal, counts } = await dispatchInboundMessage({
|
||||
|
||||
@@ -1,15 +1,9 @@
|
||||
import type { Client } from "@buape/carbon";
|
||||
|
||||
import { logVerbose } from "../../globals.js";
|
||||
|
||||
export async function sendTyping(params: { client: Client; channelId: string }) {
|
||||
try {
|
||||
const channel = await params.client.fetchChannel(params.channelId);
|
||||
if (!channel) return;
|
||||
if ("triggerTyping" in channel && typeof channel.triggerTyping === "function") {
|
||||
await channel.triggerTyping();
|
||||
}
|
||||
} catch (err) {
|
||||
logVerbose(`discord typing cue failed for channel ${params.channelId}: ${String(err)}`);
|
||||
const channel = await params.client.fetchChannel(params.channelId);
|
||||
if (!channel) return;
|
||||
if ("triggerTyping" in channel && typeof channel.triggerTyping === "function") {
|
||||
await channel.triggerTyping();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -129,6 +129,7 @@ export {
|
||||
shouldAckReaction,
|
||||
shouldAckReactionForWhatsApp,
|
||||
} from "../channels/ack-reactions.js";
|
||||
export { createTypingCallbacks } from "../channels/typing.js";
|
||||
export { resolveChannelMediaMaxBytes } from "../channels/plugins/media-limits.js";
|
||||
export type { NormalizedLocation } from "../channels/location.js";
|
||||
export { formatLocationText, toLocationContext } from "../channels/location.js";
|
||||
|
||||
@@ -25,6 +25,7 @@ import {
|
||||
import { finalizeInboundContext } from "../../auto-reply/reply/inbound-context.js";
|
||||
import { createReplyDispatcherWithTyping } from "../../auto-reply/reply/reply-dispatcher.js";
|
||||
import { recordInboundSession } from "../../channels/session.js";
|
||||
import { createTypingCallbacks } from "../../channels/typing.js";
|
||||
import { readSessionUpdatedAt, resolveStorePath } from "../../config/sessions.js";
|
||||
import { danger, logVerbose, shouldLogVerbose } from "../../globals.js";
|
||||
import { enqueueSystemEvent } from "../../infra/system-events.js";
|
||||
@@ -182,18 +183,19 @@ export function createSignalEventHandler(deps: SignalEventHandlerDeps) {
|
||||
identityName: resolveIdentityName(deps.cfg, route.agentId),
|
||||
};
|
||||
|
||||
const onReplyStart = async () => {
|
||||
try {
|
||||
const typingCallbacks = createTypingCallbacks({
|
||||
start: async () => {
|
||||
if (!ctxPayload.To) return;
|
||||
await sendTypingSignal(ctxPayload.To, {
|
||||
baseUrl: deps.baseUrl,
|
||||
account: deps.account,
|
||||
accountId: deps.accountId,
|
||||
});
|
||||
} catch (err) {
|
||||
},
|
||||
onStartError: (err) => {
|
||||
logVerbose(`signal typing cue failed for ${ctxPayload.To}: ${String(err)}`);
|
||||
}
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
const { dispatcher, replyOptions, markDispatchIdle } = createReplyDispatcherWithTyping({
|
||||
responsePrefix: resolveEffectiveMessagesConfig(deps.cfg, route.agentId).responsePrefix,
|
||||
@@ -214,7 +216,7 @@ export function createSignalEventHandler(deps: SignalEventHandlerDeps) {
|
||||
onError: (err, info) => {
|
||||
deps.runtime.error?.(danger(`signal ${info.kind} reply failed: ${String(err)}`));
|
||||
},
|
||||
onReplyStart,
|
||||
onReplyStart: typingCallbacks.onReplyStart,
|
||||
});
|
||||
|
||||
const { queuedFinal } = await dispatchInboundMessage({
|
||||
|
||||
@@ -10,6 +10,7 @@ import {
|
||||
import { dispatchInboundMessage } from "../../../auto-reply/dispatch.js";
|
||||
import { clearHistoryEntriesIfEnabled } from "../../../auto-reply/reply/history.js";
|
||||
import { removeAckReactionAfterReply } from "../../../channels/ack-reactions.js";
|
||||
import { createTypingCallbacks } from "../../../channels/typing.js";
|
||||
import { createReplyDispatcherWithTyping } from "../../../auto-reply/reply/reply-dispatcher.js";
|
||||
import { danger, logVerbose, shouldLogVerbose } from "../../../globals.js";
|
||||
import { removeSlackReaction } from "../../actions.js";
|
||||
@@ -43,14 +44,30 @@ export async function dispatchPreparedSlackMessage(prepared: PreparedSlackMessag
|
||||
hasRepliedRef,
|
||||
});
|
||||
|
||||
const onReplyStart = async () => {
|
||||
didSetStatus = true;
|
||||
await ctx.setSlackThreadStatus({
|
||||
channelId: message.channel,
|
||||
threadTs: statusThreadTs,
|
||||
status: "is typing...",
|
||||
});
|
||||
};
|
||||
const typingCallbacks = createTypingCallbacks({
|
||||
start: async () => {
|
||||
didSetStatus = true;
|
||||
await ctx.setSlackThreadStatus({
|
||||
channelId: message.channel,
|
||||
threadTs: statusThreadTs,
|
||||
status: "is typing...",
|
||||
});
|
||||
},
|
||||
stop: async () => {
|
||||
if (!didSetStatus) return;
|
||||
await ctx.setSlackThreadStatus({
|
||||
channelId: message.channel,
|
||||
threadTs: statusThreadTs,
|
||||
status: "",
|
||||
});
|
||||
},
|
||||
onStartError: (err) => {
|
||||
runtime.error?.(danger(`slack typing cue failed: ${String(err)}`));
|
||||
},
|
||||
onStopError: (err) => {
|
||||
runtime.error?.(danger(`slack typing stop failed: ${String(err)}`));
|
||||
},
|
||||
});
|
||||
|
||||
// Create mutable context for response prefix template interpolation
|
||||
let prefixContext: ResponsePrefixContext = {
|
||||
@@ -76,15 +93,10 @@ export async function dispatchPreparedSlackMessage(prepared: PreparedSlackMessag
|
||||
},
|
||||
onError: (err, info) => {
|
||||
runtime.error?.(danger(`slack ${info.kind} reply failed: ${String(err)}`));
|
||||
if (didSetStatus) {
|
||||
void ctx.setSlackThreadStatus({
|
||||
channelId: message.channel,
|
||||
threadTs: statusThreadTs,
|
||||
status: "",
|
||||
});
|
||||
}
|
||||
typingCallbacks.onIdle?.();
|
||||
},
|
||||
onReplyStart,
|
||||
onReplyStart: typingCallbacks.onReplyStart,
|
||||
onIdle: typingCallbacks.onIdle,
|
||||
});
|
||||
|
||||
const { queuedFinal, counts } = await dispatchInboundMessage({
|
||||
@@ -110,14 +122,6 @@ export async function dispatchPreparedSlackMessage(prepared: PreparedSlackMessag
|
||||
});
|
||||
markDispatchIdle();
|
||||
|
||||
if (didSetStatus) {
|
||||
await ctx.setSlackThreadStatus({
|
||||
channelId: message.channel,
|
||||
threadTs: statusThreadTs,
|
||||
status: "",
|
||||
});
|
||||
}
|
||||
|
||||
if (!queuedFinal) {
|
||||
if (prepared.isRoomish) {
|
||||
clearHistoryEntriesIfEnabled({
|
||||
|
||||
@@ -156,11 +156,7 @@ export const buildTelegramMessageContext = async ({
|
||||
}
|
||||
|
||||
const sendTyping = async () => {
|
||||
try {
|
||||
await bot.api.sendChatAction(chatId, "typing", buildTypingThreadParams(resolvedThreadId));
|
||||
} catch (err) {
|
||||
logVerbose(`telegram typing cue failed for chat ${chatId}: ${String(err)}`);
|
||||
}
|
||||
await bot.api.sendChatAction(chatId, "typing", buildTypingThreadParams(resolvedThreadId));
|
||||
};
|
||||
|
||||
const sendRecordVoice = async () => {
|
||||
|
||||
@@ -8,6 +8,7 @@ import { EmbeddedBlockChunker } from "../agents/pi-embedded-block-chunker.js";
|
||||
import { clearHistoryEntriesIfEnabled } from "../auto-reply/reply/history.js";
|
||||
import { dispatchReplyWithBufferedBlockDispatcher } from "../auto-reply/reply/provider-dispatcher.js";
|
||||
import { removeAckReactionAfterReply } from "../channels/ack-reactions.js";
|
||||
import { createTypingCallbacks } from "../channels/typing.js";
|
||||
import { danger, logVerbose } from "../globals.js";
|
||||
import { resolveMarkdownTableMode } from "../config/markdown-tables.js";
|
||||
import { deliverReplies } from "./bot/delivery.js";
|
||||
@@ -158,7 +159,12 @@ export const dispatchTelegramMessage = async ({
|
||||
onError: (err, info) => {
|
||||
runtime.error?.(danger(`telegram ${info.kind} reply failed: ${String(err)}`));
|
||||
},
|
||||
onReplyStart: sendTyping,
|
||||
onReplyStart: createTypingCallbacks({
|
||||
start: sendTyping,
|
||||
onStartError: (err) => {
|
||||
logVerbose(`telegram typing cue failed for chat ${chatId}: ${String(err)}`);
|
||||
},
|
||||
}).onReplyStart,
|
||||
},
|
||||
replyOptions: {
|
||||
skillFilter,
|
||||
|
||||
Reference in New Issue
Block a user