From cbcc67b1e25812400e8da7c8e731c1399acdfad2 Mon Sep 17 00:00:00 2001 From: Aiden Cline Date: Tue, 30 Jun 2026 13:59:33 -0500 Subject: [PATCH] refactor(opencode): single source of discovery docs, clearer attachment wording - Reword the attachment line in the execute description: attachments are opaque media handles you forward to the user, not byte-readable in code. - Strip the hardcoded tools.$rune.search/describe block from the runtime's instructions(): discovery is not a runtime feature (the embedder registers and documents it), and the baked-in object-arg form contradicted code mode's positional API. Removes the only prompt discrepancy. - Make the runtime's unknown-tool suggestion generic instead of naming $rune. Discovery is now documented in exactly one place (code-mode describe()). --- packages/opencode/src/session/code-mode.ts | 6 +++--- packages/opencode/src/session/rune/tool-runtime.ts | 14 ++++---------- 2 files changed, 7 insertions(+), 13 deletions(-) diff --git a/packages/opencode/src/session/code-mode.ts b/packages/opencode/src/session/code-mode.ts index 8289d90964..7a35f39636 100644 --- a/packages/opencode/src/session/code-mode.ts +++ b/packages/opencode/src/session/code-mode.ts @@ -181,9 +181,9 @@ export function describe(groups: Map): string { "- Call a tool by its path: `await tools..(input)`. Each resolves to `{ result, attachments? }`.", "", "Every tool call and your final `return` use the same envelope: `{ result, attachments? }`.", - "`result` is the structured data; `attachments` carries images/files for the user. Return a whole tool", - "result to forward its attachments, or return only its `.result` to drop the media. You cannot read the", - "contents of an attachment in code — only pass it along.", + "`result` is the structured data you compute over; `attachments` carry media (images, files) to show", + "the user. Return a tool's whole result to keep its attachments, or return just its `.result` to drop", + "them. Attachments are opaque handles — pass them through; their bytes aren't available in code.", "", "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.$rune.search('', { namespace })` to list a namespace.", diff --git a/packages/opencode/src/session/rune/tool-runtime.ts b/packages/opencode/src/session/rune/tool-runtime.ts index 77e74f6e6a..fe2c021390 100644 --- a/packages/opencode/src/session/rune/tool-runtime.ts +++ b/packages/opencode/src/session/rune/tool-runtime.ts @@ -151,12 +151,11 @@ export const catalog = (tools: HostTools): ReadonlyArray // `$rune` namespace), not by the runtime, so there are no reserved namespaces. export const assertValidTools = (_tools: HostTools): void => {} +// Generic Rune-language instructions. Discovery (e.g. searching a large or dynamic +// catalog) is not a runtime feature; an embedder that wants it registers ordinary +// host tools and documents them itself, so nothing is hardcoded here. export const instructions = (tools: HostTools): string => { const described = catalog(tools) - const discovery = [ - "- tools.$rune.search({ query: string, limit?: number }): Promise<{ items: Array<{ path: string; description: string }>; total: number }>", - "- tools.$rune.describe({ path: string }): Promise<{ path: string; description: string; signature: string }>", - ] const lines = [ "Write a Rune Program to answer the request. Return code only.", "Rune Programs can call explicit tools.* capabilities and transform plain data.", @@ -165,11 +164,6 @@ export const instructions = (tools: HostTools): string => { "Available Tool Capabilities:", ...described.map((tool) => `- ${tool.signature} // ${tool.description}`), "", - ...(discovery.length > 0 ? [ - "For a large or dynamic catalog, you can discover additional capabilities in the program:", - ...discovery, - "", - ] : []), "Common syntax: arrow functions and `function` declarations (hoisted) with closures, default/rest parameters, destructuring (incl. rest/defaults), optional chaining, template literals, conditionals, switch, loops, spread (arrays/objects/strings), try/catch, ternary, the `in` operator, logical assignment (??=/||=/&&=), and bitwise operators (& | ^ ~ << >> >>>). Signal failure with `throw` (any value) or `throw new Error(message)`.", "Transform data with array methods (map/filter/reduce/reduceRight/flatMap/forEach/find/findIndex/findLast/findLastIndex/sort/toSorted/slice/concat/indexOf/at/flat/reverse/toReversed/with/includes/join, plus push/pop/shift/unshift for accumulation), string methods (toLowerCase/toUpperCase/trim/split/slice/substring/replace/replaceAll/includes/startsWith/endsWith/indexOf/padStart/padEnd/repeat/charCodeAt), number methods (toFixed/toString(radix)/toPrecision), Object.keys/values/entries/fromEntries/hasOwn, Math.* (incl. PI/E), JSON.parse/stringify, Array.from/isArray/of, Number.isInteger/isNaN/parseInt, String.fromCharCode, parseInt/parseFloat, and Number/String/Boolean.", "Use Promise.all([...]) for parallel tool calls (a direct array of calls, or items.map((item) => tool call)).", @@ -182,7 +176,7 @@ const resolve = (tools: HostTools, path: ReadonlyArray): HostTool< for (const segment of path) { if (isBlockedMember(segment) || typeof value === "function" || isDefinition(value) || !Object.hasOwn(value, segment)) { - throw new ToolRuntimeError("UnknownCapability", `Unknown tool '${path.join(".")}'.`, ["Use tools.$rune.search({ query }) to find available described capabilities."]) + throw new ToolRuntimeError("UnknownCapability", `Unknown tool '${path.join(".")}'.`, ["Call a capability by its exact tools.* path."]) } value = value[segment] as HostTool | Definition | HostTools }