src / chat.ts
// Per-chat state. A chat is identified by the id LM Studio uses for its working directory,
// which is also the name of the chat's file in .lmstudio/conversations.
import * as fs from "fs";
import * as path from "path";
import { chatDir, chatsDir, readJson, writeJson } from "./store";
export interface ChatState {
chatId: string;
project?: string;
autostartOff?: boolean;
lastSeq: number;
turn?: number;
// When the user's latest message arrived, so tools can tell what happened during this reply.
turnAt?: string;
staleTurns?: number;
lastReminderTurn?: number;
recallOff?: boolean;
updated: string;
}
interface WorkingDirectoryOwner {
getWorkingDirectory(): string;
}
// Returns null for predictions with no chat attached (such as background naming work).
export function chatIdOf(ctl: WorkingDirectoryOwner): string | null {
try {
const id = path.basename(ctl.getWorkingDirectory().replace(/[\\/]+$/, ""));
return /^[A-Za-z0-9_-]{1,64}$/.test(id) ? id : null;
} catch {
return null;
}
}
const stateFile = (chatId: string) => path.join(chatDir(chatId), "state.json");
export function loadChatState(chatId: string): ChatState | null {
return readJson<ChatState>(stateFile(chatId));
}
export function saveChatState(state: ChatState): void {
state.updated = new Date().toISOString();
writeJson(stateFile(state.chatId), state);
}
export function newChatState(chatId: string): ChatState {
return { chatId, lastSeq: 0, updated: new Date().toISOString() };
}
export function listChatStates(): ChatState[] {
try {
return fs
.readdirSync(chatsDir())
.map((id) => loadChatState(id))
.filter((s): s is ChatState => !!s);
} catch {
return [];
}
}
src / chat.ts
// Per-chat state. A chat is identified by the id LM Studio uses for its working directory,
// which is also the name of the chat's file in .lmstudio/conversations.
import * as fs from "fs";
import * as path from "path";
import { chatDir, chatsDir, readJson, writeJson } from "./store";
export interface ChatState {
chatId: string;
project?: string;
autostartOff?: boolean;
lastSeq: number;
turn?: number;
// When the user's latest message arrived, so tools can tell what happened during this reply.
turnAt?: string;
staleTurns?: number;
lastReminderTurn?: number;
recallOff?: boolean;
updated: string;
}
interface WorkingDirectoryOwner {
getWorkingDirectory(): string;
}
// Returns null for predictions with no chat attached (such as background naming work).
export function chatIdOf(ctl: WorkingDirectoryOwner): string | null {
try {
const id = path.basename(ctl.getWorkingDirectory().replace(/[\\/]+$/, ""));
return /^[A-Za-z0-9_-]{1,64}$/.test(id) ? id : null;
} catch {
return null;
}
}
const stateFile = (chatId: string) => path.join(chatDir(chatId), "state.json");
export function loadChatState(chatId: string): ChatState | null {
return readJson<ChatState>(stateFile(chatId));
}
export function saveChatState(state: ChatState): void {
state.updated = new Date().toISOString();
writeJson(stateFile(state.chatId), state);
}
export function newChatState(chatId: string): ChatState {
return { chatId, lastSeq: 0, updated: new Date().toISOString() };
}
export function listChatStates(): ChatState[] {
try {
return fs
.readdirSync(chatsDir())
.map((id) => loadChatState(id))
.filter((s): s is ChatState => !!s);
} catch {
return [];
}
}