src / tools / reason.ts
import { tool, type Tool } from "@lmstudio/sdk";
import { z } from "zod";
import { clamp, type Workspace } from "../workspace";
/**
* Scratchpad notes, per workspace, for the life of the plugin process. A small
* model's reasoning is otherwise lost between tool calls -- it thinks something
* through, calls three tools, and by the time it answers the conclusion is gone.
* Writing it down and reading it back is cheap and keeps it on track.
*/
const scratchpads = new Map<string, string[]>();
const MAX_NOTES = 40;
export function reasonTools(ws: Workspace): Tool[] {
const key = ws.root;
return [
tool({
name: "think",
description:
"Write down your reasoning without taking any action. Nothing happens to the workspace. " +
"Use it to plan before a tricky change, to work through what a tool result means, or to " +
"decide between two approaches. Costs nothing and keeps your reasoning from being lost " +
"between tool calls. Especially worth doing before editing code you have just read.",
parameters: {
thought: z
.string()
.describe(
"Your reasoning: what you know, what you are unsure about, and what you will do next.",
),
},
implementation: async ({ thought }, ctx) => {
const trimmed = thought.trim();
if (trimmed === "") return "Error: the thought is empty.";
const notes = scratchpads.get(key) ?? [];
notes.push(trimmed);
if (notes.length > MAX_NOTES) notes.splice(0, notes.length - MAX_NOTES);
scratchpads.set(key, notes);
ctx.status("Thinking");
return `Noted (${notes.length} thought(s) recorded). Continue with the next step.`;
},
}),
tool({
name: "review_thinking",
description:
"Read back everything you have recorded with think in this session. Useful when a job has " +
"run long and you need to check your earlier conclusions before finishing.",
parameters: {},
implementation: async (_params, ctx) => {
const notes = scratchpads.get(key) ?? [];
if (notes.length === 0) {
return "No thoughts recorded yet. Use think to work something through.";
}
ctx.status("Reviewing notes");
return clamp(
notes.map((note, index) => `${index + 1}. ${note}`).join("\n\n"),
6000,
"thinking notes",
);
},
}),
];
}
src / tools / reason.ts
import { tool, type Tool } from "@lmstudio/sdk";
import { z } from "zod";
import { clamp, type Workspace } from "../workspace";
/**
* Scratchpad notes, per workspace, for the life of the plugin process. A small
* model's reasoning is otherwise lost between tool calls -- it thinks something
* through, calls three tools, and by the time it answers the conclusion is gone.
* Writing it down and reading it back is cheap and keeps it on track.
*/
const scratchpads = new Map<string, string[]>();
const MAX_NOTES = 40;
export function reasonTools(ws: Workspace): Tool[] {
const key = ws.root;
return [
tool({
name: "think",
description:
"Write down your reasoning without taking any action. Nothing happens to the workspace. " +
"Use it to plan before a tricky change, to work through what a tool result means, or to " +
"decide between two approaches. Costs nothing and keeps your reasoning from being lost " +
"between tool calls. Especially worth doing before editing code you have just read.",
parameters: {
thought: z
.string()
.describe(
"Your reasoning: what you know, what you are unsure about, and what you will do next.",
),
},
implementation: async ({ thought }, ctx) => {
const trimmed = thought.trim();
if (trimmed === "") return "Error: the thought is empty.";
const notes = scratchpads.get(key) ?? [];
notes.push(trimmed);
if (notes.length > MAX_NOTES) notes.splice(0, notes.length - MAX_NOTES);
scratchpads.set(key, notes);
ctx.status("Thinking");
return `Noted (${notes.length} thought(s) recorded). Continue with the next step.`;
},
}),
tool({
name: "review_thinking",
description:
"Read back everything you have recorded with think in this session. Useful when a job has " +
"run long and you need to check your earlier conclusions before finishing.",
parameters: {},
implementation: async (_params, ctx) => {
const notes = scratchpads.get(key) ?? [];
if (notes.length === 0) {
return "No thoughts recorded yet. Use think to work something through.";
}
ctx.status("Reviewing notes");
return clamp(
notes.map((note, index) => `${index + 1}. ${note}`).join("\n\n"),
6000,
"thinking notes",
);
},
}),
];
}