返回源码地图

packages/core/session/src/tool-history.ts

main snapshot · da00f7f5358f · 正文引用章节 06;完整原文可核对,不声称全文件人工逐行审计

完整原文供逐行核对;页面收录不代表每行都经过人工语义审核。MIT 许可见 许可证。

1/** Stateful reconstruction of historical tool definitions for request projection. */
2
3import type { ToolHistory, ToolSchema } from '@deepseek-ai/dsh-llm'
4import { deepFreeze } from '@deepseek-ai/dsh-util-values'
5import type { SessionEvent, SessionSeq } from './types.ts'
6
7/** Folds committed headers and developer messages independently of model capability. */
8export class ToolHistoryProjection {
9 /** Historical declarations indexed by the header sequence referenced by additions. */
10 private readonly headers = new Map<SessionSeq, readonly ToolSchema[]>()
11 /** Definitions retained in the current declaration series, including removed tools. */
12 private declared = new Map<string, ToolSchema>()
13 /** Active definitions from the latest request header. */
14 private active: readonly ToolSchema[] = []
15 /** Active names reconstructed from the baseline and recorded updates. */
16 private available = new Set<string>()
17 /** Header that starts the current declaration series; absent before the first header. */
18 private baselineSeq: SessionSeq | undefined
19 /** Immutable request snapshot of the current baseline and resolved updates. */
20 private history: ToolHistory = deepFreeze({ tools: [], updates: [] })
21
22 /**
23 * Consume the next committed event in log order.
24 * @param event - a session event, including inherited events during restoration.
25 */
26 apply(event: SessionEvent): void {
27 if (event.type === 'request/header') {
28 const tools = event.data.header.tools ?? []
29 this.headers.set(event.seq, tools)
30 // A retained name cannot acquire a different definition without losing
31 // prefix reuse; an unchanged restored name is re-offered by its recorded addition.
32 const redeclared = tools.some((tool) => {
33 const before = this.declared.get(tool.name)
34 return before !== undefined && JSON.stringify(before) !== JSON.stringify(tool)
35 })
36 if (this.baselineSeq === undefined || event.data.reason === 'series' || event.data.startsSeries || redeclared) {
37 this.baselineSeq = event.seq
38 this.declared = new Map(tools.map(tool => [tool.name, tool]))
39 this.history = deepFreeze({ tools, updates: [] })
40 this.available = new Set(tools.map(tool => tool.name))
41 }
42 this.active = tools
43 } else if (event.type === 'developer/message') {
44 const { message, headerSeq } = event.data
45 const definitions = headerSeq === undefined ? [] : this.headers.get(headerSeq)
46 const additions = message.content.flatMap((block) => {
47 if (block.type !== 'tool-addition') return []
48 const tool = definitions?.find(tool => tool.name === block.toolName)
49 if (tool === undefined) throw new Error(`tool history: missing definition for ${block.toolName}`)
50 return [tool]
51 })
52 for (const tool of additions) this.declared.set(tool.name, tool)
53 for (const block of message.content) {
54 if (block.type === 'tool-addition') this.available.add(block.toolName)
55 else if (block.type === 'tool-removal') this.available.delete(block.toolName)
56 }
57 this.history = deepFreeze({
58 tools: this.history.tools,
59 updates: [...this.history.updates, { messageId: message.id, additions }],
60 })
61 }
62 }
63
64 /**
65 * Read an immutable snapshot; subsequent events do not mutate it.
66 * @returns initial declarations and historically resolved additions for the current series.
67 */
68 snapshot(): ToolHistory {
69 // Sessions written before tool-update emission have headers without matching updates.
70 if (this.active.length !== this.available.size || this.active.some(tool => !this.available.has(tool.name))) {
71 return deepFreeze({ tools: this.active, updates: [] })
72 }
73 return this.history
74 }
75}