feat(opencode): unify code mode discovery under tools.$rune

Remove Rune's vestigial built-in $rune.search/$rune.describe (which only
saw effect-Schema Definitions, never our dynamic MCP tools) and the
reserved-namespace guard. Move our ranked search and typed describe under
tools.$rune.* — the runtime's own namespace, separate from MCP server
namespaces and collision-proof since $ never appears in a sanitized
server name. One discovery implementation, no dead code.
This commit is contained in:
Aiden Cline
2026-06-30 11:11:55 -05:00
parent ba12049ca5
commit a3bfba809f
4 changed files with 39 additions and 71 deletions
+21 -14
View File
@@ -20,7 +20,7 @@ const CODE_LIMITS: ExecutionLimits = {
export const Parameters = Schema.Struct({
code: Schema.String.annotate({
description: "JavaScript to run. Discover tools with `tools.search`/`tools.describe`, call them, and `return` the final value.",
description: "JavaScript to run. Discover tools with `tools.$rune.search`/`tools.$rune.describe`, call them, and `return` the final value.",
}),
})
@@ -42,6 +42,10 @@ export type Envelope = { result: unknown; attachments?: Attachment[] }
const IDENTIFIER = /^[A-Za-z_$][A-Za-z0-9_$]*$/
const SEARCH = "search"
const DESCRIBE = "describe"
// The runtime's own capabilities live under `tools.$rune.*`, separated from the
// MCP server namespaces. `$` can never appear in a sanitized server name, so this
// namespace is collision-proof.
const RUNE_NS = "$rune"
type CatalogEntry = {
path: string
@@ -164,15 +168,16 @@ const PREVIEW_BUDGET = 2000
/**
* The execute tool description: the calling convention, the discovery API, and the
* list of namespaces. A budgeted preview of individual tools is inlined; the full
* per-tool signatures are still fetched on demand with `tools.describe`.
* per-tool signatures are still fetched on demand with `tools.$rune.describe`.
*/
export function describe(groups: Map<string, CatalogEntry[]>): string {
const lines = [
"Execute JavaScript with access to connected MCP tools, grouped into namespaces (one per MCP server).",
"",
"Discover tools inside your program, then call them:",
"- `await tools.search(query, { namespace?, limit? })` -> `{ items: [{ path, description }], total }`",
"- `await tools.describe(path)` -> `{ path, description, signature, inputSchema, outputSchema? }`",
"The runtime provides two discovery capabilities under `tools.$rune` (its own namespace, separate",
"from your MCP servers):",
"- `await tools.$rune.search(query, { namespace?, limit? })` -> `{ items: [{ path, description }], total }`",
"- `await tools.$rune.describe(path)` -> `{ path, description, signature, inputSchema, outputSchema? }`",
"- Call a tool by its path: `await tools.<server>.<tool>(input)`. Each resolves to `{ result, attachments? }`.",
"",
"Every tool call and your final `return` use the same envelope: `{ result, attachments? }`.",
@@ -181,13 +186,13 @@ export function describe(groups: Map<string, CatalogEntry[]>): string {
"contents of an attachment in code — only pass it along.",
"",
"Compose multiple calls in one program and `return` the final value — intermediate results stay in the",
"sandbox and never re-enter the conversation. Use `tools.search('', { namespace })` to list a namespace.",
"sandbox and never re-enter the conversation. Use `tools.$rune.search('', { namespace })` to list a namespace.",
]
if (groups.size === 0) {
lines.push("", "No MCP servers are currently connected.")
return lines.join("\n")
}
lines.push("", "Available namespaces (use tools.search / tools.describe to explore tools not shown):")
lines.push("", "Available namespaces (use tools.$rune.search / tools.$rune.describe to explore tools not shown):")
let used = 0
let previewing = true
for (const [server, entries] of [...groups].sort(([a], [b]) => a.localeCompare(b))) {
@@ -456,11 +461,14 @@ export function define(
})
// The Rune host-tool tree: per-server namespaces (`tools.<server>.<tool>`)
// plus the top-level discovery helpers. The interpreter resolves and invokes
// these; approving `execute` does not approve any child call.
// plus the runtime's own discovery capabilities under `tools.$rune.*`. The
// interpreter resolves and invokes these; approving `execute` does not
// approve any child call.
const tools: HostTools = {
[SEARCH]: (query: unknown, options: unknown) => Effect.succeed(search(query, options)),
[DESCRIBE]: (path: unknown) => Effect.succeed(describeTool(path)),
[RUNE_NS]: {
[SEARCH]: (query: unknown, options: unknown) => Effect.succeed(search(query, options)),
[DESCRIBE]: (path: unknown) => Effect.succeed(describeTool(path)),
},
}
for (const entry of catalog) {
if (!entry.tool.execute) continue
@@ -487,11 +495,10 @@ export function define(
...(attachments && attachments.length > 0 ? { attachments } : {}),
} satisfies Tool.ExecuteResult<Metadata>
}
// Rune's built-in unknown-capability hint points at `$rune.search`; redirect
// the model to this integration's actual discovery entrypoint instead.
// Point the model at discovery when it references a tool that does not exist.
const hint =
result.error.kind === "UnknownCapability"
? "\nUse tools.search(query) to discover available tools."
? "\nUse tools.$rune.search(query) to discover available tools."
: ""
return {
title: "Code mode",
@@ -27,8 +27,6 @@ export type ToolDescription = {
export type SafeObject = Record<string, unknown>
const reservedNamespace = "$rune"
export class ToolReference {
constructor(readonly path: ReadonlyArray<string>) {}
}
@@ -149,11 +147,9 @@ const visibleDefinitions = <R>(tools: HostTools<R>) =>
export const catalog = <R>(tools: HostTools<R>): ReadonlyArray<ToolDescription> =>
visibleDefinitions(tools).map(({ description }) => description)
export const assertValidTools = <R>(tools: HostTools<R>): void => {
if (Object.hasOwn(tools, reservedNamespace)) {
throw new Error(`Tool namespace '${reservedNamespace}' is reserved for Rune discovery capabilities.`)
}
}
// Discovery is provided by the embedder as ordinary host tools (e.g. under a
// `$rune` namespace), not by the runtime, so there are no reserved namespaces.
export const assertValidTools = <R>(_tools: HostTools<R>): void => {}
export const instructions = <R>(tools: HostTools<R>): string => {
const described = catalog(tools)
@@ -214,7 +210,6 @@ export const make = <R>(
): ToolRuntime<R> => {
const calls: Array<ToolCall> = []
let auditBytes = 0
const visibleCatalog = visibleDefinitions(tools)
const checkedCopyIn = (value: unknown, label: string): unknown => {
const copied = copyIn(value, label, dataLimits)
@@ -248,40 +243,6 @@ export const make = <R>(
throw new ToolRuntimeError("InvalidDataValue", `Arguments for tool '${name}' exceed ${dataLimits.maxDataBytes} bytes.`)
}
const call = { name }
if (name === "$rune.search") {
const input = externalArgs[0]
if (externalArgs.length !== 1 || input === null || typeof input !== "object" || Array.isArray(input)) {
throw new ToolRuntimeError("InvalidToolInput", "tools.$rune.search expects { query?: string; limit?: number }.")
}
const request = input as { query?: unknown; limit?: unknown }
if (request.query !== undefined && typeof request.query !== "string") {
throw new ToolRuntimeError("InvalidToolInput", "tools.$rune.search query must be a string when provided.")
}
if (request.limit !== undefined && (typeof request.limit !== "number" || !Number.isFinite(request.limit) || request.limit <= 0)) {
throw new ToolRuntimeError("InvalidToolInput", "tools.$rune.search limit must be a positive number when provided.")
}
const query = typeof request.query === "string" ? request.query.toLowerCase() : ""
recordCall(call)
const matched = visibleCatalog
.filter((item) => `${item.path} ${item.definition.description}`.toLowerCase().includes(query))
.map((item) => ({ path: item.path, description: item.definition.description }))
const limit = typeof request.limit === "number" ? Math.floor(request.limit) : 12
return checkedCopyIn({ items: matched.slice(0, limit), total: matched.length }, "Result from tool '$rune.search'")
}
if (name === "$rune.describe") {
const input = externalArgs[0]
const requested = input !== null && typeof input === "object" && !Array.isArray(input)
? (input as { path?: unknown }).path
: undefined
if (externalArgs.length !== 1 || typeof requested !== "string") {
throw new ToolRuntimeError("InvalidToolInput", "tools.$rune.describe expects { path: string }.")
}
recordCall(call)
const found = visibleCatalog.find((item) => item.path === requested)
if (!found) throw new ToolRuntimeError("UnknownCapability", `Unknown tool '${String(requested)}'.`)
return checkedCopyIn(found.description, "Result from tool '$rune.describe'")
}
const tool = resolve(tools, path)
let describedInput: unknown
if (isDefinition(tool)) {
@@ -114,7 +114,7 @@ beforeAll(async () => {
describe("code mode integration (real MCP server)", () => {
test("describe exposes the typed return signature from the tool's outputSchema", async () => {
const out = await run("return await tools.describe('fixtures.add')")
const out = await run("return await tools.$rune.describe('fixtures.add')")
const desc = JSON.parse(out.output)
expect(desc.path).toBe("fixtures.add")
expect(desc.signature).toBe(
@@ -124,13 +124,13 @@ describe("code mode integration (real MCP server)", () => {
})
test("describe falls back to result: unknown when no outputSchema is declared", async () => {
const out = await run("return await tools.describe('fixtures.get_text')")
const out = await run("return await tools.$rune.describe('fixtures.get_text')")
const desc = JSON.parse(out.output)
expect(desc.signature).toContain("Promise<{ result: unknown; attachments?: Attachment[] }>")
})
test("search finds a tool by keyword", async () => {
const out = await run("return await tools.search('screenshot')")
const out = await run("return await tools.$rune.search('screenshot')")
const result = JSON.parse(out.output)
expect(result.items.map((i: any) => i.path)).toContain("fixtures.screenshot")
})
@@ -196,8 +196,8 @@ describe("code mode integration (real MCP server)", () => {
tool.execute(
{
code: `
await tools.search('add')
await tools.describe('fixtures.add')
await tools.$rune.search('add')
await tools.$rune.describe('fixtures.add')
await tools.fixtures.add({ a: 1, b: 1 })
return 'done'
`,
@@ -76,14 +76,14 @@ describe("code mode execute", () => {
)
const description = describeTools(groups)
expect(description).toContain("tools.search(query")
expect(description).toContain("tools.describe(path)")
expect(description).toContain("tools.$rune.search(query")
expect(description).toContain("tools.$rune.describe(path)")
expect(description).toContain("- github (2 tools)")
expect(description).toContain("- linear (1 tool)")
// Small catalog: individual tools are previewed inline as `<server>.<tool>`.
expect(description).toContain("github.create_issue")
expect(description).toContain("linear.search")
// ...but never full signatures (those come from tools.describe).
// ...but never full signatures (those come from tools.$rune.describe).
expect(description).not.toContain("): Promise<")
})
@@ -107,7 +107,7 @@ describe("code mode execute", () => {
expect(description).toContain("alpha.op_0")
})
test("tools.search and tools.describe expose the catalog on demand", async () => {
test("tools.$rune.search and tools.$rune.describe expose the catalog on demand", async () => {
const tool = await build({
github_create_issue: mcpTool("create_issue", () => "", {
type: "object",
@@ -119,14 +119,14 @@ describe("code mode execute", () => {
})
const searched = await Effect.runPromise(
tool.execute({ code: "return await tools.search('issue', { namespace: 'github' })" }, ctx),
tool.execute({ code: "return await tools.$rune.search('issue', { namespace: 'github' })" }, ctx),
)
const search = JSON.parse(searched.output)
expect(search.total).toBe(2)
expect(search.items.map((i: any) => i.path).sort()).toEqual(["github.create_issue", "github.list_issues"])
const described = await Effect.runPromise(
tool.execute({ code: "return await tools.describe('github.create_issue')" }, ctx),
tool.execute({ code: "return await tools.$rune.describe('github.create_issue')" }, ctx),
)
const desc = JSON.parse(described.output)
expect(desc.path).toBe("github.create_issue")
@@ -134,7 +134,7 @@ describe("code mode execute", () => {
"tools.github.create_issue(input: { title: string; body?: string }): Promise<{ result: unknown; attachments?: Attachment[] }>",
)
const missing = await Effect.runPromise(tool.execute({ code: "return await tools.describe('github.nope')" }, ctx))
const missing = await Effect.runPromise(tool.execute({ code: "return await tools.$rune.describe('github.nope')" }, ctx))
expect(JSON.parse(missing.output).error.code).toBe("tool_not_found")
})
@@ -223,7 +223,7 @@ describe("code mode execute", () => {
const output = await Effect.runPromise(tool.execute({ code: "return await tools.known.missing({})" }, ctx))
expect(output.metadata.error).toBe(true)
expect(output.output).toContain("Unknown tool 'known.missing'")
expect(output.output).toContain("tools.search")
expect(output.output).toContain("tools.$rune.search")
})
test("propagates an MCP tool error into the program", async () => {
@@ -306,7 +306,7 @@ describe("code mode execute", () => {
} as any,
}
const tool = await build(tools, defs)
const described = await Effect.runPromise(tool.execute({ code: "return await tools.describe('weather.current')" }, ctx))
const described = await Effect.runPromise(tool.execute({ code: "return await tools.$rune.describe('weather.current')" }, ctx))
const desc = JSON.parse(described.output)
expect(desc.signature).toBe(
"tools.weather.current(input: { city?: string }): Promise<{ result: { tempC: number; summary?: string }; attachments?: Attachment[] }>",
@@ -342,7 +342,7 @@ describe("code mode execute", () => {
}),
other_noop: mcpTool("noop", () => ""),
})
const out = await Effect.runPromise(tool.execute({ code: "return await tools.search('trace_id')" }, ctx))
const out = await Effect.runPromise(tool.execute({ code: "return await tools.$rune.search('trace_id')" }, ctx))
const result = JSON.parse(out.output)
expect(result.items.map((i: any) => i.path)).toEqual(["traces.lookup"])
})