import type { PermissionUpdate } from '@anthropic-ai/claude-agent-sdk' import { electronAPI } from '@electron-toolkit/preload' import type { SpanEntity, TokenUsage } from '@mcp-trace/trace-core' import type { SpanContext } from '@opentelemetry/api' import type { GitBashPathInfo, TerminalConfig, UpgradeChannel } from '@shared/config/constant' import type { LogLevel, LogSourceWithContext } from '@shared/config/logger' import type { FileChangeEvent, WebviewKeyEvent } from '@shared/config/types' import type { MCPServerLogEntry } from '@shared/config/types' import { IpcChannel } from '@shared/IpcChannel' import type { Notification } from '@types' import type { AddMemoryOptions, AssistantMessage, FileListResponse, FileMetadata, FileUploadResponse, GetApiServerStatusResult, KnowledgeBaseParams, KnowledgeItem, KnowledgeSearchResult, MCPServer, MemoryConfig, MemoryListOptions, MemorySearchOptions, OcrProvider, OcrResult, Provider, RestartApiServerStatusResult, S3Config, Shortcut, StartApiServerStatusResult, StopApiServerStatusResult, SupportedOcrFile, ThemeMode, WebDavConfig } from '@types' import type { OpenDialogOptions } from 'electron' import { contextBridge, ipcRenderer, shell, webUtils } from 'electron' import type { CreateDirectoryOptions } from 'webdav' import type { InstalledPlugin, InstallPluginOptions, ListAvailablePluginsResult, PluginMetadata, PluginResult, UninstallPluginOptions, WritePluginContentOptions } from '../renderer/src/types/plugin' import type { ActionItem } from '../renderer/src/types/selectionTypes' type DirectoryListOptions = { recursive?: boolean maxDepth?: number includeHidden?: boolean includeFiles?: boolean includeDirectories?: boolean maxEntries?: number searchPattern?: string } export function tracedInvoke(channel: string, spanContext: SpanContext | undefined, ...args: any[]) { if (spanContext) { const data = { type: 'trace', context: spanContext } return ipcRenderer.invoke(channel, ...args, data) } return ipcRenderer.invoke(channel, ...args) } // Custom APIs for renderer const api = { getAppInfo: () => ipcRenderer.invoke(IpcChannel.App_Info), getDiskInfo: (directoryPath: string): Promise<{ free: number; size: number } | null> => ipcRenderer.invoke(IpcChannel.App_GetDiskInfo, directoryPath), reload: () => ipcRenderer.invoke(IpcChannel.App_Reload), quit: () => ipcRenderer.invoke(IpcChannel.App_Quit), setProxy: (proxy: string | undefined, bypassRules?: string) => ipcRenderer.invoke(IpcChannel.App_Proxy, proxy, bypassRules), checkForUpdate: () => ipcRenderer.invoke(IpcChannel.App_CheckForUpdate), quitAndInstall: () => ipcRenderer.invoke(IpcChannel.App_QuitAndInstall), setLanguage: (lang: string) => ipcRenderer.invoke(IpcChannel.App_SetLanguage, lang), setEnableSpellCheck: (isEnable: boolean) => ipcRenderer.invoke(IpcChannel.App_SetEnableSpellCheck, isEnable), setSpellCheckLanguages: (languages: string[]) => ipcRenderer.invoke(IpcChannel.App_SetSpellCheckLanguages, languages), setLaunchOnBoot: (isActive: boolean) => ipcRenderer.invoke(IpcChannel.App_SetLaunchOnBoot, isActive), setLaunchToTray: (isActive: boolean) => ipcRenderer.invoke(IpcChannel.App_SetLaunchToTray, isActive), setTray: (isActive: boolean) => ipcRenderer.invoke(IpcChannel.App_SetTray, isActive), setTrayOnClose: (isActive: boolean) => ipcRenderer.invoke(IpcChannel.App_SetTrayOnClose, isActive), setTestPlan: (isActive: boolean) => ipcRenderer.invoke(IpcChannel.App_SetTestPlan, isActive), setTestChannel: (channel: UpgradeChannel) => ipcRenderer.invoke(IpcChannel.App_SetTestChannel, channel), setTheme: (theme: ThemeMode) => ipcRenderer.invoke(IpcChannel.App_SetTheme, theme), handleZoomFactor: (delta: number, reset: boolean = false) => ipcRenderer.invoke(IpcChannel.App_HandleZoomFactor, delta, reset), setAutoUpdate: (isActive: boolean) => ipcRenderer.invoke(IpcChannel.App_SetAutoUpdate, isActive), select: (options: Electron.OpenDialogOptions) => ipcRenderer.invoke(IpcChannel.App_Select, options), hasWritePermission: (path: string) => ipcRenderer.invoke(IpcChannel.App_HasWritePermission, path), resolvePath: (path: string) => ipcRenderer.invoke(IpcChannel.App_ResolvePath, path), isPathInside: (childPath: string, parentPath: string) => ipcRenderer.invoke(IpcChannel.App_IsPathInside, childPath, parentPath), setAppDataPath: (path: string) => ipcRenderer.invoke(IpcChannel.App_SetAppDataPath, path), getDataPathFromArgs: () => ipcRenderer.invoke(IpcChannel.App_GetDataPathFromArgs), copy: (oldPath: string, newPath: string, occupiedDirs: string[] = []) => ipcRenderer.invoke(IpcChannel.App_Copy, oldPath, newPath, occupiedDirs), setStopQuitApp: (stop: boolean, reason: string) => ipcRenderer.invoke(IpcChannel.App_SetStopQuitApp, stop, reason), flushAppData: () => ipcRenderer.invoke(IpcChannel.App_FlushAppData), isNotEmptyDir: (path: string) => ipcRenderer.invoke(IpcChannel.App_IsNotEmptyDir, path), relaunchApp: (options?: Electron.RelaunchOptions) => ipcRenderer.invoke(IpcChannel.App_RelaunchApp, options), openWebsite: (url: string) => ipcRenderer.invoke(IpcChannel.Open_Website, url), getCacheSize: () => ipcRenderer.invoke(IpcChannel.App_GetCacheSize), clearCache: () => ipcRenderer.invoke(IpcChannel.App_ClearCache), logToMain: (source: LogSourceWithContext, level: LogLevel, message: string, data: any[]) => ipcRenderer.invoke(IpcChannel.App_LogToMain, source, level, message, data), setFullScreen: (value: boolean): Promise => ipcRenderer.invoke(IpcChannel.App_SetFullScreen, value), isFullScreen: (): Promise => ipcRenderer.invoke(IpcChannel.App_IsFullScreen), getSystemFonts: (): Promise => ipcRenderer.invoke(IpcChannel.App_GetSystemFonts), mockCrashRenderProcess: () => ipcRenderer.invoke(IpcChannel.APP_CrashRenderProcess), mac: { isProcessTrusted: (): Promise => ipcRenderer.invoke(IpcChannel.App_MacIsProcessTrusted), requestProcessTrust: (): Promise => ipcRenderer.invoke(IpcChannel.App_MacRequestProcessTrust) }, notification: { send: (notification: Notification) => ipcRenderer.invoke(IpcChannel.Notification_Send, notification) }, system: { getDeviceType: () => ipcRenderer.invoke(IpcChannel.System_GetDeviceType), getHostname: () => ipcRenderer.invoke(IpcChannel.System_GetHostname), getCpuName: () => ipcRenderer.invoke(IpcChannel.System_GetCpuName), checkGitBash: (): Promise => ipcRenderer.invoke(IpcChannel.System_CheckGitBash), getGitBashPath: (): Promise => ipcRenderer.invoke(IpcChannel.System_GetGitBashPath), getGitBashPathInfo: (): Promise => ipcRenderer.invoke(IpcChannel.System_GetGitBashPathInfo), setGitBashPath: (newPath: string | null): Promise => ipcRenderer.invoke(IpcChannel.System_SetGitBashPath, newPath) }, devTools: { toggle: () => ipcRenderer.invoke(IpcChannel.System_ToggleDevTools) }, zip: { compress: (text: string) => ipcRenderer.invoke(IpcChannel.Zip_Compress, text), decompress: (text: Buffer) => ipcRenderer.invoke(IpcChannel.Zip_Decompress, text) }, backup: { backup: (filename: string, content: string, path: string, skipBackupFile: boolean) => ipcRenderer.invoke(IpcChannel.Backup_Backup, filename, content, path, skipBackupFile), restore: (path: string) => ipcRenderer.invoke(IpcChannel.Backup_Restore, path), backupToWebdav: (data: string, webdavConfig: WebDavConfig) => ipcRenderer.invoke(IpcChannel.Backup_BackupToWebdav, data, webdavConfig), restoreFromWebdav: (webdavConfig: WebDavConfig) => ipcRenderer.invoke(IpcChannel.Backup_RestoreFromWebdav, webdavConfig), listWebdavFiles: (webdavConfig: WebDavConfig) => ipcRenderer.invoke(IpcChannel.Backup_ListWebdavFiles, webdavConfig), checkConnection: (webdavConfig: WebDavConfig) => ipcRenderer.invoke(IpcChannel.Backup_CheckConnection, webdavConfig), createDirectory: (webdavConfig: WebDavConfig, path: string, options?: CreateDirectoryOptions) => ipcRenderer.invoke(IpcChannel.Backup_CreateDirectory, webdavConfig, path, options), deleteWebdavFile: (fileName: string, webdavConfig: WebDavConfig) => ipcRenderer.invoke(IpcChannel.Backup_DeleteWebdavFile, fileName, webdavConfig), backupToLocalDir: ( data: string, fileName: string, localConfig: { localBackupDir?: string; skipBackupFile?: boolean } ) => ipcRenderer.invoke(IpcChannel.Backup_BackupToLocalDir, data, fileName, localConfig), restoreFromLocalBackup: (fileName: string, localBackupDir?: string) => ipcRenderer.invoke(IpcChannel.Backup_RestoreFromLocalBackup, fileName, localBackupDir), listLocalBackupFiles: (localBackupDir?: string) => ipcRenderer.invoke(IpcChannel.Backup_ListLocalBackupFiles, localBackupDir), deleteLocalBackupFile: (fileName: string, localBackupDir?: string) => ipcRenderer.invoke(IpcChannel.Backup_DeleteLocalBackupFile, fileName, localBackupDir), checkWebdavConnection: (webdavConfig: WebDavConfig) => ipcRenderer.invoke(IpcChannel.Backup_CheckConnection, webdavConfig), backupToS3: (data: string, s3Config: S3Config) => ipcRenderer.invoke(IpcChannel.Backup_BackupToS3, data, s3Config), restoreFromS3: (s3Config: S3Config) => ipcRenderer.invoke(IpcChannel.Backup_RestoreFromS3, s3Config), listS3Files: (s3Config: S3Config) => ipcRenderer.invoke(IpcChannel.Backup_ListS3Files, s3Config), deleteS3File: (fileName: string, s3Config: S3Config) => ipcRenderer.invoke(IpcChannel.Backup_DeleteS3File, fileName, s3Config), checkS3Connection: (s3Config: S3Config) => ipcRenderer.invoke(IpcChannel.Backup_CheckS3Connection, s3Config) }, file: { select: (options?: OpenDialogOptions): Promise => ipcRenderer.invoke(IpcChannel.File_Select, options), upload: (file: FileMetadata) => ipcRenderer.invoke(IpcChannel.File_Upload, file), delete: (fileId: string) => ipcRenderer.invoke(IpcChannel.File_Delete, fileId), deleteDir: (dirPath: string) => ipcRenderer.invoke(IpcChannel.File_DeleteDir, dirPath), deleteExternalFile: (filePath: string) => ipcRenderer.invoke(IpcChannel.File_DeleteExternalFile, filePath), deleteExternalDir: (dirPath: string) => ipcRenderer.invoke(IpcChannel.File_DeleteExternalDir, dirPath), move: (path: string, newPath: string) => ipcRenderer.invoke(IpcChannel.File_Move, path, newPath), moveDir: (dirPath: string, newDirPath: string) => ipcRenderer.invoke(IpcChannel.File_MoveDir, dirPath, newDirPath), rename: (path: string, newName: string) => ipcRenderer.invoke(IpcChannel.File_Rename, path, newName), renameDir: (dirPath: string, newName: string) => ipcRenderer.invoke(IpcChannel.File_RenameDir, dirPath, newName), read: (fileId: string, detectEncoding?: boolean) => ipcRenderer.invoke(IpcChannel.File_Read, fileId, detectEncoding), readExternal: (filePath: string, detectEncoding?: boolean) => ipcRenderer.invoke(IpcChannel.File_ReadExternal, filePath, detectEncoding), clear: (spanContext?: SpanContext) => ipcRenderer.invoke(IpcChannel.File_Clear, spanContext), get: (filePath: string): Promise => ipcRenderer.invoke(IpcChannel.File_Get, filePath), createTempFile: (fileName: string): Promise => ipcRenderer.invoke(IpcChannel.File_CreateTempFile, fileName), mkdir: (dirPath: string) => ipcRenderer.invoke(IpcChannel.File_Mkdir, dirPath), write: (filePath: string, data: Uint8Array | string) => ipcRenderer.invoke(IpcChannel.File_Write, filePath, data), writeWithId: (id: string, content: string) => ipcRenderer.invoke(IpcChannel.File_WriteWithId, id, content), open: (options?: OpenDialogOptions) => ipcRenderer.invoke(IpcChannel.File_Open, options), openPath: (path: string) => ipcRenderer.invoke(IpcChannel.File_OpenPath, path), save: (path: string, content: string | NodeJS.ArrayBufferView, options?: any) => ipcRenderer.invoke(IpcChannel.File_Save, path, content, options), selectFolder: (options?: OpenDialogOptions): Promise => ipcRenderer.invoke(IpcChannel.File_SelectFolder, options), saveImage: (name: string, data: string) => ipcRenderer.invoke(IpcChannel.File_SaveImage, name, data), binaryImage: (fileId: string) => ipcRenderer.invoke(IpcChannel.File_BinaryImage, fileId), base64Image: (fileId: string): Promise<{ mime: string; base64: string; data: string }> => ipcRenderer.invoke(IpcChannel.File_Base64Image, fileId), saveBase64Image: (data: string) => ipcRenderer.invoke(IpcChannel.File_SaveBase64Image, data), savePastedImage: (imageData: Uint8Array, extension?: string) => ipcRenderer.invoke(IpcChannel.File_SavePastedImage, imageData, extension), download: (url: string, isUseContentType?: boolean) => ipcRenderer.invoke(IpcChannel.File_Download, url, isUseContentType), copy: (fileId: string, destPath: string) => ipcRenderer.invoke(IpcChannel.File_Copy, fileId, destPath), base64File: (fileId: string) => ipcRenderer.invoke(IpcChannel.File_Base64File, fileId), pdfInfo: (fileId: string) => ipcRenderer.invoke(IpcChannel.File_GetPdfInfo, fileId), getPathForFile: (file: File) => webUtils.getPathForFile(file), openFileWithRelativePath: (file: FileMetadata) => ipcRenderer.invoke(IpcChannel.File_OpenWithRelativePath, file), isTextFile: (filePath: string): Promise => ipcRenderer.invoke(IpcChannel.File_IsTextFile, filePath), getDirectoryStructure: (dirPath: string) => ipcRenderer.invoke(IpcChannel.File_GetDirectoryStructure, dirPath), listDirectory: (dirPath: string, options?: DirectoryListOptions) => ipcRenderer.invoke(IpcChannel.File_ListDirectory, dirPath, options), checkFileName: (dirPath: string, fileName: string, isFile: boolean) => ipcRenderer.invoke(IpcChannel.File_CheckFileName, dirPath, fileName, isFile), validateNotesDirectory: (dirPath: string) => ipcRenderer.invoke(IpcChannel.File_ValidateNotesDirectory, dirPath), startFileWatcher: (dirPath: string, config?: any) => ipcRenderer.invoke(IpcChannel.File_StartWatcher, dirPath, config), stopFileWatcher: () => ipcRenderer.invoke(IpcChannel.File_StopWatcher), pauseFileWatcher: () => ipcRenderer.invoke(IpcChannel.File_PauseWatcher), resumeFileWatcher: () => ipcRenderer.invoke(IpcChannel.File_ResumeWatcher), batchUploadMarkdown: (filePaths: string[], targetPath: string) => ipcRenderer.invoke(IpcChannel.File_BatchUploadMarkdown, filePaths, targetPath), onFileChange: (callback: (data: FileChangeEvent) => void) => { const listener = (_event: Electron.IpcRendererEvent, data: any) => { if (data && typeof data === 'object') { callback(data) } } ipcRenderer.on('file-change', listener) return () => ipcRenderer.off('file-change', listener) }, showInFolder: (path: string): Promise => ipcRenderer.invoke(IpcChannel.File_ShowInFolder, path) }, fs: { read: (pathOrUrl: string, encoding?: BufferEncoding) => ipcRenderer.invoke(IpcChannel.Fs_Read, pathOrUrl, encoding), readText: (pathOrUrl: string): Promise => ipcRenderer.invoke(IpcChannel.Fs_ReadText, pathOrUrl) }, export: { toWord: (markdown: string, fileName: string) => ipcRenderer.invoke(IpcChannel.Export_Word, markdown, fileName) }, obsidian: { getVaults: () => ipcRenderer.invoke(IpcChannel.Obsidian_GetVaults), getFolders: (vaultName: string) => ipcRenderer.invoke(IpcChannel.Obsidian_GetFiles, vaultName), getFiles: (vaultName: string) => ipcRenderer.invoke(IpcChannel.Obsidian_GetFiles, vaultName) }, openPath: (path: string) => ipcRenderer.invoke(IpcChannel.Open_Path, path), shortcuts: { update: (shortcuts: Shortcut[]) => ipcRenderer.invoke(IpcChannel.Shortcuts_Update, shortcuts) }, knowledgeBase: { create: (base: KnowledgeBaseParams, context?: SpanContext) => tracedInvoke(IpcChannel.KnowledgeBase_Create, context, base), reset: (base: KnowledgeBaseParams) => ipcRenderer.invoke(IpcChannel.KnowledgeBase_Reset, base), delete: (id: string) => ipcRenderer.invoke(IpcChannel.KnowledgeBase_Delete, id), add: ({ base, item, userId, forceReload = false }: { base: KnowledgeBaseParams item: KnowledgeItem userId?: string forceReload?: boolean }) => ipcRenderer.invoke(IpcChannel.KnowledgeBase_Add, { base, item, forceReload, userId }), remove: ({ uniqueId, uniqueIds, base }: { uniqueId: string; uniqueIds: string[]; base: KnowledgeBaseParams }) => ipcRenderer.invoke(IpcChannel.KnowledgeBase_Remove, { uniqueId, uniqueIds, base }), search: ({ search, base }: { search: string; base: KnowledgeBaseParams }, context?: SpanContext) => tracedInvoke(IpcChannel.KnowledgeBase_Search, context, { search, base }), rerank: ( { search, base, results }: { search: string; base: KnowledgeBaseParams; results: KnowledgeSearchResult[] }, context?: SpanContext ) => tracedInvoke(IpcChannel.KnowledgeBase_Rerank, context, { search, base, results }), checkQuota: ({ base, userId }: { base: KnowledgeBaseParams; userId: string }) => ipcRenderer.invoke(IpcChannel.KnowledgeBase_Check_Quota, base, userId) }, memory: { add: (messages: string | AssistantMessage[], options?: AddMemoryOptions) => ipcRenderer.invoke(IpcChannel.Memory_Add, messages, options), search: (query: string, options: MemorySearchOptions) => ipcRenderer.invoke(IpcChannel.Memory_Search, query, options), list: (options?: MemoryListOptions) => ipcRenderer.invoke(IpcChannel.Memory_List, options), delete: (id: string) => ipcRenderer.invoke(IpcChannel.Memory_Delete, id), update: (id: string, memory: string, metadata?: Record) => ipcRenderer.invoke(IpcChannel.Memory_Update, id, memory, metadata), get: (id: string) => ipcRenderer.invoke(IpcChannel.Memory_Get, id), setConfig: (config: MemoryConfig) => ipcRenderer.invoke(IpcChannel.Memory_SetConfig, config), deleteUser: (userId: string) => ipcRenderer.invoke(IpcChannel.Memory_DeleteUser, userId), deleteAllMemoriesForUser: (userId: string) => ipcRenderer.invoke(IpcChannel.Memory_DeleteAllMemoriesForUser, userId), getUsersList: () => ipcRenderer.invoke(IpcChannel.Memory_GetUsersList) }, window: { setMinimumSize: (width: number, height: number) => ipcRenderer.invoke(IpcChannel.Windows_SetMinimumSize, width, height), resetMinimumSize: () => ipcRenderer.invoke(IpcChannel.Windows_ResetMinimumSize), getSize: (): Promise<[number, number]> => ipcRenderer.invoke(IpcChannel.Windows_GetSize) }, fileService: { upload: (provider: Provider, file: FileMetadata): Promise => ipcRenderer.invoke(IpcChannel.FileService_Upload, provider, file), list: (provider: Provider): Promise => ipcRenderer.invoke(IpcChannel.FileService_List, provider), delete: (provider: Provider, fileId: string) => ipcRenderer.invoke(IpcChannel.FileService_Delete, provider, fileId), retrieve: (provider: Provider, fileId: string): Promise => ipcRenderer.invoke(IpcChannel.FileService_Retrieve, provider, fileId) }, selectionMenu: { action: (action: string) => ipcRenderer.invoke('selection-menu:action', action) }, vertexAI: { getAuthHeaders: (params: { projectId: string; serviceAccount?: { privateKey: string; clientEmail: string } }) => ipcRenderer.invoke(IpcChannel.VertexAI_GetAuthHeaders, params), getAccessToken: (params: { projectId: string; serviceAccount?: { privateKey: string; clientEmail: string } }) => ipcRenderer.invoke(IpcChannel.VertexAI_GetAccessToken, params), clearAuthCache: (projectId: string, clientEmail?: string) => ipcRenderer.invoke(IpcChannel.VertexAI_ClearAuthCache, projectId, clientEmail) }, ovms: { addModel: (modelName: string, modelId: string, modelSource: string, task: string) => ipcRenderer.invoke(IpcChannel.Ovms_AddModel, modelName, modelId, modelSource, task), stopAddModel: () => ipcRenderer.invoke(IpcChannel.Ovms_StopAddModel), getModels: () => ipcRenderer.invoke(IpcChannel.Ovms_GetModels), isRunning: () => ipcRenderer.invoke(IpcChannel.Ovms_IsRunning), getStatus: () => ipcRenderer.invoke(IpcChannel.Ovms_GetStatus), runOvms: () => ipcRenderer.invoke(IpcChannel.Ovms_RunOVMS), stopOvms: () => ipcRenderer.invoke(IpcChannel.Ovms_StopOVMS) }, config: { set: (key: string, value: any, isNotify: boolean = false) => ipcRenderer.invoke(IpcChannel.Config_Set, key, value, isNotify), get: (key: string) => ipcRenderer.invoke(IpcChannel.Config_Get, key) }, miniWindow: { show: () => ipcRenderer.invoke(IpcChannel.MiniWindow_Show), hide: () => ipcRenderer.invoke(IpcChannel.MiniWindow_Hide), close: () => ipcRenderer.invoke(IpcChannel.MiniWindow_Close), toggle: () => ipcRenderer.invoke(IpcChannel.MiniWindow_Toggle), setPin: (isPinned: boolean) => ipcRenderer.invoke(IpcChannel.MiniWindow_SetPin, isPinned) }, aes: { encrypt: (text: string, secretKey: string, iv: string) => ipcRenderer.invoke(IpcChannel.Aes_Encrypt, text, secretKey, iv), decrypt: (encryptedData: string, iv: string, secretKey: string) => ipcRenderer.invoke(IpcChannel.Aes_Decrypt, encryptedData, iv, secretKey) }, mcp: { removeServer: (server: MCPServer) => ipcRenderer.invoke(IpcChannel.Mcp_RemoveServer, server), restartServer: (server: MCPServer) => ipcRenderer.invoke(IpcChannel.Mcp_RestartServer, server), stopServer: (server: MCPServer) => ipcRenderer.invoke(IpcChannel.Mcp_StopServer, server), listTools: (server: MCPServer, context?: SpanContext) => tracedInvoke(IpcChannel.Mcp_ListTools, context, server), callTool: ( { server, name, args, callId }: { server: MCPServer; name: string; args: any; callId?: string }, context?: SpanContext ) => tracedInvoke(IpcChannel.Mcp_CallTool, context, { server, name, args, callId }), listPrompts: (server: MCPServer) => ipcRenderer.invoke(IpcChannel.Mcp_ListPrompts, server), getPrompt: ({ server, name, args }: { server: MCPServer; name: string; args?: Record }) => ipcRenderer.invoke(IpcChannel.Mcp_GetPrompt, { server, name, args }), listResources: (server: MCPServer) => ipcRenderer.invoke(IpcChannel.Mcp_ListResources, server), getResource: ({ server, uri }: { server: MCPServer; uri: string }) => ipcRenderer.invoke(IpcChannel.Mcp_GetResource, { server, uri }), getInstallInfo: () => ipcRenderer.invoke(IpcChannel.Mcp_GetInstallInfo), checkMcpConnectivity: (server: any) => ipcRenderer.invoke(IpcChannel.Mcp_CheckConnectivity, server), uploadDxt: async (file: File) => { const buffer = await file.arrayBuffer() return ipcRenderer.invoke(IpcChannel.Mcp_UploadDxt, buffer, file.name) }, abortTool: (callId: string) => ipcRenderer.invoke(IpcChannel.Mcp_AbortTool, callId), getServerVersion: (server: MCPServer): Promise => ipcRenderer.invoke(IpcChannel.Mcp_GetServerVersion, server), getServerLogs: (server: MCPServer): Promise => ipcRenderer.invoke(IpcChannel.Mcp_GetServerLogs, server), onServerLog: (callback: (log: MCPServerLogEntry & { serverId?: string }) => void) => { const listener = (_event: Electron.IpcRendererEvent, log: MCPServerLogEntry & { serverId?: string }) => { callback(log) } ipcRenderer.on(IpcChannel.Mcp_ServerLog, listener) return () => ipcRenderer.off(IpcChannel.Mcp_ServerLog, listener) } }, python: { execute: (script: string, context?: Record, timeout?: number) => ipcRenderer.invoke(IpcChannel.Python_Execute, script, context, timeout) }, shell: { openExternal: (url: string, options?: Electron.OpenExternalOptions) => shell.openExternal(url, options) }, copilot: { getAuthMessage: (headers?: Record) => ipcRenderer.invoke(IpcChannel.Copilot_GetAuthMessage, headers), getCopilotToken: (device_code: string, headers?: Record) => ipcRenderer.invoke(IpcChannel.Copilot_GetCopilotToken, device_code, headers), saveCopilotToken: (access_token: string) => ipcRenderer.invoke(IpcChannel.Copilot_SaveCopilotToken, access_token), getToken: (headers?: Record) => ipcRenderer.invoke(IpcChannel.Copilot_GetToken, headers), logout: () => ipcRenderer.invoke(IpcChannel.Copilot_Logout), getUser: (token: string) => ipcRenderer.invoke(IpcChannel.Copilot_GetUser, token) }, // Binary related APIs isBinaryExist: (name: string) => ipcRenderer.invoke(IpcChannel.App_IsBinaryExist, name), getBinaryPath: (name: string) => ipcRenderer.invoke(IpcChannel.App_GetBinaryPath, name), installUVBinary: () => ipcRenderer.invoke(IpcChannel.App_InstallUvBinary), installBunBinary: () => ipcRenderer.invoke(IpcChannel.App_InstallBunBinary), installOvmsBinary: () => ipcRenderer.invoke(IpcChannel.App_InstallOvmsBinary), protocol: { onReceiveData: (callback: (data: { url: string; params: any }) => void) => { const listener = (_event: Electron.IpcRendererEvent, data: { url: string; params: any }) => { callback(data) } ipcRenderer.on('protocol-data', listener) return () => { ipcRenderer.off('protocol-data', listener) } } }, nutstore: { getSSOUrl: () => ipcRenderer.invoke(IpcChannel.Nutstore_GetSsoUrl), decryptToken: (token: string) => ipcRenderer.invoke(IpcChannel.Nutstore_DecryptToken, token), getDirectoryContents: (token: string, path: string) => ipcRenderer.invoke(IpcChannel.Nutstore_GetDirectoryContents, token, path) }, searchService: { openSearchWindow: (uid: string) => ipcRenderer.invoke(IpcChannel.SearchWindow_Open, uid), closeSearchWindow: (uid: string) => ipcRenderer.invoke(IpcChannel.SearchWindow_Close, uid), openUrlInSearchWindow: (uid: string, url: string) => ipcRenderer.invoke(IpcChannel.SearchWindow_OpenUrl, uid, url) }, webview: { setOpenLinkExternal: (webviewId: number, isExternal: boolean) => ipcRenderer.invoke(IpcChannel.Webview_SetOpenLinkExternal, webviewId, isExternal), setSpellCheckEnabled: (webviewId: number, isEnable: boolean) => ipcRenderer.invoke(IpcChannel.Webview_SetSpellCheckEnabled, webviewId, isEnable), printToPDF: (webviewId: number) => ipcRenderer.invoke(IpcChannel.Webview_PrintToPDF, webviewId), saveAsHTML: (webviewId: number) => ipcRenderer.invoke(IpcChannel.Webview_SaveAsHTML, webviewId), onFindShortcut: (callback: (payload: WebviewKeyEvent) => void) => { const listener = (_event: Electron.IpcRendererEvent, payload: WebviewKeyEvent) => { callback(payload) } ipcRenderer.on(IpcChannel.Webview_SearchHotkey, listener) return () => { ipcRenderer.off(IpcChannel.Webview_SearchHotkey, listener) } } }, storeSync: { subscribe: () => ipcRenderer.invoke(IpcChannel.StoreSync_Subscribe), unsubscribe: () => ipcRenderer.invoke(IpcChannel.StoreSync_Unsubscribe), onUpdate: (action: any) => ipcRenderer.invoke(IpcChannel.StoreSync_OnUpdate, action) }, selection: { hideToolbar: () => ipcRenderer.invoke(IpcChannel.Selection_ToolbarHide), writeToClipboard: (text: string) => ipcRenderer.invoke(IpcChannel.Selection_WriteToClipboard, text), determineToolbarSize: (width: number, height: number) => ipcRenderer.invoke(IpcChannel.Selection_ToolbarDetermineSize, width, height), setEnabled: (enabled: boolean) => ipcRenderer.invoke(IpcChannel.Selection_SetEnabled, enabled), setTriggerMode: (triggerMode: string) => ipcRenderer.invoke(IpcChannel.Selection_SetTriggerMode, triggerMode), setFollowToolbar: (isFollowToolbar: boolean) => ipcRenderer.invoke(IpcChannel.Selection_SetFollowToolbar, isFollowToolbar), setRemeberWinSize: (isRemeberWinSize: boolean) => ipcRenderer.invoke(IpcChannel.Selection_SetRemeberWinSize, isRemeberWinSize), setFilterMode: (filterMode: string) => ipcRenderer.invoke(IpcChannel.Selection_SetFilterMode, filterMode), setFilterList: (filterList: string[]) => ipcRenderer.invoke(IpcChannel.Selection_SetFilterList, filterList), processAction: (actionItem: ActionItem, isFullScreen: boolean = false) => ipcRenderer.invoke(IpcChannel.Selection_ProcessAction, actionItem, isFullScreen), closeActionWindow: () => ipcRenderer.invoke(IpcChannel.Selection_ActionWindowClose), minimizeActionWindow: () => ipcRenderer.invoke(IpcChannel.Selection_ActionWindowMinimize), pinActionWindow: (isPinned: boolean) => ipcRenderer.invoke(IpcChannel.Selection_ActionWindowPin, isPinned), // [Windows only] Electron bug workaround - can be removed once https://github.com/electron/electron/issues/48554 is fixed resizeActionWindow: (deltaX: number, deltaY: number, direction: string) => ipcRenderer.invoke(IpcChannel.Selection_ActionWindowResize, deltaX, deltaY, direction) }, agentTools: { respondToPermission: (payload: { requestId: string behavior: 'allow' | 'deny' updatedInput?: Record message?: string updatedPermissions?: PermissionUpdate[] }) => ipcRenderer.invoke(IpcChannel.AgentToolPermission_Response, payload) }, quoteToMainWindow: (text: string) => ipcRenderer.invoke(IpcChannel.App_QuoteToMain, text), setDisableHardwareAcceleration: (isDisable: boolean) => ipcRenderer.invoke(IpcChannel.App_SetDisableHardwareAcceleration, isDisable), trace: { saveData: (topicId: string) => ipcRenderer.invoke(IpcChannel.TRACE_SAVE_DATA, topicId), getData: (topicId: string, traceId: string, modelName?: string) => ipcRenderer.invoke(IpcChannel.TRACE_GET_DATA, topicId, traceId, modelName), saveEntity: (entity: SpanEntity) => ipcRenderer.invoke(IpcChannel.TRACE_SAVE_ENTITY, entity), getEntity: (spanId: string) => ipcRenderer.invoke(IpcChannel.TRACE_GET_ENTITY, spanId), bindTopic: (topicId: string, traceId: string) => ipcRenderer.invoke(IpcChannel.TRACE_BIND_TOPIC, topicId, traceId), tokenUsage: (spanId: string, usage: TokenUsage) => ipcRenderer.invoke(IpcChannel.TRACE_TOKEN_USAGE, spanId, usage), cleanHistory: (topicId: string, traceId: string, modelName?: string) => ipcRenderer.invoke(IpcChannel.TRACE_CLEAN_HISTORY, topicId, traceId, modelName), cleanTopic: (topicId: string, traceId?: string) => ipcRenderer.invoke(IpcChannel.TRACE_CLEAN_TOPIC, topicId, traceId), openWindow: (topicId: string, traceId: string, autoOpen?: boolean, modelName?: string) => ipcRenderer.invoke(IpcChannel.TRACE_OPEN_WINDOW, topicId, traceId, autoOpen, modelName), setTraceWindowTitle: (title: string) => ipcRenderer.invoke(IpcChannel.TRACE_SET_TITLE, title), addEndMessage: (spanId: string, modelName: string, context: string) => ipcRenderer.invoke(IpcChannel.TRACE_ADD_END_MESSAGE, spanId, modelName, context), cleanLocalData: () => ipcRenderer.invoke(IpcChannel.TRACE_CLEAN_LOCAL_DATA), addStreamMessage: (spanId: string, modelName: string, context: string, message: any) => ipcRenderer.invoke(IpcChannel.TRACE_ADD_STREAM_MESSAGE, spanId, modelName, context, message) }, anthropic_oauth: { startOAuthFlow: () => ipcRenderer.invoke(IpcChannel.Anthropic_StartOAuthFlow), completeOAuthWithCode: (code: string) => ipcRenderer.invoke(IpcChannel.Anthropic_CompleteOAuthWithCode, code), cancelOAuthFlow: () => ipcRenderer.invoke(IpcChannel.Anthropic_CancelOAuthFlow), getAccessToken: () => ipcRenderer.invoke(IpcChannel.Anthropic_GetAccessToken), hasCredentials: () => ipcRenderer.invoke(IpcChannel.Anthropic_HasCredentials), clearCredentials: () => ipcRenderer.invoke(IpcChannel.Anthropic_ClearCredentials) }, codeTools: { run: ( cliTool: string, model: string, directory: string, env: Record, options?: { autoUpdateToLatest?: boolean; terminal?: string } ) => ipcRenderer.invoke(IpcChannel.CodeTools_Run, cliTool, model, directory, env, options), getAvailableTerminals: (): Promise => ipcRenderer.invoke(IpcChannel.CodeTools_GetAvailableTerminals), setCustomTerminalPath: (terminalId: string, path: string): Promise => ipcRenderer.invoke(IpcChannel.CodeTools_SetCustomTerminalPath, terminalId, path), getCustomTerminalPath: (terminalId: string): Promise => ipcRenderer.invoke(IpcChannel.CodeTools_GetCustomTerminalPath, terminalId), removeCustomTerminalPath: (terminalId: string): Promise => ipcRenderer.invoke(IpcChannel.CodeTools_RemoveCustomTerminalPath, terminalId) }, ocr: { ocr: (file: SupportedOcrFile, provider: OcrProvider): Promise => ipcRenderer.invoke(IpcChannel.OCR_ocr, file, provider), listProviders: (): Promise => ipcRenderer.invoke(IpcChannel.OCR_ListProviders) }, cherryai: { generateSignature: (params: { method: string; path: string; query: string; body: Record }) => ipcRenderer.invoke(IpcChannel.Cherryai_GetSignature, params) }, windowControls: { minimize: (): Promise => ipcRenderer.invoke(IpcChannel.Windows_Minimize), maximize: (): Promise => ipcRenderer.invoke(IpcChannel.Windows_Maximize), unmaximize: (): Promise => ipcRenderer.invoke(IpcChannel.Windows_Unmaximize), close: (): Promise => ipcRenderer.invoke(IpcChannel.Windows_Close), isMaximized: (): Promise => ipcRenderer.invoke(IpcChannel.Windows_IsMaximized), onMaximizedChange: (callback: (isMaximized: boolean) => void): (() => void) => { const channel = IpcChannel.Windows_MaximizedChanged const listener = (_: Electron.IpcRendererEvent, isMaximized: boolean) => callback(isMaximized) ipcRenderer.on(channel, listener) return () => { ipcRenderer.removeListener(channel, listener) } } }, apiServer: { getStatus: (): Promise => ipcRenderer.invoke(IpcChannel.ApiServer_GetStatus), start: (): Promise => ipcRenderer.invoke(IpcChannel.ApiServer_Start), restart: (): Promise => ipcRenderer.invoke(IpcChannel.ApiServer_Restart), stop: (): Promise => ipcRenderer.invoke(IpcChannel.ApiServer_Stop), onReady: (callback: () => void): (() => void) => { const listener = () => { callback() } ipcRenderer.on(IpcChannel.ApiServer_Ready, listener) return () => { ipcRenderer.removeListener(IpcChannel.ApiServer_Ready, listener) } } }, claudeCodePlugin: { listAvailable: (): Promise> => ipcRenderer.invoke(IpcChannel.ClaudeCodePlugin_ListAvailable), install: (options: InstallPluginOptions): Promise> => ipcRenderer.invoke(IpcChannel.ClaudeCodePlugin_Install, options), uninstall: (options: UninstallPluginOptions): Promise> => ipcRenderer.invoke(IpcChannel.ClaudeCodePlugin_Uninstall, options), listInstalled: (agentId: string): Promise> => ipcRenderer.invoke(IpcChannel.ClaudeCodePlugin_ListInstalled, agentId), invalidateCache: (): Promise> => ipcRenderer.invoke(IpcChannel.ClaudeCodePlugin_InvalidateCache), readContent: (sourcePath: string): Promise> => ipcRenderer.invoke(IpcChannel.ClaudeCodePlugin_ReadContent, sourcePath), writeContent: (options: WritePluginContentOptions): Promise> => ipcRenderer.invoke(IpcChannel.ClaudeCodePlugin_WriteContent, options) }, webSocket: { start: () => ipcRenderer.invoke(IpcChannel.WebSocket_Start), stop: () => ipcRenderer.invoke(IpcChannel.WebSocket_Stop), status: () => ipcRenderer.invoke(IpcChannel.WebSocket_Status), sendFile: (filePath: string) => ipcRenderer.invoke(IpcChannel.WebSocket_SendFile, filePath), getAllCandidates: () => ipcRenderer.invoke(IpcChannel.WebSocket_GetAllCandidates) } } // Use `contextBridge` APIs to expose Electron APIs to // renderer only if context isolation is enabled, otherwise // just add to the DOM global. if (process.contextIsolated) { try { contextBridge.exposeInMainWorld('electron', electronAPI) contextBridge.exposeInMainWorld('api', api) } catch (error) { console.error('[Preload]Failed to expose APIs:', error as Error) } } else { window.electron = electronAPI window.api = api } export type WindowApiType = typeof api