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<TTag extends RpcTag> = WsRpcProtocolClient[TTag];type RpcInput<TTag extends RpcTag> = Parameters<RpcMethod<TTag>>[0]; type RpcUnaryMethod<TTag extends RpcTag> = RpcMethod<TTag> extends (input: any, options?: any) => Effect.Effect<infer TSuccess, any, any> ? (input: RpcInput<TTag>) => Promise<TSuccess> : never; type RpcUnaryNoArgMethod<TTag extends RpcTag> = RpcMethod<TTag> extends (input: any, options?: any) => Effect.Effect<infer TSuccess, any, any> ? () => Promise<TSuccess> : never; type RpcStreamMethod<TTag extends RpcTag> = RpcMethod<TTag> extends (input: any, options?: any) => Stream.Stream<infer TEvent, any, any> ? (listener: (event: TEvent) => void) => () => void : never; interface GitRunStackedActionOptions { readonly onProgress?: (event: GitActionProgressEvent) => void;} export interface WsRpcClient { readonly dispose: () => Promise<void>; readonly terminal: { readonly open: RpcUnaryMethod<typeof WS_METHODS.terminalOpen>; readonly write: RpcUnaryMethod<typeof WS_METHODS.terminalWrite>; readonly resize: RpcUnaryMethod<typeof WS_METHODS.terminalResize>; readonly clear: RpcUnaryMethod<typeof WS_METHODS.terminalClear>; readonly restart: RpcUnaryMethod<typeof WS_METHODS.terminalRestart>; readonly close: RpcUnaryMethod<typeof WS_METHODS.terminalClose>; readonly onEvent: RpcStreamMethod<typeof WS_METHODS.subscribeTerminalEvents>; }; readonly projects: { readonly searchEntries: RpcUnaryMethod<typeof WS_METHODS.projectsSearchEntries>; readonly writeFile: RpcUnaryMethod<typeof WS_METHODS.projectsWriteFile>; }; readonly shell: { readonly openInEditor: (input: { readonly cwd: Parameters<NativeApi["shell"]["openInEditor"]>[0]; readonly editor: Parameters<NativeApi["shell"]["openInEditor"]>[1]; }) => ReturnType<NativeApi["shell"]["openInEditor"]>; }; readonly git: { readonly pull: RpcUnaryMethod<typeof WS_METHODS.gitPull>; readonly status: RpcUnaryMethod<typeof WS_METHODS.gitStatus>; readonly runStackedAction: ( input: GitRunStackedActionInput, options?: GitRunStackedActionOptions, ) => Promise<GitRunStackedActionResult>; readonly listBranches: RpcUnaryMethod<typeof WS_METHODS.gitListBranches>; readonly createWorktree: RpcUnaryMethod<typeof WS_METHODS.gitCreateWorktree>; readonly removeWorktree: RpcUnaryMethod<typeof WS_METHODS.gitRemoveWorktree>; readonly createBranch: RpcUnaryMethod<typeof WS_METHODS.gitCreateBranch>; readonly checkout: RpcUnaryMethod<typeof WS_METHODS.gitCheckout>; readonly init: RpcUnaryMethod<typeof WS_METHODS.gitInit>; readonly resolvePullRequest: RpcUnaryMethod<typeof WS_METHODS.gitResolvePullRequest>; readonly preparePullRequestThread: RpcUnaryMethod< typeof WS_METHODS.gitPreparePullRequestThread >; }; readonly server: { readonly getConfig: RpcUnaryNoArgMethod<typeof WS_METHODS.serverGetConfig>; readonly refreshProviders: RpcUnaryNoArgMethod<typeof WS_METHODS.serverRefreshProviders>; readonly upsertKeybinding: RpcUnaryMethod<typeof WS_METHODS.serverUpsertKeybinding>; readonly getSettings: RpcUnaryNoArgMethod<typeof WS_METHODS.serverGetSettings>; readonly updateSettings: ( patch: ServerSettingsPatch, ) => ReturnType<RpcUnaryMethod<typeof WS_METHODS.serverUpdateSettings>>; readonly subscribeConfig: RpcStreamMethod<typeof WS_METHODS.subscribeServerConfig>; readonly subscribeLifecycle: RpcStreamMethod<typeof WS_METHODS.subscribeServerLifecycle>; }; readonly orchestration: { readonly getSnapshot: RpcUnaryNoArgMethod<typeof ORCHESTRATION_WS_METHODS.getSnapshot>; readonly dispatchCommand: RpcUnaryMethod<typeof ORCHESTRATION_WS_METHODS.dispatchCommand>; readonly getTurnDiff: RpcUnaryMethod<typeof ORCHESTRATION_WS_METHODS.getTurnDiff>; readonly getFullThreadDiff: RpcUnaryMethod<typeof ORCHESTRATION_WS_METHODS.getFullThreadDiff>; readonly replayEvents: RpcUnaryMethod<typeof ORCHESTRATION_WS_METHODS.replayEvents>; readonly onDomainEvent: RpcStreamMethod<typeof WS_METHODS.subscribeOrchestrationDomainEvents>; };} 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, ), }, };}
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<TTag extends RpcTag> = WsRpcProtocolClient[TTag];
type RpcInput<TTag extends RpcTag> = Parameters<RpcMethod<TTag>>[0];
type RpcUnaryMethod<TTag extends RpcTag> =
RpcMethod<TTag> extends (input: any, options?: any) => Effect.Effect<infer TSuccess, any, any>
? (input: RpcInput<TTag>) => Promise<TSuccess>
: never;
type RpcUnaryNoArgMethod<TTag extends RpcTag> =
RpcMethod<TTag> extends (input: any, options?: any) => Effect.Effect<infer TSuccess, any, any>
? () => Promise<TSuccess>
: never;
type RpcStreamMethod<TTag extends RpcTag> =
RpcMethod<TTag> extends (input: any, options?: any) => Stream.Stream<infer TEvent, any, any>
? (listener: (event: TEvent) => void) => () => void
: never;
interface GitRunStackedActionOptions {
readonly onProgress?: (event: GitActionProgressEvent) => void;
}
export interface WsRpcClient {
readonly dispose: () => Promise<void>;
readonly terminal: {
readonly open: RpcUnaryMethod<typeof WS_METHODS.terminalOpen>;
readonly write: RpcUnaryMethod<typeof WS_METHODS.terminalWrite>;
readonly resize: RpcUnaryMethod<typeof WS_METHODS.terminalResize>;
readonly clear: RpcUnaryMethod<typeof WS_METHODS.terminalClear>;
readonly restart: RpcUnaryMethod<typeof WS_METHODS.terminalRestart>;
readonly close: RpcUnaryMethod<typeof WS_METHODS.terminalClose>;
readonly onEvent: RpcStreamMethod<typeof WS_METHODS.subscribeTerminalEvents>;
};
readonly projects: {
readonly searchEntries: RpcUnaryMethod<typeof WS_METHODS.projectsSearchEntries>;
readonly writeFile: RpcUnaryMethod<typeof WS_METHODS.projectsWriteFile>;
};
readonly shell: {
readonly openInEditor: (input: {
readonly cwd: Parameters<NativeApi["shell"]["openInEditor"]>[0];
readonly editor: Parameters<NativeApi["shell"]["openInEditor"]>[1];
}) => ReturnType<NativeApi["shell"]["openInEditor"]>;
};
readonly git: {
readonly pull: RpcUnaryMethod<typeof WS_METHODS.gitPull>;
readonly status: RpcUnaryMethod<typeof WS_METHODS.gitStatus>;
readonly runStackedAction: (
input: GitRunStackedActionInput,
options?: GitRunStackedActionOptions,
) => Promise<GitRunStackedActionResult>;
readonly listBranches: RpcUnaryMethod<typeof WS_METHODS.gitListBranches>;
readonly createWorktree: RpcUnaryMethod<typeof WS_METHODS.gitCreateWorktree>;
readonly removeWorktree: RpcUnaryMethod<typeof WS_METHODS.gitRemoveWorktree>;
readonly createBranch: RpcUnaryMethod<typeof WS_METHODS.gitCreateBranch>;
readonly checkout: RpcUnaryMethod<typeof WS_METHODS.gitCheckout>;
readonly init: RpcUnaryMethod<typeof WS_METHODS.gitInit>;
readonly resolvePullRequest: RpcUnaryMethod<typeof WS_METHODS.gitResolvePullRequest>;
readonly preparePullRequestThread: RpcUnaryMethod<
typeof WS_METHODS.gitPreparePullRequestThread
>;
};
readonly server: {
readonly getConfig: RpcUnaryNoArgMethod<typeof WS_METHODS.serverGetConfig>;
readonly refreshProviders: RpcUnaryNoArgMethod<typeof WS_METHODS.serverRefreshProviders>;
readonly upsertKeybinding: RpcUnaryMethod<typeof WS_METHODS.serverUpsertKeybinding>;
readonly getSettings: RpcUnaryNoArgMethod<typeof WS_METHODS.serverGetSettings>;
readonly updateSettings: (
patch: ServerSettingsPatch,
) => ReturnType<RpcUnaryMethod<typeof WS_METHODS.serverUpdateSettings>>;
readonly subscribeConfig: RpcStreamMethod<typeof WS_METHODS.subscribeServerConfig>;
readonly subscribeLifecycle: RpcStreamMethod<typeof WS_METHODS.subscribeServerLifecycle>;
};
readonly orchestration: {
readonly getSnapshot: RpcUnaryNoArgMethod<typeof ORCHESTRATION_WS_METHODS.getSnapshot>;
readonly dispatchCommand: RpcUnaryMethod<typeof ORCHESTRATION_WS_METHODS.dispatchCommand>;
readonly getTurnDiff: RpcUnaryMethod<typeof ORCHESTRATION_WS_METHODS.getTurnDiff>;
readonly getFullThreadDiff: RpcUnaryMethod<typeof ORCHESTRATION_WS_METHODS.getFullThreadDiff>;
readonly replayEvents: RpcUnaryMethod<typeof ORCHESTRATION_WS_METHODS.replayEvents>;
readonly onDomainEvent: RpcStreamMethod<typeof WS_METHODS.subscribeOrchestrationDomainEvents>;
};
}
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,
),
},
};
}