| |
| |
| |
| |
| |
| |
| export * as WriteTool from "./write" |
|
|
| import { ToolFailure } from "@opencode-ai/llm" |
| import { Effect, Layer, Schema } from "effect" |
| import { makeLocationNode } from "../effect/app-node" |
| import { FileMutation } from "../file-mutation" |
| import { LocationMutation } from "../location-mutation" |
| import { PermissionV2 } from "../permission" |
| import { ToolRegistry } from "./registry" |
| import { Tool } from "./tool" |
| import { Tools } from "./tools" |
|
|
| export const name = "write" |
|
|
| |
| 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}` |
|
|
| |
| |
| |
| |
| |
|
|
| const layer = Layer.effectDiscard( |
| Effect.gen(function* () { |
| const tools = yield* Tools.Service |
| const mutation = yield* LocationMutation.Service |
| const files = yield* FileMutation.Service |
| const permission = yield* PermissionV2.Service |
|
|
| yield* tools |
| .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) |
| }), |
| ) |
|
|
| export const node = makeLocationNode({ |
| name: "tool/write", |
| layer, |
| deps: [ToolRegistry.node, LocationMutation.node, FileMutation.node, PermissionV2.node], |
| }) |
|
|