Files
Moltbot/src/config/allowed-values.ts
2026-03-02 20:05:12 -05:00

99 lines
2.6 KiB
TypeScript

const MAX_ALLOWED_VALUES_HINT = 12;
const MAX_ALLOWED_VALUE_CHARS = 160;
export type AllowedValuesSummary = {
values: string[];
hiddenCount: number;
formatted: string;
};
function truncateHintText(text: string, limit: number): string {
if (text.length <= limit) {
return text;
}
return `${text.slice(0, limit)}... (+${text.length - limit} chars)`;
}
function safeStringify(value: unknown): string {
try {
const serialized = JSON.stringify(value);
if (serialized !== undefined) {
return serialized;
}
} catch {
// Fall back to string coercion when value is not JSON-serializable.
}
return String(value);
}
function toAllowedValueLabel(value: unknown): string {
if (typeof value === "string") {
return JSON.stringify(truncateHintText(value, MAX_ALLOWED_VALUE_CHARS));
}
return truncateHintText(safeStringify(value), MAX_ALLOWED_VALUE_CHARS);
}
function toAllowedValueValue(value: unknown): string {
if (typeof value === "string") {
return value;
}
return safeStringify(value);
}
function toAllowedValueDedupKey(value: unknown): string {
if (value === null) {
return "null:null";
}
const kind = typeof value;
if (kind === "string") {
return `string:${value as string}`;
}
return `${kind}:${safeStringify(value)}`;
}
export function summarizeAllowedValues(
values: ReadonlyArray<unknown>,
): AllowedValuesSummary | null {
if (values.length === 0) {
return null;
}
const deduped: Array<{ value: string; label: string }> = [];
const seenValues = new Set<string>();
for (const item of values) {
const dedupeKey = toAllowedValueDedupKey(item);
if (seenValues.has(dedupeKey)) {
continue;
}
seenValues.add(dedupeKey);
deduped.push({
value: toAllowedValueValue(item),
label: toAllowedValueLabel(item),
});
}
const shown = deduped.slice(0, MAX_ALLOWED_VALUES_HINT);
const hiddenCount = deduped.length - shown.length;
const formattedCore = shown.map((entry) => entry.label).join(", ");
const formatted =
hiddenCount > 0 ? `${formattedCore}, ... (+${hiddenCount} more)` : formattedCore;
return {
values: shown.map((entry) => entry.value),
hiddenCount,
formatted,
};
}
function messageAlreadyIncludesAllowedValues(message: string): boolean {
const lower = message.toLowerCase();
return lower.includes("(allowed:") || lower.includes("expected one of");
}
export function appendAllowedValuesHint(message: string, summary: AllowedValuesSummary): string {
if (messageAlreadyIncludesAllowedValues(message)) {
return message;
}
return `${message} (allowed: ${summary.formatted})`;
}