fix: guide coding model selection before sending
This commit is contained in:
1 parent
f34e651774
commit
bec42627f5
12 files changed
+1000
-101
No files matched your search
@@ -114,6 +114,11 @@ function runtimeError(error: unknown): never {
|
||||
throw new CodingConversationServiceError(503, 'CODING_RUNTIME_UNAVAILABLE', 'The local coding runtime is unavailable');
|
||||
}
|
||||
|
||||
function isModelSelectionError(error: unknown): boolean {
|
||||
return error instanceof CodingConversationServiceError
|
||||
&& (error.code === 'CODING_MIGRATION_MODEL_REQUIRED' || error.code === 'CODING_MODEL_UNAVAILABLE');
|
||||
}
|
||||
|
||||
async function persist<T>(operation: () => Promise<T>): Promise<T> {
|
||||
try {
|
||||
return await operation();
|
||||
@@ -415,13 +420,32 @@ export class CodingConversationService {
|
||||
}
|
||||
|
||||
async getSnapshot(conversationId: string): Promise<ConversationSnapshot> {
|
||||
this.assertNotDeleting(conversationId);
|
||||
const { project, conversation } = await this.projects.findActiveConversation(conversationId);
|
||||
const { config } = await this.projects.getConfig(project.id);
|
||||
const agent = config.agents.find((item) => item.id === conversation.agentId);
|
||||
if (this.options.readHistory && (!agent?.enabled || agent.archivedAt)) {
|
||||
return await this.options.readHistory(project.id, conversation);
|
||||
}
|
||||
await this.ensurePrepared(conversationId);
|
||||
if (this.options.readHistory && (!conversation.model || conversation.modelResolution !== 'resolved')) {
|
||||
if (this.hasWorker(conversationId)) {
|
||||
try { return await this.runtime.getSnapshot(conversationId); } catch (error) { runtimeError(error); }
|
||||
}
|
||||
if (!this.prepareFlights.has(conversationId)) {
|
||||
return await this.readModelSelectionHistory(project.id, conversation);
|
||||
}
|
||||
}
|
||||
try {
|
||||
await this.ensurePrepared(conversationId);
|
||||
} catch (error) {
|
||||
if (this.options.readHistory && isModelSelectionError(error)) {
|
||||
if (this.hasWorker(conversationId)) {
|
||||
try { return await this.runtime.getSnapshot(conversationId); } catch (runtimeFailure) { runtimeError(runtimeFailure); }
|
||||
}
|
||||
return await this.readModelSelectionHistory(project.id, conversation);
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
try {
|
||||
return await this.runtime.getSnapshot(conversationId);
|
||||
} catch (error) {
|
||||
@@ -429,6 +453,47 @@ export class CodingConversationService {
|
||||
}
|
||||
}
|
||||
|
||||
private hasWorker(conversationId: string): boolean {
|
||||
return this.runtime.getDiagnostics().workers.some((worker) => worker.conversationId === conversationId);
|
||||
}
|
||||
|
||||
private async readModelSelectionHistory(
|
||||
projectId: string,
|
||||
conversation: CodingConversationV2,
|
||||
): Promise<ConversationSnapshot> {
|
||||
const snapshot = await this.options.readHistory!(projectId, conversation);
|
||||
// A model selection or prompt may have started while durable history was read.
|
||||
// Its real runtime state, including uncertain work, must remain authoritative.
|
||||
const preparing = this.prepareFlights.get(conversation.id);
|
||||
if (preparing) {
|
||||
try { await preparing; } catch (error) {
|
||||
if (!isModelSelectionError(error)) throw error;
|
||||
}
|
||||
}
|
||||
this.assertNotDeleting(conversation.id);
|
||||
if (this.hasWorker(conversation.id)) {
|
||||
try { return await this.runtime.getSnapshot(conversation.id); } catch (error) { runtimeError(error); }
|
||||
}
|
||||
const { conversation: current } = await this.projects.findActiveConversation(conversation.id);
|
||||
this.assertNotDeleting(conversation.id);
|
||||
if (current.modelResolution !== conversation.modelResolution
|
||||
|| JSON.stringify(current.model) !== JSON.stringify(conversation.model)) {
|
||||
// A concurrent choice may have persisted and failed preparation before this
|
||||
// read completed. Recheck that model instead of masking its real failure.
|
||||
return await this.getSnapshot(conversation.id);
|
||||
}
|
||||
if (this.hasWorker(conversation.id)) {
|
||||
try { return await this.runtime.getSnapshot(conversation.id); } catch (error) { runtimeError(error); }
|
||||
}
|
||||
return {
|
||||
...snapshot,
|
||||
conversation: {
|
||||
...snapshot.conversation,
|
||||
model: { model: null, modelResolution: 'required' },
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
/** Read existing facts for work restoration without opening a model session. */
|
||||
async getStoredSnapshot(conversationId: string): Promise<ConversationSnapshot | null> {
|
||||
const { project, conversation } = await this.projects.findActiveConversation(conversationId);
|
||||
|
||||
Reference in new issue
Block a user