-
Notifications
You must be signed in to change notification settings - Fork 4
Expand file tree
/
Copy pathopencode-agentdiff.ts
More file actions
141 lines (122 loc) · 3.96 KB
/
Copy pathopencode-agentdiff.ts
File metadata and controls
141 lines (122 loc) · 3.96 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
/**
* agentdiff plugin for OpenCode
*
* Managed by `agentdiff configure`.
*/
import type { Plugin } from "@opencode-ai/plugin"
import { dirname } from "path"
const CAPTURE_SCRIPT = "__AGENTDIFF_CAPTURE_OPENCODE__"
// OpenCode tool names that write/modify files.
// OpenCode uses short lowercase names ("edit", "write") for its built-in tools.
const FILE_EDIT_TOOLS = new Set(["edit", "write", "patch", "multiedit", "replace", "create"])
type PendingEdit = {
filePath: string
repoDir: string
sessionID: string
tool: string
args: Record<string, unknown>
}
/** Resolve the file path from tool args — OpenCode uses "file" or "path" or "filePath". */
function resolveFilePath(args: Record<string, unknown>): string | undefined {
return (
(args.file as string | undefined) ??
(args.filePath as string | undefined) ??
(args.file_path as string | undefined) ??
(args.path as string | undefined) ??
(args.filename as string | undefined)
)
}
/** Spawn python3 with JSON payload on stdin. Uses Bun.spawnSync when available,
* falls back to the Bun $ shell. */
async function runCapture($: any, payload: object): Promise<void> {
const json = JSON.stringify(payload)
// Prefer Bun.spawnSync (avoids shell pipe quoting issues).
if (typeof Bun !== "undefined" && Bun.spawnSync) {
Bun.spawnSync(["python3", CAPTURE_SCRIPT], {
stdin: new TextEncoder().encode(json),
stdout: "ignore",
stderr: "ignore",
})
return
}
// Fallback: write payload to a temp file and pass it via stdin redirect.
const tmp = `/tmp/agentdiff-oc-${Date.now()}.json`
try {
await Bun.write(tmp, json)
await $`python3 ${CAPTURE_SCRIPT} < ${tmp}`.quiet()
} finally {
try { await $`rm -f ${tmp}`.quiet() } catch { /* ignore */ }
}
}
export const AgentDiffPlugin: Plugin = async (ctx) => {
const { $ } = ctx
const pendingEdits = new Map<string, PendingEdit>()
const findGitRepo = async (filePath: string): Promise<string | null> => {
try {
const dir = dirname(filePath)
const result = await $`git -C ${dir} rev-parse --show-toplevel`.quiet()
const repoRoot = result.stdout.toString().trim()
return repoRoot || null
} catch {
return null
}
}
return {
"tool.execute.before": async (input, output) => {
const tool = String(input.tool || "").toLowerCase()
if (!FILE_EDIT_TOOLS.has(tool)) {
return
}
const args = (output.args ?? {}) as Record<string, unknown>
const filePath = resolveFilePath(args)
if (!filePath) {
return
}
const repoDir = await findGitRepo(filePath)
if (!repoDir) {
return
}
pendingEdits.set(input.callID, {
filePath,
repoDir,
sessionID: input.sessionID,
tool,
args,
})
},
"tool.execute.after": async (input, _output) => {
const editInfo = pendingEdits.get(input.callID)
pendingEdits.delete(input.callID)
if (!editInfo) {
return
}
const { filePath, repoDir, sessionID, tool, args } = editInfo
// Resolve old/new strings from various possible field names OpenCode may use.
const oldString = String(
args.old_string ?? args.oldString ?? args.old ?? args.search ?? "",
)
const newString = String(
args.new_string ?? args.newString ?? args.new ?? args.replace ?? args.content ?? "",
)
const payload = {
hook_event_name: "PostToolUse",
session_id: sessionID,
model: String((input as any).modelID ?? (input as any).model ?? "opencode"),
cwd: repoDir,
tool_name: tool,
tool_input: {
filePath,
old_string: oldString,
new_string: newString,
content: String(args.content ?? ""),
},
}
try {
await runCapture($, payload)
} catch (error) {
console.error("[agentdiff] OpenCode capture failed:", String(error))
}
},
}
}
export default AgentDiffPlugin