tool for writing files

This commit is contained in:
James Long 2026-05-15 15:33:00 -04:00
commit 816f5e526b
4 changed files with 41 additions and 1 deletions

View file

@ -300,6 +300,14 @@ function createServer(options: Options) {
}, },
async (input) => toolResult(await control(options, "POST", "/experimental/simulation/filesystem/seed", input)), async (input) => toolResult(await control(options, "POST", "/experimental/simulation/filesystem/seed", input)),
) )
server.registerTool(
"simulation_control_filesystem_write",
{
description: "Write one file into the backend simulated filesystem.",
inputSchema: z.object({ path: z.string(), content: FileContentSchema }),
},
async (input) => toolResult(await control(options, "POST", "/experimental/simulation/filesystem/write", input)),
)
server.registerTool( server.registerTool(
"simulation_control_network_register", "simulation_control_network_register",
{ {

View file

@ -28,6 +28,15 @@ export const simulationRoute = HttpRouter.use((router) =>
), ),
) )
yield* router.add("POST", "/experimental/simulation/filesystem/write", () =>
json(
Effect.gen(function* () {
const input = yield* HttpServerRequest.schemaBodyJson(Simulation.FilesystemWriteInput)
return yield* simulation.writeFile(input)
}),
),
)
yield* router.add("POST", "/experimental/simulation/network/register", () => yield* router.add("POST", "/experimental/simulation/network/register", () =>
json( json(
Effect.gen(function* () { Effect.gen(function* () {

View file

@ -12,6 +12,11 @@ export const FilesystemSeedInput = Schema.Struct({
files: Schema.Record(Schema.String, FileContent), files: Schema.Record(Schema.String, FileContent),
}) })
export const FilesystemWriteInput = Schema.Struct({
path: Schema.String,
content: FileContent,
})
export const NetworkRegisterInput = Schema.Union([ export const NetworkRegisterInput = Schema.Union([
Schema.Struct({ Schema.Struct({
kind: Schema.Literal("json"), kind: Schema.Literal("json"),
@ -80,6 +85,7 @@ export class SimulationLLMError extends Schema.TaggedErrorClass<SimulationLLMErr
export interface Interface { export interface Interface {
readonly reset: () => Effect.Effect<void> readonly reset: () => Effect.Effect<void>
readonly seedFilesystem: (input: typeof FilesystemSeedInput.Type) => Effect.Effect<{ files: string[] }, unknown> readonly seedFilesystem: (input: typeof FilesystemSeedInput.Type) => Effect.Effect<{ files: string[] }, unknown>
readonly writeFile: (input: typeof FilesystemWriteInput.Type) => Effect.Effect<{ file: string }, unknown>
readonly registerNetwork: (input: typeof NetworkRegisterInput.Type) => Effect.Effect<{ registered: string }, unknown> readonly registerNetwork: (input: typeof NetworkRegisterInput.Type) => Effect.Effect<{ registered: string }, unknown>
readonly enqueueLLM: (input: typeof LLMEnqueueInput.Type) => Effect.Effect<{ queued: number }> readonly enqueueLLM: (input: typeof LLMEnqueueInput.Type) => Effect.Effect<{ queued: number }>
readonly nextLLM: () => Effect.Effect<LLMScript, SimulationLLMError> readonly nextLLM: () => Effect.Effect<LLMScript, SimulationLLMError>
@ -131,6 +137,12 @@ export const layer = Layer.effect(
return { files } return { files }
}) })
const writeFile = Effect.fn("Simulation.writeFile")(function* (input: typeof FilesystemWriteInput.Type) {
yield* fs.writeWithDirs(path.isAbsolute(input.path) ? input.path : path.join("/opencode", input.path), fileContent(input.content))
yield* Ref.update(state, (current) => ({ ...current, files: [...current.files, input.path] }))
return { file: input.path }
})
const registerNetwork = Effect.fn("Simulation.registerNetwork")(function* (input: typeof NetworkRegisterInput.Type) { const registerNetwork = Effect.fn("Simulation.registerNetwork")(function* (input: typeof NetworkRegisterInput.Type) {
switch (input.kind) { switch (input.kind) {
case "json": case "json":
@ -178,7 +190,7 @@ export const layer = Layer.effect(
} }
}) })
return Service.of({ reset, seedFilesystem, registerNetwork, enqueueLLM, nextLLM, snapshot }) return Service.of({ reset, seedFilesystem, writeFile, registerNetwork, enqueueLLM, nextLLM, snapshot })
}), }),
) )

View file

@ -28,6 +28,17 @@ describe("Simulation", () => {
}), }),
) )
it.effect("writes one file into the simulated filesystem", () =>
Effect.gen(function* () {
const simulation = yield* Simulation.Service
const fs = yield* AppFileSystem.Service
expect(yield* simulation.writeFile({ path: "src/generated.txt", content: "generated" })).toEqual({ file: "src/generated.txt" })
expect(yield* fs.readFileString("/opencode/src/generated.txt")).toBe("generated")
expect((yield* simulation.snapshot()).files).toContain("src/generated.txt")
}),
)
it.effect("registers network responses through control state", () => it.effect("registers network responses through control state", () =>
Effect.gen(function* () { Effect.gen(function* () {
const simulation = yield* Simulation.Service const simulation = yield* Simulation.Service