135 lines
4.3 KiB
TypeScript
135 lines
4.3 KiB
TypeScript
export type ProjectSessionMetadata = {
|
|
sessionId: string;
|
|
agentId: string;
|
|
archivedAt: string | null;
|
|
unreadCount: number;
|
|
createdAt: string;
|
|
updatedAt: string;
|
|
};
|
|
|
|
export type ProjectConversationState = {
|
|
schemaVersion: 1;
|
|
sessions: ProjectSessionMetadata[];
|
|
updatedAt: string;
|
|
};
|
|
|
|
function cleanString(value: unknown): string {
|
|
return typeof value === 'string' ? value.trim() : '';
|
|
}
|
|
|
|
function normalizeSession(value: unknown): ProjectSessionMetadata | null {
|
|
if (!value || typeof value !== 'object' || Array.isArray(value)) return null;
|
|
const record = value as Partial<ProjectSessionMetadata>;
|
|
const sessionId = cleanString(record.sessionId);
|
|
const agentId = cleanString(record.agentId);
|
|
if (!sessionId || !/^[a-z0-9][a-z0-9_-]{0,127}$/i.test(sessionId) || !agentId) return null;
|
|
const now = new Date().toISOString();
|
|
const createdAt = cleanString(record.createdAt) || now;
|
|
const updatedAt = cleanString(record.updatedAt) || createdAt;
|
|
const unreadCount = typeof record.unreadCount === 'number' && Number.isFinite(record.unreadCount)
|
|
? Math.max(0, Math.floor(record.unreadCount))
|
|
: 0;
|
|
return {
|
|
sessionId,
|
|
agentId,
|
|
archivedAt: cleanString(record.archivedAt) || null,
|
|
unreadCount,
|
|
createdAt,
|
|
updatedAt,
|
|
};
|
|
}
|
|
|
|
export function createProjectConversationState(now = new Date().toISOString()): ProjectConversationState {
|
|
return {
|
|
schemaVersion: 1,
|
|
sessions: [],
|
|
updatedAt: now,
|
|
};
|
|
}
|
|
|
|
export function normalizeProjectConversationState(value: unknown): ProjectConversationState {
|
|
if (!value || typeof value !== 'object' || Array.isArray(value)) {
|
|
throw new Error('Project conversation state must be an object');
|
|
}
|
|
const record = value as Partial<ProjectConversationState>;
|
|
if (record.schemaVersion !== 1) throw new Error('Unsupported project conversation state schema');
|
|
const sessions = Array.isArray(record.sessions)
|
|
? record.sessions.map(normalizeSession).filter((item): item is ProjectSessionMetadata => Boolean(item))
|
|
: [];
|
|
const deduped = new Map<string, ProjectSessionMetadata>();
|
|
for (const session of sessions) deduped.set(session.sessionId, session);
|
|
return {
|
|
schemaVersion: 1,
|
|
sessions: [...deduped.values()],
|
|
updatedAt: cleanString(record.updatedAt) || new Date().toISOString(),
|
|
};
|
|
}
|
|
|
|
export function upsertProjectSessionMetadata(
|
|
state: ProjectConversationState,
|
|
sessionId: string,
|
|
agentId: string,
|
|
now = new Date().toISOString(),
|
|
): ProjectConversationState {
|
|
const current = state.sessions.find((item) => item.sessionId === sessionId);
|
|
const next: ProjectSessionMetadata = {
|
|
sessionId,
|
|
agentId,
|
|
archivedAt: current?.archivedAt ?? null,
|
|
unreadCount: current?.unreadCount ?? 0,
|
|
createdAt: current?.createdAt ?? now,
|
|
updatedAt: now,
|
|
};
|
|
return {
|
|
schemaVersion: 1,
|
|
sessions: [next, ...state.sessions.filter((item) => item.sessionId !== sessionId)],
|
|
updatedAt: now,
|
|
};
|
|
}
|
|
|
|
export function patchProjectSessionMetadata(
|
|
state: ProjectConversationState,
|
|
sessionId: string,
|
|
patch: Partial<Pick<ProjectSessionMetadata, 'agentId' | 'archivedAt' | 'unreadCount'>>,
|
|
now = new Date().toISOString(),
|
|
): ProjectConversationState {
|
|
const sessions = state.sessions.map((item) => item.sessionId === sessionId
|
|
? {
|
|
...item,
|
|
...(patch.agentId ? { agentId: patch.agentId } : {}),
|
|
...(patch.archivedAt !== undefined ? { archivedAt: patch.archivedAt } : {}),
|
|
...(patch.unreadCount !== undefined ? { unreadCount: Math.max(0, Math.floor(patch.unreadCount)) } : {}),
|
|
updatedAt: now,
|
|
}
|
|
: item);
|
|
return { schemaVersion: 1, sessions, updatedAt: now };
|
|
}
|
|
|
|
export function removeProjectSessionMetadata(
|
|
state: ProjectConversationState,
|
|
sessionId: string,
|
|
now = new Date().toISOString(),
|
|
): ProjectConversationState {
|
|
return {
|
|
schemaVersion: 1,
|
|
sessions: state.sessions.filter((item) => item.sessionId !== sessionId),
|
|
updatedAt: now,
|
|
};
|
|
}
|
|
|
|
export function completeProjectSession(
|
|
state: ProjectConversationState,
|
|
sessionId: string,
|
|
incrementUnread: boolean,
|
|
now = new Date().toISOString(),
|
|
): ProjectConversationState {
|
|
const current = state.sessions.find((item) => item.sessionId === sessionId);
|
|
if (!current) return state;
|
|
return patchProjectSessionMetadata(
|
|
state,
|
|
sessionId,
|
|
{ unreadCount: current.unreadCount + (incrementUnread ? 1 : 0) },
|
|
now,
|
|
);
|
|
}
|