返回 DeepSeek-Reasonix
transcriptToolContent.ts
根目录 / desktop / frontend / src / lib / transcriptToolContent.ts
1 import type { Item } from "./useController";
2 import type { HistoryContentRef } from "./types";
3 import type { TranscriptBackend, SessionTranscript } from "./transcriptStoreTypes";
4 import { itemIdForToolCall } from "./transcriptRecordProjection";
5 import { fileDiffFromWire } from "./tools";
6 type ToolContentOwner = { sessions: Map<string, SessionTranscript>; backend: TranscriptBackend; requestFullContent(tabId: string, entryId: string, field: string): Promise<string | undefined> };
7 export async function readTranscriptToolContent(owner: ToolContentOwner, tabId: string, item: Extract<Item, { kind: "tool" }>, value: Record<string, unknown>): Promise<string | undefined> {
8 const session = [...owner.sessions.values()].find(session => session.tabId === tabId &&
9 [...session.contributions.values()].some(items => items.some(candidate => candidate.id === item.id)));
10 if (!session) return undefined;
11 const entryId = [...session.contributions].find(([, items]) => items.some(candidate => candidate.id === item.id))?.[0];
12 const record = entryId && session.byId.get(entryId);
13 if (!record) return undefined;
14 let calls = record.message.toolCalls ?? [];
15 let callIndex = calls.findIndex((call, index) => itemIdForToolCall(call.id, `he:${record.entryId}:tc${index}`) === item.id);
16 let call = calls[callIndex];
17 const resultId = session.matchTables.get(record.entryId)?.get(callIndex);
18 let result = resultId ? session.byId.get(resultId) : record.message.role === "tool" ? record : undefined;
19 if (record.refs.some(ref => ref.field === "canonicalMessage")) {
20 await owner.requestFullContent(tabId, record.entryId, "content");
21 calls = record.message.toolCalls ?? [];
22 callIndex = calls.findIndex((candidate, index) => itemIdForToolCall(candidate.id, `he:${record.entryId}:tc${index}`) === item.id);
23 call = calls[callIndex];
24 result = resultId ? session.byId.get(resultId) : record.message.role === "tool" ? record : undefined;
25 }
26 if (result?.refs.some(ref => ref.field === "canonicalMessage")) {
27 await owner.requestFullContent(tabId, result.entryId, "content");
28 }
29 const generation = session.generation;
30 const refs = [
31 ...record.refs.filter(ref => call && ref.toolCallId === call.id && (ref.field === "toolArguments" || ref.field === "toolDiff")),
32 ...(result?.refs.filter(ref => ref.field === "content" || ref.field === "toolResultError") ?? []),
33 ];
34 if (refs.some(ref => ref.field === "toolArguments" || ref.field === "toolDiff") && !call?.id && calls.filter(call => !call.id).length > 1) throw new Error("Ambiguous legacy tool reference");
35 const full: Record<string, unknown> = { ...value, execution: result?.message.execution ?? value.execution };
36 if (call) full.args = call.arguments;
37 if (!result && call?.resultObservation?.contentRef) {
38 const contentRef = call.resultObservation.contentRef;
39 const ref: HistoryContentRef = { entryId: `m:${call.resultObservation.messageId}`, field: "canonicalMessage", size: contentRef.bytes, chunks: Math.ceil(contentRef.bytes / (1 << 20)), canonicalRef: contentRef, revision: session.revision, revKnown: true, digest: contentRef.digest };
40 let bytes = "";
41 for (let index = 0; index < ref.chunks; index++) {
42 const chunk = await owner.backend.HistoryContentForTab(tabId, ref, index);
43 if (owner.sessions.get(session.key) !== session || generation !== session.generation || chunk.stale) throw new Error("Tool reference expired; retry");
44 bytes += chunk.data ?? "";
45 }
46 if (bytes.length !== contentRef.bytes) throw new Error("Incomplete tool result content");
47 const decoded = JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(Uint8Array.from(bytes, char => char.charCodeAt(0))));
48 full.output = decoded.raw_content || decoded.content || "";
49 full.execution = decoded.tool_execution ?? full.execution;
50 }
51
52 for (const ref of refs) {
53 let data = "";
54 for (let index = 0; index < Math.max(1, ref.chunks); index++) {
55 const chunk = await owner.backend.HistoryContentForTab(tabId, ref, index);
56 if (owner.sessions.get(session.key) !== session || generation !== session.generation || chunk.stale) throw new Error("Tool reference expired; retry");
57 data += chunk.data ?? "";
58 if (chunk.done) break;
59 }
60 if (new TextEncoder().encode(data).byteLength !== ref.size) throw new Error("Incomplete tool content");
61 if (ref.field === "toolArguments") full.args = data;
62 else if (ref.field === "content") full.output = data;
63 else if (ref.field === "toolResultError") full.error = data;
64 else full.diff = call ? fileDiffFromWire({ ...call, diff: data }) ?? data : data;
65 }
66 return JSON.stringify(full, null, 2);
67 }
68
68 lines TYPESCRIPT