Files
writing-agent/src/skills/question-protocol.ts

135 lines
3.9 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* Worker / Agent 结构化追问协议。
* UI左右分页选项卡经 composer 发送payload 须含问+答(可附自由补充)。
*/
import type {
QuestionAnswer,
QuestionItem,
QuestionOption,
} from "../types/questions.js";
export type { QuestionAnswer, QuestionItem, QuestionOption };
function letterId(i: number): string {
return String.fromCharCode(65 + (i % 26));
}
/** 把 askUser 原始值规范成 QuestionItem[](兼容纯 string[] */
export function normalizeQuestions(raw: unknown): QuestionItem[] {
if (!Array.isArray(raw)) return [];
const out: QuestionItem[] = [];
for (let i = 0; i < raw.length; i++) {
const item = raw[i];
if (typeof item === "string") {
const prompt = item.trim();
if (!prompt) continue;
out.push({
id: `q${i + 1}`,
prompt,
allowOther: true,
required: true,
});
continue;
}
if (!item || typeof item !== "object" || Array.isArray(item)) continue;
const row = item as Record<string, unknown>;
const prompt =
typeof row.prompt === "string"
? row.prompt.trim()
: typeof row.question === "string"
? row.question.trim()
: typeof row.text === "string"
? row.text.trim()
: "";
if (!prompt) continue;
const id =
typeof row.id === "string" && row.id.trim()
? row.id.trim()
: `q${i + 1}`;
let options: QuestionOption[] | undefined;
if (Array.isArray(row.options)) {
options = [];
for (let j = 0; j < row.options.length; j++) {
const opt = row.options[j];
if (typeof opt === "string") {
const label = opt.trim();
if (!label) continue;
options.push({ id: letterId(j), label, editable: true });
continue;
}
if (!opt || typeof opt !== "object" || Array.isArray(opt)) continue;
const o = opt as Record<string, unknown>;
const label =
typeof o.label === "string"
? o.label.trim()
: typeof o.text === "string"
? o.text.trim()
: "";
if (!label) continue;
options.push({
id:
typeof o.id === "string" && o.id.trim()
? o.id.trim()
: letterId(j),
label,
editable: o.editable === false ? false : true,
});
}
if (options.length === 0) options = undefined;
}
out.push({
id,
prompt,
options,
allowOther: row.allowOther === false ? false : true,
required: row.required === false ? false : true,
});
}
return out;
}
/** 发给 AI必须含问题与答案可选自由补充 */
export function formatQuestionAnswersForAi(
questions: QuestionItem[],
answers: QuestionAnswer[],
note?: string,
): string {
const byId = new Map(answers.map((a) => [a.questionId, a]));
const lines: string[] = ["【追问作答】"];
for (const q of questions) {
const a = byId.get(q.id);
const answer = a?.text?.trim() || "(未答)";
lines.push(`问:${q.prompt}`);
lines.push(`答:${answer}`);
lines.push("");
}
const trimmedNote = note?.trim();
if (trimmedNote) {
lines.push("【补充】");
lines.push(trimmedNote);
}
return lines.join("\n").trim();
}
/** 气泡摘要:答句 + 可选补充 */
export function formatQuestionAnswersForDisplay(
questions: QuestionItem[],
answers: QuestionAnswer[],
note?: string,
): string {
const byId = new Map(answers.map((a) => [a.questionId, a]));
const parts: string[] = [];
for (const q of questions) {
const a = byId.get(q.id);
if (!a?.text?.trim()) continue;
parts.push(a.text.trim());
}
const trimmedNote = note?.trim();
if (trimmedNote) parts.push(trimmedNote);
return parts.length ? parts.join("\n") : "(已提交追问作答)";
}
export function questionPrompts(questions: QuestionItem[]): string[] {
return questions.map((q) => q.prompt).filter(Boolean);
}