代码修改

This commit is contained in:
fengxiangdebaozi authored and xuhd committed 2026-05-28 18:29:37 +08:00
1 parent ef1481be63
commit a4b7ab5e9d
168 files changed
+76575

No files matched your search

@@ -0,0 +1,322 @@
export interface AgentStepItem {
title: string;
content: string;
}
export interface AgentParsed {
steps: AgentStepItem[];
thinkingMarkdown: string;
answerMarkdown: string;
thinkingComplete: boolean;
}
const STEP_RE = /<step\s+title="([^"]+)">([\s\S]*?)<\/step>/g;
const ANSWER_RE = /<answer>([\s\S]*?)<\/answer>/;
const THINKING_COMMENT_RE = /<!--\s*thinking:\s*([\s\S]*?)\s*-->/i;
const WAITING_PREFIX = '仍在等待模型首个 token...';
const stripCodeFence = (raw: string): string => {
const trimmed = raw.trim();
const match = /```(?:json)?\s*([\s\S]*?)```/i.exec(trimmed);
if (match) return match[1].trim();
return trimmed.replace(/^```(?:json)?\s*/i, '').trim();
};
const stripThinkTags = (
text: string,
): { remainder: string; thinkBlock: string; closed: boolean } => {
const start = text.indexOf('<think>');
if (start === -1) return { remainder: text, thinkBlock: '', closed: false };
const after = text.slice(start + '<think>'.length);
const endIdx = after.indexOf('</think>');
if (endIdx === -1) {
return {
remainder: text.slice(0, start),
thinkBlock: after,
closed: false,
};
}
return {
remainder: text.slice(0, start) + after.slice(endIdx + '</think>'.length),
thinkBlock: after.slice(0, endIdx),
closed: true,
};
};
const mergeStep = (steps: AgentStepItem[], nextStep: AgentStepItem) => {
const existingIndex = steps.findIndex((step) => step.title === nextStep.title);
if (existingIndex === -1) {
steps.push(nextStep);
return;
}
const existing = steps[existingIndex];
const nextIsWaiting = nextStep.content.startsWith(WAITING_PREFIX);
const existingLines = existing.content
.split('\n')
.map((line) => line.trim())
.filter(Boolean);
const stableLines = existingLines.filter((line) => !line.startsWith(WAITING_PREFIX));
if (nextIsWaiting) {
const mergedLines = stableLines.length > 0 ? [...stableLines, nextStep.content] : [nextStep.content];
steps[existingIndex] = {
...existing,
content: mergedLines.join('\n'),
};
return;
}
const normalizedLines = stableLines.includes(nextStep.content)
? stableLines
: [...stableLines, nextStep.content];
steps[existingIndex] = {
...existing,
content: normalizedLines.join('\n'),
};
};
const extractSteps = (text: string): { steps: AgentStepItem[]; remainder: string } => {
const steps: AgentStepItem[] = [];
const remainder = text.replace(STEP_RE, (_, title: string, content: string) => {
mergeStep(steps, { title: title.trim(), content: content.trim() });
return '';
});
return { steps, remainder };
};
const extractTaggedSection = (
text: string,
re: RegExp,
): { value: string; remainder: string } => {
const match = re.exec(text);
if (!match) return { value: '', remainder: text };
return {
value: match[1].trim(),
remainder: text.replace(match[0], '').trim(),
};
};
const extractJsonStringField = (
text: string,
field: string,
): { value: string; complete: boolean; matched: boolean } => {
const re = new RegExp(`"${field}"\\s*:\\s*"`);
const match = re.exec(text);
if (!match) return { value: '', complete: false, matched: false };
let i = match.index + match[0].length;
let value = '';
while (i < text.length) {
const ch = text[i];
if (ch === '\\') {
if (i + 1 >= text.length) return { value, complete: false, matched: true };
const next = text[i + 1];
switch (next) {
case 'n':
value += '\n';
break;
case 't':
value += '\t';
break;
case 'r':
value += '\r';
break;
case '"':
value += '"';
break;
case '\\':
value += '\\';
break;
case '/':
value += '/';
break;
case 'b':
value += '\b';
break;
case 'f':
value += '\f';
break;
case 'u':
if (i + 5 < text.length) {
const hex = text.slice(i + 2, i + 6);
value += String.fromCharCode(parseInt(hex, 16));
i += 6;
continue;
}
return { value, complete: false, matched: true };
default:
value += next;
}
i += 2;
} else if (ch === '"') {
return { value, complete: true, matched: true };
} else {
value += ch;
i++;
}
}
return { value, complete: false, matched: true };
};
const extractItems = (text: string): unknown[] => {
const re = /"items"\s*:\s*\[/;
const match = re.exec(text);
if (!match) return [];
let i = match.index + match[0].length;
const out: unknown[] = [];
while (i < text.length) {
while (i < text.length && /[\s,]/.test(text[i])) i++;
if (i >= text.length) break;
if (text[i] === ']') break;
if (text[i] !== '{') {
i++;
continue;
}
let depth = 0;
let inStr = false;
let escape = false;
let j = i;
for (; j < text.length; j++) {
const c = text[j];
if (escape) {
escape = false;
continue;
}
if (inStr) {
if (c === '\\') escape = true;
else if (c === '"') inStr = false;
} else if (c === '"') inStr = true;
else if (c === '{') depth++;
else if (c === '}') {
depth--;
if (depth === 0) {
j++;
break;
}
}
}
if (depth !== 0) break;
try {
out.push(JSON.parse(text.slice(i, j)));
} catch {
// ignore partial item
}
i = j;
}
return out;
};
const itemToMarkdown = (item: unknown, idx: number): string => {
if (item == null) return `${idx + 1}.`;
if (typeof item !== 'object') return `${idx + 1}. ${String(item)}`;
const o = item as Record<string, unknown>;
const title =
(typeof o.title === 'string' && o.title) ||
(typeof o.expected_behavior === 'string' && o.expected_behavior) ||
(typeof o.name === 'string' && o.name) ||
`条目 ${idx + 1}`;
const body =
(typeof o.content === 'string' && o.content) ||
(typeof o.reason === 'string' && o.reason) ||
'';
const lines: string[] = [`**${idx + 1}. ${title}**`];
if (body) lines.push(body);
if (typeof o.expected_behavior === 'string' && typeof o.driving_factor === 'string') {
if (!o.title) {
lines.length = 0;
lines.push(`**${idx + 1}. ${o.expected_behavior}**`);
}
lines.push(`_驱动因素_:${o.driving_factor}`);
}
if (Array.isArray(o.references) && o.references.length > 0) {
const refs = (o.references as unknown[])
.filter((x) => typeof x === 'string' || typeof x === 'number')
.map((x) => `\`${String(x)}\``)
.join(' ');
if (refs) lines.push(`引用:${refs}`);
}
return lines.join('\n\n');
};
const itemsToMarkdown = (items: unknown[]): string =>
items.map((item, idx) => itemToMarkdown(item, idx)).join('\n\n---\n\n');
export const parseAgentResponse = (raw: string): AgentParsed => {
if (!raw) {
return {
steps: [],
thinkingMarkdown: '',
answerMarkdown: '',
thinkingComplete: false,
};
}
const stripped = stripThinkTags(raw);
const stepsResult = extractSteps(stripped.remainder);
let body = stripCodeFence(stepsResult.remainder);
const extraThinking = stripped.thinkBlock;
let thinkingComplete = stripped.closed;
const answerSection = extractTaggedSection(body, ANSWER_RE);
body = answerSection.remainder;
const thinkingCommentSection = extractTaggedSection(body, THINKING_COMMENT_RE);
body = thinkingCommentSection.remainder;
if (body) {
const start = body.indexOf('{');
if (start !== -1) body = body.slice(start);
}
if (body) {
try {
const obj = JSON.parse(body) as Record<string, unknown>;
if (obj && typeof obj === 'object') {
const thinking = typeof obj.thinking === 'string' ? obj.thinking : '';
const items = Array.isArray(obj.items) ? (obj.items as unknown[]) : [];
const answerMarkdown = items.length > 0 ? itemsToMarkdown(items) : answerSection.value;
return {
steps: stepsResult.steps,
thinkingMarkdown: [extraThinking, thinking, thinkingCommentSection.value]
.filter(Boolean)
.join('\n\n'),
answerMarkdown,
thinkingComplete: true,
};
}
} catch {
// fall through
}
}
const thinkingField = extractJsonStringField(body, 'thinking');
const items = extractItems(body);
if (thinkingField.complete) thinkingComplete = true;
let fallbackAnswer = answerSection.value || body.trim();
if (items.length > 0) {
fallbackAnswer = itemsToMarkdown(items);
}
if (!thinkingField.matched && items.length === 0 && !extraThinking) {
return {
steps: stepsResult.steps,
thinkingMarkdown: '',
answerMarkdown: fallbackAnswer,
thinkingComplete: false,
};
}
return {
steps: stepsResult.steps,
thinkingMarkdown: [extraThinking, thinkingField.value, thinkingCommentSection.value]
.filter(Boolean)
.join('\n\n'),
answerMarkdown: fallbackAnswer,
thinkingComplete,
};
};