opencode-raven 1.2.7 → 1.2.8

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 (3) hide show
  1. package/README.md +1 -1
  2. package/index.ts +66 -8
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -47,7 +47,7 @@ Restart opencode.
47
47
  | `/raven model <name>` | Change Raven's model (requires restart) |
48
48
  | `/raven effort <value>` | Change Raven's reasoning effort (requires restart) |
49
49
  | `/raven timeout <seconds>` | Change raven_seek timeout (min 10s, takes effect immediately) |
50
- | `/raven stats` | Show context processed (session + all-time, bytes + tokens) |
50
+ | `/raven stats` | Show context saved (session + all-time, bytes + tokens) |
51
51
 
52
52
  Config persists across restarts in `~/.config/opencode/raven-config.json` (global, shared across all projects). Auto-created on first run.
53
53
 
package/index.ts CHANGED
@@ -262,7 +262,9 @@ export default ((input: PluginInput) => {
262
262
  let config = loadConfig()
263
263
  const ravenSessions = new Set<string>()
264
264
  const ravenTaskCalls = new Set<string>()
265
+ const ravenTaskPrompts = new Map<string, number>()
265
266
  const sessionAgents = new Map<string, string>()
267
+ const ravenSessionParents = new Map<string, string>()
266
268
  let updateInfo: { current: string; latest?: string; available: boolean } | undefined
267
269
  let updateCheckPromise: Promise<{ current: string; latest?: string; available: boolean }> | undefined
268
270
  let updateToastPending = false
@@ -343,6 +345,20 @@ export default ((input: PluginInput) => {
343
345
  return { current: PACKAGE_VERSION, latest, available: !!latest && compareVersions(latest, PACKAGE_VERSION) > 0 }
344
346
  }
345
347
 
348
+ async function countRavenSessionBytes(sessionId: string): Promise<number> {
349
+ // Get last assistant message token counts (matches TUI bottom bar)
350
+ const messagesResp = await client.session.messages({ path: { id: sessionId }, query: { limit: 200 } })
351
+ const messages = (messagesResp as any)?.data ?? []
352
+ // Find last assistant message with output tokens (same logic as TUI subagent-footer.tsx)
353
+ const last = [...messages].reverse().find((m: any) =>
354
+ m?.info?.role === "assistant" && m?.info?.tokens?.output > 0
355
+ )
356
+ const t = last?.info?.tokens
357
+ if (!t) return 0
358
+ const totalTokens = (t.input ?? 0) + (t.output ?? 0) + (t.reasoning ?? 0) + (t.cache?.read ?? 0) + (t.cache?.write ?? 0)
359
+ return totalTokens * 4
360
+ }
361
+
346
362
  async function getUpdateInfo(): Promise<{ current: string; latest?: string; available: boolean }> {
347
363
  if (updateInfo) return updateInfo
348
364
  if (!updateCheckPromise) {
@@ -520,10 +536,23 @@ export default ((input: PluginInput) => {
520
536
  .map((p: any) => p.text)
521
537
  const output = textParts.join("\n") || "Raven returned no results."
522
538
 
523
- // Track context saved
524
- addBytes(output.length)
539
+ // Get total Raven session context and subtract input/output to get context saved
540
+ let totalProcessed = 0
541
+ try {
542
+ totalProcessed = await countRavenSessionBytes(sessionId)
543
+ } catch { /* best-effort */ }
544
+ if (totalProcessed <= 0) {
545
+ for (const part of parts) {
546
+ if (part.text) totalProcessed += part.text.length
547
+ if (part.args) totalProcessed += JSON.stringify(part.args).length
548
+ if (part.content) totalProcessed += typeof part.content === "string" ? part.content.length : JSON.stringify(part.content).length
549
+ }
550
+ }
551
+ // Context saved = total session context − input query − compact answer returned
552
+ const saved = Math.max(0, totalProcessed - output.length - String(args.query).length)
553
+ addBytes(saved)
525
554
 
526
- return { title: "Raven Seek", metadata: { sessionId }, output: `${output}\n\n*Raven searched for ${elapsed}s — ${formatBytes(output.length)}, ~${formatTokens(output.length)} tokens*` }
555
+ return { title: "Raven Seek", metadata: { sessionId }, output: `${output}\n\n*Raven searched for ${elapsed}s — ${formatBytes(totalProcessed)} processed, ${formatTokens(totalProcessed)} tokens*` }
527
556
  } catch (err: any) {
528
557
  const elapsed = ((Date.now() - started) / 1000).toFixed(1)
529
558
  const msg = String(err?.message ?? err ?? "").toLowerCase()
@@ -550,7 +579,13 @@ export default ((input: PluginInput) => {
550
579
  }
551
580
  },
552
581
 
553
- event() {
582
+ event(input: { event: any }) {
583
+ // Track subagent session → parent mapping for accurate context counting
584
+ const evt = input.event
585
+ if (evt?.type === "session.created" && evt?.properties?.parentID) {
586
+ ravenSessionParents.set(evt.properties.parentID, evt.properties.id)
587
+ }
588
+
554
589
  if (!updateToastPending) return
555
590
  updateToastPending = false
556
591
  setTimeout(() => void notifyIfUpdateAvailable(), 500)
@@ -572,7 +607,7 @@ export default ((input: PluginInput) => {
572
607
  saveConfig(config)
573
608
  output.parts.push({ type: "text", text: "Raven search interception disabled. All agents can use search tools directly." })
574
609
  } else if (arg === "stats") {
575
- output.parts.push({ type: "text", text: `Raven context processed:\n This session: ${formatBytes(sessionBytes)} (~${formatTokens(sessionBytes)} tokens)\n All time: ${formatBytes(totalBytes)} (~${formatTokens(totalBytes)} tokens)` })
610
+ output.parts.push({ type: "text", text: `Raven context saved:\n This session: ${formatBytes(sessionBytes)} (~${formatTokens(sessionBytes)} context)\n All time: ${formatBytes(totalBytes)} (~${formatTokens(totalBytes)} context)` })
576
611
  } else if (arg === "update") {
577
612
  try {
578
613
  const info = await refreshUpdateInfo()
@@ -626,7 +661,7 @@ export default ((input: PluginInput) => {
626
661
  ? `Update: ${info.latest} available. Run /raven update, then restart opencode.`
627
662
  : `Update: up to date${info.latest ? ` (latest ${info.latest})` : ""}.`
628
663
  } catch { /* keep fallback */ }
629
- output.parts.push({ type: "text", text: `Raven is ${enabled}. Version: ${PACKAGE_VERSION}. Model: ${model}. Reasoning: ${effort}. Timeout: ${timeout}s\n${update}\n\nCommands:\n /raven on — enable search interception\n /raven off — disable search interception\n /raven update — check npm, clear plugin cache if newer, then restart opencode\n /raven model <name> — change Raven's model (requires restart)\n /raven effort <value> — change Raven's reasoning effort (requires restart)\n /raven timeout <seconds> — change raven_seek timeout\n /raven stats — show blocked calls and context saved` })
664
+ output.parts.push({ type: "text", text: `Raven is ${enabled}. Version: ${PACKAGE_VERSION}. Model: ${model}. Reasoning: ${effort}. Timeout: ${timeout}s\n${update}\n\nRaven context saved:\n This session: ${formatBytes(sessionBytes)} (~${formatTokens(sessionBytes)} context)\n All time: ${formatBytes(totalBytes)} (~${formatTokens(totalBytes)} context)\n\nCommands:\n /raven on — enable search interception\n /raven off — disable search interception\n /raven update — check npm, clear plugin cache if newer, then restart opencode\n /raven model <name> — change Raven's model (requires restart)\n /raven effort <value> — change Raven's reasoning effort (requires restart)\n /raven timeout <seconds> — change raven_seek timeout\n /raven stats — show context saved` })
630
665
  }
631
666
  },
632
667
 
@@ -645,6 +680,10 @@ export default ((input: PluginInput) => {
645
680
  const subagentType = input.tool === "task" ? (output.args.subagent_type ?? "") : ""
646
681
  if (subagentType === "raven") {
647
682
  ravenTaskCalls.add(input.callID)
683
+ const promptField = ["prompt", "description", "request", "objective", "query"].find(
684
+ (f) => f in output.args
685
+ ) ?? "prompt"
686
+ ravenTaskPrompts.set(input.callID, String(output.args[promptField] ?? "").length)
648
687
  }
649
688
  if (subagentType !== "raven" && !isExcluded(subagentType)) {
650
689
  const field = ["prompt", "description", "request", "objective", "query"].find(
@@ -666,8 +705,27 @@ export default ((input: PluginInput) => {
666
705
  "tool.execute.after"(input: any, output: any) {
667
706
  if (ravenTaskCalls.has(input.callID)) {
668
707
  ravenTaskCalls.delete(input.callID)
669
- const outputLen = String(output.output ?? "").length
670
- if (outputLen > 0) addBytes(outputLen)
708
+ const promptBytes = ravenTaskPrompts.get(input.callID) ?? 0
709
+ ravenTaskPrompts.delete(input.callID)
710
+ // Try task metadata first (built-in tools preserve metadata)
711
+ const ravenSessionId = output.metadata?.sessionId ?? ravenSessionParents.get(input.sessionID)
712
+ if (ravenSessionId) {
713
+ if (ravenSessionParents.has(input.sessionID)) ravenSessionParents.delete(input.sessionID)
714
+ void countRavenSessionBytes(ravenSessionId)
715
+ .then((total) => {
716
+ const saved = Math.max(0, total - promptBytes - String(output.output ?? "").length)
717
+ if (saved > 0) addBytes(saved)
718
+ })
719
+ .catch(() => {
720
+ const outputLen = String(output.output ?? "").length
721
+ const saved = Math.max(0, outputLen - promptBytes)
722
+ if (saved > 0) addBytes(saved)
723
+ })
724
+ } else {
725
+ const outputLen = String(output.output ?? "").length
726
+ const saved = Math.max(0, outputLen - promptBytes)
727
+ if (saved > 0) addBytes(saved)
728
+ }
671
729
  }
672
730
  },
673
731
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opencode-raven",
3
- "version": "1.2.7",
3
+ "version": "1.2.8",
4
4
  "description": "Search-first subagent for opencode — intercepts search tools and routes them through a hidden Raven agent with Context7, Exa AI, and Grep.app MCPs",
5
5
  "main": "./index.ts",
6
6
  "exports": {