返回 DeepSeek-Reasonix
searchTranscript.ts
根目录 / desktop / frontend / src / lib / searchTranscript.ts
1 import { mergeSearchSources, parseSearchSources, searchSourcesFromHistory, searchOutputMetadata, type SearchSource } from "./searchSources";
2 import type { Item } from "./useController";
3
4 type SearchState = {
5 currentAssistant?: string;
6 pendingSearchSources?: SearchSource[];
7 items: Item[];
8 };
9
10 export function historySearchCards(
11 searches: { id?: string; query?: string; sources_status?: "available" | "not_provided"; results?: { title?: string; url?: string }[] }[] | undefined,
12 ): Extract<Item, { kind: "tool" }>[] {
13 const cards: Extract<Item, { kind: "tool" }>[] = [];
14 for (const search of searches ?? []) {
15 if (!search.id) continue;
16 const lines = (search.results ?? []).flatMap((hit) => [hit.title, hit.url].filter(Boolean));
17 cards.push({
18 kind: "tool",
19 id: search.id,
20 name: "web_search",
21 args: search.query ? JSON.stringify({ query: search.query }) : "",
22 readOnly: true,
23 status: "done",
24 searchSourcesStatus: search.sources_status,
25 searchSources: (search.results ?? []).map((hit) => ({ title: hit.title, url: hit.url })),
26 output: lines.join("\n"),
27 });
28 }
29 return cards;
30 }
31
32 export function historySearchSources(
33 searches: { results?: { title?: string; url?: string }[] }[] | undefined,
34 ): SearchSource[] | undefined {
35 const sources = searchSourcesFromHistory(searches);
36 return sources.length > 0 ? sources : undefined;
37 }
38
39 export function historySearchAndAnswer(
40 id: string,
41 m: { content: string; reasoning?: string; turnFinal?: boolean; samplingCount?: number; toolCount?: number; workDurationMs?: number; turnDurationMs?: number; turnUsage?: Extract<Item, { kind: "assistant" }>["turnUsage"]; createdAt?: number; memoryCitations?: Extract<Item, { kind: "assistant" }>["memoryCitations"]; serverSearch?: { id?: string; query?: string; sources_status?: "available" | "not_provided"; results?: { title?: string; url?: string }[] }[] },
42 preserveEmpty = false,
43 ): Item[] {
44 const out: Item[] = historySearchCards(m.serverSearch);
45 const searchSources = historySearchSources(m.serverSearch);
46 if (preserveEmpty || m.content.trim() !== "" || (m.reasoning ?? "").trim() !== "" || searchSources) {
47 out.push({
48 kind: "assistant",
49 id,
50 text: m.content,
51 reasoning: m.reasoning ?? "",
52 streaming: false,
53 workDurationMs: m.workDurationMs,
54 turnFinal: m.turnFinal,
55 samplingCount: m.samplingCount,
56 toolCount: m.toolCount,
57 turnDurationMs: m.turnDurationMs,
58 turnUsage: m.turnUsage,
59 createdAt: m.createdAt,
60 memoryCitations: m.memoryCitations,
61 searchSources,
62 });
63 }
64 return out;
65 }
66
67 export function attachSearchSources<T extends SearchState>(s: T, sources: SearchSource[]): T {
68 if (sources.length === 0) return s;
69 const currentAssistant = s.currentAssistant && s.items.some(
70 (it) => it.kind === "assistant" && it.id === s.currentAssistant,
71 )
72 ? s.currentAssistant
73 : undefined;
74 if (!currentAssistant) {
75 return { ...s, pendingSearchSources: mergeSearchSources(s.pendingSearchSources, sources) };
76 }
77 return {
78 ...s,
79 items: s.items.map((it) =>
80 it.kind === "assistant" && it.id === currentAssistant
81 ? { ...it, searchSources: mergeSearchSources(it.searchSources, sources) }
82 : it,
83 ),
84 };
85 }
86
87 export function isBatchedReadOnlyTool(name: string, readOnly: boolean): boolean {
88 return readOnly && name !== "todo_write" && name !== "web_search";
89 }
90
91 export function attachWebSearchOutput<T extends SearchState>(s: T, name: string, output?: string, err?: string, toolId?: string): T {
92 if (name !== "web_search" || !output || err) return s;
93 const sources = parseSearchSources(output);
94 const withTool = toolId
95 ? {
96 ...s,
97 items: s.items.map((it) =>
98 it.kind === "tool" && it.id === toolId
99 ? { ...it, searchSources: mergeSearchSources(it.searchSources, sources), searchSourcesStatus: searchOutputMetadata(output).status, searchSummary: searchOutputMetadata(output).summary }
100 : it,
101 ),
102 }
103 : s;
104 return attachSearchSources(withTool, sources);
105 }
106
106 lines TYPESCRIPT