

























@@ -1,5 +1,7 @@
11import { isAbortRequestText } from "../auto-reply/reply/abort-primitives.js";
223+const DEFAULT_CHAT_RUN_ABORT_GRACE_MS = 60_000;
4+35export type ChatAbortControllerEntry = {
46controller: AbortController;
57sessionId: string;
@@ -8,6 +10,20 @@ export type ChatAbortControllerEntry = {
810expiresAtMs: number;
911ownerConnId?: string;
1012ownerDeviceId?: string;
13+/**
14+ * Which RPC owns this registration. Absent (undefined) is treated as
15+ * `"chat-send"` so pre-existing callers that constructed entries without
16+ * a kind keep their behavior. Consumers that need "chat.send specifically
17+ * is active" must check `kind !== "agent"`, not just `.has(runId)`.
18+ */
19+kind?: "chat-send" | "agent";
20+};
21+22+export type RegisteredChatAbortController = {
23+controller: AbortController;
24+registered: boolean;
25+entry?: ChatAbortControllerEntry;
26+cleanup: () => void;
1127};
12281329export function isChatStopCommandText(text: string): boolean {
@@ -21,14 +37,75 @@ export function resolveChatRunExpiresAtMs(params: {
2137minMs?: number;
2238maxMs?: number;
2339}): number {
24-const { now, timeoutMs, graceMs = 60_000, minMs = 2 * 60_000, maxMs = 24 * 60 * 60_000 } = params;
40+const {
41+ now,
42+ timeoutMs,
43+ graceMs = DEFAULT_CHAT_RUN_ABORT_GRACE_MS,
44+ minMs = 2 * 60_000,
45+ maxMs = 24 * 60 * 60_000,
46+} = params;
2547const boundedTimeoutMs = Math.max(0, timeoutMs);
2648const target = now + boundedTimeoutMs + graceMs;
2749const min = now + minMs;
2850const max = now + maxMs;
2951return Math.min(max, Math.max(min, target));
3052}
315354+export function resolveAgentRunExpiresAtMs(params: {
55+now: number;
56+timeoutMs: number;
57+graceMs?: number;
58+}): number {
59+const graceMs = Math.max(0, params.graceMs ?? DEFAULT_CHAT_RUN_ABORT_GRACE_MS);
60+return resolveChatRunExpiresAtMs({
61+now: params.now,
62+timeoutMs: params.timeoutMs,
63+ graceMs,
64+minMs: graceMs,
65+maxMs: Math.max(0, params.timeoutMs) + graceMs,
66+});
67+}
68+69+export function registerChatAbortController(params: {
70+chatAbortControllers: Map<string, ChatAbortControllerEntry>;
71+runId: string;
72+sessionId: string;
73+sessionKey?: string | null;
74+timeoutMs: number;
75+ownerConnId?: string;
76+ownerDeviceId?: string;
77+kind?: ChatAbortControllerEntry["kind"];
78+now?: number;
79+expiresAtMs?: number;
80+}): RegisteredChatAbortController {
81+const controller = new AbortController();
82+const cleanup = () => {
83+const entry = params.chatAbortControllers.get(params.runId);
84+if (entry?.controller === controller) {
85+params.chatAbortControllers.delete(params.runId);
86+}
87+};
88+89+if (!params.sessionKey || params.chatAbortControllers.has(params.runId)) {
90+return { controller, registered: false, cleanup };
91+}
92+93+const now = params.now ?? Date.now();
94+const entry: ChatAbortControllerEntry = {
95+ controller,
96+sessionId: params.sessionId,
97+sessionKey: params.sessionKey,
98+startedAtMs: now,
99+expiresAtMs:
100+params.expiresAtMs ?? resolveChatRunExpiresAtMs({ now, timeoutMs: params.timeoutMs }),
101+ownerConnId: params.ownerConnId,
102+ownerDeviceId: params.ownerDeviceId,
103+kind: params.kind,
104+};
105+params.chatAbortControllers.set(params.runId, entry);
106+return { controller, registered: true, entry, cleanup };
107+}
108+32109export type ChatAbortOps = {
33110chatAbortControllers: Map<string, ChatAbortControllerEntry>;
34111chatRunBuffers: Map<string, string>;
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。