| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203 |
- 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, pinned = false): CodeModeCatalog.Entry => ({
- path,
- description,
- signature: signature ?? `tools.${path}(input: {\n q: string,\n}): Promise<string>`,
- pinned,
- })
- const lookup = entry(
- "orders.lookup",
- "Look up an order by ID",
- "tools.orders.lookup(input: {\n id: string,\n}): Promise<{\n id: string,\n status: string,\n}>",
- )
- const render = (entries: ReadonlyArray<CodeModeCatalog.Entry>, budget?: number) =>
- CodeModeInstructions.render(CodeModeCatalog.summarize(entries, budget))
- const update = (
- previous: ReadonlyArray<CodeModeCatalog.Entry>,
- current: ReadonlyArray<CodeModeCatalog.Entry>,
- budget?: number,
- ) =>
- CodeModeInstructions.update(CodeModeCatalog.summarize(previous, budget), CodeModeCatalog.summarize(current, budget))
- describe("CodeModeCatalog.summarize", () => {
- test("retains namespace inventory without retaining tools outside the inline budget", () => {
- const catalog = CodeModeCatalog.summarize(
- Array.from({ length: 10_000 }, (_, index) => entry(`bulk.tool${index}`, `Tool ${index}`)),
- 0,
- )
- expect(catalog).toEqual({
- total: 10_000,
- shown: 0,
- namespaces: [{ name: "bulk", count: 10_000, entries: [] }],
- })
- })
- test("retains every namespace when no full tool listing fits", () => {
- const catalog = CodeModeCatalog.summarize(
- [entry("alpha.one", "One"), entry("beta.two", "Two"), entry("gamma.three", "Three")],
- 0,
- )
- expect(catalog.namespaces.map((namespace) => namespace.name)).toEqual(["alpha", "beta", "gamma"])
- 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")
- })
- test("limits inline descriptions to 120 characters", () => {
- const catalog = CodeModeCatalog.summarize([entry("alpha.one", "x".repeat(121))])
- const description = catalog.namespaces[0]?.entries[0]?.line.split(" // ")[1]
- expect(description).toHaveLength(120)
- expect(description).toEndWith("...")
- })
- })
- describe("CodeModeInstructions.render", () => {
- test("inlines complete catalogs without search guidance", () => {
- const instructions = render([lookup])
- expect(instructions).toContain("## Available tools")
- expect(instructions).toContain("- orders (1 tool)")
- expect(instructions).toContain(` - ${lookup.signature} // Look up an order by ID`)
- expect(instructions).not.toContain("## Search")
- expect(instructions).toContain("The Code Mode tool catalog below is complete.")
- expect(instructions).toContain("This catalog is the complete set of tools available within Code Mode.")
- expect(instructions).not.toContain("surrounding top-level agent tools")
- })
- test("adds search guidance when the catalog exceeds the budget", () => {
- const partial = render([lookup], 0)
- expect(partial).toContain("## Available tools")
- expect(partial).toContain("- orders (1 tool, none shown)")
- expect(partial).toContain("## Search")
- expect(partial).toContain("The Code Mode tool catalog below is partial.")
- expect(partial).toContain(
- "The Code Mode catalog and `search` results are the complete set of tools available within Code Mode.",
- )
- expect(partial).not.toContain("surrounding top-level agent tools")
- expect(partial).toContain("- search(input: {")
- expect(partial).toContain(" limit?: number,\n offset?: number,")
- expect(partial).not.toContain("tools.orders.lookup(input:")
- })
- test("budgets signatures round-robin so every namespace remains visible", () => {
- const cheapAlpha = entry("alpha.cheap", "Cheap")
- const cheapBeta = entry("beta.cheap", "Cheap")
- const expensive = entry(
- "alpha.expensive",
- "Expensive",
- `tools.alpha.expensive(input: {\n aVeryLongParameterName: string,\n anotherEvenLongerParameterName: number,\n yetAnotherExtremelyVerboseParameterName: string,\n}): Promise<string>`,
- )
- // Round 1 places alpha.cheap and beta.cheap; in round 2 alpha.expensive does not fit,
- // which marks only alpha done - it must NOT prevent other namespaces from inlining.
- const instructions = render([cheapAlpha, expensive, cheapBeta], 40)
- expect(instructions).toContain("## Search")
- expect(instructions).toContain("- alpha (2 tools, 1 shown)")
- expect(instructions).toContain(` - ${cheapAlpha.signature} // Cheap`)
- expect(instructions).not.toContain("tools.alpha.expensive(")
- expect(instructions).toContain("- beta (1 tool)")
- expect(instructions).toContain(` - ${cheapBeta.signature} // Cheap`)
- })
- test("charges inline JSDoc in signatures against the catalog token budget", () => {
- const documented = entry(
- "records.lookup",
- "Look up a record",
- `tools.records.lookup(input: {\n /** ${"A detailed identifier description. ".repeat(20).trim()} */\n id: string,\n}): Promise<string>`,
- )
- const instructions = render([documented], 40)
- expect(instructions).toContain("- records (1 tool, none shown)")
- expect(instructions).not.toContain("tools.records.lookup(input:")
- })
- test("renders only the no-tools notice for an empty catalog", () => {
- expect(render([])).toBe(
- "No Code Mode tools are currently available. Later Code Mode catalog updates may add or remove tools. Do not call `execute` unless there is at least one available Code Mode tool.",
- )
- })
- })
- describe("CodeModeInstructions.update", () => {
- const echo = entry("notes.echo", "Echo text")
- test("renders additions, changes, and removals as a compact semantic delta", () => {
- const changed = { ...echo, signature: "tools.notes.echo(input: {\n text: string,\n}): Promise<string>" }
- const added = entry("notes.list", "List notes")
- const unchanged = Array.from({ length: 5 }, (_, index) => entry(`stable.tool${index}`, `Stable ${index}`))
- const text = update([echo, lookup, ...unchanged], [changed, added, ...unchanged])
- expect(text).toContain("The Code Mode tool catalog has changed.")
- expect(text).toContain(`New tools are available in addition to those previously listed:\n - ${added.signature}`)
- expect(text).toContain(
- `Changed tool listings supersede the previously listed ones:\n - ${changed.signature} // Echo text`,
- )
- expect(text).toContain("The following tools are no longer available and must not be called: tools.orders.lookup.")
- expect(text).not.toContain("## Available tools")
- })
- test("names removed tools with exact callable expressions including bracket notation", () => {
- const dashed = entry("context7.resolve-library-id", "Resolve a library ID")
- const text = update([echo, dashed], [echo])
- expect(text).toContain(
- 'The following tools are no longer available and must not be called: tools.context7["resolve-library-id"].',
- )
- })
- test("restates the full catalog when the rendering mode crosses full and compact", () => {
- const wide = Array.from({ length: 40 }, (_, index) => entry(`bulk.tool${index}`, `Tool ${index}`))
- const text = update([echo], [echo, ...wide], 30)
- expect(text).toContain(
- "The Code Mode tool catalog has changed. This catalog supersedes the previous Code Mode tool catalog.",
- )
- expect(text).toContain("## Search")
- expect(text).toContain("## Available tools")
- })
- test("falls back to full replacement when the delta is larger than the catalog", () => {
- const previous = Array.from({ length: 200 }, (_, index) => entry(`bulk.tool${index}`, `Tool ${index}`))
- const text = update([...previous, echo], [echo])
- expect(text).toContain("This catalog supersedes the previous Code Mode tool catalog.")
- expect(text).toContain("## Available tools")
- expect(text).not.toContain("## Search")
- expect(text).not.toContain("The following tools are no longer available")
- })
- test("renders namespace-only deltas without persisting hidden tool entries", () => {
- const alpha = Array.from({ length: 10 }, (_, index) => entry(`alpha.tool${index}`, `Tool ${index}`))
- const text = update(alpha, [...alpha, entry("alpha.tool10", "Tool 10")], 0)
- expect(text).toContain("`alpha` now has 11 tools")
- expect(text).toContain("search them again before relying on previous results")
- expect(text).not.toContain("tools.alpha.tool10(input:")
- expect(text).not.toContain("## Available tools")
- })
- })
|