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.
Files changed (2) hide show
  1. package/index.ts +52 -55
  2. 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.replace(/^claude-/, "")
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 (state.isSubagent) continue
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(state.model),
181
- toolName: state.lastToolName,
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(state.model),
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(state.model),
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(state.model),
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
- const state = await readState(sessionID)
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(state.model),
300
+ modelName: formatModelName(await getOrLoadModel(client, sessionID)),
301
+ contextPercent: await getContextPercent(sessionID, client),
305
302
  }
306
303
  await sendAgentEvent(client, token, evt)
307
304
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "moshi-opencode-hooks",
3
- "version": "1.0.16",
3
+ "version": "1.0.17",
4
4
  "description": "OpenCode plugin for Moshi live activity integration",
5
5
  "repository": {
6
6
  "type": "git",