Files
reverse-engineering-anything/src/process/ProviderDeadline.ts
T
morluto b8a9da0c0f refactor(mcp): make analysis states unrepresentable (#468)
* refactor(config): parse capability settings into exact states

* refactor(runtime): encode replay and process outcomes exactly

* refactor: make analysis states unrepresentable

* refactor(mcp): register canonical schemas with SDK

* fix: preserve capability and catalog invariants

* fix(build): remove stale compiled modules

* fix(replay): update real verifier policy dependency

* fix(package): verify the complete MCP catalog

* fix(test): supply complete replay reload policy

* refactor(contracts): centralize output schema lookup

* refactor(mcp): delegate protocol boundaries to SDK

* fix(browser): approve real source capture

* fix(mcp): preserve catalog guidance
2026-08-10 02:28:11 +08:00

116 lines
3.7 KiB
TypeScript

/** Result of an abort-aware interval wait. */
export type AbortableWaitResult = "elapsed" | "aborted";
/** Wait for an interval while removing the supplied abort listener on settle. */
export const waitForAbortableDelay = (
milliseconds: number,
signal?: AbortSignal,
): Promise<AbortableWaitResult> => {
if (signal?.aborted === true) return Promise.resolve("aborted");
return new Promise((resolve) => {
const settle = (result: AbortableWaitResult): void => {
clearTimeout(timer);
signal?.removeEventListener("abort", onAbort);
resolve(result);
};
const onAbort = (): void => settle("aborted");
const timer = setTimeout(
() => settle("elapsed"),
Math.max(0, milliseconds),
);
signal?.addEventListener("abort", onAbort, { once: true });
});
};
/**
* One absolute deadline shared by every phase of a provider startup handshake.
*
* The deadline owns its timer and the listener attached to an optional caller
* signal. Adapters must dispose it after startup succeeds or fails.
*/
export class ProviderStartupDeadline {
readonly #controller = new AbortController();
readonly #deadlineAt: number;
readonly #externalSignal: AbortSignal | undefined;
readonly #onExternalAbort: () => void;
#timer: NodeJS.Timeout | undefined;
#interruption: "cancelled" | "timeout" | undefined;
constructor(
readonly timeoutMs: number,
signal?: AbortSignal,
) {
if (!Number.isFinite(timeoutMs) || timeoutMs <= 0)
throw new RangeError("Provider startup timeout must be positive");
this.#deadlineAt = Date.now() + timeoutMs;
this.#externalSignal = signal;
this.#onExternalAbort = () => {
if (this.#controller.signal.aborted) return;
if (Date.now() >= this.#deadlineAt) {
this.#interruption = "timeout";
this.#abort(
new DOMException("Provider startup deadline elapsed", "TimeoutError"),
);
return;
}
this.#interruption = "cancelled";
this.#abort(signal?.reason);
};
if (signal?.aborted === true) {
this.#onExternalAbort();
return;
}
signal?.addEventListener("abort", this.#onExternalAbort, { once: true });
this.#timer = setTimeout(() => {
if (this.#controller.signal.aborted) return;
this.#interruption = "timeout";
this.#abort(
new DOMException("Provider startup deadline elapsed", "TimeoutError"),
);
}, timeoutMs);
}
/** Composite signal aborted by either the caller or the absolute deadline. */
get signal(): AbortSignal {
return this.#controller.signal;
}
/** First control path that interrupted startup, if any. */
get interruption(): "cancelled" | "timeout" | undefined {
return (
this.#interruption ??
(Date.now() >= this.#deadlineAt ? "timeout" : undefined)
);
}
/** Milliseconds still available to the next startup phase. */
remainingMs(): number {
return Math.max(0, this.#deadlineAt - Date.now());
}
/** Wait for at most one polling interval under the shared deadline signal. */
wait(milliseconds: number): Promise<AbortableWaitResult> {
return waitForAbortableDelay(
Math.min(milliseconds, this.remainingMs()),
this.signal,
);
}
/** Release the deadline timer and external abort listener. */
dispose(): void {
this.#clearTimer();
this.#externalSignal?.removeEventListener("abort", this.#onExternalAbort);
}
#abort(reason: unknown): void {
this.#clearTimer();
this.#externalSignal?.removeEventListener("abort", this.#onExternalAbort);
this.#controller.abort(reason);
}
#clearTimer(): void {
if (this.#timer !== undefined) clearTimeout(this.#timer);
this.#timer = undefined;
}
}