Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 5 additions & 3 deletions packages/core/src/codemode/instructions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,15 @@ import { CodeModeCatalog } from "./catalog.js"
// prettier-ignore
const prompt = (hasMoreTools: boolean) => `# Code Mode

Use the \`execute\` tool to call the tools listed below. They cannot be called directly${hasMoreTools ? ", and neither can \`search\`. Both" : ". They"} only work inside code you pass to \`execute\`.
Code Mode adds the extra tools listed below, which you call from JavaScript inside the \`execute\` tool. Your regular tools (such as \`read\`, \`edit\`, \`shell\` and \`grep\`) are separate: keep calling them directly as usual. They do not exist inside \`execute\`.

${hasMoreTools ? `The catalog is partial. Inside \`execute\`, use \`search(...)\` to find a tool, then call it by the \`path\` in the result. \`search\` is synchronous. Call it without \`await\`; it does not return a Promise. Do not guess tool names.
The tools listed below cannot be called directly${hasMoreTools ? ", and neither can \`search\`. Both" : ". They"} only work inside code you pass to \`execute\`.

${hasMoreTools ? `The catalog is partial. Before calling a tool that is not listed below, find it with the global \`search(...)\` function (not \`tools.search\`). Do not guess tool names. \`search\` is synchronous. Call it without \`await\`; it does not return a Promise. Each result's \`path\` is the complete call expression: write it out as code, for example \`await tools.ns.tool_name({ ... })\`. Do not use it as a key such as \`tools[path]\`.

- ${searchSignature}` : "The catalog is complete. Do not guess tool names."}

## Available tools`
## Code Mode tools (call only inside \`execute\`)`

export function render(catalog: CodeModeCatalog.Summary) {
if (catalog.total === 0)
Expand Down
21 changes: 12 additions & 9 deletions packages/core/test/codemode/catalog.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -117,21 +117,24 @@ describe("CodeModeCatalog.summarize", () => {
describe("CodeModeInstructions.render", () => {
test("inlines complete catalogs without search guidance", () => {
const instructions = render([lookup])
expect(instructions).toContain("## Available tools")
expect(instructions).toContain("## Code Mode tools (call only inside `execute`)")
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("They cannot be called directly. They only work inside code you pass to `execute`.")
expect(instructions).toContain("Your regular tools (such as `read`, `edit`, `shell` and `grep`) are separate")
expect(instructions).toContain(
"The tools listed below cannot be called directly. They only work inside code you pass to `execute`.",
)
expect(instructions).toContain("The catalog is complete. Do not guess tool names.")
})

test("adds search guidance when the catalog exceeds the budget", () => {
const partial = render([lookup], 0)
expect(partial).toContain("## Available tools")
expect(partial).toContain("## Code Mode tools (call only inside `execute`)")
expect(partial).toContain("- orders (1 tool, none shown)")
expect(partial).toContain("They cannot be called directly, and neither can `search`.")
expect(partial).toContain("The tools listed below cannot be called directly, and neither can `search`.")
expect(partial).toContain(
"The catalog is partial. Inside `execute`, use `search(...)` to find a tool, then call it by the `path` in the result. `search` is synchronous. Call it without `await`; it does not return a Promise.",
"The catalog is partial. Before calling a tool that is not listed below, find it with the global `search(...)` function (not `tools.search`). Do not guess tool names. `search` is synchronous. Call it without `await`; it does not return a Promise.",
)
expect(partial).toContain("- search({")
expect(partial).toContain(" /** @integer @exclusiveMinimum 0 */\n limit?: number,")
Expand Down Expand Up @@ -194,7 +197,7 @@ describe("CodeModeInstructions.update", () => {
`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")
expect(text).not.toContain("## Code Mode tools (call only inside `execute`)")
})

test("names removed tools with exact callable expressions including bracket notation", () => {
Expand Down Expand Up @@ -225,14 +228,14 @@ describe("CodeModeInstructions.update", () => {
"The Code Mode tool catalog has changed. This catalog supersedes the previous Code Mode tool catalog.",
)
expect(text).toContain("The catalog is partial.")
expect(text).toContain("## Available tools")
expect(text).toContain("## Code Mode tools (call only inside `execute`)")
})

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).toContain("## Code Mode tools (call only inside `execute`)")
expect(text).toContain("The catalog is complete.")
expect(text).not.toContain("The following tools are no longer available")
})
Expand All @@ -243,6 +246,6 @@ describe("CodeModeInstructions.update", () => {
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")
expect(text).not.toContain("## Code Mode tools (call only inside `execute`)")
})
})
8 changes: 5 additions & 3 deletions packages/core/test/codemode/instructions.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,16 +46,18 @@ describe("CodeModeInstructions", () => {
it.effect("renders the initial catalog, semantic deltas, and removal", () =>
Effect.gen(function* () {
const initialized = yield* readInitial(CodeModeInstructions.make({ tools: [echo] }))
expect(initialized.text).toContain("Use the `execute` tool to call the tools listed below.")
expect(initialized.text).toContain(
"Code Mode adds the extra tools listed below, which you call from JavaScript inside the `execute` tool.",
)
expect(initialized.text).toContain("The catalog is complete.")
expect(initialized.text).toContain("## Available tools")
expect(initialized.text).toContain("## Code Mode tools (call only inside `execute`)")
expect(initialized.text).toContain(` - ${echo.signature} // Echo text`)

const added = yield* readUpdate(CodeModeInstructions.make({ tools: [echo, lookup] }), initialized)
expect(added.text).toContain("The Code Mode tool catalog has changed.")
expect(added.text).toContain("New tools are available in addition to those previously listed:")
expect(added.text).toContain(` - ${lookup.signature} // Look up an order`)
expect(added.text).not.toContain("## Available tools")
expect(added.text).not.toContain("## Code Mode tools (call only inside `execute`)")

const removed = yield* readUpdate(CodeModeInstructions.make({ tools: [echo] }), { values: added.values })
expect(removed.text).toBe(
Expand Down
Loading