feat(engine): sort tools deterministically for prompt cache stability
This commit is contained in:
35
packages/engine/src/__tests__/tool-ordering.test.ts
Normal file
35
packages/engine/src/__tests__/tool-ordering.test.ts
Normal file
@@ -0,0 +1,35 @@
|
||||
import { describe, it, expect } from "vitest";
|
||||
|
||||
/**
|
||||
* Verifies that tools are sorted deterministically by name.
|
||||
* This is critical for prompt caching — tool schemas are part of the
|
||||
* API request, and reordering them breaks cache prefix matching.
|
||||
*/
|
||||
describe("deterministic tool ordering", () => {
|
||||
it("sorts tools alphabetically by name", () => {
|
||||
const tools = [
|
||||
{ name: "write", execute: async () => {} },
|
||||
{ name: "bash", execute: async () => {} },
|
||||
{ name: "read", execute: async () => {} },
|
||||
{ name: "edit", execute: async () => {} },
|
||||
];
|
||||
|
||||
const sorted = [...tools].sort((a, b) => a.name.localeCompare(b.name));
|
||||
|
||||
expect(sorted.map((t) => t.name)).toEqual(["bash", "edit", "read", "write"]);
|
||||
});
|
||||
|
||||
it("is stable across repeated sorts", () => {
|
||||
const tools = [
|
||||
{ name: "grep" },
|
||||
{ name: "bash" },
|
||||
{ name: "find" },
|
||||
{ name: "read" },
|
||||
];
|
||||
|
||||
const sorted1 = [...tools].sort((a, b) => a.name.localeCompare(b.name));
|
||||
const sorted2 = [...tools].sort((a, b) => a.name.localeCompare(b.name));
|
||||
|
||||
expect(sorted1.map((t) => t.name)).toEqual(sorted2.map((t) => t.name));
|
||||
});
|
||||
});
|
||||
@@ -1757,6 +1757,10 @@ export async function createFnAgent(options: AgentOptions): Promise<AgentResult>
|
||||
boundaryContext.worktreePath,
|
||||
boundaryContext.worktreeProjectRoot,
|
||||
);
|
||||
// Sort tools alphabetically by name for deterministic ordering.
|
||||
// Prompt caching requires the tool list to be byte-identical across
|
||||
// sessions — reordering breaks cache prefix matching.
|
||||
customToolList.sort((a, b) => a.name.localeCompare(b.name));
|
||||
// Last-chance abort hook. Fires *here* — after every awaited setup step
|
||||
// in createFnAgent (provider registration, worktree validation, resource
|
||||
// loader reload) and immediately before the actual LLM session spawn.
|
||||
|
||||
Reference in New Issue
Block a user