feat(core): support pinned Code Mode tools (#39550)

This commit is contained in:
Aiden Cline 2026-07-29 13:16:49 -05:00 committed by GitHub
commit 5cb633a48e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 77 additions and 16 deletions

View file

@ -16,6 +16,7 @@ describe("CodeMode", () => {
description: "Echo text",
input: Schema.Struct({ text: Schema.String }),
output: Schema.String,
options: { pinned: true },
execute: ({ text }) => Effect.succeed({ output: text }),
}),
)
@ -27,6 +28,7 @@ describe("CodeMode", () => {
path: "echo",
description: "Echo text",
signature: "tools.echo(input: {\n text: string,\n}): Promise<string>",
pinned: true,
},
])
}).pipe(

View file

@ -2,10 +2,11 @@ import { describe, expect, test } from "bun:test"
import { CodeModeCatalog } from "@opencode-ai/core/codemode/catalog"
import { CodeModeInstructions } from "@opencode-ai/core/codemode/instructions"
const entry = (path: string, description: string, signature?: string): CodeModeCatalog.Entry => ({
const entry = (path: string, description: string, signature?: string, pinned = false): CodeModeCatalog.Entry => ({
path,
description,
signature: signature ?? `tools.${path}(input: {\n q: string,\n}): Promise<string>`,
pinned,
})
const lookup = entry(
@ -46,6 +47,30 @@ describe("CodeModeCatalog.summarize", () => {
expect(catalog.namespaces.every((namespace) => namespace.entries.length === 0)).toBe(true)
})
test("always retains pinned tools beyond the inline budget", () => {
const pinned = [
entry("alpha.first", "First", undefined, true),
entry("beta.second", "Second", undefined, true),
]
const catalog = CodeModeCatalog.summarize([...pinned, entry("alpha.unpinned", "Unpinned")], 0)
expect(catalog.shown).toBe(2)
expect(catalog.namespaces.flatMap((namespace) => namespace.entries.map((item) => item.path))).toEqual([
"alpha.first",
"beta.second",
])
})
test("spends the budget remaining after pinned tools on unpinned tools", () => {
const pinned = entry("alpha.pinned", "Pinned", undefined, true)
const unpinned = entry("beta.unpinned", "Unpinned")
const pinCost = Math.round(` - ${pinned.signature} // Pinned`.length / 4)
const unpinnedCost = Math.round(` - ${unpinned.signature} // Unpinned`.length / 4)
expect(CodeModeCatalog.summarize([pinned, unpinned], pinCost + unpinnedCost).shown).toBe(2)
expect(CodeModeCatalog.summarize([pinned, unpinned], pinCost + unpinnedCost - 1).shown).toBe(1)
})
test("retains only the rendered portion of inline descriptions", () => {
const catalog = CodeModeCatalog.summarize([entry("alpha.one", `Summary\n${"detail".repeat(10_000)}`)])
expect(catalog.namespaces[0]?.entries[0]?.line).toEndWith("// Summary")

View file

@ -67,7 +67,7 @@ const transform = (
) =>
service.transform((draft) =>
Object.entries(tools).forEach(([name, tool]) =>
draft.add({ ...tool, name, options: { ...tool.options, ...options } }),
draft.add({ ...tool, name, options: options ?? tool.options }),
),
)

View file

@ -231,7 +231,7 @@ const permission = Layer.succeed(
const transformTools = (registry: Tool.Interface, tools: Readonly<Record<string, Info>>, options?: Tool.Options) =>
registry.transform((draft) =>
Object.entries(tools).forEach(([name, tool]) =>
draft.add({ ...tool, name, options: { ...tool.options, ...options } }),
draft.add({ ...tool, name, options: options ?? tool.options }),
),
)
const echo = Layer.effectDiscard(