diff --git a/src/shared/prompt-async-gate.ts b/src/shared/prompt-async-gate.ts index 30850a54b..df49a5ed1 100644 --- a/src/shared/prompt-async-gate.ts +++ b/src/shared/prompt-async-gate.ts @@ -38,6 +38,30 @@ type PromptClient = { } } +type InternalPromptDispatchClient = { + session?: { + status?: () => Promise + messages?: (input: { path: { id: string }; query: PromptMessagesQuery }) => Promise + promptAsync?: (input: TInput) => Promise + prompt?: (input: TInput) => Promise + } +} + +export type InternalPromptDispatchMode = "async" | "sync" + +export type InternalPromptDispatchArgs = { + mode: InternalPromptDispatchMode + client: InternalPromptDispatchClient + sessionID: string + input: TInput + source: string + settleMs?: number + postDispatchHoldMs?: number + dispatchTimeoutMs?: number + checkStatus?: boolean + checkToolState?: boolean +} + type PromptAsyncReservation = { source: string reservedAt: number @@ -45,18 +69,20 @@ type PromptAsyncReservation = { expiresAt?: number } -declare function setTimeout(callback: () => void, delay?: number): ReturnType -declare function clearTimeout(timeout: ReturnType): void +declare function setTimeout(callback: () => void, delay?: number): unknown +declare function clearTimeout(timeout: unknown): void let promptGateMessagesFetchTimeoutMsForTesting: number | undefined -export type PromptAsyncGateResult = +export type InternalPromptDispatchResult = | { status: "dispatched"; response: unknown } | { status: "active" } | { status: "reserved"; reservedBy: string } | { status: "unavailable" } | { status: "failed"; error: unknown } +export type PromptAsyncGateResult = InternalPromptDispatchResult + type PromptAsyncReservationReleaseOptions = { reservedBy?: string | readonly string[] reservedByPrefix?: string | readonly string[] @@ -121,7 +147,7 @@ async function withDispatchTimeout( return operation } - let timeoutID: ReturnType | undefined + let timeoutID: unknown const timeoutPromise = new Promise((_, reject) => { timeoutID = setTimeout(() => { reject(new Error(`${operationName} timed out after ${dispatchTimeoutMs}ms`)) @@ -260,7 +286,7 @@ async function dispatchAfterSessionIdle(args: { checkStatus: boolean checkToolState: boolean dispatch: (input: TInput) => Promise -}): Promise { +}): Promise { const { sessionName, client, @@ -360,6 +386,68 @@ async function dispatchAfterSessionIdle(args: { } } +function getInternalPromptDispatcher( + mode: InternalPromptDispatchMode, + session: InternalPromptDispatchClient["session"], +): { + sessionName: "promptAsync" | "prompt" + dispatch?: (input: TInput) => Promise +} { + if (mode === "async") { + if (typeof session?.promptAsync !== "function") { + return { sessionName: "promptAsync" } + } + const dispatchPromptAsync = session.promptAsync.bind(session) + return { + sessionName: "promptAsync", + dispatch: (input) => dispatchPromptAsync(input), + } + } + + if (typeof session?.prompt !== "function") { + return { sessionName: "prompt" } + } + const dispatchPrompt = session.prompt.bind(session) + return { + sessionName: "prompt", + dispatch: (input) => dispatchPrompt(input), + } +} + +export async function dispatchInternalPrompt( + args: InternalPromptDispatchArgs, +): Promise { + const { + client, + sessionID, + input, + source, + settleMs = DEFAULT_SESSION_IDLE_SETTLE_MS, + } = args + const postDispatchHoldMs = args.postDispatchHoldMs ?? DEFAULT_PROMPT_ASYNC_POST_DISPATCH_HOLD_MS + const dispatchTimeoutMs = args.dispatchTimeoutMs ?? DEFAULT_PROMPT_DISPATCH_TIMEOUT_MS + const { sessionName, dispatch } = getInternalPromptDispatcher(args.mode, client.session) + + if (!dispatch) { + log(`[prompt-async-gate] ${sessionName} unavailable`, { sessionID, source }) + return { status: "unavailable" } + } + + return dispatchAfterSessionIdle({ + sessionName, + client, + sessionID, + input, + source, + settleMs, + postDispatchHoldMs, + dispatchTimeoutMs, + checkStatus: args.checkStatus !== false, + checkToolState: args.checkToolState !== false, + dispatch, + }) +} + export async function promptAsyncAfterSessionIdle(args: { client: PromptAsyncClient sessionID: string @@ -371,35 +459,9 @@ export async function promptAsyncAfterSessionIdle(arg checkStatus?: boolean checkToolState?: boolean }): Promise { - const { - client, - sessionID, - input, - source, - settleMs = DEFAULT_SESSION_IDLE_SETTLE_MS, - } = args - const postDispatchHoldMs = args.postDispatchHoldMs ?? DEFAULT_PROMPT_ASYNC_POST_DISPATCH_HOLD_MS - const dispatchTimeoutMs = args.dispatchTimeoutMs ?? DEFAULT_PROMPT_DISPATCH_TIMEOUT_MS - const session = client.session - - if (typeof session?.promptAsync !== "function") { - log("[prompt-async-gate] promptAsync unavailable", { sessionID, source }) - return { status: "unavailable" } - } - const dispatchPromptAsync = session.promptAsync.bind(session) - - return dispatchAfterSessionIdle({ - sessionName: "promptAsync", - client, - sessionID, - input, - source, - settleMs, - postDispatchHoldMs, - dispatchTimeoutMs, - checkStatus: args.checkStatus !== false, - checkToolState: args.checkToolState !== false, - dispatch: (dispatchInput) => dispatchPromptAsync(dispatchInput), + return dispatchInternalPrompt({ + ...args, + mode: "async", }) } @@ -414,35 +476,9 @@ export async function promptAfterSessionIdle(args: { checkStatus?: boolean checkToolState?: boolean }): Promise { - const { - client, - sessionID, - input, - source, - settleMs = DEFAULT_SESSION_IDLE_SETTLE_MS, - } = args - const postDispatchHoldMs = args.postDispatchHoldMs ?? DEFAULT_PROMPT_ASYNC_POST_DISPATCH_HOLD_MS - const dispatchTimeoutMs = args.dispatchTimeoutMs ?? DEFAULT_PROMPT_DISPATCH_TIMEOUT_MS - const session = client.session - - if (typeof session?.prompt !== "function") { - log("[prompt-async-gate] prompt unavailable", { sessionID, source }) - return { status: "unavailable" } - } - const dispatchPrompt = session.prompt.bind(session) - - return dispatchAfterSessionIdle({ - sessionName: "prompt", - client, - sessionID, - input, - source, - settleMs, - postDispatchHoldMs, - dispatchTimeoutMs, - checkStatus: args.checkStatus !== false, - checkToolState: args.checkToolState !== false, - dispatch: (dispatchInput) => dispatchPrompt(dispatchInput), + return dispatchInternalPrompt({ + ...args, + mode: "sync", }) }