Merge PR #4271: fix(task): abort underlying MCP call when proxy tool timeout fires (@metaphorics)

This commit is contained in:
can1357
2026-07-05 13:39:09 +02:00
3 changed files with 180 additions and 3 deletions
+18 -3
View File
@@ -215,7 +215,12 @@ function renderIrcPeerRoster(selfId: string): string {
return lines.join("\n");
}
function withAbortTimeout<T>(promise: Promise<T>, timeoutMs: number, signal?: AbortSignal): Promise<T> {
function withAbortTimeout<T>(
promise: Promise<T>,
timeoutMs: number,
signal?: AbortSignal,
timeoutController?: AbortController,
): Promise<T> {
if (signal?.aborted) {
return Promise.reject(new ToolAbortError());
}
@@ -225,6 +230,7 @@ function withAbortTimeout<T>(promise: Promise<T>, timeoutMs: number, signal?: Ab
const timeoutId = setTimeout(() => {
if (settled) return;
settled = true;
timeoutController?.abort(new DOMException(`MCP tool call timed out after ${timeoutMs}ms`, "TimeoutError"));
reject(new Error(`MCP tool call timed out after ${timeoutMs}ms`));
}, timeoutMs);
@@ -232,6 +238,7 @@ function withAbortTimeout<T>(promise: Promise<T>, timeoutMs: number, signal?: Ab
if (settled) return;
settled = true;
clearTimeout(timeoutId);
timeoutController?.abort();
reject(new ToolAbortError());
};
@@ -708,13 +715,21 @@ export function createMCPProxyTools(mcpManager: MCPManager): CustomTool[] {
const serverName = mcpTool.mcpServerName ?? "";
const mcpToolName = mcpTool.mcpToolName ?? "";
try {
const timeoutController = new AbortController();
const timeoutSignal = timeoutController.signal;
const combinedSignal = signal ? AbortSignal.any([signal, timeoutSignal]) : timeoutSignal;
const result = await withAbortTimeout(
(async () => {
const connection = await mcpManager.waitForConnection(serverName);
return callTool(connection, mcpToolName, params as Record<string, unknown>, { signal });
const connection = await untilAborted(combinedSignal, () =>
mcpManager.waitForConnection(serverName),
);
return callTool(connection, mcpToolName, params as Record<string, unknown>, {
signal: combinedSignal,
});
})(),
MCP_CALL_TIMEOUT_MS,
signal,
timeoutController,
);
return {
content: (result.content ?? []).map(item =>