Spaces:
Configuration error
Configuration error
| import type { NormalizedUsage } from "../../agents/usage.js"; | |
| import { getChannelDock } from "../../channels/dock.js"; | |
| import type { ChannelId, ChannelThreadingToolContext } from "../../channels/plugins/types.js"; | |
| import { normalizeAnyChannelId, normalizeChannelId } from "../../channels/registry.js"; | |
| import type { MoltbotConfig } from "../../config/config.js"; | |
| import { isReasoningTagProvider } from "../../utils/provider-utils.js"; | |
| import { estimateUsageCost, formatTokenCount, formatUsd } from "../../utils/usage-format.js"; | |
| import type { TemplateContext } from "../templating.js"; | |
| import type { ReplyPayload } from "../types.js"; | |
| import type { FollowupRun } from "./queue.js"; | |
| const BUN_FETCH_SOCKET_ERROR_RE = /socket connection was closed unexpectedly/i; | |
| /** | |
| * Build provider-specific threading context for tool auto-injection. | |
| */ | |
| export function buildThreadingToolContext(params: { | |
| sessionCtx: TemplateContext; | |
| config: MoltbotConfig | undefined; | |
| hasRepliedRef: { value: boolean } | undefined; | |
| }): ChannelThreadingToolContext { | |
| const { sessionCtx, config, hasRepliedRef } = params; | |
| if (!config) return {}; | |
| const rawProvider = sessionCtx.Provider?.trim().toLowerCase(); | |
| if (!rawProvider) return {}; | |
| const provider = normalizeChannelId(rawProvider) ?? normalizeAnyChannelId(rawProvider); | |
| // Fallback for unrecognized/plugin channels (e.g., BlueBubbles before plugin registry init) | |
| const dock = provider ? getChannelDock(provider) : undefined; | |
| if (!dock?.threading?.buildToolContext) { | |
| return { | |
| currentChannelId: sessionCtx.To?.trim() || undefined, | |
| currentChannelProvider: provider ?? (rawProvider as ChannelId), | |
| hasRepliedRef, | |
| }; | |
| } | |
| const context = | |
| dock.threading.buildToolContext({ | |
| cfg: config, | |
| accountId: sessionCtx.AccountId, | |
| context: { | |
| Channel: sessionCtx.Provider, | |
| From: sessionCtx.From, | |
| To: sessionCtx.To, | |
| ChatType: sessionCtx.ChatType, | |
| ReplyToId: sessionCtx.ReplyToId, | |
| ThreadLabel: sessionCtx.ThreadLabel, | |
| MessageThreadId: sessionCtx.MessageThreadId, | |
| }, | |
| hasRepliedRef, | |
| }) ?? {}; | |
| return { | |
| ...context, | |
| currentChannelProvider: provider!, // guaranteed non-null since dock exists | |
| }; | |
| } | |
| export const isBunFetchSocketError = (message?: string) => | |
| Boolean(message && BUN_FETCH_SOCKET_ERROR_RE.test(message)); | |
| export const formatBunFetchSocketError = (message: string) => { | |
| const trimmed = message.trim(); | |
| return [ | |
| "⚠️ LLM connection failed. This could be due to server issues, network problems, or context length exceeded (e.g., with local LLMs like LM Studio). Original error:", | |
| "```", | |
| trimmed || "Unknown error", | |
| "```", | |
| ].join("\n"); | |
| }; | |
| export const formatResponseUsageLine = (params: { | |
| usage?: NormalizedUsage; | |
| showCost: boolean; | |
| costConfig?: { | |
| input: number; | |
| output: number; | |
| cacheRead: number; | |
| cacheWrite: number; | |
| }; | |
| }): string | null => { | |
| const usage = params.usage; | |
| if (!usage) return null; | |
| const input = usage.input; | |
| const output = usage.output; | |
| if (typeof input !== "number" && typeof output !== "number") return null; | |
| const inputLabel = typeof input === "number" ? formatTokenCount(input) : "?"; | |
| const outputLabel = typeof output === "number" ? formatTokenCount(output) : "?"; | |
| const cost = | |
| params.showCost && typeof input === "number" && typeof output === "number" | |
| ? estimateUsageCost({ | |
| usage: { | |
| input, | |
| output, | |
| cacheRead: usage.cacheRead, | |
| cacheWrite: usage.cacheWrite, | |
| }, | |
| cost: params.costConfig, | |
| }) | |
| : undefined; | |
| const costLabel = params.showCost ? formatUsd(cost) : undefined; | |
| const suffix = costLabel ? ` · est ${costLabel}` : ""; | |
| return `Usage: ${inputLabel} in / ${outputLabel} out${suffix}`; | |
| }; | |
| export const appendUsageLine = (payloads: ReplyPayload[], line: string): ReplyPayload[] => { | |
| let index = -1; | |
| for (let i = payloads.length - 1; i >= 0; i -= 1) { | |
| if (payloads[i]?.text) { | |
| index = i; | |
| break; | |
| } | |
| } | |
| if (index === -1) return [...payloads, { text: line }]; | |
| const existing = payloads[index]; | |
| const existingText = existing.text ?? ""; | |
| const separator = existingText.endsWith("\n") ? "" : "\n"; | |
| const next = { | |
| ...existing, | |
| text: `${existingText}${separator}${line}`, | |
| }; | |
| const updated = payloads.slice(); | |
| updated[index] = next; | |
| return updated; | |
| }; | |
| export const resolveEnforceFinalTag = (run: FollowupRun["run"], provider: string) => | |
| Boolean(run.enforceFinalTag || isReasoningTagProvider(provider)); | |