fix(core): gate v2 edit tools by model (#34558)

Co-authored-by: Aiden Cline <aidenpcline@gmail.com>
This commit is contained in:
opencode-agent[bot] 2026-06-29 23:53:41 -05:00 committed by GitHub
commit 524ee8fc03
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 83 additions and 36 deletions

View file

@ -194,7 +194,9 @@ export const layer = Layer.effect(
const entries = yield* SessionHistory.entriesForRunner(db, session.id, system.baselineSeq)
const context = entries.map((entry) => entry.message)
const isLastStep = agent.info?.steps !== undefined && currentStep >= agent.info.steps
const toolMaterialization = isLastStep ? undefined : yield* tools.materialize(agent.info?.permissions)
const toolMaterialization = isLastStep
? undefined
: yield* tools.materialize({ permissions: agent.info?.permissions, model })
const promptCacheKey = /^ses_[0-9a-f]{64}$/.test(session.id) ? session.id.slice(4) : session.id
const request = LLM.request({
model,

View file

@ -21,11 +21,16 @@ export type ExecuteInput = {
}
export interface Interface {
readonly materialize: (permissions?: PermissionV2.Ruleset) => Effect.Effect<Materialization>
readonly materialize: (input: MaterializeInput) => Effect.Effect<Materialization>
/** Internal registration capability exposed publicly only through Tools.Service. */
readonly register: (tools: Readonly<Record<string, AnyTool>>) => Effect.Effect<void, RegistrationError, Scope.Scope>
}
export interface MaterializeInput {
readonly model: { readonly id: string; readonly provider: string }
readonly permissions?: PermissionV2.Ruleset
}
export interface Materialization {
readonly definitions: ReadonlyArray<ToolDefinition>
readonly settle: (input: ExecuteInput) => Effect.Effect<Settlement, ToolOutputStore.Error>
@ -102,14 +107,19 @@ const registryLayer = Layer.effect(
}),
)
}),
materialize: Effect.fn("ToolRegistry.materialize")(function* (permissions = []) {
materialize: Effect.fn("ToolRegistry.materialize")(function* (input) {
const registrations = new Map(applications.entries())
for (const [name, entries] of local) {
const registration = entries.at(-1)?.registration
if (registration) registrations.set(name, registration)
}
for (const [name, registration] of registrations)
if (whollyDisabled(permission(registration.tool, name), permissions)) registrations.delete(name)
// OpenAI/GPT models use apply_patch; every other model uses edit and write.
const usePatch = input.model.provider.toLowerCase() === "openai" || input.model.id.toLowerCase().includes("gpt")
for (const [name, registration] of registrations) {
const wrongEditTool = name === "apply_patch" ? !usePatch : (name === "edit" || name === "write") && usePatch
if (wrongEditTool || whollyDisabled(permission(registration.tool, name), input.permissions ?? []))
registrations.delete(name)
}
return {
definitions: Array.from(registrations, ([name, registration]) => definition(name, registration.tool)),
settle: (input) => {