refactor: use discriminated union for remote vs local result details

Replace the inline union cast in renderResult with a proper
discriminated union (LocalResultDetails | RemoteResultDetails)
keyed on the `remote` field. Improves type safety and makes
the rendering logic self-documenting.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Facu_Viñas 2026-03-11 18:03:31 -03:00
parent cd813ddded
commit 090554373c

View file

@ -21,12 +21,27 @@ import {
// ─── Types ────────────────────────────────────────────────────────────────────
interface AskUserQuestionsDetails {
interface LocalResultDetails {
remote?: false;
questions: Question[];
response: RoundResult | null;
cancelled: boolean;
}
interface RemoteResultDetails {
remote: true;
channel: string;
timed_out: boolean;
promptId?: string;
threadUrl?: string;
status?: string;
questions?: Question[];
response?: import("./remote-questions/types.js").RemoteAnswer;
error?: boolean;
}
type AskUserQuestionsDetails = LocalResultDetails | RemoteResultDetails;
// ─── Schema ───────────────────────────────────────────────────────────────────
const OptionSchema = Type.Object({
@ -134,13 +149,13 @@ export default function AskUserQuestions(pi: ExtensionAPI) {
if (!hasAnswers) {
return {
content: [{ type: "text", text: "ask_user_questions was cancelled before receiving a response" }],
details: { questions: params.questions, response: null, cancelled: true } as AskUserQuestionsDetails,
details: { questions: params.questions, response: null, cancelled: true } satisfies LocalResultDetails,
};
}
return {
content: [{ type: "text", text: formatForLLM(result) }],
details: { questions: params.questions, response: result, cancelled: false } as AskUserQuestionsDetails,
details: { questions: params.questions, response: result, cancelled: false } satisfies LocalResultDetails,
};
},
@ -168,31 +183,28 @@ export default function AskUserQuestions(pi: ExtensionAPI) {
},
renderResult(result, _options, theme) {
const details = result.details as (AskUserQuestionsDetails & { remote?: boolean; channel?: string; timed_out?: boolean; threadUrl?: string; promptId?: string; status?: string }) | undefined;
const details = result.details as AskUserQuestionsDetails | undefined;
if (!details) {
const text = result.content[0];
return new Text(text?.type === "text" ? text.text : "", 0, 0);
}
// Remote channel result
// Remote channel result (discriminated on details.remote === true)
if (details.remote) {
if (details.timed_out) {
const channelLabel = details.channel ?? "remote";
return new Text(
`${theme.fg("warning", `${channelLabel} — timed out`)}${details.threadUrl ? theme.fg("dim", ` ${details.threadUrl}`) : ""}`,
`${theme.fg("warning", `${details.channel} — timed out`)}${details.threadUrl ? theme.fg("dim", ` ${details.threadUrl}`) : ""}`,
0,
0,
);
}
const remoteResponse = details.response as import("./remote-questions/channels.js").RemoteAnswer | undefined;
const questions = (details.questions ?? []) as Question[];
const lines: string[] = [];
const channelLabel = details.channel ?? "remote";
lines.push(theme.fg("dim", channelLabel));
if (remoteResponse) {
lines.push(theme.fg("dim", details.channel));
if (details.response) {
for (const q of questions) {
const answer = remoteResponse.answers[q.id];
const answer = details.response.answers[q.id];
if (!answer) {
lines.push(`${theme.fg("accent", q.header)}: ${theme.fg("dim", "(no answer)")}`);
continue;