openskynet / src /discord /monitor /auto-presence.ts
Darochin's picture
Mirror OpenSkyNet workspace snapshot from Git HEAD
fc93158 verified
import type { Activity, UpdatePresenceData } from "@buape/carbon/gateway";
import {
clearExpiredCooldowns,
ensureAuthProfileStore,
isProfileInCooldown,
resolveProfilesUnavailableReason,
type AuthProfileFailureReason,
type AuthProfileStore,
} from "../../agents/auth-profiles.js";
import type { DiscordAccountConfig, DiscordAutoPresenceConfig } from "../../config/config.js";
import { warn } from "../../globals.js";
import { resolveDiscordPresenceUpdate } from "./presence.js";
const DEFAULT_CUSTOM_ACTIVITY_TYPE = 4;
const CUSTOM_STATUS_NAME = "Custom Status";
const DEFAULT_INTERVAL_MS = 30_000;
const DEFAULT_MIN_UPDATE_INTERVAL_MS = 15_000;
const MIN_INTERVAL_MS = 5_000;
const MIN_UPDATE_INTERVAL_MS = 1_000;
export type DiscordAutoPresenceState = "healthy" | "degraded" | "exhausted";
type ResolvedDiscordAutoPresenceConfig = {
enabled: boolean;
intervalMs: number;
minUpdateIntervalMs: number;
healthyText?: string;
degradedText?: string;
exhaustedText?: string;
};
export type DiscordAutoPresenceDecision = {
state: DiscordAutoPresenceState;
unavailableReason?: AuthProfileFailureReason | null;
presence: UpdatePresenceData;
};
type PresenceGateway = {
isConnected: boolean;
updatePresence: (payload: UpdatePresenceData) => void;
};
function normalizeOptionalText(value: unknown): string | undefined {
if (typeof value !== "string") {
return undefined;
}
const trimmed = value.trim();
return trimmed.length > 0 ? trimmed : undefined;
}
function clampPositiveInt(value: unknown, fallback: number, minValue: number): number {
if (typeof value !== "number" || !Number.isFinite(value)) {
return fallback;
}
const rounded = Math.round(value);
if (rounded <= 0) {
return fallback;
}
return Math.max(minValue, rounded);
}
function resolveAutoPresenceConfig(
config?: DiscordAutoPresenceConfig,
): ResolvedDiscordAutoPresenceConfig {
const intervalMs = clampPositiveInt(config?.intervalMs, DEFAULT_INTERVAL_MS, MIN_INTERVAL_MS);
const minUpdateIntervalMs = clampPositiveInt(
config?.minUpdateIntervalMs,
DEFAULT_MIN_UPDATE_INTERVAL_MS,
MIN_UPDATE_INTERVAL_MS,
);
return {
enabled: config?.enabled === true,
intervalMs,
minUpdateIntervalMs,
healthyText: normalizeOptionalText(config?.healthyText),
degradedText: normalizeOptionalText(config?.degradedText),
exhaustedText: normalizeOptionalText(config?.exhaustedText),
};
}
function buildCustomStatusActivity(text: string): Activity {
return {
name: CUSTOM_STATUS_NAME,
type: DEFAULT_CUSTOM_ACTIVITY_TYPE,
state: text,
};
}
function renderTemplate(
template: string,
vars: Record<string, string | undefined>,
): string | undefined {
const rendered = template
.replace(/\{([a-zA-Z0-9_]+)\}/g, (_full, key: string) => vars[key] ?? "")
.replace(/\s+/g, " ")
.trim();
return rendered.length > 0 ? rendered : undefined;
}
function isExhaustedUnavailableReason(reason: AuthProfileFailureReason | null): boolean {
if (!reason) {
return false;
}
return (
reason === "rate_limit" ||
reason === "overloaded" ||
reason === "billing" ||
reason === "auth" ||
reason === "auth_permanent"
);
}
function formatUnavailableReason(reason: AuthProfileFailureReason | null): string {
if (!reason) {
return "unknown";
}
return reason.replace(/_/g, " ");
}
function resolveAuthAvailability(params: { store: AuthProfileStore; now: number }): {
state: DiscordAutoPresenceState;
unavailableReason?: AuthProfileFailureReason | null;
} {
const profileIds = Object.keys(params.store.profiles);
if (profileIds.length === 0) {
return { state: "degraded", unavailableReason: null };
}
clearExpiredCooldowns(params.store, params.now);
const hasUsableProfile = profileIds.some(
(profileId) => !isProfileInCooldown(params.store, profileId, params.now),
);
if (hasUsableProfile) {
return { state: "healthy", unavailableReason: null };
}
const unavailableReason = resolveProfilesUnavailableReason({
store: params.store,
profileIds,
now: params.now,
});
if (isExhaustedUnavailableReason(unavailableReason)) {
return {
state: "exhausted",
unavailableReason,
};
}
return {
state: "degraded",
unavailableReason,
};
}
function resolvePresenceActivities(params: {
state: DiscordAutoPresenceState;
cfg: ResolvedDiscordAutoPresenceConfig;
basePresence: UpdatePresenceData | null;
unavailableReason?: AuthProfileFailureReason | null;
}): Activity[] {
const reasonLabel = formatUnavailableReason(params.unavailableReason ?? null);
if (params.state === "healthy") {
if (params.cfg.healthyText) {
return [buildCustomStatusActivity(params.cfg.healthyText)];
}
return params.basePresence?.activities ?? [];
}
if (params.state === "degraded") {
const template = params.cfg.degradedText ?? "runtime degraded";
const text = renderTemplate(template, { reason: reasonLabel });
return text ? [buildCustomStatusActivity(text)] : [];
}
const defaultTemplate = isExhaustedUnavailableReason(params.unavailableReason ?? null)
? "token exhausted"
: "model unavailable ({reason})";
const template = params.cfg.exhaustedText ?? defaultTemplate;
const text = renderTemplate(template, { reason: reasonLabel });
return text ? [buildCustomStatusActivity(text)] : [];
}
function resolvePresenceStatus(state: DiscordAutoPresenceState): UpdatePresenceData["status"] {
if (state === "healthy") {
return "online";
}
if (state === "exhausted") {
return "dnd";
}
return "idle";
}
export function resolveDiscordAutoPresenceDecision(params: {
discordConfig: Pick<
DiscordAccountConfig,
"autoPresence" | "activity" | "status" | "activityType" | "activityUrl"
>;
authStore: AuthProfileStore;
gatewayConnected: boolean;
now?: number;
}): DiscordAutoPresenceDecision | null {
const autoPresence = resolveAutoPresenceConfig(params.discordConfig.autoPresence);
if (!autoPresence.enabled) {
return null;
}
const now = params.now ?? Date.now();
const basePresence = resolveDiscordPresenceUpdate(params.discordConfig);
const availability = resolveAuthAvailability({
store: params.authStore,
now,
});
const state = params.gatewayConnected ? availability.state : "degraded";
const unavailableReason = params.gatewayConnected
? availability.unavailableReason
: (availability.unavailableReason ?? "unknown");
const activities = resolvePresenceActivities({
state,
cfg: autoPresence,
basePresence,
unavailableReason,
});
return {
state,
unavailableReason,
presence: {
since: null,
activities,
status: resolvePresenceStatus(state),
afk: false,
},
};
}
function stablePresenceSignature(payload: UpdatePresenceData): string {
return JSON.stringify({
status: payload.status,
afk: payload.afk,
since: payload.since,
activities: payload.activities.map((activity) => ({
type: activity.type,
name: activity.name,
state: activity.state,
url: activity.url,
})),
});
}
export type DiscordAutoPresenceController = {
start: () => void;
stop: () => void;
refresh: () => void;
runNow: () => void;
enabled: boolean;
};
export function createDiscordAutoPresenceController(params: {
accountId: string;
discordConfig: Pick<
DiscordAccountConfig,
"autoPresence" | "activity" | "status" | "activityType" | "activityUrl"
>;
gateway: PresenceGateway;
loadAuthStore?: () => AuthProfileStore;
now?: () => number;
setIntervalFn?: typeof setInterval;
clearIntervalFn?: typeof clearInterval;
log?: (message: string) => void;
}): DiscordAutoPresenceController {
const autoCfg = resolveAutoPresenceConfig(params.discordConfig.autoPresence);
if (!autoCfg.enabled) {
return {
enabled: false,
start: () => undefined,
stop: () => undefined,
refresh: () => undefined,
runNow: () => undefined,
};
}
const loadAuthStore = params.loadAuthStore ?? (() => ensureAuthProfileStore());
const now = params.now ?? (() => Date.now());
const setIntervalFn = params.setIntervalFn ?? setInterval;
const clearIntervalFn = params.clearIntervalFn ?? clearInterval;
let timer: ReturnType<typeof setInterval> | undefined;
let lastAppliedSignature: string | null = null;
let lastAppliedAt = 0;
const runEvaluation = (options?: { force?: boolean }) => {
let decision: DiscordAutoPresenceDecision | null = null;
try {
decision = resolveDiscordAutoPresenceDecision({
discordConfig: params.discordConfig,
authStore: loadAuthStore(),
gatewayConnected: params.gateway.isConnected,
now: now(),
});
} catch (err) {
params.log?.(
warn(
`discord: auto-presence evaluation failed for account ${params.accountId}: ${String(err)}`,
),
);
return;
}
if (!decision || !params.gateway.isConnected) {
return;
}
const forceApply = options?.force === true;
const ts = now();
const signature = stablePresenceSignature(decision.presence);
if (!forceApply && signature === lastAppliedSignature) {
return;
}
if (!forceApply && lastAppliedAt > 0 && ts - lastAppliedAt < autoCfg.minUpdateIntervalMs) {
return;
}
params.gateway.updatePresence(decision.presence);
lastAppliedSignature = signature;
lastAppliedAt = ts;
};
return {
enabled: true,
runNow: () => runEvaluation(),
refresh: () => runEvaluation({ force: true }),
start: () => {
if (timer) {
return;
}
runEvaluation({ force: true });
timer = setIntervalFn(() => runEvaluation(), autoCfg.intervalMs);
},
stop: () => {
if (!timer) {
return;
}
clearIntervalFn(timer);
timer = undefined;
},
};
}
export const __testing = {
resolveAutoPresenceConfig,
resolveAuthAvailability,
stablePresenceSignature,
};