src / preprocess.ts
import { type ChatMessage, type PromptPreprocessor, type PromptPreprocessorController } from "@lmstudio/sdk";
import { EXAMPLE_PATH, getWorkspace, hasWorkspace } from "./workspace";
/** True once the one-time briefing has been injected in this plugin process. */
let briefedInThisProcess = false;
const NO_WORKSPACE_REMINDER =
`[code-agent] No working folder set yet. Ask the user for their project's absolute path and call set_workspace with it (e.g. "${EXAMPLE_PATH}") before using any file tool.`;
function briefing(): string {
return (
`[code-agent] Working folder: ${getWorkspace()}. ` +
"All file tools resolve paths under this folder; stay context-light — delegate self-contained subtasks " +
"(multi-file research, boilerplate or repetitive edits) to the `subagent` tool. It shares this workspace and returns a short summary. " +
"Use `run` for builds, tests, installs. Prefer `edit_file` over full rewrites."
);
}
/**
* Prompt preprocessor: enforces working-folder-first and gives one lightweight
* delegation briefing per chat. After that, the always-present tool descriptions
* (especially `subagent`) carry the guidance at zero recurring cost.
*/
export const codeAgentPreprocess: PromptPreprocessor = async (ctl: PromptPreprocessorController, userMessage: ChatMessage): Promise<string> => {
const orig = userMessage.getText();
if (!hasWorkspace()) {
// Fires every turn until the folder is set; self-resolving and cheap.
return `${NO_WORKSPACE_REMINDER}\n\n${orig}`;
}
// Brief on the first turn of a chat (empty history) or, if the workspace was
// set mid-conversation in this process, right after that — at most once per chat.
let firstTurnHere = false;
try {
const chat = await ctl.pullHistory();
firstTurnHere = chat.getLength() === 0;
} catch {
// History unavailable; fall back to the process-level flag below.
}
if (firstTurnHere || !briefedInThisProcess) {
briefedInThisProcess = true;
return `${briefing()}\n\n${orig}`;
}
return orig;
};src / preprocess.ts
import { type ChatMessage, type PromptPreprocessor, type PromptPreprocessorController } from "@lmstudio/sdk";
import { EXAMPLE_PATH, getWorkspace, hasWorkspace } from "./workspace";
/** True once the one-time briefing has been injected in this plugin process. */
let briefedInThisProcess = false;
const NO_WORKSPACE_REMINDER =
`[code-agent] No working folder set yet. Ask the user for their project's absolute path and call set_workspace with it (e.g. "${EXAMPLE_PATH}") before using any file tool.`;
function briefing(): string {
return (
`[code-agent] Working folder: ${getWorkspace()}. ` +
"All file tools resolve paths under this folder; stay context-light — delegate self-contained subtasks " +
"(multi-file research, boilerplate or repetitive edits) to the `subagent` tool. It shares this workspace and returns a short summary. " +
"Use `run` for builds, tests, installs. Prefer `edit_file` over full rewrites."
);
}
/**
* Prompt preprocessor: enforces working-folder-first and gives one lightweight
* delegation briefing per chat. After that, the always-present tool descriptions
* (especially `subagent`) carry the guidance at zero recurring cost.
*/
export const codeAgentPreprocess: PromptPreprocessor = async (ctl: PromptPreprocessorController, userMessage: ChatMessage): Promise<string> => {
const orig = userMessage.getText();
if (!hasWorkspace()) {
// Fires every turn until the folder is set; self-resolving and cheap.
return `${NO_WORKSPACE_REMINDER}\n\n${orig}`;
}
// Brief on the first turn of a chat (empty history) or, if the workspace was
// set mid-conversation in this process, right after that — at most once per chat.
let firstTurnHere = false;
try {
const chat = await ctl.pullHistory();
firstTurnHere = chat.getLength() === 0;
} catch {
// History unavailable; fall back to the process-level flag below.
}
if (firstTurnHere || !briefedInThisProcess) {
briefedInThisProcess = true;
return `${briefing()}\n\n${orig}`;
}
return orig;
};