mirror of
https://github.com/anomalyco/opencode.git
synced 2026-07-10 04:58:28 +00:00
93 lines
3.9 KiB
TypeScript
93 lines
3.9 KiB
TypeScript
/**
|
|
* Model-facing V2 file-write leaf. Relative paths resolve within the active
|
|
* Location. Absolute paths inside that Location are accepted, while explicit
|
|
* absolute external paths retain mutation capability through a separate
|
|
* external_directory approval before edit approval.
|
|
*/
|
|
export * as WriteTool from "./write"
|
|
|
|
import type { PluginContext } from "@opencode-ai/plugin/v2/effect"
|
|
import { ToolFailure } from "@opencode-ai/llm"
|
|
import { Effect, Schema } from "effect"
|
|
import { FileMutation } from "../file-mutation"
|
|
import { LocationMutation } from "../location-mutation"
|
|
import { PermissionV2 } from "../permission"
|
|
import { Tool } from "./tool"
|
|
|
|
export const name = "write"
|
|
|
|
// TODO: Revisit whether model-facing mutation schemas should prefer absolute `filePath` naming for trained-in compatibility after evaluating model behavior.
|
|
export const Input = Schema.Struct({
|
|
path: Schema.String.annotate({
|
|
description:
|
|
"File path to write. Relative paths resolve within the active Location. Absolute paths inside that Location are accepted; external absolute paths require external_directory approval.",
|
|
}),
|
|
content: Schema.String.annotate({ description: "Content to write to the file" }),
|
|
})
|
|
|
|
export const Output = Schema.Struct({
|
|
operation: Schema.Literal("write"),
|
|
target: Schema.String,
|
|
resource: Schema.String,
|
|
existed: Schema.Boolean,
|
|
})
|
|
export type Output = typeof Output.Type
|
|
|
|
export const toModelOutput = (output: Output) =>
|
|
`${output.existed ? "Wrote" : "Created"} file successfully: ${output.resource}`
|
|
|
|
/** Deferred V2 write UX integrations remain visible at the model-facing seam. */
|
|
// TODO: Add formatter integration after V2 formatter runtime exists.
|
|
// TODO: Publish watcher/file-edit events after V2 watcher integration exists.
|
|
// TODO: Add snapshots / undo after design exists.
|
|
// TODO: Add LSP notification and diagnostics after V2 LSP runtime exists.
|
|
|
|
export const Plugin = {
|
|
id: "opencode.tool.write",
|
|
effect: Effect.fn("WriteTool.Plugin")(function* (ctx: PluginContext) {
|
|
const mutation = yield* LocationMutation.Service
|
|
const files = yield* FileMutation.Service
|
|
const permission = yield* PermissionV2.Service
|
|
|
|
yield* ctx.tool
|
|
.register({
|
|
[name]: Tool.withPermission(
|
|
Tool.make({
|
|
description:
|
|
"Write content to one file. Relative paths resolve within the active Location. Absolute paths inside the Location are accepted. Explicit external absolute paths require external_directory approval before edit approval.",
|
|
input: Input,
|
|
output: Output,
|
|
toModelOutput: ({ output }) => [{ type: "text", text: toModelOutput(output) }],
|
|
execute: (input, context) =>
|
|
Effect.gen(function* () {
|
|
const source = {
|
|
type: "tool" as const,
|
|
messageID: context.assistantMessageID,
|
|
callID: context.toolCallID,
|
|
}
|
|
const target = yield* mutation.resolve({ path: input.path, kind: "file" })
|
|
const external = target.externalDirectory
|
|
if (external)
|
|
yield* permission.assert({
|
|
...LocationMutation.externalDirectoryPermission(external),
|
|
sessionID: context.sessionID,
|
|
agent: context.agent,
|
|
source,
|
|
})
|
|
yield* permission.assert({
|
|
action: "edit",
|
|
resources: [target.resource],
|
|
save: ["*"],
|
|
sessionID: context.sessionID,
|
|
agent: context.agent,
|
|
source,
|
|
})
|
|
return yield* files.writeTextPreservingBom({ target, content: input.content })
|
|
}).pipe(Effect.mapError(() => new ToolFailure({ message: `Unable to write ${input.path}` }))),
|
|
}),
|
|
"edit",
|
|
),
|
|
})
|
|
.pipe(Effect.orDie)
|
|
}),
|
|
}
|