diff --git a/src/agent/director.ts b/src/agent/director.ts index b0dc8dcd9..6943c4172 100644 --- a/src/agent/director.ts +++ b/src/agent/director.ts @@ -475,6 +475,7 @@ class ChatDirectorImpl extends DefaultDirector { private readonly askOperatorCalls = new Set(); private readonly _systemPrompt: string; private _toolDefinitions: ToolDefinition[]; + private _toolSetDigest: string | undefined; private inactivityTimeoutMs: number | undefined; private totalTimeoutMs: number | undefined; // Host-owned live object, attached via setWorkflowCoordinator — never a @@ -699,9 +700,10 @@ class ChatDirectorImpl extends DefaultDirector { } updateToolDefinitions(toolDefinitions: ToolDefinition[]): void { - const before = toolSetDigest(this._toolDefinitions); + const before = this._toolSetDigest ?? toolSetDigest(this._toolDefinitions); const after = toolSetDigest(toolDefinitions); this._toolDefinitions = toolDefinitions; + this._toolSetDigest = after; if (before === after) return; logger.debug`tool-set-changed count=${String(this._toolDefinitions.length)} before=${before} after=${after}`; } diff --git a/src/agent/tool-set-digest.test.ts b/src/agent/tool-set-digest.test.ts new file mode 100644 index 000000000..cf616c4fa --- /dev/null +++ b/src/agent/tool-set-digest.test.ts @@ -0,0 +1,47 @@ +import { describe, expect, test } from "bun:test"; +import { withMockedModule } from "../../testkit/mock-module.js"; + +let hashCalls = 0; +await withMockedModule( + import.meta.resolve("node:crypto"), + (real) => ({ + ...real, + createHash: (algorithm: string) => { + hashCalls += 1; + return real.createHash(algorithm); + }, + }), +); + +const { createChatDirector } = await import("./director.js"); + +const base = { + name: "read_file", + description: "read a file", + inputSchema: { type: "object" }, +}; + +describe("toolSetDigest memoization", () => { + test("hashes each installed tool set once per director", () => { + const director = createChatDirector("system", [], {}); + const tools = [{ ...base }]; + const changed = [{ ...base, description: "changed" }]; + + director.updateToolDefinitions(tools); + hashCalls = 0; + director.updateToolDefinitions(changed); + expect(hashCalls).toBe(1); + }); + + test("recomputes when a new tool set arrives", () => { + const director = createChatDirector("system", [], {}); + const tools = [{ ...base }]; + const changed = [{ ...base, description: "changed" }]; + + director.updateToolDefinitions(tools); + director.updateToolDefinitions(changed); + hashCalls = 0; + director.updateToolDefinitions([{ ...base }]); + expect(hashCalls).toBe(1); + }); +});