moshi-opencode-hooks 1.0.15 → 1.0.17
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 +61 -60
- 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,
|
|
@@ -124,13 +114,45 @@ function formatToolName(toolName: string): string {
|
|
|
124
114
|
|
|
125
115
|
function formatModelName(model: string | undefined): string | undefined {
|
|
126
116
|
if (!model) return undefined
|
|
127
|
-
return model.
|
|
117
|
+
return model.split("/").pop()
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
async function getContextPercent(sessionId: string, client: Parameters<Plugin>[0]["client"]): Promise<number | undefined> {
|
|
121
|
+
try {
|
|
122
|
+
const sessionRes = await client.session.get({ path: { id: sessionId } })
|
|
123
|
+
const sessionData = (sessionRes as any).data
|
|
124
|
+
if (!sessionData) return undefined
|
|
125
|
+
|
|
126
|
+
const model = sessionData.model as { limit?: { context?: number } } | undefined
|
|
127
|
+
const contextLimit = model?.limit?.context
|
|
128
|
+
if (!contextLimit) return undefined
|
|
129
|
+
|
|
130
|
+
const messagesRes = await client.session.messages({ path: { id: sessionId }, query: { limit: 1 } })
|
|
131
|
+
const messagesData = (messagesRes as any).data
|
|
132
|
+
const messages = messagesData?.messages ?? messagesData
|
|
133
|
+
const lastMsg = Array.isArray(messages) ? messages[messages.length - 1] : messages
|
|
134
|
+
if (!lastMsg?.tokens?.input) return undefined
|
|
135
|
+
|
|
136
|
+
const totalInputTokens = lastMsg.tokens.input
|
|
137
|
+
return Math.min(100, Math.round((totalInputTokens / contextLimit) * 100))
|
|
138
|
+
} catch {
|
|
139
|
+
}
|
|
140
|
+
return undefined
|
|
128
141
|
}
|
|
129
142
|
|
|
130
143
|
const pkg = await import("./package.json", { assert: { type: "json" } })
|
|
131
144
|
const VERSION = pkg.default.version
|
|
132
145
|
|
|
133
146
|
export const MoshiHooks: Plugin = async ({ client, directory }) => {
|
|
147
|
+
setTimeout(() => {
|
|
148
|
+
client.tui.showToast({
|
|
149
|
+
body: {
|
|
150
|
+
message: `moshi-opencode-hooks v${VERSION} is active`,
|
|
151
|
+
variant: "info",
|
|
152
|
+
},
|
|
153
|
+
})
|
|
154
|
+
}, 5000)
|
|
155
|
+
|
|
134
156
|
const setupEventSubscription = async () => {
|
|
135
157
|
try {
|
|
136
158
|
const events = await client.event.subscribe()
|
|
@@ -140,24 +162,9 @@ export const MoshiHooks: Plugin = async ({ client, directory }) => {
|
|
|
140
162
|
|
|
141
163
|
const sessionId = (event as any).sessionId ?? "unknown"
|
|
142
164
|
const projectName = directory ? basename(directory) : undefined
|
|
143
|
-
const state = await readState(sessionId)
|
|
144
|
-
|
|
145
|
-
if (event.type === "session.created") {
|
|
146
|
-
const isSubagent = await isSubagentSession(sessionId)
|
|
147
|
-
await writeState(sessionId, {
|
|
148
|
-
model: (event as any).properties?.model,
|
|
149
|
-
isSubagent,
|
|
150
|
-
})
|
|
151
|
-
continue
|
|
152
|
-
}
|
|
153
165
|
|
|
154
166
|
if (event.type === "session.idle") {
|
|
155
|
-
if (
|
|
156
|
-
|
|
157
|
-
const now = Date.now() / 1000
|
|
158
|
-
if (state.lastStopTime && now - state.lastStopTime < 5) continue
|
|
159
|
-
|
|
160
|
-
await writeState(sessionId, { lastStopTime: now })
|
|
167
|
+
if (await isSubagentSession(sessionId)) continue
|
|
161
168
|
|
|
162
169
|
const evt: AgentEvent = {
|
|
163
170
|
source: "opencode",
|
|
@@ -168,8 +175,8 @@ export const MoshiHooks: Plugin = async ({ client, directory }) => {
|
|
|
168
175
|
message: "",
|
|
169
176
|
eventId: crypto.randomUUID(),
|
|
170
177
|
projectName,
|
|
171
|
-
modelName: formatModelName(
|
|
172
|
-
|
|
178
|
+
modelName: formatModelName(await getOrLoadModel(client, sessionId)),
|
|
179
|
+
contextPercent: await getContextPercent(sessionId, client),
|
|
173
180
|
}
|
|
174
181
|
await sendAgentEvent(client, token, evt)
|
|
175
182
|
}
|
|
@@ -193,11 +200,6 @@ export const MoshiHooks: Plugin = async ({ client, directory }) => {
|
|
|
193
200
|
}
|
|
194
201
|
|
|
195
202
|
return {
|
|
196
|
-
"tui.toast.show": async (_input: unknown, output: any) => {
|
|
197
|
-
output.message = `moshi-opencode-hooks v${VERSION} is active`
|
|
198
|
-
output.type = "info"
|
|
199
|
-
},
|
|
200
|
-
|
|
201
203
|
"tool.execute.before": async (input, output) => {
|
|
202
204
|
const token = await loadToken()
|
|
203
205
|
if (!token) return
|
|
@@ -206,9 +208,6 @@ export const MoshiHooks: Plugin = async ({ client, directory }) => {
|
|
|
206
208
|
if (!tool || !INTERESTING_TOOLS.has(tool.toLowerCase())) return
|
|
207
209
|
if (await isSubagentSession(sessionID)) return
|
|
208
210
|
|
|
209
|
-
await writeState(sessionID, { lastToolName: tool })
|
|
210
|
-
|
|
211
|
-
const state = await readState(sessionID)
|
|
212
211
|
const projectName = directory ? basename(directory) : undefined
|
|
213
212
|
|
|
214
213
|
if (tool === "question") {
|
|
@@ -226,8 +225,9 @@ export const MoshiHooks: Plugin = async ({ client, directory }) => {
|
|
|
226
225
|
message: lines.join("\n---\n").slice(0, 512),
|
|
227
226
|
eventId: crypto.randomUUID(),
|
|
228
227
|
projectName,
|
|
229
|
-
modelName: formatModelName(
|
|
228
|
+
modelName: formatModelName(await getOrLoadModel(client, sessionID)),
|
|
230
229
|
toolName: tool,
|
|
230
|
+
contextPercent: await getContextPercent(sessionID, client),
|
|
231
231
|
}
|
|
232
232
|
await sendAgentEvent(client, token, evt)
|
|
233
233
|
return
|
|
@@ -242,8 +242,9 @@ export const MoshiHooks: Plugin = async ({ client, directory }) => {
|
|
|
242
242
|
message: "",
|
|
243
243
|
eventId: crypto.randomUUID(),
|
|
244
244
|
projectName,
|
|
245
|
-
modelName: formatModelName(
|
|
245
|
+
modelName: formatModelName(await getOrLoadModel(client, sessionID)),
|
|
246
246
|
toolName: tool,
|
|
247
|
+
contextPercent: await getContextPercent(sessionID, client),
|
|
247
248
|
}
|
|
248
249
|
await sendAgentEvent(client, token, evt)
|
|
249
250
|
},
|
|
@@ -258,7 +259,6 @@ export const MoshiHooks: Plugin = async ({ client, directory }) => {
|
|
|
258
259
|
|
|
259
260
|
if (tool === "question") return
|
|
260
261
|
|
|
261
|
-
const state = await readState(sessionID)
|
|
262
262
|
const projectName = directory ? basename(directory) : undefined
|
|
263
263
|
|
|
264
264
|
const evt: AgentEvent = {
|
|
@@ -270,8 +270,9 @@ export const MoshiHooks: Plugin = async ({ client, directory }) => {
|
|
|
270
270
|
message: "",
|
|
271
271
|
eventId: crypto.randomUUID(),
|
|
272
272
|
projectName,
|
|
273
|
-
modelName: formatModelName(
|
|
273
|
+
modelName: formatModelName(await getOrLoadModel(client, sessionID)),
|
|
274
274
|
toolName: tool,
|
|
275
|
+
contextPercent: await getContextPercent(sessionID, client),
|
|
275
276
|
}
|
|
276
277
|
await sendAgentEvent(client, token, evt)
|
|
277
278
|
},
|
|
@@ -281,8 +282,7 @@ export const MoshiHooks: Plugin = async ({ client, directory }) => {
|
|
|
281
282
|
if (!token) return
|
|
282
283
|
|
|
283
284
|
const sessionID = (input as any).sessionID ?? "unknown"
|
|
284
|
-
|
|
285
|
-
if (state.isSubagent) return
|
|
285
|
+
if (await isSubagentSession(sessionID)) return
|
|
286
286
|
|
|
287
287
|
const projectName = directory ? basename(directory) : undefined
|
|
288
288
|
|
|
@@ -297,7 +297,8 @@ export const MoshiHooks: Plugin = async ({ client, directory }) => {
|
|
|
297
297
|
message: prompt.slice(0, 256),
|
|
298
298
|
eventId: crypto.randomUUID(),
|
|
299
299
|
projectName,
|
|
300
|
-
modelName: formatModelName(
|
|
300
|
+
modelName: formatModelName(await getOrLoadModel(client, sessionID)),
|
|
301
|
+
contextPercent: await getContextPercent(sessionID, client),
|
|
301
302
|
}
|
|
302
303
|
await sendAgentEvent(client, token, evt)
|
|
303
304
|
},
|