effect(patch,tool): migrate patch/index and tool/read to AppFileSystem (#27155)
This commit is contained in:
@@ -119,7 +119,7 @@ export const ApplyPatchTool = Tool.define(
|
||||
|
||||
// Apply the update chunks to get new content
|
||||
try {
|
||||
const fileUpdate = Patch.deriveNewContentsFromChunks(filePath, hunk.chunks)
|
||||
const fileUpdate = Patch.deriveNewContentsFromChunks(filePath, hunk.chunks, Bom.join(source.text, source.bom))
|
||||
newContent = fileUpdate.content
|
||||
bom = fileUpdate.bom
|
||||
} catch (error) {
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
import { Effect, Option, Schema, Scope } from "effect"
|
||||
import { Effect, Option, Schema, Scope, Stream } from "effect"
|
||||
import { NonNegativeInt } from "@opencode-ai/core/schema"
|
||||
import { createReadStream } from "fs"
|
||||
import * as path from "path"
|
||||
import { createInterface } from "readline"
|
||||
import * as Tool from "./tool"
|
||||
import { AppFileSystem } from "@opencode-ai/core/filesystem"
|
||||
import { LSP } from "@/lsp/lsp"
|
||||
@@ -105,6 +103,49 @@ export const ReadTool = Tool.define(
|
||||
)
|
||||
})
|
||||
|
||||
const lines = Effect.fn("ReadTool.lines")(function* (filepath: string, opts: { limit: number; offset: number }) {
|
||||
const start = opts.offset - 1
|
||||
const raw: string[] = []
|
||||
const flags = { bytes: 0, count: 0, cut: false, more: false, done: false }
|
||||
|
||||
// Note: prefer manual TextDecoder over Stream.decodeText — when the source stream
|
||||
// ends without flushing, decodeText drops the final unterminated line. We also
|
||||
// avoid Stream.runForEachWhile (it currently swallows the final unterminated
|
||||
// line of the upstream splitLines pipeline) and instead toggle a `done` flag
|
||||
// and ignore subsequent lines.
|
||||
const decoder = new TextDecoder("utf-8")
|
||||
yield* fs.stream(filepath).pipe(
|
||||
Stream.map((bytes) => decoder.decode(bytes, { stream: true })),
|
||||
Stream.splitLines,
|
||||
Stream.runForEach((text) =>
|
||||
Effect.sync(() => {
|
||||
if (flags.done) return
|
||||
flags.count += 1
|
||||
if (flags.count <= start) return
|
||||
|
||||
if (raw.length >= opts.limit) {
|
||||
flags.more = true
|
||||
return
|
||||
}
|
||||
|
||||
const line = text.length > MAX_LINE_LENGTH ? text.substring(0, MAX_LINE_LENGTH) + MAX_LINE_SUFFIX : text
|
||||
const size = Buffer.byteLength(line, "utf-8") + (raw.length > 0 ? 1 : 0)
|
||||
if (flags.bytes + size > MAX_BYTES) {
|
||||
flags.cut = true
|
||||
flags.more = true
|
||||
flags.done = true
|
||||
return
|
||||
}
|
||||
|
||||
raw.push(line)
|
||||
flags.bytes += size
|
||||
}),
|
||||
),
|
||||
)
|
||||
|
||||
return { raw, count: flags.count, cut: flags.cut, more: flags.more, offset: opts.offset }
|
||||
})
|
||||
|
||||
const isBinaryFile = (filepath: string, bytes: Uint8Array) => {
|
||||
const ext = path.extname(filepath).toLowerCase()
|
||||
switch (ext) {
|
||||
@@ -247,9 +288,7 @@ export const ReadTool = Tool.define(
|
||||
return yield* Effect.fail(new Error(`Cannot read binary file: ${filepath}`))
|
||||
}
|
||||
|
||||
const file = yield* Effect.promise(() =>
|
||||
lines(filepath, { limit: params.limit ?? DEFAULT_READ_LIMIT, offset: params.offset || 1 }),
|
||||
)
|
||||
const file = yield* lines(filepath, { limit: params.limit ?? DEFAULT_READ_LIMIT, offset: params.offset || 1 })
|
||||
if (file.count < file.offset && !(file.count === 0 && file.offset === 1)) {
|
||||
return yield* Effect.fail(
|
||||
new Error(`Offset ${file.offset} is out of range for this file (${file.count} lines)`),
|
||||
@@ -296,47 +335,3 @@ export const ReadTool = Tool.define(
|
||||
}
|
||||
}),
|
||||
)
|
||||
|
||||
async function lines(filepath: string, opts: { limit: number; offset: number }) {
|
||||
const stream = createReadStream(filepath, { encoding: "utf8" })
|
||||
const rl = createInterface({
|
||||
input: stream,
|
||||
// Note: we use the crlfDelay option to recognize all instances of CR LF
|
||||
// ('\r\n') in file as a single line break.
|
||||
crlfDelay: Infinity,
|
||||
})
|
||||
|
||||
const start = opts.offset - 1
|
||||
const raw: string[] = []
|
||||
let bytes = 0
|
||||
let count = 0
|
||||
let cut = false
|
||||
let more = false
|
||||
try {
|
||||
for await (const text of rl) {
|
||||
count += 1
|
||||
if (count <= start) continue
|
||||
|
||||
if (raw.length >= opts.limit) {
|
||||
more = true
|
||||
continue
|
||||
}
|
||||
|
||||
const line = text.length > MAX_LINE_LENGTH ? text.substring(0, MAX_LINE_LENGTH) + MAX_LINE_SUFFIX : text
|
||||
const size = Buffer.byteLength(line, "utf-8") + (raw.length > 0 ? 1 : 0)
|
||||
if (bytes + size > MAX_BYTES) {
|
||||
cut = true
|
||||
more = true
|
||||
break
|
||||
}
|
||||
|
||||
raw.push(line)
|
||||
bytes += size
|
||||
}
|
||||
} finally {
|
||||
rl.close()
|
||||
stream.destroy()
|
||||
}
|
||||
|
||||
return { raw, count, cut, more, offset: opts.offset }
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user