* Copyright (c) Microsoft Corporation. All rights reserved.
*--------------------------------------------------------------------------------------------*/
import { CancellationTokenSource, ResponseError } from "vscode-jsonrpc/node";
import type { CancellationToken, Disposable, MessageConnection } from "./jsonrpcTypes.js";
import type {
InstallationConfirmationRequest,
InstallationDecision,
InstallationsConfirmResult,
} from "./generated/rpc.js";
export interface InstallationConfirmationReceiver {
confirm(
request: InstallationConfirmationRequest,
token: CancellationToken
): Promise<InstallationsConfirmResult>;
}
* Collects an explicit human decision for the complete runtime review.
* Match operationId and policySessionId to the exact action registered by the host
* before displaying it. Refuse unknown actions or incomplete reviews; never infer
* authority from the current session or a global pending slot.
*
* The SDK echoes the original challenge and fingerprint. Separately spawned UI
* work must observe the token to retire itself when the request or connection ends.
* Registering this callback does not enable installation capabilities.
* @experimental
*/
export type InstallationConfirmationHandler = (
request: InstallationConfirmationRequest,
token: CancellationToken
) => InstallationDecision | Promise<InstallationDecision>;
export function createInstallationConfirmationAdapter(
connection: MessageConnection,
handler: InstallationConfirmationHandler
): InstallationConfirmationReceiver {
const closed = new CancellationTokenSource();
const closeSubscription = connection.onClose(() => closed.cancel());
const disposeSubscription = connection.onDispose(() => {
closed.cancel();
closed.dispose();
closeSubscription.dispose();
disposeSubscription.dispose();
});
return {
async confirm(request, token) {
if (!token) {
throw new Error("Installation confirmation request cancellation is unavailable");
}
const { confirmationId, reviewFingerprint } = request;
const combined = new CancellationTokenSource();
const subscriptions: Disposable[] = [];
const cancelRequest = () => combined.cancel();
const cancelConnection = () => combined.cancel();
const checkCancellation = () => {
if (closed.token.isCancellationRequested) {
throw new Error("Installation confirmation connection closed");
}
if (token.isCancellationRequested) {
throw new ResponseError(-32800, "Installation confirmation request cancelled");
}
};
try {
subscriptions.push(
token.onCancellationRequested(cancelRequest),
closed.token.onCancellationRequested(cancelConnection)
);
if (token.isCancellationRequested || closed.token.isCancellationRequested) {
combined.cancel();
}
checkCancellation();
const cancelled = new Promise<never>((_resolve, reject) => {
subscriptions.push(
token.onCancellationRequested(() =>
reject(
new ResponseError(
-32800,
"Installation confirmation request cancelled"
)
)
),
closed.token.onCancellationRequested(() =>
reject(new Error("Installation confirmation connection closed"))
)
);
});
const decision = await Promise.race([
Promise.resolve().then(() => {
checkCancellation();
return handler(request, combined.token);
}),
cancelled,
]);
checkCancellation();
if (decision !== "confirm" && decision !== "decline" && decision !== "cancel") {
throw new Error("Invalid installation confirmation decision");
}
return { confirmationId, reviewFingerprint, decision };
} finally {
for (const subscription of subscriptions) subscription.dispose();
combined.dispose();
}
},
};
}