Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/main/ipc/handlers/chat.handlers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ export const registerChatHandlers = (
/**
* Send chat message and stream response
*/
ipcMain.handle('chat:send', (_event, request: ChatSendRequest) => {
ipcMain.handle('chat:send', async (_event, request: ChatSendRequest) => {
const mainWindow = getMainWindow();
if (!mainWindow) {
throw new Error('No active window');
Expand Down
78 changes: 66 additions & 12 deletions src/main/services/chat.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,12 @@ import type {
import { toAnthropicTools } from '../tools/converter.js';
import { executeTool, listTools } from '../tools/index.js';
import type { ToolContext } from '../tools/types.js';
import {
appendMessage,
createConversation as createPersistedConversation,
restoreConversation,
updateUsageStats,
} from './conversation.store.service.js';
import { loadProviders, resolveProviderToken } from './model.providers.service.js';
import { getActiveWorkspace } from './workspace.service.js';

Expand Down Expand Up @@ -97,28 +103,65 @@ const getAnthropicClient = async (): Promise<{ client: Anthropic; isOAuth: boole

/**
* Get or create conversation
* Creates in-memory conversation and persists to disk
*/
const getOrCreateConversation = (
const getOrCreateConversation = async (
sessionConfig: SessionConfig,
conversationId?: string
): Conversation => {
): Promise<Conversation> => {
// Try to get existing conversation from memory
if (conversationId && conversations.has(conversationId)) {
const existing = conversations.get(conversationId);
if (existing) return existing;
if (existing) {
console.log('[ChatService] Using existing in-memory conversation', { id: conversationId });
return existing;
}
}

// Try to restore from disk if we have an ID
if (conversationId) {
console.log('[ChatService] Trying to restore conversation from disk', { id: conversationId });
const restored = await restoreConversation(conversationId);
if (restored) {
console.log('[ChatService] Restored conversation from disk', {
id: restored.id,
messages: restored.messages.length,
});
const conversation: Conversation = {
id: restored.id,
config: restored.config,
messages: restored.messages,
createdAt: restored.createdAt,
updatedAt: restored.updatedAt,
};
conversations.set(conversation.id, conversation);
return conversation;
}
}

const id = conversationId ?? generateId('conv');
const now = new Date().toISOString();
// Create new conversation - persist to disk first
console.log('[ChatService] Creating new conversation', { model: sessionConfig.model });

const persisted = await createPersistedConversation({
system: sessionConfig.system ?? '',
model: sessionConfig.model,
provider: 'anthropic', // TODO: detect from model
agentId: sessionConfig.agent,
tools: sessionConfig.tools,
modelConfig: sessionConfig.modelConfig,
});

console.log('[ChatService] Conversation persisted', { id: persisted.id });

const conversation: Conversation = {
id,
id: persisted.id,
config: sessionConfig,
messages: [],
createdAt: now,
updatedAt: now,
createdAt: persisted.metadata.createdAt,
updatedAt: persisted.metadata.updatedAt,
};

conversations.set(id, conversation);
conversations.set(conversation.id, conversation);
return conversation;
};

Expand Down Expand Up @@ -278,15 +321,15 @@ const executeToolCall = async (
/**
* Send a chat message and stream response
*/
export const sendMessage = (
export const sendMessage = async (
request: ChatSendRequest,
emitEvent: (event: ChatEvent) => void
): ChatSendResponse => {
): Promise<ChatSendResponse> => {
const validated = chatSendRequestSchema.parse(request);
const config = validated.config;

const requestId = generateId('req');
const conversation = getOrCreateConversation(config, config.conversationId);
const conversation = await getOrCreateConversation(config, config.conversationId);
const abortController = new AbortController();

activeRequests.set(requestId, abortController);
Expand All @@ -301,6 +344,10 @@ export const sendMessage = (
conversation.messages.push(userMessage);
conversation.updatedAt = userMessage.timestamp;

// Persist user message to disk
console.log('[ChatService] Persisting user message', { conversationId: conversation.id });
await appendMessage(conversation.id, userMessage);

// Start streaming in background
void streamResponse(
requestId,
Expand Down Expand Up @@ -423,6 +470,13 @@ const streamResponse = async (
conversation.messages.push(assistantMessage);
conversation.updatedAt = assistantMessage.timestamp;

// Persist assistant message and usage to disk
console.log('[ChatService] Persisting assistant message', {
conversationId: conversation.id,
});
await appendMessage(conversation.id, assistantMessage);
await updateUsageStats(conversation.id, totalInputTokens, totalOutputTokens);

// Emit done
emitEvent({
requestId,
Expand Down
Loading
Loading