fix: copilot SDK cleanup and TypeScript errors

This commit is contained in:
Hector Flores 2026-01-29 11:20:59 -06:00
parent d2b732487b
commit 59d383f7b6
2 changed files with 69 additions and 25 deletions

View File

@ -127,15 +127,6 @@ export async function runCopilotCliAgent(params: {
systemPrompt, systemPrompt,
timeoutMs: params.timeoutMs, timeoutMs: params.timeoutMs,
sessionId: params.cliSessionId, sessionId: params.cliSessionId,
onEvent: (event) => {
// Forward streaming events if streamParams is provided
if (params.streamParams?.onDelta && event.type === "assistant.message_delta") {
const data = event.data as { content?: string };
if (typeof data.content === "string") {
params.streamParams.onDelta(data.content);
}
}
},
}); });
const text = result.text?.trim(); const text = result.text?.trim();

View File

@ -84,7 +84,7 @@ export async function createCopilotClient(
cliPath: options?.cliPath ?? "copilot", cliPath: options?.cliPath ?? "copilot",
cwd: options?.cwd, cwd: options?.cwd,
logLevel: options?.logLevel ?? "warning", logLevel: options?.logLevel ?? "warning",
autoRestart: options?.autoRestart ?? true, autoRestart: false, // Disable auto-restart to avoid keeping event loop alive
useStdio: true, // Use stdio transport for better process control useStdio: true, // Use stdio transport for better process control
autoStart: false, // We'll start manually for better error handling autoStart: false, // We'll start manually for better error handling
env: options?.env, env: options?.env,
@ -223,22 +223,68 @@ export async function runCopilotAgent(params: RunCopilotAgentParams): Promise<Co
durationMs: Date.now() - started, durationMs: Date.now() - started,
}; };
} finally { } finally {
// Clean up in reverse order: unsubscribe, destroy session (if new), stop client // Clean up in order: unsubscribe, abort session, destroy session, stop client
// NOTE: Due to a limitation in vscode-jsonrpc (used by @github/copilot-sdk),
// the process may not exit cleanly after cleanup. This is acceptable for gateway
// usage but means CLI one-off commands will hang. The SDK team should fix this.
if (unsubscribe) { if (unsubscribe) {
unsubscribe(); unsubscribe();
} }
// Only destroy session if we created a new one (not resuming) // Abort any in-flight request, then destroy the session
if (session && !params.sessionId) { if (session) {
await session.destroy().catch(() => { try {
// Ignore cleanup errors await session.abort();
}); } catch {
// Ignore abort errors (may not have active request)
}
// Only destroy session if we created a new one (not resuming)
if (!params.sessionId) {
try {
await session.destroy();
} catch {
// Ignore destroy errors
}
}
} }
// Always stop the client // Access internal SDK handles BEFORE cleanup so we can unref them after
await client.stop().catch(() => { const clientAny = client as unknown as {
// Ignore cleanup errors cliProcess?: {
}); unref?: () => void;
stdin?: { unref?: () => void };
stdout?: { unref?: () => void };
stderr?: { unref?: () => void };
removeAllListeners?: () => void;
};
socket?: { unref?: () => void };
};
const cliProcess = clientAny.cliProcess;
const socket = clientAny.socket;
// Stop the client gracefully first, then force if needed
try {
await client.stop();
} catch {
try {
await client.forceStop();
} catch {
// Ignore forceStop errors
}
}
// Unref all handles to allow Node to exit (works around SDK cleanup bug)
if (cliProcess) {
cliProcess.unref?.();
cliProcess.stdin?.unref?.();
cliProcess.stdout?.unref?.();
cliProcess.stderr?.unref?.();
cliProcess.removeAllListeners?.();
}
if (socket) {
socket.unref?.();
}
} }
} }
@ -248,7 +294,7 @@ export async function runCopilotAgent(params: RunCopilotAgentParams): Promise<Co
export async function listCopilotSessions(options?: { export async function listCopilotSessions(options?: {
cliPath?: string; cliPath?: string;
cwd?: string; cwd?: string;
}): Promise<Array<{ sessionId: string; model?: string; createdAt?: string }>> { }): Promise<Array<{ sessionId: string; createdAt?: string }>> {
const client = await createCopilotClient({ const client = await createCopilotClient({
cliPath: options?.cliPath, cliPath: options?.cliPath,
cwd: options?.cwd, cwd: options?.cwd,
@ -259,11 +305,14 @@ export async function listCopilotSessions(options?: {
const sessions = await client.listSessions(); const sessions = await client.listSessions();
return sessions.map((s) => ({ return sessions.map((s) => ({
sessionId: s.sessionId, sessionId: s.sessionId,
model: s.model, createdAt: s.startTime?.toISOString(),
createdAt: s.createdAt,
})); }));
} finally { } finally {
await client.stop().catch(() => {}); try {
await client.stop();
} catch {
// Ignore cleanup errors
}
} }
} }
@ -287,6 +336,10 @@ export async function deleteCopilotSession(
await client.deleteSession(sessionId); await client.deleteSession(sessionId);
log.info("deleted copilot session", { sessionId }); log.info("deleted copilot session", { sessionId });
} finally { } finally {
await client.stop().catch(() => {}); try {
await client.stop();
} catch {
// Ignore cleanup errors
}
} }
} }