mirror of
https://github.com/deepseek-ai/deepseek-harness
synced 2026-08-15 21:04:50 +00:00
fix(client): bound nested Tool call depth
This commit is contained in:
@@ -10,6 +10,9 @@ interface ProjectedBlock {
|
||||
value: ToolCallBlock
|
||||
}
|
||||
|
||||
/** Fixed wire-safety ceiling for every recursive Tool call consumer. */
|
||||
export const MAX_TOOL_CALL_TREE_DEPTH = 256
|
||||
|
||||
function sameReferences<T>(
|
||||
left: readonly T[],
|
||||
right: readonly T[],
|
||||
@@ -24,6 +27,7 @@ function sameReferences<T>(
|
||||
*/
|
||||
export class ToolCallTree {
|
||||
private readonly childrenByParent = new Map<string, readonly ToolCallBlock[]>()
|
||||
private readonly depthByCall = new Map<string, number>()
|
||||
private readonly projectedByCall = new Map<string, ProjectedBlock>()
|
||||
private revision = 0
|
||||
private nodesCache: {
|
||||
@@ -40,6 +44,7 @@ export class ToolCallTree {
|
||||
/** Forget all event-derived child calls before replaying a new window. */
|
||||
reset(): void {
|
||||
this.childrenByParent.clear()
|
||||
this.depthByCall.clear()
|
||||
this.projectedByCall.clear()
|
||||
this.revision++
|
||||
}
|
||||
@@ -68,7 +73,7 @@ export class ToolCallTree {
|
||||
subCalls: [],
|
||||
}
|
||||
const siblings = this.childrenByParent.get(data.parentCallId) ?? []
|
||||
if (this.wouldCreateCycle(data.parentCallId, data.subCallId)) return true
|
||||
if (!this.acceptEdge(data.parentCallId, data.subCallId)) return true
|
||||
this.childrenByParent.set(data.parentCallId, [...siblings, running])
|
||||
this.revision++
|
||||
return true
|
||||
@@ -84,7 +89,7 @@ export class ToolCallTree {
|
||||
}
|
||||
const siblings = this.childrenByParent.get(data.parentCallId) ?? []
|
||||
const at = siblings.findIndex(sub => sub.callId === data.subCallId)
|
||||
if (at === -1 && this.wouldCreateCycle(data.parentCallId, data.subCallId)) return true
|
||||
if (at === -1 && !this.acceptEdge(data.parentCallId, data.subCallId)) return true
|
||||
const started = at === -1 ? undefined : siblings[at]
|
||||
const settled: ToolResultNode = {
|
||||
kind: 'tool-result',
|
||||
@@ -163,6 +168,33 @@ export class ToolCallTree {
|
||||
return value
|
||||
}
|
||||
|
||||
/**
|
||||
* Accept an edge only when every recursive consumer can traverse it safely.
|
||||
* Host-minted ids exclude cycles and current bindings emit one level; a
|
||||
* malformed wire/history edge is consumed without hiding the rest of the session.
|
||||
*/
|
||||
private acceptEdge(parentCallId: string, subCallId: string): boolean {
|
||||
if (this.wouldCreateCycle(parentCallId, subCallId)) return false
|
||||
const pending = [{
|
||||
callId: subCallId,
|
||||
depth: (this.depthByCall.get(parentCallId) ?? 1) + 1,
|
||||
}]
|
||||
const updates = new Map<string, number>()
|
||||
for (const candidate of pending) {
|
||||
const knownDepth = updates.get(candidate.callId)
|
||||
?? this.depthByCall.get(candidate.callId)
|
||||
?? 1
|
||||
if (candidate.depth <= knownDepth) continue
|
||||
if (candidate.depth > MAX_TOOL_CALL_TREE_DEPTH) return false
|
||||
updates.set(candidate.callId, candidate.depth)
|
||||
for (const child of this.childrenByParent.get(candidate.callId) ?? []) {
|
||||
pending.push({ callId: child.callId, depth: candidate.depth + 1 })
|
||||
}
|
||||
}
|
||||
for (const [callId, depth] of updates) this.depthByCall.set(callId, depth)
|
||||
return true
|
||||
}
|
||||
|
||||
private wouldCreateCycle(parentCallId: string, subCallId: string): boolean {
|
||||
if (parentCallId === subCallId) return true
|
||||
const pending = [subCallId]
|
||||
|
||||
Reference in New Issue
Block a user