feat(core): deliver CodeMode catalog through instructions

This commit is contained in:
Aiden Cline
2026-07-20 16:56:46 -05:00
parent 87d5b27668
commit 534343d05f
12 changed files with 273 additions and 33 deletions
+7
View File
@@ -120,6 +120,13 @@ export type Runtime<R = never> = {
readonly execute: (code: string) => Effect.Effect<Result, never, R>
}
export const description = [
"Run JavaScript in a confined Code Mode runtime through { code }.",
"Call Code Mode tools through `tools` using the exact paths and signatures from the instructions.",
"Use `search({ query })` to discover exact signatures when needed.",
"Await important calls and use `Promise.all` for independent calls.",
].join("\n")
const validateLimit = (name: keyof ExecutionLimits, value: number | undefined, minimum: number): number | undefined => {
if (value !== undefined && (!Number.isSafeInteger(value) || value < minimum)) {
throw new RangeError(`${name} must be a safe integer greater than or equal to ${minimum}.`)
+17
View File
@@ -500,6 +500,23 @@ describe("CodeMode schema flexibility", () => {
})
describe("CodeMode public contract", () => {
test("keeps the tool description independent from the catalog", () => {
const runtime = CodeMode.make({
tools: {
lookup: Tool.make({
description: "Look up a record",
input: Schema.Struct({}),
output: Schema.String,
run: () => Effect.succeed("found"),
}),
},
})
expect(CodeMode.description).toContain("confined Code Mode runtime")
expect(CodeMode.description).not.toContain("lookup")
expect(runtime.instructions()).toContain("tools.lookup(input:")
})
const lookup = Tool.make({
description: "Look up an order by ID",
input: Schema.Struct({ id: Schema.String }),
+80
View File
@@ -0,0 +1,80 @@
export * as CodeMode from "./codemode"
import { Context, Effect, Layer, Scope } from "effect"
import { makeLocationNode } from "./effect/app-node"
import { PermissionV2 } from "./permission"
import { ExecuteTool } from "./tool/execute"
import { permission, registrationEntries, type AnyTool } from "./tool/tool"
import { Tools } from "./tool/tools"
import { Wildcard } from "./util/wildcard"
export interface Materialization {
readonly tool?: AnyTool
readonly instructions?: string
}
export interface Interface {
readonly register: (
tools: Readonly<Record<string, AnyTool>>,
options?: Tools.RegisterOptions,
) => Effect.Effect<void, never, Scope.Scope>
readonly materialize: (permissions?: PermissionV2.Ruleset) => Effect.Effect<Materialization>
}
export class Service extends Context.Service<Service, Interface>()("@opencode/v2/CodeMode") {}
const layer = Layer.effect(
Service,
Effect.gen(function* () {
const local = new Map<
string,
Array<{ readonly token: object; readonly registration: ExecuteTool.Registration }>
>()
return Service.of({
register: Effect.fn("CodeMode.register")(function* (tools, options) {
const entries = registrationEntries(tools, options?.namespace)
if (entries.length === 0) return
yield* Effect.uninterruptible(
Effect.gen(function* () {
const token = {}
for (const entry of entries)
local.set(entry.key, [
...(local.get(entry.key) ?? []),
{ token, registration: { tool: entry.tool, name: entry.name, namespace: entry.namespace } },
])
yield* Effect.addFinalizer(() =>
Effect.sync(() => {
for (const entry of entries) {
const registrations = local.get(entry.key)?.filter((item) => item.token !== token) ?? []
if (registrations.length > 0) local.set(entry.key, registrations)
else local.delete(entry.key)
}
}),
)
}),
)
}),
materialize: Effect.fn("CodeMode.materialize")(function* (permissions) {
const registrations = new Map<string, ExecuteTool.Registration>()
const rules = permissions ?? []
for (const [name, entries] of local) {
const registration = entries.at(-1)?.registration
if (!registration) continue
const rule = rules.findLast((rule) => Wildcard.match(permission(registration.tool, name), rule.action))
if (rule?.resource === "*" && rule.effect === "deny") continue
registrations.set(name, registration)
}
if (registrations.size === 0) return {}
const executeRule = rules.findLast((rule) => Wildcard.match("execute", rule.action))
if (executeRule?.resource === "*" && executeRule.effect === "deny") return {}
return {
tool: ExecuteTool.create(registrations),
instructions: ExecuteTool.instructions(registrations),
}
}),
})
}),
)
export const node = makeLocationNode({ service: Service, layer, deps: [] })
@@ -0,0 +1,45 @@
export * as CodeModeInstructions from "./instructions"
import { Context, Effect, Layer, Schema } from "effect"
import { AgentV2 } from "../agent"
import { CodeMode } from "../codemode"
import { makeLocationNode } from "../effect/app-node"
import { Instructions } from "../instructions/index"
export interface Interface {
readonly load: (agent: AgentV2.Selection) => Effect.Effect<Instructions.Instructions>
}
export class Service extends Context.Service<Service, Interface>()("@opencode/v2/CodeModeInstructions") {}
const layer = Layer.effect(
Service,
Effect.gen(function* () {
const codeMode = yield* CodeMode.Service
return Service.of({
load: Effect.fn("CodeModeInstructions.load")(function* (selection) {
const instructions = selection.info
? (yield* codeMode.materialize(selection.info.permissions)).instructions
: undefined
return Instructions.make({
key: Instructions.Key.make("core/codemode"),
codec: Schema.toCodecJson(Schema.String),
read: Effect.succeed(instructions ?? Instructions.removed),
render: {
initial: (current) => current,
changed: (_previous, current) =>
[
"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.",
},
})
}),
})
}),
)
export const node = makeLocationNode({ service: Service, layer, deps: [CodeMode.node] })
+4
View File
@@ -2,6 +2,8 @@ import { Effect, Layer, LayerMap } from "effect"
import { AgentV2 } from "./agent"
import { AISDK } from "./aisdk"
import { Catalog } from "./catalog"
import { CodeMode } from "./codemode"
import { CodeModeInstructions } from "./codemode/instructions"
import { CommandV2 } from "./command"
import { Config } from "./config"
import { LayerNode } from "./effect/layer-node"
@@ -66,6 +68,7 @@ const locationServiceNodes = [
Pty.node,
Shell.node,
SkillV2.node,
CodeMode.node,
InstructionBuiltIns.node,
InstructionDiscovery.node,
LocationMutation.node,
@@ -77,6 +80,7 @@ const locationServiceNodes = [
ToolRegistry.toolsNode,
Image.node,
SkillInstructions.node,
CodeModeInstructions.node,
ReferenceInstructions.node,
InstructionEntry.node,
Form.node,
+4
View File
@@ -12,6 +12,7 @@ import { McpInstructions } from "../mcp/instructions"
import { PluginSupervisor } from "../plugin/supervisor"
import { ReferenceInstructions } from "../reference/instructions"
import { SkillInstructions } from "../skill/instructions"
import { CodeModeInstructions } from "../codemode/instructions"
import { AgentNotFoundError } from "./error"
import { SessionHistory } from "./history"
import { InstructionEntry } from "./instruction-entry"
@@ -54,6 +55,7 @@ const layer = Layer.effect(
Effect.gen(function* () {
const agents = yield* AgentV2.Service
const builtins = yield* InstructionBuiltIns.Service
const codeModeInstructions = yield* CodeModeInstructions.Service
const db = (yield* Database.Service).db
const discovery = yield* InstructionDiscovery.Service
const entries = yield* InstructionEntry.Service
@@ -77,6 +79,7 @@ const layer = Layer.effect(
const instructions = yield* Effect.all(
[
builtins.load(sessionID),
codeModeInstructions.load(agent),
discovery.load(),
skillInstructions.load(agent),
referenceInstructions.load(),
@@ -109,6 +112,7 @@ export const node = makeLocationNode({
layer,
deps: [
AgentV2.node,
CodeModeInstructions.node,
Database.node,
InstructionBuiltIns.node,
InstructionDiscovery.node,
+26 -21
View File
@@ -36,34 +36,15 @@ type CollectedFiles = {
readonly files: Array<typeof ExecuteFile.Type>
}
interface Registration {
export interface Registration {
readonly tool: AnyTool
readonly name: string
readonly namespace?: string
}
export const create = (registrations: ReadonlyMap<string, Registration>) => {
const runtime = (
invoke: (name: string, registration: Registration, input: unknown) => Effect.Effect<unknown, unknown>,
hooks?: CodeMode.ToolCallHooks,
) => {
const tools: Record<string, Tool.Definition<never>> = {}
for (const [name, registration] of registrations) {
const child = definition(name, registration.tool)
const value = Tool.make({
description: child.description,
input: child.inputSchema,
output: child.outputSchema,
run: (input) => invoke(name, registration, input),
})
const path = registration.namespace === undefined ? registration.name : `${registration.namespace}.${registration.name}`
tools[path] = value
}
return CodeMode.make<typeof tools>({ tools, ...hooks })
}
const discovery = runtime(() => Effect.fail(toolError("Execute context is unavailable")))
return make({
description: discovery.instructions(),
description: CodeMode.description,
input: CodeMode.Input,
output: ExecuteOutput,
structured: ExecuteMetadata,
@@ -92,6 +73,7 @@ export const create = (registrations: ReadonlyMap<string, Registration>) => {
),
)
const result = yield* runtime(
registrations,
(name, registration, input) =>
Effect.gen(function* () {
const index = yield* Ref.getAndUpdate(callIndex, (index) => index + 1)
@@ -141,6 +123,29 @@ export const create = (registrations: ReadonlyMap<string, Registration>) => {
})
}
export const instructions = (registrations: ReadonlyMap<string, Registration>) => {
return runtime(registrations, () => Effect.fail(toolError("Execute context is unavailable"))).instructions()
}
function runtime(
registrations: ReadonlyMap<string, Registration>,
invoke: (name: string, registration: Registration, input: unknown) => Effect.Effect<unknown, unknown>,
hooks?: CodeMode.ToolCallHooks,
) {
const tools: Record<string, Tool.Definition<never>> = {}
for (const [name, registration] of registrations) {
const child = definition(name, registration.tool)
const path = registration.namespace === undefined ? registration.name : `${registration.namespace}.${registration.name}`
tools[path] = Tool.make({
description: child.description,
input: child.inputSchema,
output: child.outputSchema,
run: (input) => invoke(name, registration, input),
})
}
return CodeMode.make<typeof tools>({ tools, ...hooks })
}
function displayInput(input: unknown): Record<string, unknown> | undefined {
if (input === null || input === undefined) return
if (typeof input !== "object" || Array.isArray(input)) return { input }
+8 -10
View File
@@ -9,7 +9,7 @@ import { SessionMessage } from "../session/message"
import { SessionSchema } from "../session/schema"
import { ToolOutputStore } from "../tool-output-store"
import { Wildcard } from "../util/wildcard"
import { ExecuteTool } from "./execute"
import { CodeMode } from "../codemode"
import {
definition,
permission,
@@ -67,6 +67,7 @@ const registryLayer = Layer.effect(
const resources = yield* ToolOutputStore.Service
const toolHooks = yield* ToolHooks.Service
const image = yield* Image.Service
const codeMode = yield* CodeMode.Service
type NormalizedItem = ToolOutput["content"][number] | "decode" | "size"
const normalizeImages = Effect.fn("ToolRegistry.normalizeImages")(function* (content: ToolOutput["content"]) {
@@ -104,7 +105,6 @@ const registryLayer = Layer.effect(
readonly tool: AnyTool
readonly name: string
readonly namespace?: string
readonly codemode: boolean
}
const local = new Map<string, Array<{ readonly token: object; readonly registration: Registration }>>()
@@ -203,6 +203,7 @@ const registryLayer = Layer.effect(
return yield* Effect.fail(
new RegistrationError({ name: reserved.key, message: 'Tool name "execute" is reserved for CodeMode' }),
)
if (codemode) return yield* codeMode.register(tools, options)
yield* Effect.uninterruptible(
Effect.gen(function* () {
const token = {}
@@ -215,7 +216,6 @@ const registryLayer = Layer.effect(
tool: entry.tool,
name: entry.name,
namespace: entry.namespace,
codemode,
},
},
])
@@ -234,17 +234,15 @@ const registryLayer = Layer.effect(
}),
materialize: Effect.fn("ToolRegistry.materialize")(function* (permissions) {
const direct = new Map<string, Registration>()
const codemode = new Map<string, Registration>()
const rules = permissions ?? []
for (const [name, entries] of local) {
const registration = entries.at(-1)?.registration
if (!registration) continue
if (whollyDisabled(permission(registration.tool, name), rules)) continue
if (registration.codemode) codemode.set(name, registration)
else direct.set(name, registration)
direct.set(name, registration)
}
const execute =
codemode.size > 0 && !whollyDisabled("execute", rules) ? ExecuteTool.create(codemode) : undefined
const codeModeMaterialization = yield* codeMode.materialize(permissions)
const execute = codeModeMaterialization.tool
return {
definitions: [
...Array.from(direct, ([name, registration]) => definition(name, registration.tool)),
@@ -278,11 +276,11 @@ function whollyDisabled(action: string, rules: PermissionV2.Ruleset) {
export const node = makeLocationNode({
service: Service,
layer,
deps: [ToolOutputStore.node, ToolHooks.node, Image.node],
deps: [CodeMode.node, ToolOutputStore.node, ToolHooks.node, Image.node],
})
export const toolsNode = makeLocationNode({
service: Tools.Service,
layer,
deps: [ToolOutputStore.node, ToolHooks.node, Image.node],
deps: [CodeMode.node, ToolOutputStore.node, ToolHooks.node, Image.node],
})
+27
View File
@@ -0,0 +1,27 @@
import { describe, expect } from "bun:test"
import { CodeMode } from "@opencode-ai/core/codemode"
import { AppNodeBuilder } from "@opencode-ai/core/effect/app-node-builder"
import { Tool } from "@opencode-ai/core/tool/tool"
import { Effect, Schema } from "effect"
import { it } from "./lib/effect"
describe("CodeMode", () => {
it.effect("owns registrations, execute, and catalog materialization", () =>
Effect.gen(function* () {
const codeMode = yield* CodeMode.Service
yield* codeMode.register({
echo: Tool.make({
description: "Echo text",
input: Schema.Struct({ text: Schema.String }),
output: Schema.String,
execute: ({ text }) => Effect.succeed(text),
}),
})
const materialized = yield* codeMode.materialize()
expect(materialized.tool).toBeDefined()
expect(materialized.instructions).toContain("Echo text")
expect(materialized.instructions).toContain("tools.echo(input:")
}).pipe(Effect.scoped, Effect.provide(AppNodeBuilder.build(CodeMode.node))),
)
})
@@ -0,0 +1,49 @@
import { describe, expect } from "bun:test"
import { AgentV2 } from "@opencode-ai/core/agent"
import { CodeMode } from "@opencode-ai/core/codemode"
import { CodeModeInstructions } from "@opencode-ai/core/codemode/instructions"
import { AppNodeBuilder } from "@opencode-ai/core/effect/app-node-builder"
import { Effect, Layer } from "effect"
import { it } from "../lib/effect"
import { readInitial, readUpdate } from "../lib/instructions"
const agent = AgentV2.Info.make(AgentV2.Info.empty(AgentV2.ID.make("build")))
describe("CodeModeInstructions", () => {
it.effect("renders catalog changes and removal", () => {
let catalog: string | undefined = "Initial Code Mode catalog"
const layer = AppNodeBuilder.build(CodeModeInstructions.node, [
[
CodeMode.node,
Layer.mock(CodeMode.Service, {
materialize: () => Effect.succeed({ ...(catalog === undefined ? {} : { instructions: catalog }) }),
register: () => Effect.void,
}),
],
])
return Effect.gen(function* () {
const instructions = yield* CodeModeInstructions.Service
const initialized = yield* instructions.load({ id: agent.id, info: agent }).pipe(Effect.flatMap(readInitial))
expect(initialized.text).toBe("Initial Code Mode catalog")
catalog = "Updated Code Mode catalog"
expect(
yield* instructions
.load({ id: agent.id, info: agent })
.pipe(Effect.flatMap((context) => readUpdate(context, initialized))),
).toMatchObject({
text: "The Code Mode tool catalog has changed. This catalog supersedes the previous Code Mode tool catalog.\n\nUpdated Code Mode catalog",
})
catalog = undefined
expect(
yield* instructions
.load({ id: agent.id, info: agent })
.pipe(Effect.flatMap((context) => readUpdate(context, initialized))),
).toMatchObject({
text: "Code Mode tools are no longer available. Do not use any previously listed Code Mode tools.",
})
}).pipe(Effect.provide(layer))
})
})
+3 -2
View File
@@ -638,9 +638,10 @@ it.effect("advertises MCP output schemas to Code Mode", () =>
Effect.gen(function* () {
const registry = yield* ToolRegistry.Service
yield* waitForTool(registry, "execute")
const execute = (yield* toolDefinitions(registry)).find((tool) => tool.name === "execute")
const materialized = yield* registry.materialize()
const execute = materialized.definitions.find((tool) => tool.name === "execute")
expect(execute?.description).toContain("tools.demo.search(input: {}): Promise<{\n ok: boolean,\n}>")
expect(execute?.description).not.toContain("tools.demo.search")
}),
)
@@ -466,6 +466,9 @@ describe("ToolRegistry", () => {
})
.pipe(Scope.provide(scope))
const materialized = yield* service.materialize()
const execute = materialized.definitions.find((tool) => tool.name === "execute")
expect(execute?.description).toContain("confined Code Mode runtime")
expect(execute?.description).not.toContain("Echo text")
yield* Scope.close(scope, Exit.void)
yield* service.register({
echo: Tool.make({