moshi-opencode-hooks 1.0.16 → 1.0.18
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/index.ts +79 -56
- package/package.json +1 -1
package/index.ts
CHANGED
|
@@ -6,13 +6,6 @@ const TOKEN_PATH = `${homedir()}/.config/moshi/token`
|
|
|
6
6
|
const API_URL = "https://api.getmoshi.app/api/v1/agent-events"
|
|
7
7
|
const INTERESTING_TOOLS = new Set(["bash", "edit", "write", "read", "glob", "grep", "task", "question", "apply_patch", "webfetch", "websearch"])
|
|
8
8
|
|
|
9
|
-
interface HookState {
|
|
10
|
-
model?: string
|
|
11
|
-
lastToolName?: string
|
|
12
|
-
lastStopTime?: number
|
|
13
|
-
isSubagent?: boolean
|
|
14
|
-
}
|
|
15
|
-
|
|
16
9
|
interface AgentEvent {
|
|
17
10
|
source: "opencode"
|
|
18
11
|
eventType: "pre_tool" | "post_tool" | "notification" | "stop"
|
|
@@ -27,25 +20,6 @@ interface AgentEvent {
|
|
|
27
20
|
contextPercent?: number
|
|
28
21
|
}
|
|
29
22
|
|
|
30
|
-
function statePath(sessionId: string): string {
|
|
31
|
-
return `/tmp/moshi-opencode-hook-${sessionId}.json`
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
async function readState(sessionId: string): Promise<HookState> {
|
|
35
|
-
try {
|
|
36
|
-
const file = Bun.file(statePath(sessionId))
|
|
37
|
-
if (!file.size) return {}
|
|
38
|
-
return await file.json()
|
|
39
|
-
} catch {
|
|
40
|
-
return {}
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
async function writeState(sessionId: string, patch: Partial<HookState>): Promise<void> {
|
|
45
|
-
const existing = await readState(sessionId)
|
|
46
|
-
await Bun.write(statePath(sessionId), JSON.stringify({ ...existing, ...patch }))
|
|
47
|
-
}
|
|
48
|
-
|
|
49
23
|
async function loadToken(): Promise<string | null> {
|
|
50
24
|
try {
|
|
51
25
|
const text = await Bun.file(TOKEN_PATH).text()
|
|
@@ -95,6 +69,22 @@ async function sendEvent(token: string, event: AgentEvent): Promise<Response> {
|
|
|
95
69
|
}
|
|
96
70
|
}
|
|
97
71
|
|
|
72
|
+
async function getModel(client: Parameters<Plugin>[0]["client"], sessionId: string): Promise<string | undefined> {
|
|
73
|
+
try {
|
|
74
|
+
const res = await (client.session.get as any)({ sessionID: sessionId })
|
|
75
|
+
return res.data?.model
|
|
76
|
+
} catch {
|
|
77
|
+
return undefined
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
async function getOrLoadModel(
|
|
82
|
+
client: Parameters<Plugin>[0]["client"],
|
|
83
|
+
sessionId: string,
|
|
84
|
+
): Promise<string | undefined> {
|
|
85
|
+
return await getModel(client, sessionId)
|
|
86
|
+
}
|
|
87
|
+
|
|
98
88
|
async function sendAgentEvent(
|
|
99
89
|
client: Parameters<Plugin>[0]["client"],
|
|
100
90
|
token: string,
|
|
@@ -122,9 +112,58 @@ function formatToolName(toolName: string): string {
|
|
|
122
112
|
return toolName.charAt(0).toUpperCase() + toolName.slice(1)
|
|
123
113
|
}
|
|
124
114
|
|
|
115
|
+
function formatToolDescription(tool: string, args: any): string {
|
|
116
|
+
switch (tool) {
|
|
117
|
+
case "bash":
|
|
118
|
+
return args?.description?.slice(0, 200) ?? ""
|
|
119
|
+
case "edit":
|
|
120
|
+
return args?.filePath ?? ""
|
|
121
|
+
case "write":
|
|
122
|
+
return args?.filePath ?? ""
|
|
123
|
+
case "read":
|
|
124
|
+
return args?.filePath ?? ""
|
|
125
|
+
case "glob":
|
|
126
|
+
return args?.pattern ?? ""
|
|
127
|
+
case "grep":
|
|
128
|
+
return args?.pattern ?? ""
|
|
129
|
+
case "task":
|
|
130
|
+
return args?.description?.slice(0, 100) ?? ""
|
|
131
|
+
case "apply_patch":
|
|
132
|
+
return args?.description?.slice(0, 200) ?? ""
|
|
133
|
+
case "webfetch":
|
|
134
|
+
case "websearch":
|
|
135
|
+
return args?.url ?? ""
|
|
136
|
+
default:
|
|
137
|
+
return ""
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
125
141
|
function formatModelName(model: string | undefined): string | undefined {
|
|
126
142
|
if (!model) return undefined
|
|
127
|
-
return model.
|
|
143
|
+
return model.split("/").pop()
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
async function getContextPercent(sessionId: string, client: Parameters<Plugin>[0]["client"]): Promise<number | undefined> {
|
|
147
|
+
try {
|
|
148
|
+
const sessionRes = await (client.session.get as any)({ sessionID: sessionId })
|
|
149
|
+
const sessionData = sessionRes.data
|
|
150
|
+
if (!sessionData) return undefined
|
|
151
|
+
|
|
152
|
+
const model = sessionData.model as { limit?: { context?: number } } | undefined
|
|
153
|
+
const contextLimit = model?.limit?.context
|
|
154
|
+
if (!contextLimit) return undefined
|
|
155
|
+
|
|
156
|
+
const messagesRes = await (client.session.messages as any)({ sessionID: sessionId, limit: 1 })
|
|
157
|
+
const messagesData = messagesRes.data
|
|
158
|
+
const messages = messagesData?.messages ?? messagesData
|
|
159
|
+
const lastMsg = Array.isArray(messages) ? messages[messages.length - 1] : messages
|
|
160
|
+
if (!lastMsg?.tokens?.input) return undefined
|
|
161
|
+
|
|
162
|
+
const totalInputTokens = lastMsg.tokens.input
|
|
163
|
+
return Math.min(100, Math.round((totalInputTokens / contextLimit) * 100))
|
|
164
|
+
} catch {
|
|
165
|
+
return undefined
|
|
166
|
+
}
|
|
128
167
|
}
|
|
129
168
|
|
|
130
169
|
const pkg = await import("./package.json", { assert: { type: "json" } })
|
|
@@ -149,24 +188,9 @@ export const MoshiHooks: Plugin = async ({ client, directory }) => {
|
|
|
149
188
|
|
|
150
189
|
const sessionId = (event as any).sessionId ?? "unknown"
|
|
151
190
|
const projectName = directory ? basename(directory) : undefined
|
|
152
|
-
const state = await readState(sessionId)
|
|
153
|
-
|
|
154
|
-
if (event.type === "session.created") {
|
|
155
|
-
const isSubagent = await isSubagentSession(sessionId)
|
|
156
|
-
await writeState(sessionId, {
|
|
157
|
-
model: (event as any).properties?.model,
|
|
158
|
-
isSubagent,
|
|
159
|
-
})
|
|
160
|
-
continue
|
|
161
|
-
}
|
|
162
191
|
|
|
163
192
|
if (event.type === "session.idle") {
|
|
164
|
-
if (
|
|
165
|
-
|
|
166
|
-
const now = Date.now() / 1000
|
|
167
|
-
if (state.lastStopTime && now - state.lastStopTime < 5) continue
|
|
168
|
-
|
|
169
|
-
await writeState(sessionId, { lastStopTime: now })
|
|
193
|
+
if (await isSubagentSession(sessionId)) continue
|
|
170
194
|
|
|
171
195
|
const evt: AgentEvent = {
|
|
172
196
|
source: "opencode",
|
|
@@ -177,8 +201,8 @@ export const MoshiHooks: Plugin = async ({ client, directory }) => {
|
|
|
177
201
|
message: "",
|
|
178
202
|
eventId: crypto.randomUUID(),
|
|
179
203
|
projectName,
|
|
180
|
-
modelName: formatModelName(
|
|
181
|
-
|
|
204
|
+
modelName: formatModelName(await getOrLoadModel(client, sessionId)),
|
|
205
|
+
contextPercent: await getContextPercent(sessionId, client),
|
|
182
206
|
}
|
|
183
207
|
await sendAgentEvent(client, token, evt)
|
|
184
208
|
}
|
|
@@ -210,9 +234,6 @@ export const MoshiHooks: Plugin = async ({ client, directory }) => {
|
|
|
210
234
|
if (!tool || !INTERESTING_TOOLS.has(tool.toLowerCase())) return
|
|
211
235
|
if (await isSubagentSession(sessionID)) return
|
|
212
236
|
|
|
213
|
-
await writeState(sessionID, { lastToolName: tool })
|
|
214
|
-
|
|
215
|
-
const state = await readState(sessionID)
|
|
216
237
|
const projectName = directory ? basename(directory) : undefined
|
|
217
238
|
|
|
218
239
|
if (tool === "question") {
|
|
@@ -230,8 +251,9 @@ export const MoshiHooks: Plugin = async ({ client, directory }) => {
|
|
|
230
251
|
message: lines.join("\n---\n").slice(0, 512),
|
|
231
252
|
eventId: crypto.randomUUID(),
|
|
232
253
|
projectName,
|
|
233
|
-
modelName: formatModelName(
|
|
254
|
+
modelName: formatModelName(await getOrLoadModel(client, sessionID)),
|
|
234
255
|
toolName: tool,
|
|
256
|
+
contextPercent: await getContextPercent(sessionID, client),
|
|
235
257
|
}
|
|
236
258
|
await sendAgentEvent(client, token, evt)
|
|
237
259
|
return
|
|
@@ -243,11 +265,12 @@ export const MoshiHooks: Plugin = async ({ client, directory }) => {
|
|
|
243
265
|
sessionId: sessionID,
|
|
244
266
|
category: "tool_running",
|
|
245
267
|
title: `Running ${formatToolName(tool)}`,
|
|
246
|
-
message:
|
|
268
|
+
message: formatToolDescription(tool, output.args),
|
|
247
269
|
eventId: crypto.randomUUID(),
|
|
248
270
|
projectName,
|
|
249
|
-
modelName: formatModelName(
|
|
271
|
+
modelName: formatModelName(await getOrLoadModel(client, sessionID)),
|
|
250
272
|
toolName: tool,
|
|
273
|
+
contextPercent: await getContextPercent(sessionID, client),
|
|
251
274
|
}
|
|
252
275
|
await sendAgentEvent(client, token, evt)
|
|
253
276
|
},
|
|
@@ -262,7 +285,6 @@ export const MoshiHooks: Plugin = async ({ client, directory }) => {
|
|
|
262
285
|
|
|
263
286
|
if (tool === "question") return
|
|
264
287
|
|
|
265
|
-
const state = await readState(sessionID)
|
|
266
288
|
const projectName = directory ? basename(directory) : undefined
|
|
267
289
|
|
|
268
290
|
const evt: AgentEvent = {
|
|
@@ -274,8 +296,9 @@ export const MoshiHooks: Plugin = async ({ client, directory }) => {
|
|
|
274
296
|
message: "",
|
|
275
297
|
eventId: crypto.randomUUID(),
|
|
276
298
|
projectName,
|
|
277
|
-
modelName: formatModelName(
|
|
299
|
+
modelName: formatModelName(await getOrLoadModel(client, sessionID)),
|
|
278
300
|
toolName: tool,
|
|
301
|
+
contextPercent: await getContextPercent(sessionID, client),
|
|
279
302
|
}
|
|
280
303
|
await sendAgentEvent(client, token, evt)
|
|
281
304
|
},
|
|
@@ -285,8 +308,7 @@ export const MoshiHooks: Plugin = async ({ client, directory }) => {
|
|
|
285
308
|
if (!token) return
|
|
286
309
|
|
|
287
310
|
const sessionID = (input as any).sessionID ?? "unknown"
|
|
288
|
-
|
|
289
|
-
if (state.isSubagent) return
|
|
311
|
+
if (await isSubagentSession(sessionID)) return
|
|
290
312
|
|
|
291
313
|
const projectName = directory ? basename(directory) : undefined
|
|
292
314
|
|
|
@@ -301,7 +323,8 @@ export const MoshiHooks: Plugin = async ({ client, directory }) => {
|
|
|
301
323
|
message: prompt.slice(0, 256),
|
|
302
324
|
eventId: crypto.randomUUID(),
|
|
303
325
|
projectName,
|
|
304
|
-
modelName: formatModelName(
|
|
326
|
+
modelName: formatModelName(await getOrLoadModel(client, sessionID)),
|
|
327
|
+
contextPercent: await getContextPercent(sessionID, client),
|
|
305
328
|
}
|
|
306
329
|
await sendAgentEvent(client, token, evt)
|
|
307
330
|
},
|