diff --git a/packages/core/src/tool/registry.ts b/packages/core/src/tool/registry.ts index 9316f6f8f6e..6688c7ef01d 100644 --- a/packages/core/src/tool/registry.ts +++ b/packages/core/src/tool/registry.ts @@ -323,7 +323,10 @@ const registryLayer = Layer.effect( const codemodeTool = (yield* codeMode.materialize(permissions)).tool return { definitions: [ - ...Array.from(direct, ([name, registration]) => toLLMDefinition(name, registration.tool)), + // Definitions are prompt-cache prefix bytes, so order only after effective registrations settle. + ...Array.from(direct) + .sort(([left], [right]) => (left < right ? -1 : left > right ? 1 : 0)) + .map(([name, registration]) => toLLMDefinition(name, registration.tool)), ...(codemodeTool ? [toLLMDefinition("execute", codemodeTool)] : []), ], execute: (input: ExecuteInput) => { diff --git a/packages/core/test/plugin.test.ts b/packages/core/test/plugin.test.ts index f643e2a36ed..ca191a8b56d 100644 --- a/packages/core/test/plugin.test.ts +++ b/packages/core/test/plugin.test.ts @@ -300,8 +300,8 @@ describe("PluginV2", () => { yield* plugins.activate([versioned(plugin)]) expect((yield* registry.snapshot()).definitions.map((tool) => tool.name)).toEqual([ - "plain", "context7_look_up", + "plain", "execute", ]) }), diff --git a/packages/core/test/session-runner-tool-registry.test.ts b/packages/core/test/session-runner-tool-registry.test.ts index 29e1e17c145..3109509581a 100644 --- a/packages/core/test/session-runner-tool-registry.test.ts +++ b/packages/core/test/session-runner-tool-registry.test.ts @@ -121,6 +121,33 @@ describe("ToolRegistry", () => { }), ) + it.effect("canonicalizes effective definitions and keeps Code Mode last", () => + Effect.gen(function* () { + const service = yield* ToolRegistry.Service + const tool = make() + const capture = (registrations: Parameters[0]) => + Effect.scoped( + Effect.gen(function* () { + yield* service.registerBatch(registrations) + return (yield* service.snapshot()).definitions + }), + ) + const first = yield* capture([ + { tools: { zeta: tool, alpha: tool }, options: { codemode: false } }, + { tools: { beta: tool }, options: { namespace: "alpha", codemode: false } }, + { tools: { echo: tool } }, + ]) + const second = yield* capture([ + { tools: { echo: tool } }, + { tools: { beta: tool }, options: { namespace: "alpha", codemode: false } }, + { tools: { alpha: tool, zeta: tool }, options: { codemode: false } }, + ]) + + expect(first).toEqual(second) + expect(first.map((definition) => definition.name)).toEqual(["alpha", "alpha_beta", "zeta", "execute"]) + }), + ) + it.effect("filters disabled tools with edit aliases and ordered wildcard precedence", () => Effect.gen(function* () { const service = yield* ToolRegistry.Service @@ -142,7 +169,7 @@ describe("ToolRegistry", () => { { action: "*", resource: "*", effect: "deny" }, ]), ).toEqual([]) - expect(yield* names([{ action: "edit", resource: "*", effect: "deny" }])).toEqual(["question", "bash"]) + expect(yield* names([{ action: "edit", resource: "*", effect: "deny" }])).toEqual(["bash", "question"]) }), ) diff --git a/packages/core/test/session-runner.test.ts b/packages/core/test/session-runner.test.ts index 2b1c386cd4e..04f5ba0d5f7 100644 --- a/packages/core/test/session-runner.test.ts +++ b/packages/core/test/session-runner.test.ts @@ -1099,7 +1099,7 @@ describe("SessionRunnerLLM", () => { expect(requests).toHaveLength(1) expect(requests[0]?.model).toBe(model) - expect(requests[0]?.tools.map((tool) => tool.name)).toEqual(["echo", "defect", "storefail"]) + expect(requests[0]?.tools.map((tool) => tool.name)).toEqual(["defect", "echo", "storefail"]) expect(requests[0]?.messages.map((message) => ({ role: message.role, content: message.content }))).toEqual([ { role: "user", content: [{ type: "text", text: "First" }] }, { role: "user", content: [{ type: "text", text: "Second" }] }, @@ -2392,7 +2392,7 @@ describe("SessionRunnerLLM", () => { yield* session.resume(sessionID) expect(requests).toHaveLength(1) - expect(requests[0]?.tools.map((tool) => tool.name)).toEqual(["echo", "defect", "storefail"]) + expect(requests[0]?.tools.map((tool) => tool.name)).toEqual(["defect", "echo", "storefail"]) expect(yield* session.context(sessionID)).toMatchObject([ { type: "user", text: "Use tools" }, {