aps-agent/server/core/storage/sessions.ts

116 lines
3.8 KiB
TypeScript
Raw Normal View History

/** 会话与消息的读写(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;
}
}