feat(opencode): state whether the code mode tool list is complete or partial

The preview now reports its own comprehensiveness so the model knows when it
has the whole catalog vs. when it must search:

- Overall: "This is the COMPLETE list ..." when every tool fits the budget,
  else "This is a PARTIAL list — X of Y tools are shown ...".
- Per namespace: a fully-shown server reads `- github (2 tools)`; a truncated
  one is annotated `- alpha (70 tools, 31 shown)` or `- zeta (1 tool, none shown)`.

When complete, the model isn't pushed toward needless $rune.search calls; when
partial, exactly what's missing is unambiguous.
This commit is contained in:
Aiden Cline
2026-06-30 16:56:37 -05:00
parent 55aa8cce44
commit dc75ea0cc0
2 changed files with 48 additions and 22 deletions
+41 -17
View File
@@ -200,26 +200,50 @@ export function describe(groups: Map<string, CatalogEntry[]>): string {
lines.push("", "No MCP servers are currently connected.")
return lines.join("\n")
}
// Select which signatures fit the budget (cheapest first within each server,
// servers alphabetical) before emitting, so the list can state exactly how
// comprehensive it is — overall and per namespace.
const ordered = [...groups].sort(([a], [b]) => a.localeCompare(b))
const shown = new Map<string, string[]>()
let used = 0
let budgetLeft = true
let totalTools = 0
let totalShown = 0
for (const [server, entries] of ordered) {
totalTools += entries.length
const picked: string[] = []
if (budgetLeft) {
for (const entry of entries) {
const line = ` - ${previewSignature(entry)}`
if (used + line.length > PREVIEW_BUDGET) {
budgetLeft = false
break
}
picked.push(line)
used += line.length
}
}
shown.set(server, picked)
totalShown += picked.length
}
const complete = totalShown === totalTools
lines.push(
"",
"Every connected server is listed below with its tool count. A budgeted sample of tool",
"signatures is shown inline; any tool not shown must be found with `tools.$rune.search` /",
"`tools.$rune.describe` before you call it.",
complete
? "This is the COMPLETE list of available tools — every connected tool is shown below with its call signature. Use `tools.$rune.describe(path)` for a tool's full types."
: `This is a PARTIAL list — ${totalShown} of ${totalTools} tools are shown below. Any tool not listed must be found with \`tools.$rune.search\` first; use \`tools.$rune.describe(path)\` for full types.`,
)
let used = 0
let previewing = true
for (const [server, entries] of [...groups].sort(([a], [b]) => a.localeCompare(b))) {
lines.push(`- ${server} (${entries.length} tool${entries.length === 1 ? "" : "s"})`)
if (!previewing) continue
for (const entry of entries) {
const line = ` - ${previewSignature(entry)}`
if (used + line.length > PREVIEW_BUDGET) {
previewing = false
break
}
lines.push(line)
used += line.length
}
for (const [server, entries] of ordered) {
const picked = shown.get(server)!
const total = entries.length
const count = `${total} tool${total === 1 ? "" : "s"}`
// Annotate only when a namespace is not fully shown, so a comprehensive
// namespace reads cleanly and a truncated one is unambiguous.
const label =
picked.length === total ? count : picked.length === 0 ? `${count}, none shown` : `${count}, ${picked.length} shown`
lines.push(`- ${server} (${label})`)
for (const line of picked) lines.push(line)
}
return lines.join("\n")
}
@@ -82,7 +82,8 @@ describe("code mode execute", () => {
expect(description).toContain("tools.$rune.search(query")
expect(description).toContain("tools.$rune.describe(path)")
// Every namespace is always listed, with counts.
// Small catalog: the list is comprehensive and says so, with clean counts.
expect(description).toContain("This is the COMPLETE list")
expect(description).toContain("- github (2 tools)")
expect(description).toContain("- linear (1 tool)")
// Tools are previewed inline as compact, directly-callable input signatures.
@@ -104,10 +105,11 @@ describe("code mode execute", () => {
const groups = groupByServer(tools, ["alpha", "zeta"])
const description = describeTools(groups)
// Every namespace is always present, with counts.
expect(description).toContain("- alpha (60 tools)")
expect(description).toContain("- zeta (1 tool)")
// The preview is budget-capped, so the later namespace's signatures are not all inlined.
// The list states it is partial, and every namespace is still present with its total.
expect(description).toContain("This is a PARTIAL list")
expect(description).toContain("- alpha (60 tools")
// The later namespace is fully truncated, and says so.
expect(description).toContain("- zeta (1 tool, none shown)")
expect(description).not.toContain("tools.zeta.only_tool(")
// Some early signatures are still previewed.
expect(description).toContain("tools.alpha.op_0(")