Files
adiuva/src/main/ipc.ts
Roberto Musso 00a43e0fbc feat: Integrate GitHub Copilot SDK and LangChain for provider-independent orchestration
- Added @github/copilot-sdk and related dependencies for GitHub Copilot integration.
- Implemented ChatCopilot adapter for LangChain compatibility.
- Created LLM factory to return provider-specific models (OpenAI, Anthropic, Copilot).
- Developed Orchestrator agent using LangGraph for intent routing and context assembly.
- Enhanced IPC communication for streaming AI responses to the renderer.
- Updated progress documentation with implementation details and learnings.
2026-02-23 17:58:00 +01:00

96 lines
2.4 KiB
TypeScript

/**
* Custom electron IPC ↔ tRPC v11 bridge.
*
* Replaces the incompatible electron-trpc package (v0.7.x bundles tRPC v10's
* callProcedure which checks `procedure._def[type]`, while tRPC v11 uses
* `procedure._def.type`).
*/
import { ipcMain, type BrowserWindow } from 'electron';
import {
callTRPCProcedure,
getErrorShape,
getTRPCErrorFromUnknown,
transformTRPCResponse,
type AnyRouter,
} from '@trpc/server';
export const IPC_CHANNEL = 'trpc';
/** Context passed to every tRPC procedure via the IPC bridge. */
export type TRPCContext = {
/** The IPC sender — available for streaming chunks back to the renderer. */
sender?: Electron.WebContents;
};
interface IPCRequest {
method: 'request';
operation: {
id: number;
type: 'query' | 'mutation' | 'subscription';
path: string;
input?: unknown;
};
}
export function createIPCHandler<TRouter extends AnyRouter>({
router,
windows = [],
}: {
router: TRouter;
windows?: BrowserWindow[];
}) {
const config = router._def._config;
ipcMain.on(IPC_CHANNEL, async (event, message: IPCRequest) => {
if (message.method !== 'request') return;
const { id, type, path, input } = message.operation;
// Deserialize input through transformer (identity by default)
const rawInput = input !== undefined
? config.transformer.input.deserialize(input)
: undefined;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const respond = (response: any) => {
if (event.sender.isDestroyed()) return;
const serialised = transformTRPCResponse(config, response);
event.reply(IPC_CHANNEL, serialised);
};
try {
const result = await callTRPCProcedure({
router,
path,
getRawInput: async () => rawInput,
ctx: { sender: event.sender } satisfies TRPCContext,
type,
signal: undefined as unknown as AbortSignal,
batchIndex: 0,
});
respond({ id, result: { type: 'data', data: result } });
} catch (cause) {
const error = getTRPCErrorFromUnknown(cause);
respond({
id,
error: getErrorShape({
config,
error,
type,
path,
input: rawInput,
ctx: {},
}),
});
}
});
// Attach additional windows later if needed
return {
attachWindow(_win: BrowserWindow) {
// No per-window setup needed — ipcMain.on is global
},
};
}