mirror of
https://github.com/sst/opencode.git
synced 2025-08-22 14:04:07 +00:00
addressingcomments
This commit is contained in:
parent
92eee5ee1e
commit
9c247a72e3
3 changed files with 170 additions and 2 deletions
164
packages/opencode/src/tool/morphedit.ts
Normal file
164
packages/opencode/src/tool/morphedit.ts
Normal file
|
@ -0,0 +1,164 @@
|
|||
import { z } from "zod"
|
||||
import * as path from "path"
|
||||
import { Tool } from "./tool"
|
||||
import { createTwoFilesPatch } from "diff"
|
||||
import { Permission } from "../permission"
|
||||
import DESCRIPTION from "./morphedit.txt"
|
||||
import { App } from "../app/app"
|
||||
import { File } from "../file"
|
||||
import { Bus } from "../bus"
|
||||
import { FileTime } from "../file/time"
|
||||
import { Filesystem } from "../util/filesystem"
|
||||
import { Agent } from "../agent/agent"
|
||||
import { LSP } from "../lsp"
|
||||
|
||||
// OpenAI-compatible client for Morph API
|
||||
class MorphClient {
|
||||
private apiKey: string
|
||||
private baseURL = "https://api.morphllm.com/v1"
|
||||
|
||||
constructor(apiKey: string) {
|
||||
this.apiKey = apiKey
|
||||
}
|
||||
|
||||
async apply(instruction: string, initialCode: string, codeEdit: string): Promise<string> {
|
||||
const response = await fetch(`${this.baseURL}/chat/completions`, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
"Authorization": `Bearer ${this.apiKey}`,
|
||||
},
|
||||
body: JSON.stringify({
|
||||
model: "morph-v3-large",
|
||||
messages: [
|
||||
{
|
||||
role: "user",
|
||||
content: `<instruction>${instruction}</instruction>\n<code>${initialCode}</code>\n<update>${codeEdit}</update>`,
|
||||
},
|
||||
],
|
||||
}),
|
||||
})
|
||||
|
||||
if (!response.ok) {
|
||||
const errorText = await response.text()
|
||||
throw new Error(`Morph API error: ${response.status} ${response.statusText}\n${errorText}`)
|
||||
}
|
||||
|
||||
const result = await response.json()
|
||||
return result.choices[0].message.content
|
||||
}
|
||||
}
|
||||
|
||||
function trimDiff(diff: string): string {
|
||||
return diff
|
||||
.split("\n")
|
||||
.slice(4) // Remove the header lines
|
||||
.join("\n")
|
||||
}
|
||||
|
||||
export const MorphEditTool = Tool.define("edit", {
|
||||
description: DESCRIPTION,
|
||||
parameters: z.object({
|
||||
target_file: z.string().describe("The target file to modify"),
|
||||
instructions: z.string().describe("A single sentence written in the first person describing what you're changing. Used to help disambiguate uncertainty in the edit."),
|
||||
code_edit: z.string().describe("Specify ONLY the precise lines of code that you wish to edit. Use `// ... existing code ...` for unchanged sections."),
|
||||
}),
|
||||
async execute(params, ctx) {
|
||||
if (!params.target_file) {
|
||||
throw new Error("target_file is required")
|
||||
}
|
||||
|
||||
if (!params.instructions) {
|
||||
throw new Error("instructions is required")
|
||||
}
|
||||
|
||||
if (!params.code_edit) {
|
||||
throw new Error("code_edit is required")
|
||||
}
|
||||
|
||||
// Check for Morph API key
|
||||
const morphApiKey = process.env['MORPH_API_KEY']
|
||||
if (!morphApiKey) {
|
||||
throw new Error("MORPH_API_KEY environment variable is required for morphedit tool")
|
||||
}
|
||||
|
||||
const app = App.info()
|
||||
const filePath = path.isAbsolute(params.target_file) ? params.target_file : path.join(app.path.cwd, params.target_file)
|
||||
|
||||
if (!Filesystem.contains(app.path.cwd, filePath)) {
|
||||
throw new Error(`File ${filePath} is not in the current working directory`)
|
||||
}
|
||||
|
||||
const agent = await Agent.get(ctx.agent)
|
||||
|
||||
// Read the existing file
|
||||
const file = Bun.file(filePath)
|
||||
const stats = await file.stat().catch(() => {})
|
||||
if (!stats) throw new Error(`File ${filePath} not found`)
|
||||
if (stats.isDirectory()) throw new Error(`Path is a directory, not a file: ${filePath}`)
|
||||
|
||||
await FileTime.assert(ctx.sessionID, filePath)
|
||||
const initialCode = await file.text()
|
||||
|
||||
// Use Morph API to apply the edit
|
||||
const morphClient = new MorphClient(morphApiKey)
|
||||
let mergedCode: string
|
||||
|
||||
try {
|
||||
mergedCode = await morphClient.apply(params.instructions, initialCode, params.code_edit)
|
||||
} catch (error) {
|
||||
throw new Error(`Failed to apply edit with Morph: ${error instanceof Error ? error.message : String(error)}`)
|
||||
}
|
||||
|
||||
const diff = trimDiff(createTwoFilesPatch(filePath, filePath, initialCode, mergedCode))
|
||||
|
||||
// Check permissions if needed
|
||||
if (agent.permission.edit === "ask") {
|
||||
await Permission.ask({
|
||||
type: "edit",
|
||||
sessionID: ctx.sessionID,
|
||||
messageID: ctx.messageID,
|
||||
callID: ctx.callID,
|
||||
title: "🚀 Morph Fast Apply: " + filePath,
|
||||
metadata: {
|
||||
filePath,
|
||||
diff,
|
||||
morphApplied: true,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
// Write the merged code to file
|
||||
await Bun.write(filePath, mergedCode)
|
||||
await Bus.publish(File.Event.Edited, {
|
||||
file: filePath,
|
||||
})
|
||||
|
||||
FileTime.read(ctx.sessionID, filePath)
|
||||
|
||||
let output = ""
|
||||
await LSP.touchFile(filePath, true)
|
||||
const diagnostics = await LSP.diagnostics()
|
||||
for (const [file, issues] of Object.entries(diagnostics)) {
|
||||
if (issues.length === 0) continue
|
||||
if (file === filePath) {
|
||||
output += `\nThis file has errors, please fix\n<file_diagnostics>\n${issues.map(LSP.Diagnostic.pretty).join("\n")}\n</file_diagnostics>\n`
|
||||
continue
|
||||
}
|
||||
output += `\n<project_diagnostics>\n${file}\n${issues
|
||||
.filter((item) => item.severity === 1)
|
||||
.map(LSP.Diagnostic.pretty)
|
||||
.join("\n")}\n</project_diagnostics>\n`
|
||||
}
|
||||
|
||||
return {
|
||||
metadata: {
|
||||
diagnostics,
|
||||
diff,
|
||||
morphApplied: true,
|
||||
},
|
||||
title: `🚀 ${path.relative(app.path.root, filePath)}`,
|
||||
output: output || `Successfully applied edit using Morph Fast Apply:\n\n${diff}`,
|
||||
}
|
||||
},
|
||||
})
|
|
@ -1,4 +1,4 @@
|
|||
🚀 MORPH FAST APPLY - Use this tool to make an edit to an existing file.
|
||||
MORPH FAST APPLY - Use this tool to make an edit to an existing file.
|
||||
|
||||
This will be read by a less intelligent model, which will quickly apply the edit. You should make it clear what the edit is, while also minimizing the unchanged code you write.
|
||||
When writing the edit, you should specify each edit in sequence, with the special comment // ... existing code ... to represent unchanged code in between edited lines.
|
|
@ -1,6 +1,7 @@
|
|||
import z from "zod"
|
||||
import { BashTool } from "./bash"
|
||||
import { EditTool } from "./edit"
|
||||
import { MorphEditTool } from "./morphedit"
|
||||
import { GlobTool } from "./glob"
|
||||
import { GrepTool } from "./grep"
|
||||
import { ListTool } from "./ls"
|
||||
|
@ -14,10 +15,13 @@ import { InvalidTool } from "./invalid"
|
|||
import type { Agent } from "../agent/agent"
|
||||
|
||||
export namespace ToolRegistry {
|
||||
// Use Morph Fast Apply as the default edit tool if MORPH_API_KEY is present
|
||||
const DefaultEditTool = process.env['MORPH_API_KEY'] ? MorphEditTool : EditTool
|
||||
|
||||
const ALL = [
|
||||
InvalidTool,
|
||||
BashTool,
|
||||
EditTool,
|
||||
DefaultEditTool,
|
||||
WebFetchTool,
|
||||
GlobTool,
|
||||
GrepTool,
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue