src / preprocessor.ts

/**
 * @file Merged preprocessor — runs memory injection first, then tools RAG/docs injection.
 *
 * Order: memory (prepends relevant memories) → tools (appends RAG citations, docs, delegation hints).
 * The model sees: [memories] + [user message] + [RAG citations / docs / delegation]
 */

import type { PromptPreprocessorController } from "@lmstudio/sdk";
import { promptPreprocessor as memoryPreprocessor } from "./memory/preprocessor";
import { promptPreprocessor as toolsPreprocessor } from "./tools/promptPreprocessor";

export async function maestroPreprocessor(
  ctl: PromptPreprocessorController,
  userMessage: any,
): Promise<any> {
  // Stage 1: inject memories (expects string in, string out)
  let messageText: string;
  if (typeof userMessage === "string") {
    messageText = userMessage;
  } else if (typeof userMessage?.getText === "function") {
    messageText = userMessage.getText();
  } else {
    messageText = String(userMessage);
  }

  let afterMemory: string;
  try {
    const memResult = await memoryPreprocessor(ctl as any, messageText);
    afterMemory = typeof memResult === "string" ? memResult : messageText;
  } catch {
    // Memory preprocessor failed — continue without it
    afterMemory = messageText;
  }

  // Stage 2: run tools preprocessor (expects ChatMessage or string)
  try {
    // If the SDK gives us a ChatMessage object, create a wrapper with the memory-enriched text
    if (typeof userMessage !== "string" && typeof userMessage?.getText === "function") {
      // Replace the text content if memories were injected
      if (afterMemory !== messageText && typeof userMessage.replaceText === "function") {
        userMessage.replaceText(afterMemory);
      }
      return toolsPreprocessor(ctl, userMessage);
    }

    // String-based SDK — pass enriched string directly
    return toolsPreprocessor(ctl, afterMemory as any);
  } catch {
    // Tools preprocessor failed — return memory-enriched content
    return afterMemory;
  }
}