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

116 lines
3.8 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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