feat(core): render CodeMode catalog deltas from structured snapshots (#38183)

This commit is contained in:
Aiden Cline 2026-07-24 10:20:26 -05:00 committed by GitHub
commit 4605308be2
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
19 changed files with 565 additions and 431 deletions

View file

@ -0,0 +1,103 @@
export * as CodeModeCatalog from "./catalog"
import { Schema } from "effect"
export const Entry = Schema.Struct({
path: Schema.String,
description: Schema.String,
signature: Schema.String,
})
export type Entry = typeof Entry.Type
const Listing = Schema.Struct({
path: Schema.String,
line: Schema.String,
})
const Namespace = Schema.Struct({
name: Schema.String,
count: Schema.Number,
entries: Schema.Array(Listing),
})
export const Summary = Schema.Struct({
total: Schema.Number,
shown: Schema.Number,
namespaces: Schema.Array(Namespace),
})
export type Summary = typeof Summary.Type
const DESCRIPTION_LIMIT = 120
const CHARACTERS_PER_TOKEN = 4
const INLINE_BUDGET = 2_000
// Keep every namespace searchable, then select full listings one per namespace per round,
// considering shorter listings first until the inline budget is exhausted.
export function summarize(entries: ReadonlyArray<Entry>, budget = INLINE_BUDGET): Summary {
const namespaces = [...Map.groupBy(entries, (entry) => entry.path.split(".", 1)[0] ?? entry.path)]
.sort(([left], [right]) => {
if (left < right) return -1
if (left > right) return 1
return 0
})
.map(([name, namespaceEntries]) => {
const listings = namespaceEntries
.map((entry) => {
const firstLine = entry.description.split("\n", 1)[0]?.trim() ?? ""
const description =
firstLine.length > DESCRIPTION_LIMIT
? firstLine.slice(0, DESCRIPTION_LIMIT - 3) + "..."
: firstLine
const suffix = description.length === 0 ? "" : ` // ${description}`
return { path: entry.path, line: ` - ${entry.signature}${suffix}` }
})
.toSorted((left, right) => {
if (left.path < right.path) return -1
if (left.path > right.path) return 1
return 0
})
return {
name,
listings,
selectionOrder: rankListings(listings),
selectedListings: new Set<typeof Listing.Type>(),
}
})
const active = new Set(namespaces)
let remaining = budget
while (active.size > 0) {
for (const namespace of active) {
const candidate = namespace.selectionOrder[namespace.selectedListings.size]
if (!candidate || candidate.cost > remaining) {
active.delete(namespace)
continue
}
namespace.selectedListings.add(candidate.listing)
remaining -= candidate.cost
if (namespace.selectedListings.size === namespace.selectionOrder.length) active.delete(namespace)
}
}
const namespaceSummaries = namespaces.map((namespace) => ({
name: namespace.name,
count: namespace.listings.length,
entries: namespace.listings.filter((listing) => namespace.selectedListings.has(listing)),
}))
return {
total: entries.length,
shown: namespaceSummaries.reduce((total, namespace) => total + namespace.entries.length, 0),
namespaces: namespaceSummaries,
}
}
function rankListings(listings: ReadonlyArray<typeof Listing.Type>) {
return listings
.map((listing) => ({ listing, cost: Math.round(listing.line.length / CHARACTERS_PER_TOKEN) }))
.toSorted((left, right) => {
if (left.cost !== right.cost) return left.cost - right.cost
if (left.listing.path < right.listing.path) return -1
if (left.listing.path > right.listing.path) return 1
return 0
})
}

View file

@ -1,24 +1,141 @@
export * as CodeModeInstructions from "./instructions"
import { searchSignature, toolExpression } from "@opencode-ai/codemode"
import { Effect, Schema } from "effect"
import { Instructions } from "../instructions/index"
import { CodeModeCatalog } from "./catalog"
const key = Instructions.Key.make("core/codemode")
const codec = Schema.toCodecJson(Schema.String)
const render = {
initial: (current: string) => current,
changed: (_previous: string, current: string) =>
[
"The Code Mode tool catalog has changed. This catalog supersedes the previous Code Mode tool catalog.",
current,
].join("\n\n"),
removed: () => "Code Mode tools are no longer available. Do not use any previously listed Code Mode tools.",
// prettier-ignore
const prompt = (hasMoreTools: boolean) => `Run JavaScript to orchestrate tool calls and compose their results. Imports, direct filesystem access, and timers are unavailable. Do not use \`fetch\`; all external access goes through \`tools\`.
Prefer an explicit \`return\`; if omitted, the final top-level expression becomes the result. Await tool calls before returning; any calls still pending when execution ends are interrupted. Run independent calls concurrently with \`Promise.all\`.
Do not infer or normalize tool names; use only the exact signatures shown below${hasMoreTools ? " or returned by `search`" : ""}, preserving bracket notation such as \`tools.<namespace>["tool-name"](input)\`.${hasMoreTools ? `
## Search
Only some tool signatures are shown. Use \`search\` to discover exact paths and signatures for additional tools:
- ${searchSignature}` : ""}
## Available tools`
export function render(catalog: CodeModeCatalog.Summary) {
if (catalog.total === 0) return "No tools are currently available."
const tools = catalog.namespaces.flatMap((namespace) => {
const count = namespace.count === 1 ? "1 tool" : `${namespace.count} tools`
const label =
namespace.entries.length === namespace.count
? count
: namespace.entries.length === 0
? `${count}, none shown`
: `${count}, ${namespace.entries.length} shown`
return [`- ${namespace.name} (${label})`, ...namespace.entries.map((entry) => entry.line)]
})
return `${prompt(catalog.shown < catalog.total)}
${tools.join("\n")}`
}
export const make = (content?: string): Instructions.Instructions =>
Instructions.make({
export function update(previous: CodeModeCatalog.Summary, current: CodeModeCatalog.Summary) {
const full = `The Code Mode tool catalog has changed. This catalog supersedes the previous Code Mode tool catalog.
${render(current)}`
const previousComplete = previous.shown === previous.total
const currentComplete = current.shown === current.total
if (previousComplete !== currentComplete) return full
const diff = Instructions.diffByKey(
previous.namespaces.flatMap((namespace) => namespace.entries),
current.namespaces.flatMap((namespace) => namespace.entries),
(entry) => entry.path,
(before, after) => before.line !== after.line,
)
const entriesChanged = diff.added.length > 0 || diff.removed.length > 0 || diff.changed.length > 0
if (!currentComplete) {
if (entriesChanged) return full
const namespaces = Instructions.diffByKey(
previous.namespaces,
current.namespaces,
(namespace) => namespace.name,
(before, after) => before.count !== after.count,
)
const changed = namespaces.added.length > 0 || namespaces.removed.length > 0 || namespaces.changed.length > 0
if (!changed) return full
const parts = ["The Code Mode tool catalog has changed."]
if (namespaces.added.length > 0) {
parts.push(
`New tool namespaces are available: ${namespaces.added
.map((namespace) => `\`${namespace.name}\` (${namespace.count} tools)`)
.join(", ")}.`,
)
}
if (namespaces.changed.length > 0) {
parts.push(
`The following namespace inventories changed; search them again before relying on previous results: ${namespaces.changed
.map((change) => `\`${change.current.name}\` now has ${change.current.count} tools`)
.join(", ")}.`,
)
}
if (namespaces.removed.length > 0) {
parts.push(
`The following tool namespaces are no longer available and must not be used: ${namespaces.removed
.map((namespace) => `\`${namespace.name}\``)
.join(", ")}.`,
)
}
const delta = parts.join("\n\n")
if (delta.length < full.length) return delta
return full
}
if (!entriesChanged) return full
const parts = ["The Code Mode tool catalog has changed."]
if (diff.added.length > 0) {
parts.push(
[
"New tools are available in addition to those previously listed:",
...diff.added.map((entry) => entry.line),
].join("\n"),
)
}
if (diff.changed.length > 0) {
parts.push(
[
"Changed tool listings supersede the previously listed ones:",
...diff.changed.map((change) => change.current.line),
].join("\n"),
)
}
if (diff.removed.length > 0) {
parts.push(
`The following tools are no longer available and must not be called: ${diff.removed
.map((entry) => toolExpression(entry.path))
.join(", ")}.`,
)
}
const delta = parts.join("\n\n")
if (delta.length < full.length) return delta
return full
}
const key = Instructions.Key.make("core/codemode")
const codec = Schema.toCodecJson(CodeModeCatalog.Summary)
export const make = (entries?: ReadonlyArray<CodeModeCatalog.Entry>): Instructions.Instructions => {
const catalog = CodeModeCatalog.summarize(entries ?? [])
return Instructions.make({
key,
codec,
read: Effect.succeed(content ?? Instructions.removed),
render,
read: Effect.succeed(catalog.total === 0 ? Instructions.removed : catalog),
render: {
initial: render,
changed: update,
removed: () => "Code Mode tools are no longer available. Do not use any previously listed Code Mode tools.",
},
})
}