99 lines
3.8 KiB
TypeScript
99 lines
3.8 KiB
TypeScript
import { nanoid } from "nanoid";
|
|
import type { AssistantConversationItem, AssistantSessionState } from "../types/assistant";
|
|
import type { InvestigationState } from "../types/stage1Contracts";
|
|
import { FEATURE_ASSISTANT_INVESTIGATION_STATE_V1 } from "../config";
|
|
import { cloneInvestigationState, createEmptyInvestigationState } from "./investigationState";
|
|
|
|
const MAX_ITEMS_PER_SESSION = 200;
|
|
|
|
function cloneItem(item: AssistantConversationItem): AssistantConversationItem {
|
|
return {
|
|
...item,
|
|
debug: item.debug ? { ...item.debug } : null
|
|
};
|
|
}
|
|
|
|
function cloneSession(state: AssistantSessionState): AssistantSessionState {
|
|
return {
|
|
session_id: state.session_id,
|
|
updated_at: state.updated_at,
|
|
items: state.items.map(cloneItem),
|
|
investigation_state: cloneInvestigationState(state.investigation_state)
|
|
};
|
|
}
|
|
|
|
function normalizeSessionShape(state: AssistantSessionState): AssistantSessionState {
|
|
const legacy = state as AssistantSessionState & {
|
|
investigation_state?: InvestigationState | null;
|
|
items?: AssistantConversationItem[];
|
|
updated_at?: string;
|
|
};
|
|
const normalizedItems = Array.isArray(legacy.items) ? legacy.items : [];
|
|
const investigationState =
|
|
FEATURE_ASSISTANT_INVESTIGATION_STATE_V1
|
|
? legacy.investigation_state ?? createEmptyInvestigationState(state.session_id)
|
|
: legacy.investigation_state ?? null;
|
|
|
|
state.items = normalizedItems;
|
|
state.updated_at = typeof legacy.updated_at === "string" && legacy.updated_at.trim() ? legacy.updated_at : new Date().toISOString();
|
|
state.investigation_state = investigationState;
|
|
return state;
|
|
}
|
|
|
|
export class AssistantSessionStore {
|
|
private readonly sessions = new Map<string, AssistantSessionState>();
|
|
|
|
public ensureSession(sessionId?: string): AssistantSessionState {
|
|
const resolvedId = (sessionId ?? "").trim() || `asst-${nanoid(10)}`;
|
|
const existing = this.sessions.get(resolvedId);
|
|
if (existing) {
|
|
return cloneSession(normalizeSessionShape(existing));
|
|
}
|
|
const created: AssistantSessionState = {
|
|
session_id: resolvedId,
|
|
updated_at: new Date().toISOString(),
|
|
items: [],
|
|
investigation_state: FEATURE_ASSISTANT_INVESTIGATION_STATE_V1 ? createEmptyInvestigationState(resolvedId) : null
|
|
};
|
|
this.sessions.set(resolvedId, created);
|
|
return cloneSession(created);
|
|
}
|
|
|
|
public appendItem(sessionId: string, item: AssistantConversationItem): AssistantConversationItem {
|
|
const session = this.ensureMutableSession(sessionId);
|
|
session.items.push(item);
|
|
if (session.items.length > MAX_ITEMS_PER_SESSION) {
|
|
session.items = session.items.slice(session.items.length - MAX_ITEMS_PER_SESSION);
|
|
}
|
|
session.updated_at = new Date().toISOString();
|
|
return cloneItem(item);
|
|
}
|
|
|
|
public getSession(sessionId: string): AssistantSessionState | null {
|
|
const found = this.sessions.get(sessionId);
|
|
return found ? cloneSession(normalizeSessionShape(found)) : null;
|
|
}
|
|
|
|
public setInvestigationState(sessionId: string, state: InvestigationState | null): InvestigationState | null {
|
|
const session = this.ensureMutableSession(sessionId);
|
|
session.investigation_state = cloneInvestigationState(state);
|
|
session.updated_at = new Date().toISOString();
|
|
return cloneInvestigationState(session.investigation_state);
|
|
}
|
|
|
|
private ensureMutableSession(sessionId: string): AssistantSessionState {
|
|
const existing = this.sessions.get(sessionId);
|
|
if (existing) {
|
|
return normalizeSessionShape(existing);
|
|
}
|
|
const created: AssistantSessionState = {
|
|
session_id: sessionId,
|
|
updated_at: new Date().toISOString(),
|
|
items: [],
|
|
investigation_state: FEATURE_ASSISTANT_INVESTIGATION_STATE_V1 ? createEmptyInvestigationState(sessionId) : null
|
|
};
|
|
this.sessions.set(sessionId, created);
|
|
return created;
|
|
}
|
|
}
|