Skip to content

Commit 0c6539d

Browse files
committed
fix: register OpenResponses runId in chatAbortControllers for chat.abort support
OpenResponses HTTP API tasks (resp_xxx) were not registered in the Gateway's chatAbortControllers Map, causing chat.abort requests to silently fail with aborted: false. This patch: - Creates an AbortController for each OpenResponses request and registers it in chatAbortControllers with the responseId as key - Cleans up the registration on completion, error, client disconnect, and stream finalization - Threads chatAbortControllers from server-runtime-state through server-http to the OpenResponses handler Fixes #30558
1 parent 4da4cc9 commit 0c6539d

File tree

3 files changed

+31
-1
lines changed

3 files changed

+31
-1
lines changed

src/gateway/openresponses-http.ts

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@ import { defaultRuntime } from "../runtime.js";
3232
import { resolveAssistantStreamDeltaText } from "./agent-event-assistant-text.js";
3333
import type { AuthRateLimiter } from "./auth-rate-limit.js";
3434
import type { ResolvedGatewayAuth } from "./auth.js";
35+
import type { ChatAbortControllerEntry } from "./chat-abort.js";
36+
import { resolveChatRunExpiresAtMs } from "./chat-abort.js";
3537
import { sendJson, setSseHeaders, writeDone } from "./http-common.js";
3638
import { handleGatewayPostJsonEndpoint } from "./http-endpoint-helpers.js";
3739
import { resolveAgentIdForRequest, resolveSessionKey } from "./http-utils.js";
@@ -52,6 +54,7 @@ type OpenResponsesHttpOptions = {
5254
trustedProxies?: string[];
5355
allowRealIpFallback?: boolean;
5456
rateLimiter?: AuthRateLimiter;
57+
chatAbortControllers?: Map<string, ChatAbortControllerEntry>;
5558
};
5659

5760
const DEFAULT_BODY_BYTES = 20 * 1024 * 1024;
@@ -449,6 +452,23 @@ export async function handleOpenResponsesHttpRequest(
449452
? { maxTokens: payload.max_output_tokens }
450453
: undefined;
451454

455+
// Register abort controller so chat.abort can find this run
456+
const abortController = new AbortController();
457+
if (opts.chatAbortControllers) {
458+
const now = Date.now();
459+
const timeoutMs = 10 * 60_000; // 10 minute default
460+
opts.chatAbortControllers.set(responseId, {
461+
controller: abortController,
462+
sessionId: responseId,
463+
sessionKey,
464+
startedAtMs: now,
465+
expiresAtMs: resolveChatRunExpiresAtMs({ now, timeoutMs }),
466+
});
467+
}
468+
const cleanupAbortController = () => {
469+
opts.chatAbortControllers?.delete(responseId);
470+
};
471+
452472
if (!stream) {
453473
try {
454474
const result = await runResponsesAgentCommand({
@@ -525,6 +545,8 @@ export async function handleOpenResponsesHttpRequest(
525545
error: { code: "api_error", message: "internal error" },
526546
});
527547
sendJson(res, 500, response);
548+
} finally {
549+
cleanupAbortController();
528550
}
529551
return true;
530552
}
@@ -603,6 +625,7 @@ export async function handleOpenResponsesHttpRequest(
603625
return;
604626
}
605627
finalizeRequested = { status, text };
628+
cleanupAbortController();
606629
maybeFinalize();
607630
};
608631

@@ -679,6 +702,7 @@ export async function handleOpenResponsesHttpRequest(
679702
req.on("close", () => {
680703
closed = true;
681704
unsubscribe();
705+
cleanupAbortController();
682706
});
683707

684708
void (async () => {
@@ -819,6 +843,7 @@ export async function handleOpenResponsesHttpRequest(
819843
usage: finalUsage,
820844
});
821845

846+
cleanupAbortController();
822847
writeSseEvent(res, { type: "response.failed", response: errorResponse });
823848
emitAgentEvent({
824849
runId: responseId,

src/gateway/server-http.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ import {
3232
type ResolvedGatewayAuth,
3333
} from "./auth.js";
3434
import { CANVAS_CAPABILITY_TTL_MS, normalizeCanvasScopedUrl } from "./canvas-capability.js";
35+
import type { ChatAbortControllerEntry } from "./chat-abort.js";
3536
import {
3637
handleControlUiAvatarRequest,
3738
handleControlUiHttpRequest,
@@ -463,6 +464,7 @@ export function createGatewayHttpServer(opts: {
463464
resolvedAuth: ResolvedGatewayAuth;
464465
/** Optional rate limiter for auth brute-force protection. */
465466
rateLimiter?: AuthRateLimiter;
467+
chatAbortControllers?: Map<string, ChatAbortControllerEntry>;
466468
tlsOptions?: TlsOptions;
467469
}): HttpServer {
468470
const {
@@ -479,6 +481,7 @@ export function createGatewayHttpServer(opts: {
479481
handlePluginRequest,
480482
resolvedAuth,
481483
rateLimiter,
484+
chatAbortControllers,
482485
} = opts;
483486
const httpServer: HttpServer = opts.tlsOptions
484487
? createHttpsServer(opts.tlsOptions, (req, res) => {
@@ -555,6 +558,7 @@ export function createGatewayHttpServer(opts: {
555558
trustedProxies,
556559
allowRealIpFallback,
557560
rateLimiter,
561+
chatAbortControllers,
558562
})
559563
) {
560564
return;

src/gateway/server-runtime-state.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,7 @@ export async function createGatewayRuntimeState(params: {
125125
}
126126
const httpServers: HttpServer[] = [];
127127
const httpBindHosts: string[] = [];
128+
const chatAbortControllers = new Map<string, ChatAbortControllerEntry>();
128129
for (const host of bindHosts) {
129130
const httpServer = createGatewayHttpServer({
130131
canvasHost,
@@ -140,6 +141,7 @@ export async function createGatewayRuntimeState(params: {
140141
handlePluginRequest,
141142
resolvedAuth: params.resolvedAuth,
142143
rateLimiter: params.rateLimiter,
144+
chatAbortControllers,
143145
tlsOptions: params.gatewayTls?.enabled ? params.gatewayTls.tlsOptions : undefined,
144146
});
145147
try {
@@ -187,7 +189,6 @@ export async function createGatewayRuntimeState(params: {
187189
const chatDeltaSentAt = chatRunState.deltaSentAt;
188190
const addChatRun = chatRunRegistry.add;
189191
const removeChatRun = chatRunRegistry.remove;
190-
const chatAbortControllers = new Map<string, ChatAbortControllerEntry>();
191192
const toolEventRecipients = createToolEventRecipientRegistry();
192193

193194
return {

0 commit comments

Comments
 (0)