Files
moeka-project/integrations/satori-bot/src/core/utils.ts
T
2026-08-26 19:49:58 +08:00

100 lines
2.7 KiB
TypeScript

import type { SatoriEvent, SatoriMessage } from '../adapter/satori/types'
import type { Action, BotContext, ChatContext } from './types'
/**
* Format debug context for logging
* Creates a summary of bot state for debugging
*/
export function formatDebugContext(
ctx: BotContext,
chatCtx?: ChatContext,
): Record<string, unknown> {
const unreadEventsSummary = Object.fromEntries(
Object.entries(ctx.unreadEvents).map(([key, value]) => [key, value.length]),
)
const context: Record<string, unknown> = {
messageQueueLength: ctx.eventQueue.length,
totalUnreadCount: Object.values(ctx.unreadEvents).reduce((acc, cur) => acc + cur.length, 0),
unreadEvents: unreadEventsSummary,
}
if (chatCtx) {
context.channelId = chatCtx.channelId
context.totalActionsInContext = chatCtx.actions.length
const lastActions = chatCtx.actions.slice(-3).map(action => ({
action: action.action.action,
result: typeof action.result === 'string' ? action.result.substring(0, 100) : String(action.result).substring(0, 100),
}))
context.lastActions = lastActions
}
return context
}
/**
* Safely extract string content from message
...
* Handles string, array, and other types
*/
export function getMessageContentString(content: unknown): string {
if (typeof content === 'string') {
return content
}
if (Array.isArray(content)) {
return content.map(c => typeof c === 'string' ? c : JSON.stringify(c)).join(' ')
}
return String(content || '')
}
/**
* Check if a message is from the bot itself
* Checks multiple possible sources for user ID
*/
export function isBotOwnMessage(
message: SatoriMessage,
event: SatoriEvent,
selfId?: string,
): boolean {
if (!selfId) {
return false
}
const sourceUserId = event.user?.id
|| event.member?.user?.id
|| message.user?.id
|| message.member?.user?.id
return sourceUserId === selfId
}
/**
* Intelligently truncate action history while preserving logical chains.
* If the first action in the kept list is a 'continue', it backtracks to include
* the action that triggered it, ensuring the LLM has full context of its sequence.
*/
export function trimActions(
actions: { action: Action, result: unknown }[],
max: number,
keep: number,
): { action: Action, result: unknown }[] {
if (actions.length <= max) {
return actions
}
let startIndex = actions.length - keep
// Backtrack to avoid starting with a 'continue' action which lacks its previous context
while (startIndex > 0) {
const currentAction = actions[startIndex].action
if (currentAction.action === 'continue') {
startIndex--
}
else {
break
}
}
return actions.slice(startIndex)
}