refactor: Improves abort signal handling
This commit is contained in:
parent
38e33f063b
commit
b9e08737e1
|
|
@ -55,6 +55,7 @@ import type {
|
|||
DatabaseMessageExtraImageFile
|
||||
} from '$lib/types/database';
|
||||
import { AttachmentType, MessageRole } from '$lib/enums';
|
||||
import { isAbortError } from '$lib/utils';
|
||||
|
||||
/**
|
||||
* Converts API messages to agentic format.
|
||||
|
|
@ -507,7 +508,7 @@ export class AgenticClient {
|
|||
const executionResult = await mcpClient.executeTool(mcpCall, signal);
|
||||
result = executionResult.content;
|
||||
} catch (error) {
|
||||
if (error instanceof Error && error.name === 'AbortError') {
|
||||
if (isAbortError(error)) {
|
||||
onComplete?.(
|
||||
'',
|
||||
undefined,
|
||||
|
|
|
|||
|
|
@ -12,7 +12,8 @@ import {
|
|||
normalizeModelName,
|
||||
filterByLeafNodeId,
|
||||
findDescendantMessages,
|
||||
findLeafNode
|
||||
findLeafNode,
|
||||
isAbortError
|
||||
} from '$lib/utils';
|
||||
import { agenticStore } from '$lib/stores/agentic.svelte';
|
||||
import { DEFAULT_CONTEXT } from '$lib/constants/default-context';
|
||||
|
|
@ -132,7 +133,8 @@ export class ChatClient {
|
|||
* @param type - Message type (text or root)
|
||||
* @param parent - Parent message ID, or '-1' to append to conversation end
|
||||
* @param extras - Optional attachments (images, files, etc.)
|
||||
* @returns The created message or null if failed
|
||||
* @returns The created message
|
||||
* @throws Error if no active conversation or database operation fails
|
||||
*/
|
||||
async addMessage(
|
||||
role: MessageRole,
|
||||
|
|
@ -140,56 +142,50 @@ export class ChatClient {
|
|||
type: MessageType = MessageType.TEXT,
|
||||
parent: string = '-1',
|
||||
extras?: DatabaseMessageExtra[]
|
||||
): Promise<DatabaseMessage | null> {
|
||||
): Promise<DatabaseMessage> {
|
||||
const activeConv = conversationsStore.activeConversation;
|
||||
if (!activeConv) {
|
||||
console.error('No active conversation when trying to add message');
|
||||
return null;
|
||||
throw new Error('No active conversation when trying to add message');
|
||||
}
|
||||
|
||||
try {
|
||||
let parentId: string | null = null;
|
||||
let parentId: string | null = null;
|
||||
|
||||
if (parent === '-1') {
|
||||
const activeMessages = conversationsStore.activeMessages;
|
||||
if (activeMessages.length > 0) {
|
||||
parentId = activeMessages[activeMessages.length - 1].id;
|
||||
} else {
|
||||
const allMessages = await conversationsStore.getConversationMessages(activeConv.id);
|
||||
const rootMessage = allMessages.find((m) => m.parent === null && m.type === 'root');
|
||||
if (!rootMessage) {
|
||||
parentId = await DatabaseService.createRootMessage(activeConv.id);
|
||||
} else {
|
||||
parentId = rootMessage.id;
|
||||
}
|
||||
}
|
||||
if (parent === '-1') {
|
||||
const activeMessages = conversationsStore.activeMessages;
|
||||
if (activeMessages.length > 0) {
|
||||
parentId = activeMessages[activeMessages.length - 1].id;
|
||||
} else {
|
||||
parentId = parent;
|
||||
const allMessages = await conversationsStore.getConversationMessages(activeConv.id);
|
||||
const rootMessage = allMessages.find((m) => m.parent === null && m.type === 'root');
|
||||
if (!rootMessage) {
|
||||
parentId = await DatabaseService.createRootMessage(activeConv.id);
|
||||
} else {
|
||||
parentId = rootMessage.id;
|
||||
}
|
||||
}
|
||||
|
||||
const message = await DatabaseService.createMessageBranch(
|
||||
{
|
||||
convId: activeConv.id,
|
||||
role,
|
||||
content,
|
||||
type,
|
||||
timestamp: Date.now(),
|
||||
toolCalls: '',
|
||||
children: [],
|
||||
extra: extras
|
||||
},
|
||||
parentId
|
||||
);
|
||||
|
||||
conversationsStore.addMessageToActive(message);
|
||||
await conversationsStore.updateCurrentNode(message.id);
|
||||
conversationsStore.updateConversationTimestamp();
|
||||
|
||||
return message;
|
||||
} catch (error) {
|
||||
console.error('Failed to add message:', error);
|
||||
return null;
|
||||
} else {
|
||||
parentId = parent;
|
||||
}
|
||||
|
||||
const message = await DatabaseService.createMessageBranch(
|
||||
{
|
||||
convId: activeConv.id,
|
||||
role,
|
||||
content,
|
||||
type,
|
||||
timestamp: Date.now(),
|
||||
toolCalls: '',
|
||||
children: [],
|
||||
extra: extras
|
||||
},
|
||||
parentId
|
||||
);
|
||||
|
||||
conversationsStore.addMessageToActive(message);
|
||||
await conversationsStore.updateCurrentNode(message.id);
|
||||
conversationsStore.updateConversationTimestamp();
|
||||
|
||||
return message;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -334,9 +330,11 @@ export class ChatClient {
|
|||
*
|
||||
*/
|
||||
|
||||
private async createAssistantMessage(parentId?: string): Promise<DatabaseMessage | null> {
|
||||
private async createAssistantMessage(parentId?: string): Promise<DatabaseMessage> {
|
||||
const activeConv = conversationsStore.activeConversation;
|
||||
if (!activeConv) return null;
|
||||
if (!activeConv) {
|
||||
throw new Error('No active conversation when creating assistant message');
|
||||
}
|
||||
|
||||
return await DatabaseService.createMessageBranch(
|
||||
{
|
||||
|
|
@ -406,12 +404,10 @@ export class ChatClient {
|
|||
parentIdForUserMessage ?? '-1',
|
||||
extras
|
||||
);
|
||||
if (!userMessage) throw new Error('Failed to add user message');
|
||||
if (isNewConversation && content)
|
||||
await conversationsStore.updateConversationName(currentConv.id, content.trim());
|
||||
|
||||
const assistantMessage = await this.createAssistantMessage(userMessage.id);
|
||||
if (!assistantMessage) throw new Error('Failed to create assistant message');
|
||||
|
||||
conversationsStore.addMessageToActive(assistantMessage);
|
||||
await this.streamChatCompletion(
|
||||
|
|
@ -419,7 +415,7 @@ export class ChatClient {
|
|||
assistantMessage
|
||||
);
|
||||
} catch (error) {
|
||||
if (this.isAbortError(error)) {
|
||||
if (isAbortError(error)) {
|
||||
this.store.setChatLoading(currentConv.id, false);
|
||||
return;
|
||||
}
|
||||
|
|
@ -611,7 +607,7 @@ export class ChatClient {
|
|||
onError: (error: Error) => {
|
||||
this.store.setStreamingActive(false);
|
||||
|
||||
if (this.isAbortError(error)) {
|
||||
if (isAbortError(error)) {
|
||||
this.store.setChatLoading(assistantMessage.convId, false);
|
||||
this.store.clearChatStreaming(assistantMessage.convId);
|
||||
this.store.setProcessingState(assistantMessage.convId, null);
|
||||
|
|
@ -806,7 +802,6 @@ export class ChatClient {
|
|||
this.store.clearChatStreaming(activeConv.id);
|
||||
|
||||
const assistantMessage = await this.createAssistantMessage();
|
||||
if (!assistantMessage) throw new Error('Failed to create assistant message');
|
||||
|
||||
conversationsStore.addMessageToActive(assistantMessage);
|
||||
|
||||
|
|
@ -822,7 +817,7 @@ export class ChatClient {
|
|||
}
|
||||
);
|
||||
} catch (error) {
|
||||
if (!this.isAbortError(error)) console.error('Failed to update message:', error);
|
||||
if (!isAbortError(error)) console.error('Failed to update message:', error);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -861,14 +856,13 @@ export class ChatClient {
|
|||
? conversationsStore.activeMessages[conversationsStore.activeMessages.length - 1].id
|
||||
: undefined;
|
||||
const assistantMessage = await this.createAssistantMessage(parentMessageId);
|
||||
if (!assistantMessage) throw new Error('Failed to create assistant message');
|
||||
conversationsStore.addMessageToActive(assistantMessage);
|
||||
await this.streamChatCompletion(
|
||||
conversationsStore.activeMessages.slice(0, -1),
|
||||
assistantMessage
|
||||
);
|
||||
} catch (error) {
|
||||
if (!this.isAbortError(error)) console.error('Failed to regenerate message:', error);
|
||||
if (!isAbortError(error)) console.error('Failed to regenerate message:', error);
|
||||
this.store.setChatLoading(activeConv?.id || '', false);
|
||||
}
|
||||
}
|
||||
|
|
@ -926,7 +920,7 @@ export class ChatClient {
|
|||
modelToUse
|
||||
);
|
||||
} catch (error) {
|
||||
if (!this.isAbortError(error))
|
||||
if (!isAbortError(error))
|
||||
console.error('Failed to regenerate message with branching:', error);
|
||||
this.store.setChatLoading(activeConv?.id || '', false);
|
||||
}
|
||||
|
|
@ -1153,7 +1147,7 @@ export class ChatClient {
|
|||
},
|
||||
|
||||
onError: async (error: Error) => {
|
||||
if (this.isAbortError(error)) {
|
||||
if (isAbortError(error)) {
|
||||
if (hasReceivedContent && appendedContent) {
|
||||
await DatabaseService.updateMessage(msg.id, {
|
||||
content: originalContent + appendedContent,
|
||||
|
|
@ -1189,7 +1183,7 @@ export class ChatClient {
|
|||
abortController.signal
|
||||
);
|
||||
} catch (error) {
|
||||
if (!this.isAbortError(error)) console.error('Failed to continue message:', error);
|
||||
if (!isAbortError(error)) console.error('Failed to continue message:', error);
|
||||
if (activeConv) this.store.setChatLoading(activeConv.id, false);
|
||||
}
|
||||
}
|
||||
|
|
@ -1564,10 +1558,6 @@ export class ChatClient {
|
|||
*
|
||||
*/
|
||||
|
||||
private isAbortError(error: unknown): boolean {
|
||||
return error instanceof Error && (error.name === 'AbortError' || error instanceof DOMException);
|
||||
}
|
||||
|
||||
private isChatLoading(convId: string): boolean {
|
||||
const streamingState = this.store.getChatStreaming(convId);
|
||||
return streamingState !== undefined;
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { getJsonHeaders, formatAttachmentText } from '$lib/utils';
|
||||
import { getJsonHeaders, formatAttachmentText, isAbortError } from '$lib/utils';
|
||||
import { AGENTIC_REGEX } from '$lib/constants/agentic';
|
||||
import { AttachmentType, MessageRole, ReasoningFormat } from '$lib/enums';
|
||||
import type { ApiChatMessageContentPart } from '$lib/types/api';
|
||||
|
|
@ -257,7 +257,7 @@ export class ChatService {
|
|||
);
|
||||
}
|
||||
} catch (error) {
|
||||
if (error instanceof Error && error.name === 'AbortError') {
|
||||
if (isAbortError(error)) {
|
||||
console.log('Chat completion request was aborted');
|
||||
return;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -37,6 +37,7 @@ import type {
|
|||
} from '$lib/types';
|
||||
import { MCPConnectionPhase, MCPLogLevel, MCPTransportType } from '$lib/enums';
|
||||
import { DEFAULT_MCP_CONFIG } from '$lib/constants/mcp';
|
||||
import { throwIfAborted, isAbortError } from '$lib/utils';
|
||||
|
||||
interface ToolResultContentItem {
|
||||
type: string;
|
||||
|
|
@ -341,9 +342,7 @@ export class MCPService {
|
|||
params: ToolCallParams,
|
||||
signal?: AbortSignal
|
||||
): Promise<ToolExecutionResult> {
|
||||
if (signal?.aborted) {
|
||||
throw new DOMException('Aborted', 'AbortError');
|
||||
}
|
||||
throwIfAborted(signal);
|
||||
|
||||
try {
|
||||
const result = await connection.client.callTool(
|
||||
|
|
@ -357,7 +356,7 @@ export class MCPService {
|
|||
isError: (result as ToolCallResult).isError ?? false
|
||||
};
|
||||
} catch (error) {
|
||||
if (error instanceof DOMException && error.name === 'AbortError') {
|
||||
if (isAbortError(error)) {
|
||||
throw error;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -395,7 +395,7 @@ class ChatStore {
|
|||
type: MessageType = MessageType.TEXT,
|
||||
parent: string = '-1',
|
||||
extras?: DatabaseMessageExtra[]
|
||||
): Promise<DatabaseMessage | null> {
|
||||
): Promise<DatabaseMessage> {
|
||||
return chatClient.addMessage(role, content, type, parent, extras);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -122,3 +122,12 @@ export { parseAgenticContent, type AgenticSection } from './agentic';
|
|||
|
||||
// Cache utilities
|
||||
export { TTLCache, ReactiveTTLMap, type TTLCacheOptions } from './cache-ttl';
|
||||
|
||||
// Abort signal utilities
|
||||
export {
|
||||
throwIfAborted,
|
||||
isAbortError,
|
||||
createLinkedController,
|
||||
createTimeoutSignal,
|
||||
withAbortSignal
|
||||
} from './abort';
|
||||
|
|
|
|||
Loading…
Reference in New Issue