File size: 3,079 Bytes
f59fbe2 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 | import { randomUUID } from "node:crypto";
import type {
ApprovalRequest,
ApprovalResponse as CoreApprovalResponse,
QuestionRequest,
QuestionResult,
} from "@moonshot-ai/kimi-code-sdk";
import type { ApprovalResponse, QuestionRequest as LegacyQuestionRequest } from "../../shared/legacy-sdk";
import { describeToolDisplay, toLegacyDisplay } from "./tool-display";
export type ReverseRpcEvent =
| { type: "ApprovalRequest"; payload: ReturnType<typeof approvalPayload> }
| { type: "QuestionRequest"; payload: LegacyQuestionRequest };
export class ReverseRpcController {
private readonly approvals = new Map<string, (response: CoreApprovalResponse) => void>();
private readonly questions = new Map<string, (result: QuestionResult) => void>();
constructor(private readonly emit: (event: ReverseRpcEvent) => void) {}
requestApproval(request: ApprovalRequest): Promise<CoreApprovalResponse> {
const id = randomUUID();
return new Promise((resolve) => {
this.approvals.set(id, resolve);
this.emit({ type: "ApprovalRequest", payload: approvalPayload(id, request) });
});
}
requestQuestion(request: QuestionRequest): Promise<QuestionResult> {
const id = randomUUID();
return new Promise((resolve) => {
this.questions.set(id, resolve);
this.emit({
type: "QuestionRequest",
payload: {
id,
tool_call_id: request.toolCallId ?? "",
questions: request.questions.map((question) => ({
question: question.question,
header: question.header,
options: question.options.map((option) => ({
label: option.label,
description: option.description,
})),
multi_select: question.multiSelect,
})),
},
});
});
}
respondApproval(id: string, response: ApprovalResponse): boolean {
const resolve = this.approvals.get(id);
if (!resolve) return false;
this.approvals.delete(id);
if (response === "approve_for_session") {
resolve({ decision: "approved", scope: "session" });
} else if (response === "approve") {
resolve({ decision: "approved" });
} else {
resolve({ decision: "rejected" });
}
return true;
}
respondQuestion(id: string, answers: Record<string, string>): boolean {
const resolve = this.questions.get(id);
if (!resolve) return false;
this.questions.delete(id);
resolve({ answers });
return true;
}
cancelAll(reason: string): void {
for (const resolve of this.approvals.values()) {
resolve({ decision: "cancelled", feedback: reason });
}
for (const resolve of this.questions.values()) {
resolve(null);
}
this.approvals.clear();
this.questions.clear();
}
}
function approvalPayload(id: string, request: ApprovalRequest) {
return {
id,
tool_call_id: request.toolCallId,
sender: request.toolName,
action: request.action,
description: describeToolDisplay(request.display),
display: toLegacyDisplay(request.display),
};
}
|