mirror of
https://github.com/sst/opencode.git
synced 2025-09-04 12:10:37 +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.
|
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.
|
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 z from "zod"
|
||||||
import { BashTool } from "./bash"
|
import { BashTool } from "./bash"
|
||||||
import { EditTool } from "./edit"
|
import { EditTool } from "./edit"
|
||||||
|
import { MorphEditTool } from "./morphedit"
|
||||||
import { GlobTool } from "./glob"
|
import { GlobTool } from "./glob"
|
||||||
import { GrepTool } from "./grep"
|
import { GrepTool } from "./grep"
|
||||||
import { ListTool } from "./ls"
|
import { ListTool } from "./ls"
|
||||||
|
@ -14,10 +15,13 @@ import { InvalidTool } from "./invalid"
|
||||||
import type { Agent } from "../agent/agent"
|
import type { Agent } from "../agent/agent"
|
||||||
|
|
||||||
export namespace ToolRegistry {
|
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 = [
|
const ALL = [
|
||||||
InvalidTool,
|
InvalidTool,
|
||||||
BashTool,
|
BashTool,
|
||||||
EditTool,
|
DefaultEditTool,
|
||||||
WebFetchTool,
|
WebFetchTool,
|
||||||
GlobTool,
|
GlobTool,
|
||||||
GrepTool,
|
GrepTool,
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue