import { type GitActionProgressEvent, type GitRunStackedActionInput, type GitRunStackedActionResult, type NativeApi, ORCHESTRATION_WS_METHODS, type ServerSettingsPatch, WS_METHODS, } from "@t3tools/contracts"; import { Effect, Stream } from "effect"; import { type WsRpcProtocolClient } from "./rpc/protocol"; import { WsTransport } from "./wsTransport"; type RpcTag = keyof WsRpcProtocolClient & string; type RpcMethod = WsRpcProtocolClient[TTag]; type RpcInput = Parameters>[0]; type RpcUnaryMethod = RpcMethod extends (input: any, options?: any) => Effect.Effect ? (input: RpcInput) => Promise : never; type RpcUnaryNoArgMethod = RpcMethod extends (input: any, options?: any) => Effect.Effect ? () => Promise : never; type RpcStreamMethod = RpcMethod extends (input: any, options?: any) => Stream.Stream ? (listener: (event: TEvent) => void) => () => void : never; interface GitRunStackedActionOptions { readonly onProgress?: (event: GitActionProgressEvent) => void; } export interface WsRpcClient { readonly dispose: () => Promise; readonly terminal: { readonly open: RpcUnaryMethod; readonly write: RpcUnaryMethod; readonly resize: RpcUnaryMethod; readonly clear: RpcUnaryMethod; readonly restart: RpcUnaryMethod; readonly close: RpcUnaryMethod; readonly onEvent: RpcStreamMethod; }; readonly projects: { readonly searchEntries: RpcUnaryMethod; readonly writeFile: RpcUnaryMethod; }; readonly shell: { readonly openInEditor: (input: { readonly cwd: Parameters[0]; readonly editor: Parameters[1]; }) => ReturnType; }; readonly git: { readonly pull: RpcUnaryMethod; readonly status: RpcUnaryMethod; readonly runStackedAction: ( input: GitRunStackedActionInput, options?: GitRunStackedActionOptions, ) => Promise; readonly listBranches: RpcUnaryMethod; readonly createWorktree: RpcUnaryMethod; readonly removeWorktree: RpcUnaryMethod; readonly createBranch: RpcUnaryMethod; readonly checkout: RpcUnaryMethod; readonly init: RpcUnaryMethod; readonly resolvePullRequest: RpcUnaryMethod; readonly preparePullRequestThread: RpcUnaryMethod< typeof WS_METHODS.gitPreparePullRequestThread >; }; readonly server: { readonly getConfig: RpcUnaryNoArgMethod; readonly refreshProviders: RpcUnaryNoArgMethod; readonly upsertKeybinding: RpcUnaryMethod; readonly getSettings: RpcUnaryNoArgMethod; readonly updateSettings: ( patch: ServerSettingsPatch, ) => ReturnType>; readonly subscribeConfig: RpcStreamMethod; readonly subscribeLifecycle: RpcStreamMethod; }; readonly orchestration: { readonly getSnapshot: RpcUnaryNoArgMethod; readonly dispatchCommand: RpcUnaryMethod; readonly getTurnDiff: RpcUnaryMethod; readonly getFullThreadDiff: RpcUnaryMethod; readonly replayEvents: RpcUnaryMethod; readonly onDomainEvent: RpcStreamMethod; }; } let sharedWsRpcClient: WsRpcClient | null = null; export function getWsRpcClient(): WsRpcClient { if (sharedWsRpcClient) { return sharedWsRpcClient; } sharedWsRpcClient = createWsRpcClient(); return sharedWsRpcClient; } export async function __resetWsRpcClientForTests() { await sharedWsRpcClient?.dispose(); sharedWsRpcClient = null; } export function createWsRpcClient(transport = new WsTransport()): WsRpcClient { return { dispose: () => transport.dispose(), terminal: { open: (input) => transport.request((client) => client[WS_METHODS.terminalOpen](input)), write: (input) => transport.request((client) => client[WS_METHODS.terminalWrite](input)), resize: (input) => transport.request((client) => client[WS_METHODS.terminalResize](input)), clear: (input) => transport.request((client) => client[WS_METHODS.terminalClear](input)), restart: (input) => transport.request((client) => client[WS_METHODS.terminalRestart](input)), close: (input) => transport.request((client) => client[WS_METHODS.terminalClose](input)), onEvent: (listener) => transport.subscribe((client) => client[WS_METHODS.subscribeTerminalEvents]({}), listener), }, projects: { searchEntries: (input) => transport.request((client) => client[WS_METHODS.projectsSearchEntries](input)), writeFile: (input) => transport.request((client) => client[WS_METHODS.projectsWriteFile](input)), }, shell: { openInEditor: (input) => transport.request((client) => client[WS_METHODS.shellOpenInEditor](input)), }, git: { pull: (input) => transport.request((client) => client[WS_METHODS.gitPull](input)), status: (input) => transport.request((client) => client[WS_METHODS.gitStatus](input)), runStackedAction: async (input, options) => { let result: GitRunStackedActionResult | null = null; await transport.requestStream( (client) => client[WS_METHODS.gitRunStackedAction](input), (event) => { options?.onProgress?.(event); if (event.kind === "action_finished") { result = event.result; } }, ); if (result) { return result; } throw new Error("Git action stream completed without a final result."); }, listBranches: (input) => transport.request((client) => client[WS_METHODS.gitListBranches](input)), createWorktree: (input) => transport.request((client) => client[WS_METHODS.gitCreateWorktree](input)), removeWorktree: (input) => transport.request((client) => client[WS_METHODS.gitRemoveWorktree](input)), createBranch: (input) => transport.request((client) => client[WS_METHODS.gitCreateBranch](input)), checkout: (input) => transport.request((client) => client[WS_METHODS.gitCheckout](input)), init: (input) => transport.request((client) => client[WS_METHODS.gitInit](input)), resolvePullRequest: (input) => transport.request((client) => client[WS_METHODS.gitResolvePullRequest](input)), preparePullRequestThread: (input) => transport.request((client) => client[WS_METHODS.gitPreparePullRequestThread](input)), }, server: { getConfig: () => transport.request((client) => client[WS_METHODS.serverGetConfig]({})), refreshProviders: () => transport.request((client) => client[WS_METHODS.serverRefreshProviders]({})), upsertKeybinding: (input) => transport.request((client) => client[WS_METHODS.serverUpsertKeybinding](input)), getSettings: () => transport.request((client) => client[WS_METHODS.serverGetSettings]({})), updateSettings: (patch) => transport.request((client) => client[WS_METHODS.serverUpdateSettings]({ patch })), subscribeConfig: (listener) => transport.subscribe((client) => client[WS_METHODS.subscribeServerConfig]({}), listener), subscribeLifecycle: (listener) => transport.subscribe((client) => client[WS_METHODS.subscribeServerLifecycle]({}), listener), }, orchestration: { getSnapshot: () => transport.request((client) => client[ORCHESTRATION_WS_METHODS.getSnapshot]({})), dispatchCommand: (input) => transport.request((client) => client[ORCHESTRATION_WS_METHODS.dispatchCommand](input)), getTurnDiff: (input) => transport.request((client) => client[ORCHESTRATION_WS_METHODS.getTurnDiff](input)), getFullThreadDiff: (input) => transport.request((client) => client[ORCHESTRATION_WS_METHODS.getFullThreadDiff](input)), replayEvents: (input) => transport .request((client) => client[ORCHESTRATION_WS_METHODS.replayEvents](input)) .then((events) => [...events]), onDomainEvent: (listener) => transport.subscribe( (client) => client[WS_METHODS.subscribeOrchestrationDomainEvents]({}), listener, ), }, }; }