@serkanalgur/opencodev2-slim 2.1.0 → 2.2.0

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/package.json +1 -1
  2. package/src/tui.tsx +235 -13
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@serkanalgur/opencodev2-slim",
3
- "version": "2.1.0",
3
+ "version": "2.2.0",
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
@@ -157,17 +157,38 @@ async function measureSession(context: any, sessionID: string): Promise<Measured
157
157
  (typeof tokens.reasoning === "number" ? tokens.reasoning : 0) +
158
158
  (typeof tokens.cache?.read === "number" ? tokens.cache.read : 0) +
159
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
160
+
161
+ // Resolve context limit: try model.list() first, then session info, then default.
162
+ let contextLimit = 200000
163
+ const modelID: string = info.model?.id || ""
164
+ const providerID: string = info.model?.providerID || ""
165
+
166
+ try {
167
+ const modelList: any = await context.client.model.list()
168
+ const models: any[] = modelList?.data ?? modelList ?? []
169
+ // Find by exact match (providerID/modelID), then by modelID alone
170
+ const found = models.find(
171
+ (m: any) => m.providerID === providerID && m.modelID === modelID,
172
+ ) || models.find((m: any) => m.modelID === modelID)
173
+ if (found?.limit?.context && found.limit.context > 0) {
174
+ contextLimit = found.limit.context
175
+ }
176
+ } catch {
177
+ // Fall through to session info or default
178
+ }
179
+
180
+ // Fallback: session info might have model.limit.context
181
+ if (contextLimit === 200000 && info.model?.limit?.context && info.model.limit.context > 0) {
182
+ contextLimit = info.model.limit.context
183
+ }
184
+
164
185
  const usagePercent =
165
186
  contextLimit > 0 ? Math.min(100, Math.round((tokenCount / contextLimit) * 100)) : 0
166
187
  return {
167
188
  tokens: tokenCount,
168
189
  cost: typeof info.cost === "number" ? info.cost : 0,
169
190
  contextLimit,
170
- model: info.model?.id || "unknown",
191
+ model: modelID || "unknown",
171
192
  usagePercent,
172
193
  }
173
194
  } catch {
@@ -178,7 +199,7 @@ async function measureSession(context: any, sessionID: string): Promise<Measured
178
199
  export default Plugin.define({
179
200
  id: "opencodev2-slim.cli",
180
201
  setup(context) {
181
- // Register the command inside the "app" slot render, where the keymap
202
+ // Register commands inside the "app" slot render, where the keymap
182
203
  // provider is available (consistent with OpenCode V2 CLI plugins).
183
204
  context.ui.slot({
184
205
  append: "app",
@@ -187,6 +208,7 @@ export default Plugin.define({
187
208
  mode: "global",
188
209
  priority: 10,
189
210
  commands: [
211
+ // ─── /panel ────────────────────────────────────
190
212
  {
191
213
  id: "opencodev2-slim.panel",
192
214
  title: "Show Slim Context Panel",
@@ -212,16 +234,12 @@ export default Plugin.define({
212
234
  }
213
235
 
214
236
  try {
215
- // Make sure the cached transcript is loaded before reading it.
216
237
  await context.data.session.message.sync(sessionID)
217
238
  const messages =
218
239
  context.data.session.message.list(sessionID) || []
219
- // Prefer live server-measured context numbers when available.
220
240
  const real = await measureSession(context, sessionID)
221
241
  const stats = deriveStats(messages)
222
242
  const text = renderPanelText(sessionID, stats, real)
223
- // Inject the panel as plain text into the session stream,
224
- // so it doesn't take over OpenCode's own panel UI.
225
243
  await context.client.session.synthetic({
226
244
  sessionID,
227
245
  text,
@@ -236,6 +254,210 @@ export default Plugin.define({
236
254
  }
237
255
  },
238
256
  },
257
+
258
+ // ─── /compress ─────────────────────────────────
259
+ {
260
+ id: "opencodev2-slim.compress",
261
+ title: "Compress Context",
262
+ group: "Slim",
263
+ palette: true,
264
+ slash: {
265
+ name: "compress",
266
+ aliases: ["slim-compress"],
267
+ args: [
268
+ {
269
+ name: "focus",
270
+ description: "What to compress (e.g., 'old exploration')",
271
+ required: false,
272
+ },
273
+ ],
274
+ },
275
+ enabled: true,
276
+ suggested: true,
277
+ run: async (input: unknown, event: unknown) => {
278
+ const sessionID =
279
+ resolveCurrentSession(context) ||
280
+ (event && typeof event === "object" && "sessionID" in event
281
+ ? (event as any).sessionID
282
+ : null)
283
+
284
+ if (!sessionID) {
285
+ context.ui.toast.show({
286
+ title: "Slim Compress",
287
+ message: "No active session found.",
288
+ variant: "warning",
289
+ })
290
+ return
291
+ }
292
+
293
+ try {
294
+ const args = (input as any) || {}
295
+ const focus = args.focus || "user-requested compression"
296
+
297
+ // Inject a synthetic message asking the assistant to compress.
298
+ const prompt = `Please call the compress tool now with: compress({ focus: "${focus}", mode: "auto" })`
299
+ await context.client.session.synthetic({
300
+ sessionID,
301
+ text: prompt,
302
+ description: "slim-compress",
303
+ })
304
+
305
+ context.ui.toast.show({
306
+ title: "Slim Compress",
307
+ message: `Compression requested: "${focus}". The assistant will process it on the next turn.`,
308
+ variant: "success",
309
+ duration: 3000,
310
+ })
311
+ } catch (e) {
312
+ context.ui.toast.show({
313
+ title: "Slim Compress",
314
+ message: `Error: ${e instanceof Error ? e.message : e}`,
315
+ variant: "error",
316
+ })
317
+ }
318
+ },
319
+ },
320
+
321
+ // ─── /status ──────────────────────────────────
322
+ {
323
+ id: "opencodev2-slim.status",
324
+ title: "Show Compact Status",
325
+ group: "Slim",
326
+ palette: true,
327
+ slash: { name: "status", aliases: ["slim-status"] },
328
+ enabled: true,
329
+ suggested: false,
330
+ run: async (input: unknown, event: unknown) => {
331
+ const sessionID =
332
+ resolveCurrentSession(context) ||
333
+ (event && typeof event === "object" && "sessionID" in event
334
+ ? (event as any).sessionID
335
+ : null)
336
+
337
+ if (!sessionID) {
338
+ context.ui.toast.show({
339
+ title: "Slim Status",
340
+ message: "No active session found.",
341
+ variant: "warning",
342
+ })
343
+ return
344
+ }
345
+
346
+ try {
347
+ const real = await measureSession(context, sessionID)
348
+ if (!real) {
349
+ context.ui.toast.show({
350
+ title: "Slim Status",
351
+ message: "Could not measure session.",
352
+ variant: "warning",
353
+ })
354
+ return
355
+ }
356
+
357
+ const status =
358
+ real.usagePercent >= 90
359
+ ? "🔴 CRITICAL"
360
+ : real.usagePercent >= 70
361
+ ? "🟡 WARNING"
362
+ : "🟢 HEALTHY"
363
+
364
+ const text = [
365
+ `**Context Status:** ${status}`,
366
+ `**Usage:** ${real.tokens.toLocaleString()} / ${real.contextLimit.toLocaleString()} tokens (${real.usagePercent}%)`,
367
+ `**Model:** ${real.model}`,
368
+ real.cost > 0 ? `**Cost:** $${real.cost.toFixed(4)}` : "",
369
+ ]
370
+ .filter(Boolean)
371
+ .join("\n")
372
+
373
+ await context.client.session.synthetic({
374
+ sessionID,
375
+ text,
376
+ description: "slim-status",
377
+ })
378
+ } catch (e) {
379
+ context.ui.toast.show({
380
+ title: "Slim Status",
381
+ message: `Error: ${e instanceof Error ? e.message : e}`,
382
+ variant: "error",
383
+ })
384
+ }
385
+ },
386
+ },
387
+
388
+ // ─── /slim-debug ──────────────────────────────
389
+ {
390
+ id: "opencodev2-slim.debug",
391
+ title: "Toggle Slim Debug Mode",
392
+ group: "Slim",
393
+ palette: true,
394
+ slash: { name: "slim-debug", aliases: ["debug-slim"] },
395
+ enabled: true,
396
+ suggested: false,
397
+ run: async (input: unknown, event: unknown) => {
398
+ const sessionID =
399
+ resolveCurrentSession(context) ||
400
+ (event && typeof event === "object" && "sessionID" in event
401
+ ? (event as any).sessionID
402
+ : null)
403
+
404
+ if (!sessionID) {
405
+ context.ui.toast.show({
406
+ title: "Slim Debug",
407
+ message: "No active session found.",
408
+ variant: "warning",
409
+ })
410
+ return
411
+ }
412
+
413
+ try {
414
+ // Read current config
415
+ const configPath = `${process.env.HOME || "~"}/.config/opencode/slim.jsonc`
416
+ const fs = await import("fs")
417
+ let debug = false
418
+ if (fs.existsSync(configPath)) {
419
+ const content = fs.readFileSync(configPath, "utf-8")
420
+ const match = content.match(/"debug"\s*:\s*(true|false)/)
421
+ if (match) debug = match[1] === "true"
422
+ }
423
+
424
+ // Toggle
425
+ debug = !debug
426
+
427
+ // Write back
428
+ const { parse } = await import("jsonc-parser")
429
+ let config: any = {}
430
+ if (fs.existsSync(configPath)) {
431
+ config = parse(fs.readFileSync(configPath, "utf-8")) || {}
432
+ }
433
+ config.debug = debug
434
+
435
+ const dir = `${process.env.HOME || "~"}/.config/opencode`
436
+ if (!fs.existsSync(dir)) {
437
+ fs.mkdirSync(dir, { recursive: true })
438
+ }
439
+ fs.writeFileSync(
440
+ configPath,
441
+ JSON.stringify(config, null, 2),
442
+ "utf-8",
443
+ )
444
+
445
+ const text = `**Slim Debug Mode:** ${debug ? "ON 🔴" : "OFF ⚪"}\n\nDebug logs will ${debug ? "now" : "no longer"} appear in the console.`
446
+
447
+ await context.client.session.synthetic({
448
+ sessionID,
449
+ text,
450
+ description: "slim-debug",
451
+ })
452
+ } catch (e) {
453
+ context.ui.toast.show({
454
+ title: "Slim Debug",
455
+ message: `Error: ${e instanceof Error ? e.message : e}`,
456
+ variant: "error",
457
+ })
458
+ }
459
+ },
460
+ },
239
461
  ],
240
462
  }))
241
463
  return null
@@ -243,10 +465,10 @@ export default Plugin.define({
243
465
  })
244
466
 
245
467
  context.ui.toast.show({
246
- title: "Slim Plugin",
247
- message: "Use /panel to print the context panel as a message.",
468
+ title: "Slim Plugin v2.1.0",
469
+ message: "Commands: /panel, /compress, /status, /slim-debug",
248
470
  variant: "success",
249
- duration: 3000,
471
+ duration: 4000,
250
472
  })
251
473
 
252
474
  return () => {}