feat(desktop-client/yuanbao): capture richer DOM citation sources

Raise the response capture body limit, parse siteName and source
indexes from yuanbao DOM links, merge ordered DOM sources with the
streamed list, and reject runtime/build-diagnostic strings from being
treated as a question.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-30 17:12:18 +08:00
parent fb166fa9fe
commit e5f6e93cea
2 changed files with 619 additions and 44 deletions
@@ -3,10 +3,15 @@ import { describe, expect, it } from "vitest";
import { __yuanbaoTestUtils } from "./yuanbao";
const {
appendUniqueSourceItems,
buildOrderedDomSourceItems,
buildSourceItem,
extractCitationMarkerIndexes,
extractQuestionText,
findUnresolvedCitationIndexes,
mergeText,
normalizeSourceUrl,
parseYuanbaoCaptures,
resolveCitationsFromMarkers,
} = __yuanbaoTestUtils;
@@ -29,6 +34,18 @@ describe("yuanbao adapter helpers", () => {
});
});
it("rejects browser and build diagnostics as monitoring questions", () => {
expect(extractQuestionText({ question_text: "合肥全屋定制推荐" })).toBe("合肥全屋定制推荐");
expect(() => extractQuestionText({
question_text:
"The resource http://localhost:8000/static/js/runtime.c632b2fe.js was preloaded using link preload but not used within a few seconds from the window's load event.",
})).toThrow("yuanbao_question_text_runtime_diagnostic");
expect(() => extractQuestionText({
question_text:
"failed to solve: process \"/bin/sh -c go build\" did not complete successfully: exit code: 1",
})).toThrow("yuanbao_question_text_runtime_diagnostic");
});
it("merges streaming answer fragments without duplicating a repeated prefix", () => {
const first = "通过三轮搜索,我已经掌握了合肥全屋定制市场的高性价比品牌。一定要合同中的定标准确和责任。";
const repeated = "通过三轮搜索,我已经掌握了合肥全屋定制市场的高性价比品牌。结合你的预算,我建议优先看本土老牌。";
@@ -49,4 +66,77 @@ describe("yuanbao adapter helpers", () => {
"https://example.com/3",
]);
});
it("preserves indexed DOM citation slots even when URLs repeat", () => {
const domSources = buildOrderedDomSourceItems([
{
url: "https://example.com/repeated",
title: "来源 1",
text: null,
siteName: "Example",
sourceIndex: 1,
},
{
url: "https://example.com/repeated",
title: "来源 2",
text: null,
siteName: "Example",
sourceIndex: 2,
},
{
url: "https://example.com/third",
title: "来源 3",
text: null,
siteName: "Example",
sourceIndex: 3,
},
]);
expect(domSources).toHaveLength(3);
expect(domSources.map((item) => item.title)).toEqual(["来源 1", "来源 2", "来源 3"]);
expect(findUnresolvedCitationIndexes("正文 [citation:2] [citation:3]", domSources)).toEqual([]);
});
it("keeps primary DOM citation order when appending parsed extras", () => {
const primary = [
{ url: "https://example.com/1", normalized_url: "https://example.com/1" },
{ url: "https://example.com/2", normalized_url: "https://example.com/2" },
];
const appended = appendUniqueSourceItems(primary, [
{ url: "https://example.com/2", normalized_url: "https://example.com/2", title: "duplicate" },
{ url: "https://example.com/3", normalized_url: "https://example.com/3" },
]);
expect(appended.map((item) => item.url)).toEqual([
"https://example.com/1",
"https://example.com/2",
"https://example.com/3",
]);
});
it("does not report incomplete citations when the answer has no markers", () => {
expect(findUnresolvedCitationIndexes("正文没有引用小图标", [])).toEqual([]);
});
it("collects source candidates from deep search thinking fields", () => {
const summary = parseYuanbaoCaptures([
{
url: "https://yuanbao.tencent.com/api/chat",
status: 200,
contentType: "text/event-stream",
body: [
"data: {\"type\":\"deepSearch\",\"contents\":[{\"type\":\"step\",\"webPages\":[{\"targetUrl\":\"https://example.com/a#ref\",\"title\":\"来源 A\",\"siteName\":\"Example\"}],\"references\":[{\"sourceUrl\":\"https://example.com/b\",\"title\":\"来源 B\"}]}]}",
"data: {\"type\":\"text\",\"msg\":\"正文 [citation:1] [citation:2]\"}",
].join("\n\n"),
},
]);
expect(summary.searchResults.map((item) => item.url)).toContain("https://example.com/a");
expect(summary.citations.map((item) => item.url)).toContain("https://example.com/b");
expect(resolveCitationsFromMarkers(summary.answer, [
summary.citations,
summary.searchResults,
[...summary.citations, ...summary.searchResults],
]).map((item) => item.url)).toEqual(["https://example.com/b", "https://example.com/a"]);
});
});