116 lines
3.8 KiB
TypeScript
116 lines
3.8 KiB
TypeScript
|
|
/** 会话与消息的读写(A3.7)。只碰平台库,不涉及业务数据(ADR-0008)。 */
|
|||
|
|
import type { ChatMessage, SessionSummary } from '@aps/contracts';
|
|||
|
|
|
|||
|
|
import type { Database } from './db.js';
|
|||
|
|
|
|||
|
|
export interface StoredSession extends SessionSummary {
|
|||
|
|
owner: { userId: string; tenantId: string; displayName: string; clientKind: 'desktop' | 'browser' };
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
interface SessionRow {
|
|||
|
|
session_id: string;
|
|||
|
|
project_id: string | null;
|
|||
|
|
scope: string;
|
|||
|
|
status: string;
|
|||
|
|
title: string;
|
|||
|
|
owner_user_id: string;
|
|||
|
|
owner_tenant_id: string;
|
|||
|
|
owner_display_name: string;
|
|||
|
|
owner_client_kind: string;
|
|||
|
|
created_at: number;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
interface MessageRow {
|
|||
|
|
message_id: string;
|
|||
|
|
session_id: string;
|
|||
|
|
role: string;
|
|||
|
|
text: string;
|
|||
|
|
ts: number;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const toSession = (row: SessionRow): StoredSession => ({
|
|||
|
|
sessionId: row.session_id,
|
|||
|
|
projectId: row.project_id,
|
|||
|
|
scope: row.scope as SessionSummary['scope'],
|
|||
|
|
status: row.status as SessionSummary['status'],
|
|||
|
|
title: row.title,
|
|||
|
|
createdAt: row.created_at,
|
|||
|
|
owner: {
|
|||
|
|
userId: row.owner_user_id,
|
|||
|
|
tenantId: row.owner_tenant_id,
|
|||
|
|
displayName: row.owner_display_name,
|
|||
|
|
clientKind: row.owner_client_kind as 'desktop' | 'browser',
|
|||
|
|
},
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
const toMessage = (row: MessageRow): ChatMessage => ({
|
|||
|
|
messageId: row.message_id,
|
|||
|
|
role: row.role as ChatMessage['role'],
|
|||
|
|
ts: row.ts,
|
|||
|
|
text: row.text,
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
export class SessionRepository {
|
|||
|
|
constructor(private readonly db: Database) {}
|
|||
|
|
|
|||
|
|
insertSession(session: StoredSession): void {
|
|||
|
|
this.db
|
|||
|
|
.prepare(
|
|||
|
|
'insert into sessions (session_id, project_id, scope, status, title, owner_user_id, owner_tenant_id, owner_display_name, owner_client_kind, created_at) values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)',
|
|||
|
|
)
|
|||
|
|
.run(
|
|||
|
|
session.sessionId,
|
|||
|
|
session.projectId,
|
|||
|
|
session.scope,
|
|||
|
|
session.status,
|
|||
|
|
session.title,
|
|||
|
|
session.owner.userId,
|
|||
|
|
session.owner.tenantId,
|
|||
|
|
session.owner.displayName,
|
|||
|
|
session.owner.clientKind,
|
|||
|
|
session.createdAt,
|
|||
|
|
);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
getSession(sessionId: string): StoredSession | undefined {
|
|||
|
|
const row = this.db.prepare('select * from sessions where session_id = ?').get(sessionId) as unknown as SessionRow | undefined;
|
|||
|
|
return row ? toSession(row) : undefined;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
listSessions(projectId?: string): StoredSession[] {
|
|||
|
|
const rows = (
|
|||
|
|
projectId === undefined
|
|||
|
|
? this.db.prepare('select * from sessions order by created_at desc').all()
|
|||
|
|
: this.db.prepare('select * from sessions where project_id = ? order by created_at desc').all(projectId)
|
|||
|
|
) as unknown as SessionRow[];
|
|||
|
|
return rows.map(toSession);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
appendMessage(sessionId: string, message: ChatMessage): void {
|
|||
|
|
this.db
|
|||
|
|
.prepare('insert into messages (message_id, session_id, role, text, ts) values (?, ?, ?, ?, ?)')
|
|||
|
|
.run(message.messageId, sessionId, message.role, message.text, message.ts);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
listMessages(sessionId: string): ChatMessage[] {
|
|||
|
|
const rows = this.db
|
|||
|
|
.prepare('select * from messages where session_id = ? order by ts asc, rowid asc')
|
|||
|
|
.all(sessionId) as unknown as MessageRow[];
|
|||
|
|
return rows.map(toMessage);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
rememberIdempotencyKey(key: string, sessionId: string, messageId: string, runId: string): void {
|
|||
|
|
this.db
|
|||
|
|
.prepare(
|
|||
|
|
'insert or ignore into idempotency_keys (idempotency_key, session_id, message_id, run_id, created_at) values (?, ?, ?, ?, ?)',
|
|||
|
|
)
|
|||
|
|
.run(key, sessionId, messageId, runId, Date.now());
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
findByIdempotencyKey(key: string): { sessionId: string; messageId: string; runId: string } | undefined {
|
|||
|
|
const row = this.db.prepare('select session_id, message_id, run_id from idempotency_keys where idempotency_key = ?').get(key) as
|
|||
|
|
| { session_id: string; message_id: string; run_id: string }
|
|||
|
|
| undefined;
|
|||
|
|
return row ? { sessionId: row.session_id, messageId: row.message_id, runId: row.run_id } : undefined;
|
|||
|
|
}
|
|||
|
|
}
|