feat: replace Code conversation archive with deletion
This commit is contained in:
1 parent
140f6be8db
commit
bf101c1e52
19 files changed
+1001
-292
No files matched your search
@@ -50,6 +50,7 @@ interface AcceptanceRecord {
|
||||
export interface CodingConversationServiceOptions {
|
||||
readHistory?(projectId: string, conversation: CodingConversationV2): Promise<ConversationSnapshot>;
|
||||
onDelete?(projectId: string, conversationId: string): Promise<void>;
|
||||
deleteSession?(input: { projectId: string; sessionKey: string }): Promise<void>;
|
||||
archiveSession?(input: {
|
||||
projectId: string;
|
||||
sessionKey: string;
|
||||
@@ -267,6 +268,7 @@ class PatchBatchScheduler {
|
||||
export class CodingConversationService {
|
||||
private readonly prepareFlights = new Map<string, Promise<PrepareConversationInput>>();
|
||||
private readonly acceptances = new Map<string, AcceptanceRecord>();
|
||||
private readonly deleteFlights = new Map<string, Promise<void>>();
|
||||
|
||||
constructor(
|
||||
readonly projects: CodingProjectService,
|
||||
@@ -307,7 +309,9 @@ export class CodingConversationService {
|
||||
}
|
||||
|
||||
private async assertUnarchived(conversationId: string): Promise<void> {
|
||||
this.assertNotDeleting(conversationId);
|
||||
const { conversation } = await this.projects.findActiveConversation(conversationId);
|
||||
this.assertNotDeleting(conversationId);
|
||||
if (conversation.archivedAt) {
|
||||
throw new CodingConversationServiceError(409, 'CODING_CONVERSATION_ARCHIVED', '请先恢复已归档的对话,再继续操作。');
|
||||
}
|
||||
@@ -380,13 +384,26 @@ export class CodingConversationService {
|
||||
}
|
||||
|
||||
async deleteConversation(conversationId: string): Promise<void> {
|
||||
const { project, conversation } = await this.projects.findActiveConversation(conversationId);
|
||||
const prior = this.deleteFlights.get(conversationId);
|
||||
if (prior) return await prior;
|
||||
const flight = this.performDelete(conversationId).finally(() => {
|
||||
if (this.deleteFlights.get(conversationId) === flight) this.deleteFlights.delete(conversationId);
|
||||
});
|
||||
this.deleteFlights.set(conversationId, flight);
|
||||
return await flight;
|
||||
}
|
||||
|
||||
private async performDelete(conversationId: string): Promise<void> {
|
||||
const { project } = await this.projects.findActiveConversation(conversationId);
|
||||
await this.prepareFlights.get(conversationId)?.catch(() => undefined);
|
||||
try {
|
||||
await this.runtime.dispose(conversationId, 'conversation_deleted');
|
||||
} catch (error) {
|
||||
runtimeError(error);
|
||||
}
|
||||
await this.archiveSession(project.id, conversation.sessionKey);
|
||||
// A prepare already in flight may have created the durable session binding.
|
||||
const conversation = await this.projects.conversationStore(project.path).get(conversationId);
|
||||
await this.deleteSession(project.id, conversation?.sessionKey);
|
||||
await this.options.onDelete?.(project.id, conversationId);
|
||||
await persist(() => this.projects.conversationStore(project.path).delete(conversationId));
|
||||
this.titleContexts.delete(conversationId);
|
||||
@@ -394,6 +411,7 @@ export class CodingConversationService {
|
||||
for (const key of [...this.acceptances.keys()]) {
|
||||
if (key.startsWith(`${conversationId}\u0000`)) this.acceptances.delete(key);
|
||||
}
|
||||
this.metadataChanged(project.id, conversationId);
|
||||
}
|
||||
|
||||
async getSnapshot(conversationId: string): Promise<ConversationSnapshot> {
|
||||
@@ -419,6 +437,7 @@ export class CodingConversationService {
|
||||
attachments?: unknown;
|
||||
}): Promise<CodingPromptAcceptance> {
|
||||
const conversationId = requiredString(input.conversationId, 'Conversation id', 128);
|
||||
this.assertNotDeleting(conversationId);
|
||||
const clientRequestId = typeof input.clientRequestId === 'string' ? input.clientRequestId : '';
|
||||
if (!REQUEST_ID_PATTERN.test(clientRequestId)) {
|
||||
throw new CodingConversationServiceError(400, 'CODING_CONVERSATION_REQUEST_INVALID', 'Client request id is invalid');
|
||||
@@ -462,6 +481,7 @@ export class CodingConversationService {
|
||||
try {
|
||||
await this.assertUnarchived(conversationId);
|
||||
await this.ensurePrepared(conversationId);
|
||||
this.assertNotDeleting(conversationId);
|
||||
const acceptance = await this.runtime.prompt({
|
||||
conversationId,
|
||||
clientRequestId,
|
||||
@@ -725,6 +745,7 @@ export class CodingConversationService {
|
||||
|
||||
private ensurePrepared(conversationId: string): Promise<PrepareConversationInput> {
|
||||
const id = conversationId.trim();
|
||||
this.assertNotDeleting(id);
|
||||
const prior = this.prepareFlights.get(id);
|
||||
if (prior) return prior;
|
||||
const flight = (async () => {
|
||||
@@ -749,6 +770,7 @@ export class CodingConversationService {
|
||||
modelResolution: conversation.modelResolution,
|
||||
},
|
||||
};
|
||||
this.assertNotDeleting(id);
|
||||
try { await this.runtime.prepare(prepared); } catch (error) { runtimeError(error); }
|
||||
if (this.titleContexts.has(id)) {
|
||||
const snapshot = await this.runtime.getSnapshot(id);
|
||||
@@ -799,4 +821,18 @@ export class CodingConversationService {
|
||||
}
|
||||
await persist(() => this.options.archiveSession!({ projectId, sessionKey }));
|
||||
}
|
||||
|
||||
private assertNotDeleting(conversationId: string): void {
|
||||
if (this.deleteFlights.has(conversationId)) {
|
||||
throw new CodingConversationServiceError(409, 'CODING_CONVERSATION_DELETING', '对话正在删除,请稍候。');
|
||||
}
|
||||
}
|
||||
|
||||
private async deleteSession(projectId: string, sessionKey: string | undefined): Promise<void> {
|
||||
if (!sessionKey) return;
|
||||
if (!this.options.deleteSession) {
|
||||
throw new CodingConversationServiceError(500, 'CODING_STORAGE_WRITE_FAILED', 'Coding session deletion is unavailable');
|
||||
}
|
||||
await persist(() => this.options.deleteSession!({ projectId, sessionKey }));
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user