fix: only include semantic search tool hint when enabled

This commit is contained in:
Josh Holmer
2026-05-15 08:14:56 -04:00
parent 863ca97ab2
commit deddacf063
4 changed files with 93 additions and 24 deletions
@@ -13,6 +13,9 @@ const log = Log.create({ service: "kilocode-tool-registry" })
type Deps = { agent: Agent.Interface; truncate: Truncate.Interface }
export namespace KiloToolRegistry {
const hint =
"- When you are doing an open-ended search where you do not know the exact symbol name, use the `semantic_search` tool first to narrow down the search scope, then follow up with `Grep` and/or `Read`"
/** Resolve Kilo-specific tool Infos outside any InstanceState, so their Truncate/Agent deps are
* satisfied at the outer registry scope instead of leaking into InstanceState's Effect. */
export function infos() {
@@ -84,4 +87,12 @@ export namespace KiloToolRegistry {
...(Flag.KILO_CLIENT === "vscode" && cfg.experimental?.agent_manager_tool === true ? [tools.manager] : []),
]
}
export function describe(tools: Tool.Def[], extra: { semantic?: Tool.Def }): Tool.Def[] {
if (!extra.semantic) return tools
return tools.map((tool) => {
if (tool.id !== "glob" && tool.id !== "grep") return tool
return { ...tool, description: `${tool.description}\n${hint}` }
})
}
}
-1
View File
@@ -5,5 +5,4 @@
- Returns file paths and line numbers with at least one match sorted by modification time
- Use this tool when you need to find files containing specific patterns
- If you need to identify/count the number of matches within files, use the Bash tool with `rg` (ripgrep) directly. Do NOT use `grep`.
- When you are doing an open-ended search where you do not know the exact symbol name, use the SemanticSearch tool instead
- When you are doing a deep search that may require multiple tool invocations, use the Task tool instead
+25 -22
View File
@@ -226,28 +226,31 @@ export const layer: Layer.Layer<
return {
custom,
builtin: [
tool.invalid,
...(questionEnabled ? [tool.question] : []),
tool.bash,
tool.read,
tool.glob,
tool.grep,
tool.edit,
tool.write,
tool.task,
tool.fetch,
tool.todo,
tool.search,
tool.skill,
tool.patch,
// kilocode_change start
tool.plan,
...(["cli", "vscode"].includes(Flag.KILO_CLIENT) ? [tool.suggest] : []),
...KiloToolRegistry.extra(kilo, cfg),
// kilocode_change end
...(Flag.KILO_EXPERIMENTAL_LSP_TOOL ? [tool.lsp] : []),
],
builtin: KiloToolRegistry.describe(
[
tool.invalid,
...(questionEnabled ? [tool.question] : []),
tool.bash,
tool.read,
tool.glob,
tool.grep,
tool.edit,
tool.write,
tool.task,
tool.fetch,
tool.todo,
tool.search,
tool.skill,
tool.patch,
// kilocode_change start
tool.plan,
...(["cli", "vscode"].includes(Flag.KILO_CLIENT) ? [tool.suggest] : []),
...KiloToolRegistry.extra(kilo, cfg),
// kilocode_change end
...(Flag.KILO_EXPERIMENTAL_LSP_TOOL ? [tool.lsp] : []),
],
kilo,
), // kilocode_change
task: tool.task,
read: tool.read,
}
@@ -1,10 +1,12 @@
import { afterEach, describe, expect, spyOn, test } from "bun:test"
import { Effect, Layer, Schema } from "effect"
import * as Log from "@opencode-ai/core/util/log"
import { Agent } from "../../src/agent/agent"
import { KiloIndexing } from "../../src/kilocode/indexing"
import { KilocodeBootstrap } from "../../src/kilocode/bootstrap"
import { KiloSessions } from "../../src/kilo-sessions/kilo-sessions"
import { KiloToolRegistry } from "../../src/kilocode/tool/registry"
import { ModelID, ProviderID } from "../../src/provider/schema"
import { ToolRegistry } from "../../src/tool/registry"
import type * as Tool from "../../src/tool/tool"
import { Instance } from "../../src/project/instance"
@@ -13,7 +15,11 @@ import * as CrossSpawnSpawner from "@opencode-ai/core/cross-spawn-spawner"
import { testEffect } from "../lib/effect"
const node = CrossSpawnSpawner.defaultLayer
const it = testEffect(Layer.mergeAll(ToolRegistry.defaultLayer, node))
const it = testEffect(Layer.mergeAll(Agent.defaultLayer, ToolRegistry.defaultLayer, node))
const ref = {
providerID: ProviderID.make("test"),
modelID: ModelID.make("test-model"),
}
afterEach(async () => {
await disposeAllInstances()
@@ -120,6 +126,56 @@ describe("kilocode tool registry indexing", () => {
),
)
it.live("omits semantic_search hint from glob and grep descriptions when indexing is not ready", () =>
provideTmpdirInstance(
() =>
Effect.gen(function* () {
const ready = spyOn(KiloIndexing, "ready").mockReturnValue(false)
try {
const agent = yield* Agent.Service
const build = yield* agent.get("build")
const registry = yield* ToolRegistry.Service
const tools = yield* registry.tools({ ...ref, agent: build })
const glob = tools.find((tool) => tool.id === "glob")?.description ?? ""
const grep = tools.find((tool) => tool.id === "grep")?.description ?? ""
expect(glob).not.toContain("semantic_search")
expect(grep).not.toContain("semantic_search")
} finally {
ready.mockRestore()
}
}),
{ git: true },
),
)
it.live("includes semantic_search hint in glob and grep descriptions when indexing is ready", () =>
provideTmpdirInstance(
() =>
Effect.gen(function* () {
const ready = spyOn(KiloIndexing, "ready").mockReturnValue(true)
try {
const agent = yield* Agent.Service
const build = yield* agent.get("build")
const registry = yield* ToolRegistry.Service
const tools = yield* registry.tools({ ...ref, agent: build })
const ids = tools.map((tool) => tool.id)
const glob = tools.find((tool) => tool.id === "glob")?.description ?? ""
const grep = tools.find((tool) => tool.id === "grep")?.description ?? ""
expect(ids).toContain("semantic_search")
expect(glob).toContain("semantic_search")
expect(grep).toContain("semantic_search")
} finally {
ready.mockRestore()
}
}),
{ git: true },
),
)
test("conditionally includes Kilo registry extras", () => {
const prev = process.env["KILO_CLIENT"]
const def = (id: string): Tool.Def => ({