@serkanalgur/opencodev2-slim 2.1.0 → 2.2.1
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/package.json +1 -1
- package/src/index.ts +2 -2
- package/src/tui.tsx +266 -13
package/package.json
CHANGED
package/src/index.ts
CHANGED
|
@@ -279,7 +279,7 @@ export default Plugin.define({
|
|
|
279
279
|
required: ["focus"],
|
|
280
280
|
additionalProperties: false,
|
|
281
281
|
},
|
|
282
|
-
options: { codemode:
|
|
282
|
+
options: { codemode: false },
|
|
283
283
|
execute: async (input, context) => {
|
|
284
284
|
const args = input as {
|
|
285
285
|
focus: string
|
|
@@ -425,7 +425,7 @@ export default Plugin.define({
|
|
|
425
425
|
properties: {},
|
|
426
426
|
additionalProperties: false,
|
|
427
427
|
},
|
|
428
|
-
options: { codemode:
|
|
428
|
+
options: { codemode: false },
|
|
429
429
|
execute: async (_input, context) => {
|
|
430
430
|
const sessionId = context.sessionID
|
|
431
431
|
const config = getConfig(sessionId)
|
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
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
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:
|
|
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
|
|
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,241 @@ 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
|
+
name: "mode",
|
|
275
|
+
description: "Compression mode: auto, range, or topic",
|
|
276
|
+
required: false,
|
|
277
|
+
},
|
|
278
|
+
{
|
|
279
|
+
name: "keepRecent",
|
|
280
|
+
description: "Number of recent messages to keep (default: 5)",
|
|
281
|
+
required: false,
|
|
282
|
+
},
|
|
283
|
+
],
|
|
284
|
+
},
|
|
285
|
+
enabled: true,
|
|
286
|
+
suggested: true,
|
|
287
|
+
run: async (input: unknown, event: unknown) => {
|
|
288
|
+
const sessionID =
|
|
289
|
+
resolveCurrentSession(context) ||
|
|
290
|
+
(event && typeof event === "object" && "sessionID" in event
|
|
291
|
+
? (event as any).sessionID
|
|
292
|
+
: null)
|
|
293
|
+
|
|
294
|
+
if (!sessionID) {
|
|
295
|
+
context.ui.toast.show({
|
|
296
|
+
title: "Slim Compress",
|
|
297
|
+
message: "No active session found.",
|
|
298
|
+
variant: "warning",
|
|
299
|
+
})
|
|
300
|
+
return
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
try {
|
|
304
|
+
const args = (input as any) || {}
|
|
305
|
+
const focus = args.focus || "user-requested compression"
|
|
306
|
+
const mode = args.mode || "auto"
|
|
307
|
+
const keepRecent = args.keepRecent ?? 5
|
|
308
|
+
|
|
309
|
+
// Measure current state first
|
|
310
|
+
const real = await measureSession(context, sessionID)
|
|
311
|
+
|
|
312
|
+
const statusLine = real
|
|
313
|
+
? `${real.tokens.toLocaleString()} tokens (${real.usagePercent}% of ${real.contextLimit.toLocaleString()})`
|
|
314
|
+
: "unknown"
|
|
315
|
+
|
|
316
|
+
const text = [
|
|
317
|
+
`**Slim Compress**`,
|
|
318
|
+
``,
|
|
319
|
+
`**Current state:** ${statusLine}`,
|
|
320
|
+
``,
|
|
321
|
+
`Ready to compress with:`,
|
|
322
|
+
`- **Focus:** ${focus}`,
|
|
323
|
+
`- **Mode:** ${mode}`,
|
|
324
|
+
`- **Keep recent:** ${keepRecent} messages`,
|
|
325
|
+
``,
|
|
326
|
+
`> The assistant will now call the compress tool.`,
|
|
327
|
+
`> Or type: \`compress({ focus: "${focus}", mode: "${mode}", keepRecent: ${keepRecent} })\``,
|
|
328
|
+
].join("\n")
|
|
329
|
+
|
|
330
|
+
await context.client.session.synthetic({
|
|
331
|
+
sessionID,
|
|
332
|
+
text,
|
|
333
|
+
description: "slim-compress",
|
|
334
|
+
})
|
|
335
|
+
|
|
336
|
+
context.ui.toast.show({
|
|
337
|
+
title: "Slim Compress",
|
|
338
|
+
message: `Compression ready: "${focus}". Assistant will process it.`,
|
|
339
|
+
variant: "success",
|
|
340
|
+
duration: 3000,
|
|
341
|
+
})
|
|
342
|
+
} catch (e) {
|
|
343
|
+
context.ui.toast.show({
|
|
344
|
+
title: "Slim Compress",
|
|
345
|
+
message: `Error: ${e instanceof Error ? e.message : e}`,
|
|
346
|
+
variant: "error",
|
|
347
|
+
})
|
|
348
|
+
}
|
|
349
|
+
},
|
|
350
|
+
},
|
|
351
|
+
|
|
352
|
+
// ─── /status ──────────────────────────────────
|
|
353
|
+
{
|
|
354
|
+
id: "opencodev2-slim.status",
|
|
355
|
+
title: "Show Compact Status",
|
|
356
|
+
group: "Slim",
|
|
357
|
+
palette: true,
|
|
358
|
+
slash: { name: "status", aliases: ["slim-status"] },
|
|
359
|
+
enabled: true,
|
|
360
|
+
suggested: false,
|
|
361
|
+
run: async (input: unknown, event: unknown) => {
|
|
362
|
+
const sessionID =
|
|
363
|
+
resolveCurrentSession(context) ||
|
|
364
|
+
(event && typeof event === "object" && "sessionID" in event
|
|
365
|
+
? (event as any).sessionID
|
|
366
|
+
: null)
|
|
367
|
+
|
|
368
|
+
if (!sessionID) {
|
|
369
|
+
context.ui.toast.show({
|
|
370
|
+
title: "Slim Status",
|
|
371
|
+
message: "No active session found.",
|
|
372
|
+
variant: "warning",
|
|
373
|
+
})
|
|
374
|
+
return
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
try {
|
|
378
|
+
const real = await measureSession(context, sessionID)
|
|
379
|
+
if (!real) {
|
|
380
|
+
context.ui.toast.show({
|
|
381
|
+
title: "Slim Status",
|
|
382
|
+
message: "Could not measure session.",
|
|
383
|
+
variant: "warning",
|
|
384
|
+
})
|
|
385
|
+
return
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
const status =
|
|
389
|
+
real.usagePercent >= 90
|
|
390
|
+
? "🔴 CRITICAL"
|
|
391
|
+
: real.usagePercent >= 70
|
|
392
|
+
? "🟡 WARNING"
|
|
393
|
+
: "🟢 HEALTHY"
|
|
394
|
+
|
|
395
|
+
const text = [
|
|
396
|
+
`**Context Status:** ${status}`,
|
|
397
|
+
`**Usage:** ${real.tokens.toLocaleString()} / ${real.contextLimit.toLocaleString()} tokens (${real.usagePercent}%)`,
|
|
398
|
+
`**Model:** ${real.model}`,
|
|
399
|
+
real.cost > 0 ? `**Cost:** $${real.cost.toFixed(4)}` : "",
|
|
400
|
+
]
|
|
401
|
+
.filter(Boolean)
|
|
402
|
+
.join("\n")
|
|
403
|
+
|
|
404
|
+
await context.client.session.synthetic({
|
|
405
|
+
sessionID,
|
|
406
|
+
text,
|
|
407
|
+
description: "slim-status",
|
|
408
|
+
})
|
|
409
|
+
} catch (e) {
|
|
410
|
+
context.ui.toast.show({
|
|
411
|
+
title: "Slim Status",
|
|
412
|
+
message: `Error: ${e instanceof Error ? e.message : e}`,
|
|
413
|
+
variant: "error",
|
|
414
|
+
})
|
|
415
|
+
}
|
|
416
|
+
},
|
|
417
|
+
},
|
|
418
|
+
|
|
419
|
+
// ─── /slim-debug ──────────────────────────────
|
|
420
|
+
{
|
|
421
|
+
id: "opencodev2-slim.debug",
|
|
422
|
+
title: "Toggle Slim Debug Mode",
|
|
423
|
+
group: "Slim",
|
|
424
|
+
palette: true,
|
|
425
|
+
slash: { name: "slim-debug", aliases: ["debug-slim"] },
|
|
426
|
+
enabled: true,
|
|
427
|
+
suggested: false,
|
|
428
|
+
run: async (input: unknown, event: unknown) => {
|
|
429
|
+
const sessionID =
|
|
430
|
+
resolveCurrentSession(context) ||
|
|
431
|
+
(event && typeof event === "object" && "sessionID" in event
|
|
432
|
+
? (event as any).sessionID
|
|
433
|
+
: null)
|
|
434
|
+
|
|
435
|
+
if (!sessionID) {
|
|
436
|
+
context.ui.toast.show({
|
|
437
|
+
title: "Slim Debug",
|
|
438
|
+
message: "No active session found.",
|
|
439
|
+
variant: "warning",
|
|
440
|
+
})
|
|
441
|
+
return
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
try {
|
|
445
|
+
// Read current config
|
|
446
|
+
const configPath = `${process.env.HOME || "~"}/.config/opencode/slim.jsonc`
|
|
447
|
+
const fs = await import("fs")
|
|
448
|
+
let debug = false
|
|
449
|
+
if (fs.existsSync(configPath)) {
|
|
450
|
+
const content = fs.readFileSync(configPath, "utf-8")
|
|
451
|
+
const match = content.match(/"debug"\s*:\s*(true|false)/)
|
|
452
|
+
if (match) debug = match[1] === "true"
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
// Toggle
|
|
456
|
+
debug = !debug
|
|
457
|
+
|
|
458
|
+
// Write back
|
|
459
|
+
const { parse } = await import("jsonc-parser")
|
|
460
|
+
let config: any = {}
|
|
461
|
+
if (fs.existsSync(configPath)) {
|
|
462
|
+
config = parse(fs.readFileSync(configPath, "utf-8")) || {}
|
|
463
|
+
}
|
|
464
|
+
config.debug = debug
|
|
465
|
+
|
|
466
|
+
const dir = `${process.env.HOME || "~"}/.config/opencode`
|
|
467
|
+
if (!fs.existsSync(dir)) {
|
|
468
|
+
fs.mkdirSync(dir, { recursive: true })
|
|
469
|
+
}
|
|
470
|
+
fs.writeFileSync(
|
|
471
|
+
configPath,
|
|
472
|
+
JSON.stringify(config, null, 2),
|
|
473
|
+
"utf-8",
|
|
474
|
+
)
|
|
475
|
+
|
|
476
|
+
const text = `**Slim Debug Mode:** ${debug ? "ON 🔴" : "OFF ⚪"}\n\nDebug logs will ${debug ? "now" : "no longer"} appear in the console.`
|
|
477
|
+
|
|
478
|
+
await context.client.session.synthetic({
|
|
479
|
+
sessionID,
|
|
480
|
+
text,
|
|
481
|
+
description: "slim-debug",
|
|
482
|
+
})
|
|
483
|
+
} catch (e) {
|
|
484
|
+
context.ui.toast.show({
|
|
485
|
+
title: "Slim Debug",
|
|
486
|
+
message: `Error: ${e instanceof Error ? e.message : e}`,
|
|
487
|
+
variant: "error",
|
|
488
|
+
})
|
|
489
|
+
}
|
|
490
|
+
},
|
|
491
|
+
},
|
|
239
492
|
],
|
|
240
493
|
}))
|
|
241
494
|
return null
|
|
@@ -243,10 +496,10 @@ export default Plugin.define({
|
|
|
243
496
|
})
|
|
244
497
|
|
|
245
498
|
context.ui.toast.show({
|
|
246
|
-
title: "Slim Plugin",
|
|
247
|
-
message: "
|
|
499
|
+
title: "Slim Plugin v2.1.0",
|
|
500
|
+
message: "Commands: /panel, /compress, /status, /slim-debug",
|
|
248
501
|
variant: "success",
|
|
249
|
-
duration:
|
|
502
|
+
duration: 4000,
|
|
250
503
|
})
|
|
251
504
|
|
|
252
505
|
return () => {}
|