@serkanalgur/opencodev2-slim 2.0.12 → 2.0.13

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 +7 -0
  2. package/package.json +1 -1
  3. package/src/tui.tsx +63 -7
package/README.md CHANGED
@@ -141,6 +141,13 @@ Note: Compression is performed by the AI assistant using the `compress` tool. Th
141
141
 
142
142
  ## Changelog
143
143
 
144
+ ### 2.0.13
145
+
146
+ - Fix `/panel` in the TUI not reflecting real context usage:
147
+ - The TUI command now reads live server measurements (`Session.Info.tokens` + `cost` + `model` + context window) via `context.client.session.get()` and prints them (measured tokens, %, cost, model) at the bottom of the panel, matching what the `panel` tool reports.
148
+ - Resolve the active session from `context.ui.router.current()` instead of a non-existent `context.router`, so the panel targets the focused session rather than always the first one.
149
+ - Call `context.data.session.message.sync()` before reading the transcript so stats aren't computed from an empty/stale cache.
150
+
144
151
  ### 2.0.12
145
152
 
146
153
  - Bind panel/nudge to real OpenCode context measurements:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@serkanalgur/opencodev2-slim",
3
- "version": "2.0.12",
3
+ "version": "2.0.13",
4
4
  "description": "Smart context management plugin for OpenCode v2 - semantic compression, cost-aware pruning, adaptive thresholds",
5
5
  "keywords": [
6
6
  "opencode",
package/src/tui.tsx CHANGED
@@ -94,7 +94,14 @@ export function deriveStats(messages: readonly unknown[]): PanelStats {
94
94
  }
95
95
 
96
96
  // Builds a human-readable panel as plain text (injected into the message stream).
97
- function renderPanelText(sessionID: string, stats: PanelStats): string {
97
+ function renderPanelText(
98
+ sessionID: string,
99
+ stats: PanelStats,
100
+ real?: MeasuredReal | null,
101
+ ): string {
102
+ const limit = real?.contextLimit ?? 0
103
+ const pct = real?.usagePercent ?? 0
104
+ const status = real ? (pct >= 90 ? "critical" : pct >= 70 ? "warning" : "healthy") : "n/a"
98
105
  const lines: string[] = []
99
106
  lines.push("┌─────────────────────────────────────────────────────────────┐")
100
107
  lines.push("│ SLIM CONTEXT PANEL │")
@@ -107,22 +114,67 @@ function renderPanelText(sessionID: string, stats: PanelStats): string {
107
114
  lines.push(`│ Tool calls: ${stats.toolCalls} Compactions: ${stats.compactionCount}`)
108
115
  lines.push(`│ Tokens (est): User ${stats.tokensByRole.user} | Assistant ${stats.tokensByRole.assistant} | System ${stats.tokensByRole.system}`)
109
116
  lines.push(`│ Total token estimate: ${stats.totalTokens}`)
117
+ if (real) {
118
+ lines.push("├─────────────────────────────────────────────────────────────┤")
119
+ lines.push(`│ Measured tokens: ${real.tokens} (${pct}% of ${limit}) [${status}]`)
120
+ if (real.cost > 0) lines.push(`│ Cost: $${real.cost.toFixed(6)}`)
121
+ lines.push(`│ Model: ${real.model}`)
122
+ }
110
123
  lines.push("└─────────────────────────────────────────────────────────────┘")
111
124
  return lines.join("\n")
112
125
  }
113
126
 
114
- // Resolves the "current" session: the focused session if any, else the most recent.
127
+ // Resolves the "current" session: the router-focused session if any, else the most recent.
115
128
  function resolveCurrentSession(context: any): string | null {
116
129
  const sessions = context.data.session.list() || []
117
130
  if (sessions.length === 0) return null
118
- // Prefer the focused session if exposed; otherwise fall back to the first.
119
- const focused = context.router?.current?.()
120
- if (focused && typeof focused === "object" && "sessionID" in focused) {
121
- return focused.sessionID as string
131
+ // The TUI host exposes the active route via context.ui.router (not context.router).
132
+ const route = context.ui?.router?.current?.()
133
+ if (route && typeof route === "object" && "sessionID" in route) {
134
+ return route.sessionID as string
122
135
  }
123
136
  return sessions[0].id
124
137
  }
125
138
 
139
+ // Server-measured context numbers for a session (Session.Info.tokens + cost + model),
140
+ // mirroring what the `panel` tool in index.ts reads via ctx.session.get().
141
+ interface MeasuredReal {
142
+ tokens: number
143
+ cost: number
144
+ contextLimit: number
145
+ model: string
146
+ usagePercent: number
147
+ }
148
+
149
+ async function measureSession(context: any, sessionID: string): Promise<MeasuredReal | null> {
150
+ try {
151
+ const info: any = await context.client.session.get({ sessionID })
152
+ if (!info) return null
153
+ const tokens: any = info.tokens ?? {}
154
+ const tokenCount =
155
+ (typeof tokens.input === "number" ? tokens.input : 0) +
156
+ (typeof tokens.output === "number" ? tokens.output : 0) +
157
+ (typeof tokens.reasoning === "number" ? tokens.reasoning : 0) +
158
+ (typeof tokens.cache?.read === "number" ? tokens.cache.read : 0) +
159
+ (typeof tokens.cache?.write === "number" ? tokens.cache.write : 0)
160
+ const contextLimit: number =
161
+ typeof info.model?.limit?.context === "number" && info.model.limit.context > 0
162
+ ? info.model.limit.context
163
+ : 200000
164
+ const usagePercent =
165
+ contextLimit > 0 ? Math.min(100, Math.round((tokenCount / contextLimit) * 100)) : 0
166
+ return {
167
+ tokens: tokenCount,
168
+ cost: typeof info.cost === "number" ? info.cost : 0,
169
+ contextLimit,
170
+ model: info.model?.id || "unknown",
171
+ usagePercent,
172
+ }
173
+ } catch {
174
+ return null
175
+ }
176
+ }
177
+
126
178
  export default Plugin.define({
127
179
  id: "opencodev2-slim.cli",
128
180
  setup(context) {
@@ -160,10 +212,14 @@ export default Plugin.define({
160
212
  }
161
213
 
162
214
  try {
215
+ // Make sure the cached transcript is loaded before reading it.
216
+ await context.data.session.message.sync(sessionID)
163
217
  const messages =
164
218
  context.data.session.message.list(sessionID) || []
219
+ // Prefer live server-measured context numbers when available.
220
+ const real = await measureSession(context, sessionID)
165
221
  const stats = deriveStats(messages)
166
- const text = renderPanelText(sessionID, stats)
222
+ const text = renderPanelText(sessionID, stats, real)
167
223
  // Inject the panel as plain text into the session stream,
168
224
  // so it doesn't take over OpenCode's own panel UI.
169
225
  await context.client.session.synthetic({