import { spawn, spawnSync, type ChildProcessWithoutNullStreams } from "node:child_process";import readline from "node:readline";import { readCodexAccountSnapshot, type CodexAccountSnapshot } from "./codexAccount"; interface JsonRpcProbeResponse { readonly id?: unknown; readonly result?: unknown; readonly error?: { readonly message?: unknown; };} function readErrorMessage(response: JsonRpcProbeResponse): string | undefined { return typeof response.error?.message === "string" ? response.error.message : undefined;} export function buildCodexInitializeParams() { return { clientInfo: { name: "t3code_desktop", title: "T3 Code Desktop", version: "0.1.0", }, capabilities: { experimentalApi: true, }, } as const;} export function killCodexChildProcess(child: ChildProcessWithoutNullStreams): void { if (process.platform === "win32" && child.pid !== undefined) { try { spawnSync("taskkill", ["/pid", String(child.pid), "/T", "/F"], { stdio: "ignore" }); return; } catch { // Fall through to direct kill when taskkill is unavailable. } } child.kill();} export async function probeCodexAccount(input: { readonly binaryPath: string; readonly homePath?: string; readonly signal?: AbortSignal;}): Promise<CodexAccountSnapshot> { return await new Promise((resolve, reject) => { const child = spawn(input.binaryPath, ["app-server"], { env: { ...process.env, ...(input.homePath ? { CODEX_HOME: input.homePath } : {}), }, stdio: ["pipe", "pipe", "pipe"], shell: process.platform === "win32", }); const output = readline.createInterface({ input: child.stdout }); let completed = false; const cleanup = () => { output.removeAllListeners(); output.close(); child.removeAllListeners(); if (!child.killed) { killCodexChildProcess(child); } }; const finish = (callback: () => void) => { if (completed) return; completed = true; cleanup(); callback(); }; const fail = (error: unknown) => finish(() => reject( error instanceof Error ? error : new Error(`Codex account probe failed: ${String(error)}.`), ), ); if (input.signal?.aborted) { fail(new Error("Codex account probe aborted.")); return; } input.signal?.addEventListener("abort", () => fail(new Error("Codex account probe aborted."))); const writeMessage = (message: unknown) => { if (!child.stdin.writable) { fail(new Error("Cannot write to codex app-server stdin.")); return; } child.stdin.write(`${JSON.stringify(message)}\n`); }; output.on("line", (line) => { let parsed: unknown; try { parsed = JSON.parse(line); } catch { fail(new Error("Received invalid JSON from codex app-server during account probe.")); return; } if (!parsed || typeof parsed !== "object") { return; } const response = parsed as JsonRpcProbeResponse; if (response.id === 1) { const errorMessage = readErrorMessage(response); if (errorMessage) { fail(new Error(`initialize failed: ${errorMessage}`)); return; } writeMessage({ method: "initialized" }); writeMessage({ id: 2, method: "account/read", params: {} }); return; } if (response.id === 2) { const errorMessage = readErrorMessage(response); if (errorMessage) { fail(new Error(`account/read failed: ${errorMessage}`)); return; } finish(() => resolve(readCodexAccountSnapshot(response.result))); } }); child.once("error", fail); child.once("exit", (code, signal) => { if (completed) return; fail( new Error( `codex app-server exited before probe completed (code=${code ?? "null"}, signal=${signal ?? "null"}).`, ), ); }); writeMessage({ id: 1, method: "initialize", params: buildCodexInitializeParams(), }); });}
import { spawn, spawnSync, type ChildProcessWithoutNullStreams } from "node:child_process";
import readline from "node:readline";
import { readCodexAccountSnapshot, type CodexAccountSnapshot } from "./codexAccount";
interface JsonRpcProbeResponse {
readonly id?: unknown;
readonly result?: unknown;
readonly error?: {
readonly message?: unknown;
};
}
function readErrorMessage(response: JsonRpcProbeResponse): string | undefined {
return typeof response.error?.message === "string" ? response.error.message : undefined;
}
export function buildCodexInitializeParams() {
return {
clientInfo: {
name: "t3code_desktop",
title: "T3 Code Desktop",
version: "0.1.0",
},
capabilities: {
experimentalApi: true,
},
} as const;
}
export function killCodexChildProcess(child: ChildProcessWithoutNullStreams): void {
if (process.platform === "win32" && child.pid !== undefined) {
try {
spawnSync("taskkill", ["/pid", String(child.pid), "/T", "/F"], { stdio: "ignore" });
return;
} catch {
// Fall through to direct kill when taskkill is unavailable.
}
}
child.kill();
}
export async function probeCodexAccount(input: {
readonly binaryPath: string;
readonly homePath?: string;
readonly signal?: AbortSignal;
}): Promise<CodexAccountSnapshot> {
return await new Promise((resolve, reject) => {
const child = spawn(input.binaryPath, ["app-server"], {
env: {
...process.env,
...(input.homePath ? { CODEX_HOME: input.homePath } : {}),
},
stdio: ["pipe", "pipe", "pipe"],
shell: process.platform === "win32",
});
const output = readline.createInterface({ input: child.stdout });
let completed = false;
const cleanup = () => {
output.removeAllListeners();
output.close();
child.removeAllListeners();
if (!child.killed) {
killCodexChildProcess(child);
}
};
const finish = (callback: () => void) => {
if (completed) return;
completed = true;
cleanup();
callback();
};
const fail = (error: unknown) =>
finish(() =>
reject(
error instanceof Error
? error
: new Error(`Codex account probe failed: ${String(error)}.`),
),
);
if (input.signal?.aborted) {
fail(new Error("Codex account probe aborted."));
return;
}
input.signal?.addEventListener("abort", () => fail(new Error("Codex account probe aborted.")));
const writeMessage = (message: unknown) => {
if (!child.stdin.writable) {
fail(new Error("Cannot write to codex app-server stdin."));
return;
}
child.stdin.write(`${JSON.stringify(message)}\n`);
};
output.on("line", (line) => {
let parsed: unknown;
try {
parsed = JSON.parse(line);
} catch {
fail(new Error("Received invalid JSON from codex app-server during account probe."));
return;
}
if (!parsed || typeof parsed !== "object") {
return;
}
const response = parsed as JsonRpcProbeResponse;
if (response.id === 1) {
const errorMessage = readErrorMessage(response);
if (errorMessage) {
fail(new Error(`initialize failed: ${errorMessage}`));
return;
}
writeMessage({ method: "initialized" });
writeMessage({ id: 2, method: "account/read", params: {} });
return;
}
if (response.id === 2) {
const errorMessage = readErrorMessage(response);
if (errorMessage) {
fail(new Error(`account/read failed: ${errorMessage}`));
return;
}
finish(() => resolve(readCodexAccountSnapshot(response.result)));
}
});
child.once("error", fail);
child.once("exit", (code, signal) => {
if (completed) return;
fail(
new Error(
`codex app-server exited before probe completed (code=${code ?? "null"}, signal=${signal ?? "null"}).`,
),
);
});
writeMessage({
id: 1,
method: "initialize",
params: buildCodexInitializeParams(),
});
});
}