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()).
This commit is contained in:
Aiden Cline
2026-06-30 13:59:33 -05:00
parent a3bfba809f
commit cbcc67b1e2
2 changed files with 7 additions and 13 deletions
+3 -3
View File
@@ -181,9 +181,9 @@ export function describe(groups: Map<string, CatalogEntry[]>): string {
"- 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? }`.",
"`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.",
@@ -151,12 +151,11 @@ export const catalog = <R>(tools: HostTools<R>): ReadonlyArray<ToolDescription>
// `$rune` namespace), not by the runtime, so there are no reserved namespaces.
export const assertValidTools = <R>(_tools: HostTools<R>): 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 = <R>(tools: HostTools<R>): 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 = <R>(tools: HostTools<R>): 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 = <R>(tools: HostTools<R>, path: ReadonlyArray<string>): 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<R> | Definition<R> | HostTools<R>
}