Files
zn-ai/electron/gateway/manager.ts

149 lines
4.4 KiB
TypeScript

import { BrowserWindow } from 'electron';
import { windowManager } from '@electron/service/window-service';
import logManager from '@electron/service/logger';
import { updateTrayStatus } from '@electron/main/tray';
import type { GatewayEvent, RuntimeRefreshTopic } from './types';
import * as chatHandlers from './handlers/chat';
import * as providerHandlers from './handlers/provider';
import * as skillHandlers from './handlers/skills';
type RuntimeChangeBroadcast = {
topics: RuntimeRefreshTopic[];
reason?: string;
warnings?: string[];
channelType?: string;
accountId?: string;
};
class GatewayManager {
private initialized = false;
private status: 'connected' | 'disconnected' | 'reconnecting' = 'disconnected';
private setStatus(status: 'connected' | 'disconnected' | 'reconnecting'): void {
this.status = status;
updateTrayStatus(status);
this.broadcast({ type: 'gateway:status', status });
}
async init(): Promise<void> {
if (this.initialized) return;
this.initialized = true;
logManager.info('GatewayManager initialized');
this.setStatus('connected');
}
async start(): Promise<void> {
await this.init();
}
async stop(): Promise<void> {
this.initialized = false;
this.setStatus('disconnected');
}
async restart(options?: RuntimeChangeBroadcast): Promise<void> {
this.initialized = false;
this.setStatus('reconnecting');
await this.init();
if (options) {
this.notifyRuntimeChanged(options);
}
}
getStatus(): {
status: 'connected' | 'disconnected' | 'reconnecting';
initialized: boolean;
mode: 'in-process';
} {
return {
status: this.status,
initialized: this.initialized,
mode: 'in-process',
};
}
async checkHealth(): Promise<{
ok: boolean;
status: 'connected' | 'disconnected' | 'reconnecting';
initialized: boolean;
mode: 'in-process';
}> {
return {
ok: this.initialized && this.status === 'connected',
...this.getStatus(),
};
}
async rpc(method: string, params: any): Promise<any> {
if (!this.initialized) {
await this.init();
}
logManager.info(`Gateway RPC: ${method}`, params);
switch (method) {
case 'chat.send':
return chatHandlers.handleChatSend(params, (event) => this.broadcast(event));
case 'chat.history':
return chatHandlers.handleChatHistory(params);
case 'chat.abort':
return chatHandlers.handleChatAbort(params, (event) => this.broadcast(event));
case 'session.list':
return chatHandlers.handleSessionList();
case 'session.delete':
return chatHandlers.handleSessionDelete(params);
case 'provider.list':
return providerHandlers.handleProviderList();
case 'provider.getDefault':
return providerHandlers.handleProviderGetDefault();
case 'skills.status':
return skillHandlers.handleSkillsStatus();
case 'skills.update':
return skillHandlers.handleSkillsUpdate(params);
default:
throw new Error(`Unknown gateway RPC method: ${method}`);
}
}
broadcast(event: GatewayEvent): void {
const mainWindow = BrowserWindow.getAllWindows().find(
(win) => windowManager.getName(win) === 'main'
);
if (mainWindow && !mainWindow.isDestroyed()) {
mainWindow.webContents.send('gateway:event', event);
}
}
notifyRuntimeChanged(options: RuntimeChangeBroadcast): void {
const topics = Array.from(new Set(options.topics.filter(Boolean)));
if (topics.length === 0) {
return;
}
this.broadcast({
type: 'runtime:changed',
topics,
reason: options.reason,
warnings: options.warnings && options.warnings.length > 0 ? options.warnings : undefined,
channelType: options.channelType,
accountId: options.accountId,
syncedAt: new Date().toISOString(),
});
}
reloadProviders(options?: RuntimeChangeBroadcast): void {
logManager.info('GatewayManager reloading providers');
// For now, providers are resolved on each chat.send call,
// so no in-memory cache to invalidate. Future: notify active sessions.
this.notifyRuntimeChanged({
topics: options?.topics ?? ['providers', 'models'],
reason: options?.reason ?? 'providers:reload',
warnings: options?.warnings,
channelType: options?.channelType,
accountId: options?.accountId,
});
}
}
export const gatewayManager = new GatewayManager();