Skip to content

Commit 5891700

Browse files
spaces > tabs
1 parent de2c5b6 commit 5891700

2 files changed

Lines changed: 486 additions & 486 deletions

File tree

pi/answer-extension.ts

Lines changed: 100 additions & 100 deletions
Original file line numberDiff line numberDiff line change
@@ -11,18 +11,18 @@
1111
import type { ExtensionAPI, ExtensionCommandContext } from "@mariozechner/pi-coding-agent";
1212

1313
function getLastAssistantText(ctx: ExtensionCommandContext): string | null {
14-
const branch = ctx.sessionManager.getBranch();
15-
for (let i = branch.length - 1; i >= 0; i--) {
16-
const entry = branch[i];
17-
if (entry.type !== "message") continue;
18-
const msg = entry.message;
19-
if (!("role" in msg) || msg.role !== "assistant") continue;
20-
const textParts = msg.content
21-
.filter((c): c is { type: "text"; text: string } => c.type === "text")
22-
.map((c) => c.text);
23-
if (textParts.length > 0) return textParts.join("\n");
24-
}
25-
return null;
14+
const branch = ctx.sessionManager.getBranch();
15+
for (let i = branch.length - 1; i >= 0; i--) {
16+
const entry = branch[i];
17+
if (entry.type !== "message") continue;
18+
const msg = entry.message;
19+
if (!("role" in msg) || msg.role !== "assistant") continue;
20+
const textParts = msg.content
21+
.filter((c): c is { type: "text"; text: string } => c.type === "text")
22+
.map((c) => c.text);
23+
if (textParts.length > 0) return textParts.join("\n");
24+
}
25+
return null;
2626
}
2727

2828
/**
@@ -33,98 +33,98 @@ function getLastAssistantText(ctx: ExtensionCommandContext): string | null {
3333
* common markdown emphasis so the prompt reads cleanly.
3434
*/
3535
function extractQuestions(text: string): string[] {
36-
const questions: string[] = [];
37-
const seen = new Set<string>();
38-
39-
// Split into "sentences" by terminal punctuation while keeping the '?' tokens.
40-
// Regex captures text up to and including ? . or ! (or end-of-string).
41-
const sentenceRe = /[^.!?\n]*\?/g;
42-
let match: RegExpExecArray | null;
43-
while ((match = sentenceRe.exec(text)) !== null) {
44-
let q = match[0].trim();
45-
if (!q.endsWith("?")) continue;
46-
47-
// Strip leading list/number markers: "- ", "* ", "1. ", "1) ", "**1.** "
48-
q = q.replace(/^[-*+]\s+/, "");
49-
q = q.replace(/^\*+\s*/, "");
50-
q = q.replace(/^\d+[.)]\s+/, "");
51-
q = q.replace(/^\*\*[^*]+\*\*\s*[:\-]?\s*/, "");
52-
53-
// Strip surrounding markdown emphasis
54-
q = q.replace(/\*\*/g, "").replace(/__/g, "").replace(/`/g, "");
55-
56-
// Collapse whitespace
57-
q = q.replace(/\s+/g, " ").trim();
58-
59-
if (q.length < 3) continue;
60-
if (seen.has(q)) continue;
61-
seen.add(q);
62-
questions.push(q);
63-
}
64-
65-
return questions;
36+
const questions: string[] = [];
37+
const seen = new Set<string>();
38+
39+
// Split into "sentences" by terminal punctuation while keeping the '?' tokens.
40+
// Regex captures text up to and including ? . or ! (or end-of-string).
41+
const sentenceRe = /[^.!?\n]*\?/g;
42+
let match: RegExpExecArray | null;
43+
while ((match = sentenceRe.exec(text)) !== null) {
44+
let q = match[0].trim();
45+
if (!q.endsWith("?")) continue;
46+
47+
// Strip leading list/number markers: "- ", "* ", "1. ", "1) ", "**1.** "
48+
q = q.replace(/^[-*+]\s+/, "");
49+
q = q.replace(/^\*+\s*/, "");
50+
q = q.replace(/^\d+[.)]\s+/, "");
51+
q = q.replace(/^\*\*[^*]+\*\*\s*[:\-]?\s*/, "");
52+
53+
// Strip surrounding markdown emphasis
54+
q = q.replace(/\*\*/g, "").replace(/__/g, "").replace(/`/g, "");
55+
56+
// Collapse whitespace
57+
q = q.replace(/\s+/g, " ").trim();
58+
59+
if (q.length < 3) continue;
60+
if (seen.has(q)) continue;
61+
seen.add(q);
62+
questions.push(q);
63+
}
64+
65+
return questions;
6666
}
6767

6868
function formatQA(pairs: Array<{ question: string; answer: string }>): string {
69-
const lines: string[] = [
70-
"Here are my answers to the questions you asked:",
71-
"",
72-
];
73-
pairs.forEach((p, i) => {
74-
lines.push(`${i + 1}. ${p.question}`);
75-
lines.push(` ${p.answer}`);
76-
lines.push("");
77-
});
78-
lines.push("Please continue based on these answers.");
79-
return lines.join("\n");
69+
const lines: string[] = [
70+
"Here are my answers to the questions you asked:",
71+
"",
72+
];
73+
pairs.forEach((p, i) => {
74+
lines.push(`${i + 1}. ${p.question}`);
75+
lines.push(` ${p.answer}`);
76+
lines.push("");
77+
});
78+
lines.push("Please continue based on these answers.");
79+
return lines.join("\n");
8080
}
8181

8282
export default function (pi: ExtensionAPI) {
83-
pi.registerCommand("answer", {
84-
description: "Extract questions from the last AI response and answer them interactively",
85-
handler: async (_args, ctx) => {
86-
if (!ctx.hasUI) {
87-
ctx.ui.notify("/answer requires interactive mode", "error");
88-
return;
89-
}
90-
91-
const lastText = getLastAssistantText(ctx);
92-
if (!lastText) {
93-
ctx.ui.notify("No previous assistant message found", "error");
94-
return;
95-
}
96-
97-
const questions = extractQuestions(lastText);
98-
if (questions.length === 0) {
99-
ctx.ui.notify("No questions found in the last AI response", "warning");
100-
return;
101-
}
102-
103-
ctx.ui.notify(`Found ${questions.length} question${questions.length === 1 ? "" : "s"}`, "info");
104-
105-
const pairs: Array<{ question: string; answer: string }> = [];
106-
for (let i = 0; i < questions.length; i++) {
107-
const q = questions[i];
108-
const title = `Question ${i + 1}/${questions.length}: ${q}`;
109-
const answer = await ctx.ui.input(title, "Type your answer...");
110-
111-
if (answer === null || answer === undefined) {
112-
ctx.ui.notify("Cancelled", "info");
113-
return;
114-
}
115-
116-
pairs.push({ question: q, answer: answer.trim() || "(no answer)" });
117-
}
118-
119-
const reply = formatQA(pairs);
120-
121-
if (ctx.isIdle()) {
122-
pi.sendUserMessage(reply);
123-
} else {
124-
pi.sendUserMessage(reply, { deliverAs: "followUp" });
125-
}
126-
127-
ctx.ui.notify("Answers sent to AI", "info");
128-
},
129-
});
83+
pi.registerCommand("answer", {
84+
description: "Extract questions from the last AI response and answer them interactively",
85+
handler: async (_args, ctx) => {
86+
if (!ctx.hasUI) {
87+
ctx.ui.notify("/answer requires interactive mode", "error");
88+
return;
89+
}
90+
91+
const lastText = getLastAssistantText(ctx);
92+
if (!lastText) {
93+
ctx.ui.notify("No previous assistant message found", "error");
94+
return;
95+
}
96+
97+
const questions = extractQuestions(lastText);
98+
if (questions.length === 0) {
99+
ctx.ui.notify("No questions found in the last AI response", "warning");
100+
return;
101+
}
102+
103+
ctx.ui.notify(`Found ${questions.length} question${questions.length === 1 ? "" : "s"}`, "info");
104+
105+
const pairs: Array<{ question: string; answer: string }> = [];
106+
for (let i = 0; i < questions.length; i++) {
107+
const q = questions[i];
108+
const title = `Question ${i + 1}/${questions.length}: ${q}`;
109+
const answer = await ctx.ui.input(title, "Type your answer...");
110+
111+
if (answer === null || answer === undefined) {
112+
ctx.ui.notify("Cancelled", "info");
113+
return;
114+
}
115+
116+
pairs.push({ question: q, answer: answer.trim() || "(no answer)" });
117+
}
118+
119+
const reply = formatQA(pairs);
120+
121+
if (ctx.isIdle()) {
122+
pi.sendUserMessage(reply);
123+
} else {
124+
pi.sendUserMessage(reply, { deliverAs: "followUp" });
125+
}
126+
127+
ctx.ui.notify("Answers sent to AI", "info");
128+
},
129+
});
130130
}

0 commit comments

Comments
 (0)