moshi-opencode-hooks 1.0.16 → 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 +52 -55
- 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,7 +114,30 @@ 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" } })
|
|
@@ -149,24 +162,9 @@ export const MoshiHooks: Plugin = async ({ client, directory }) => {
|
|
|
149
162
|
|
|
150
163
|
const sessionId = (event as any).sessionId ?? "unknown"
|
|
151
164
|
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
165
|
|
|
163
166
|
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 })
|
|
167
|
+
if (await isSubagentSession(sessionId)) continue
|
|
170
168
|
|
|
171
169
|
const evt: AgentEvent = {
|
|
172
170
|
source: "opencode",
|
|
@@ -177,8 +175,8 @@ export const MoshiHooks: Plugin = async ({ client, directory }) => {
|
|
|
177
175
|
message: "",
|
|
178
176
|
eventId: crypto.randomUUID(),
|
|
179
177
|
projectName,
|
|
180
|
-
modelName: formatModelName(
|
|
181
|
-
|
|
178
|
+
modelName: formatModelName(await getOrLoadModel(client, sessionId)),
|
|
179
|
+
contextPercent: await getContextPercent(sessionId, client),
|
|
182
180
|
}
|
|
183
181
|
await sendAgentEvent(client, token, evt)
|
|
184
182
|
}
|
|
@@ -210,9 +208,6 @@ export const MoshiHooks: Plugin = async ({ client, directory }) => {
|
|
|
210
208
|
if (!tool || !INTERESTING_TOOLS.has(tool.toLowerCase())) return
|
|
211
209
|
if (await isSubagentSession(sessionID)) return
|
|
212
210
|
|
|
213
|
-
await writeState(sessionID, { lastToolName: tool })
|
|
214
|
-
|
|
215
|
-
const state = await readState(sessionID)
|
|
216
211
|
const projectName = directory ? basename(directory) : undefined
|
|
217
212
|
|
|
218
213
|
if (tool === "question") {
|
|
@@ -230,8 +225,9 @@ export const MoshiHooks: Plugin = async ({ client, directory }) => {
|
|
|
230
225
|
message: lines.join("\n---\n").slice(0, 512),
|
|
231
226
|
eventId: crypto.randomUUID(),
|
|
232
227
|
projectName,
|
|
233
|
-
modelName: formatModelName(
|
|
228
|
+
modelName: formatModelName(await getOrLoadModel(client, sessionID)),
|
|
234
229
|
toolName: tool,
|
|
230
|
+
contextPercent: await getContextPercent(sessionID, client),
|
|
235
231
|
}
|
|
236
232
|
await sendAgentEvent(client, token, evt)
|
|
237
233
|
return
|
|
@@ -246,8 +242,9 @@ export const MoshiHooks: Plugin = async ({ client, directory }) => {
|
|
|
246
242
|
message: "",
|
|
247
243
|
eventId: crypto.randomUUID(),
|
|
248
244
|
projectName,
|
|
249
|
-
modelName: formatModelName(
|
|
245
|
+
modelName: formatModelName(await getOrLoadModel(client, sessionID)),
|
|
250
246
|
toolName: tool,
|
|
247
|
+
contextPercent: await getContextPercent(sessionID, client),
|
|
251
248
|
}
|
|
252
249
|
await sendAgentEvent(client, token, evt)
|
|
253
250
|
},
|
|
@@ -262,7 +259,6 @@ export const MoshiHooks: Plugin = async ({ client, directory }) => {
|
|
|
262
259
|
|
|
263
260
|
if (tool === "question") return
|
|
264
261
|
|
|
265
|
-
const state = await readState(sessionID)
|
|
266
262
|
const projectName = directory ? basename(directory) : undefined
|
|
267
263
|
|
|
268
264
|
const evt: AgentEvent = {
|
|
@@ -274,8 +270,9 @@ export const MoshiHooks: Plugin = async ({ client, directory }) => {
|
|
|
274
270
|
message: "",
|
|
275
271
|
eventId: crypto.randomUUID(),
|
|
276
272
|
projectName,
|
|
277
|
-
modelName: formatModelName(
|
|
273
|
+
modelName: formatModelName(await getOrLoadModel(client, sessionID)),
|
|
278
274
|
toolName: tool,
|
|
275
|
+
contextPercent: await getContextPercent(sessionID, client),
|
|
279
276
|
}
|
|
280
277
|
await sendAgentEvent(client, token, evt)
|
|
281
278
|
},
|
|
@@ -285,8 +282,7 @@ export const MoshiHooks: Plugin = async ({ client, directory }) => {
|
|
|
285
282
|
if (!token) return
|
|
286
283
|
|
|
287
284
|
const sessionID = (input as any).sessionID ?? "unknown"
|
|
288
|
-
|
|
289
|
-
if (state.isSubagent) return
|
|
285
|
+
if (await isSubagentSession(sessionID)) return
|
|
290
286
|
|
|
291
287
|
const projectName = directory ? basename(directory) : undefined
|
|
292
288
|
|
|
@@ -301,7 +297,8 @@ export const MoshiHooks: Plugin = async ({ client, directory }) => {
|
|
|
301
297
|
message: prompt.slice(0, 256),
|
|
302
298
|
eventId: crypto.randomUUID(),
|
|
303
299
|
projectName,
|
|
304
|
-
modelName: formatModelName(
|
|
300
|
+
modelName: formatModelName(await getOrLoadModel(client, sessionID)),
|
|
301
|
+
contextPercent: await getContextPercent(sessionID, client),
|
|
305
302
|
}
|
|
306
303
|
await sendAgentEvent(client, token, evt)
|
|
307
304
|
},
|