فهرست منبع

fix(core): stabilize tool definition ordering (#38590)

Kit Langton 3 هفته پیش
والد
کامیت
c228fc4886

+ 4 - 1
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) => {

+ 1 - 1
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",
       ])
     }),

+ 28 - 1
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<typeof service.registerBatch>[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"])
     }),
   )
 

+ 2 - 2
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" },
         {