// LangGraph streaming handler for IPC transport
// Handles streaming from LangGraph assistant over dedicated event channels
ipcMain.handle('lg/stream/start', async (event, payload) => {
const {
runId,
eventChannel,
input,
context,
command,
config,
}: {
runId: string;
eventChannel: string;
input: any;
context?: { model?: string; webSearch?: boolean; threadId?: string };
command?: any;
config?: any;
} = payload;
log.info(`Starting LangGraph stream ${runId} on channel ${eventChannel}`);
log.debug(`Context:`, context);
try {
// Initialize LangGraph client with production credentials
const client = new Client({
apiUrl: 'http://localhost:2024/',
});
// Get the first assistant
const assistants = await client.assistants.search({
metadata: null,
offset: 0,
limit: 5,
});
if (!assistants || assistants.length === 0) {
throw new Error('No assistants found');
}
const assistant = assistants[0];
log.info(`Using assistant: ${assistant.assistant_id}`);
// Use existing thread or create new one
const threadId = context?.threadId;
log.info(`Thread ID: ${threadId || 'creating new thread'}`);
// Build config with context as configurable parameters
const streamConfig = {
...config,
configurable: {
...config?.configurable,
...(context && {
model: context.model,
webSearch: context.webSearch,
}),
},
};
// Start streaming from LangGraph - handle null vs string threadId properly
const streamResponse = threadId
? client.runs.stream(threadId, assistant.assistant_id, {
input: input || {},
config: streamConfig,
streamMode: 'messages-tuple',
})
: client.runs.stream(null, assistant.assistant_id, {
input: input || {},
config: streamConfig,
streamMode: 'messages',
});
let newThreadId: string | null = null;
// Stream events back to the renderer via the dedicated event channel
// eslint-disable-next-line no-restricted-syntax
for await (const chunk of streamResponse) {
log.debug(`Stream chunk:`, chunk);
// Capture thread_id from metadata events
if (chunk.event === 'metadata' && chunk.data?.thread_id) {
newThreadId = chunk.data.thread_id;
log.info(`Thread ID from stream: ${newThreadId}`);
}
// Forward the stream event to the renderer
event.sender.send(eventChannel, {
event: chunk.event,
data: chunk.data,
id: runId,
});
}
// Send thread_id back to renderer so it can be used for future messages
if (newThreadId) {
event.sender.send(eventChannel, {
event: 'thread',
data: { thread_id: newThreadId },
id: runId,
});
}
log.info(`Stream ${runId} completed successfully`);
return { success: true, threadId: newThreadId };
} catch (error) {
log.error(`Error in LangGraph stream ${runId}:`, error);
// Send error event to renderer
event.sender.send(eventChannel, {
event: 'error',
data: {
error: error instanceof Error ? error.name : 'StreamError',
message: error instanceof Error ? error.message : 'Unknown error',
},
id: runId,
});
throw error;
}
});
// LangGraph stream cancellation handler
ipcMain.handle('lg/stream/cancel', async (event, payload) => {
const { runId }: { runId: string } = payload;
log.info(`Cancellation requested for stream ${runId}`);
// TODO: Implement cancellation logic if LangGraph SDK supports it
// For now, just acknowledge the cancellation request
return { success: true, runId };
});