opencode-visual-cache 1.2.9-beta.0 → 1.2.9
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/dist/_version.d.ts +1 -1
- package/dist/_version.js +1 -1
- package/dist/index.js +111 -147
- package/package.json +1 -1
- package/src/_version.ts +1 -1
- package/src/index.tsx +996 -1080
package/dist/_version.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const PLUGIN_VERSION = "1.2.9
|
|
1
|
+
export declare const PLUGIN_VERSION = "1.2.9";
|
package/dist/_version.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
// auto-generated
|
|
2
|
-
export const PLUGIN_VERSION = "1.2.9
|
|
2
|
+
export const PLUGIN_VERSION = "1.2.9";
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "@opentui/solid/jsx-runtime";
|
|
2
|
-
import { createMemo, createSignal, createEffect, onMount, onCleanup, Show } from "solid-js";
|
|
2
|
+
import { createMemo, createSignal, createEffect, onMount, onCleanup, Show, untrack } from "solid-js";
|
|
3
3
|
import { PLUGIN_VERSION } from "./_version";
|
|
4
4
|
// ── terminal-width helpers ────────────────────────────────────────
|
|
5
5
|
// CJK characters occupy 2 terminal columns; padEnd/padStart count
|
|
@@ -306,29 +306,33 @@ function TokenCachePanel(props) {
|
|
|
306
306
|
output: 0, apiOutput: 0, apiInput: 0, stepCost: 0,
|
|
307
307
|
});
|
|
308
308
|
const [lastHasDist, setLastHasDist] = createSignal(false);
|
|
309
|
-
const
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
309
|
+
const [dataSignal, setDataSignal] = createSignal({
|
|
310
|
+
hitRate: 0, read: 0, write: 0, freshInput: 0, output: 0,
|
|
311
|
+
cost: 0, saved: 0, model: "", inputRate: 0, cacheReadRate: 0, cacheWriteRate: 0,
|
|
312
|
+
hasPricing: false, hasData: false, trend: 0, hasTrendData: false,
|
|
313
|
+
providerName: "", sessionHitRate: 0,
|
|
314
|
+
dist: { system: 0, user: 0, agent: 0, toolCall: 0, toolResult: 0, output: 0, apiOutput: 0, apiInput: 0, stepCost: 0 },
|
|
315
|
+
hasDistData: false,
|
|
316
|
+
});
|
|
317
|
+
const [refreshTick, setRefreshTick] = createSignal(0);
|
|
318
|
+
createEffect(() => {
|
|
319
|
+
const sid = props.sessionId;
|
|
320
|
+
void refreshTick();
|
|
321
|
+
void partVersion();
|
|
322
|
+
// 自然追踪 messages 和 provider(SDK 数据就绪时自动重新执行)
|
|
323
|
+
const msgs = props.api.state.session.messages(sid);
|
|
324
|
+
let input = 0, read = 0, write = 0, output = 0, cost = 0, pid = "", mid = "";
|
|
325
|
+
let prevMsgHitRate = -1, lastMsgHitRate = -1;
|
|
321
326
|
for (const msg of msgs) {
|
|
322
327
|
if (msg.role !== "assistant")
|
|
323
328
|
continue;
|
|
324
329
|
const t = msg.tokens;
|
|
325
330
|
if (!t)
|
|
326
331
|
continue;
|
|
327
|
-
const
|
|
328
|
-
|
|
329
|
-
if (msgInputTokens > 0) {
|
|
332
|
+
const mit = num(t.input) + num(t.cache?.read), mrt = num(t.cache?.read);
|
|
333
|
+
if (mit > 0) {
|
|
330
334
|
prevMsgHitRate = lastMsgHitRate;
|
|
331
|
-
lastMsgHitRate = (
|
|
335
|
+
lastMsgHitRate = (mrt / mit) * 100;
|
|
332
336
|
}
|
|
333
337
|
input += num(t.input);
|
|
334
338
|
read += num(t.cache?.read);
|
|
@@ -340,12 +344,8 @@ function TokenCachePanel(props) {
|
|
|
340
344
|
mid = msg.modelID;
|
|
341
345
|
}
|
|
342
346
|
}
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
let inputRate = 0;
|
|
346
|
-
let cacheReadRate = 0;
|
|
347
|
-
let cacheWriteRate = 0;
|
|
348
|
-
if (read > 0 && pid && mid) {
|
|
347
|
+
let saved = 0, inputRate = 0, cacheReadRate = 0, cacheWriteRate = 0;
|
|
348
|
+
if (read > 0 && pid && mid)
|
|
349
349
|
for (const provider of props.api.state.provider) {
|
|
350
350
|
if (provider.id !== pid)
|
|
351
351
|
continue;
|
|
@@ -355,152 +355,115 @@ function TokenCachePanel(props) {
|
|
|
355
355
|
inputRate = num(model.cost.input);
|
|
356
356
|
cacheReadRate = num(model.cost.cache?.read);
|
|
357
357
|
cacheWriteRate = num(model.cost.cache?.write);
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
saved = (read * diff) / 1_000_000;
|
|
358
|
+
if (inputRate > cacheReadRate)
|
|
359
|
+
saved = (read * (inputRate - cacheReadRate)) / 1_000_000;
|
|
361
360
|
break;
|
|
362
361
|
}
|
|
363
|
-
}
|
|
364
|
-
// `input` from the API represents fresh (non-cached) tokens.
|
|
365
362
|
const hitRate = lastMsgHitRate >= 0 ? lastMsgHitRate : 0;
|
|
366
|
-
|
|
367
|
-
const
|
|
368
|
-
const sessionHitRate = freshTotal > 0 ? (read / freshTotal) * 100 : 0;
|
|
369
|
-
const model = mid.split("/").pop() ?? mid;
|
|
370
|
-
const hasPricing = inputRate > 0 || cacheReadRate > 0 || cacheWriteRate > 0;
|
|
371
|
-
let trend = 0;
|
|
363
|
+
const freshTotal = input + read, sessionHitRate = freshTotal > 0 ? (read / freshTotal) * 100 : 0;
|
|
364
|
+
const model = mid.split("/").pop() ?? mid, hasPricing = inputRate > 0 || cacheReadRate > 0 || cacheWriteRate > 0;
|
|
372
365
|
const hasTrendData = prevMsgHitRate >= 0 && lastMsgHitRate >= 0;
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
// Wrapped in try-catch so a part fetching failure never crashes the panel.
|
|
379
|
-
let dist = { system: 0, user: 0, agent: 0, toolCall: 0, toolResult: 0, output: 0, apiOutput: 0, apiInput: 0, stepCost: 0 };
|
|
380
|
-
let hasDistData = false;
|
|
381
|
-
try {
|
|
382
|
-
partVersion(); // track part changes for reactivity
|
|
383
|
-
dist = { system: 0, user: 0, agent: 0, toolCall: 0, toolResult: 0, output: 0, apiOutput: 0, apiInput: 0, stepCost: 0 };
|
|
384
|
-
// Read agent system prompt once before the message loop. Reading it
|
|
385
|
-
// inside the per-user-message branch risks transient unavailability
|
|
386
|
-
// (api.state.config not yet resolved during streaming) silently
|
|
387
|
-
// resetting a previously-computed value and causing display flicker.
|
|
366
|
+
const trend = hasTrendData ? lastMsgHitRate - prevMsgHitRate : 0, providerName = pid || "";
|
|
367
|
+
// untrack 只包裹已知触发死锁的 API
|
|
368
|
+
const distData = untrack(() => {
|
|
369
|
+
let dist = { system: 0, user: 0, agent: 0, toolCall: 0, toolResult: 0, output: 0, apiOutput: 0, apiInput: 0, stepCost: 0 };
|
|
370
|
+
let hasDistData = false;
|
|
388
371
|
try {
|
|
389
|
-
const session = props.api.state.session.get(props.
|
|
390
|
-
const cfg = props.api.state.config;
|
|
372
|
+
const session = props.api.state.session.get(sid), cfg = props.api.state.config;
|
|
391
373
|
const agentName = String(session?.agent ?? cfg?.default_agent ?? "build");
|
|
392
374
|
const agents = cfg?.agent;
|
|
393
375
|
const agentCfg = agents?.[agentName];
|
|
394
376
|
const sysPrompt = typeof agentCfg?.prompt === "string" ? agentCfg.prompt : "";
|
|
395
377
|
if (sysPrompt)
|
|
396
378
|
dist.system = estimateTokens(sysPrompt);
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
try {
|
|
406
|
-
parts = props.api.state.part(msg.id);
|
|
407
|
-
}
|
|
408
|
-
catch { }
|
|
409
|
-
for (const p of parts) {
|
|
410
|
-
if (p.type === "text" && !p.synthetic && !p.ignored) {
|
|
411
|
-
dist.user += estimateTokens(p.text);
|
|
379
|
+
for (const msg of msgs) {
|
|
380
|
+
if (msg.role === "user") {
|
|
381
|
+
const um = msg;
|
|
382
|
+
if (um.system)
|
|
383
|
+
dist.system += estimateTokens(um.system);
|
|
384
|
+
let parts = [];
|
|
385
|
+
try {
|
|
386
|
+
parts = props.api.state.part(msg.id);
|
|
412
387
|
}
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
if (
|
|
416
|
-
dist.user += estimateTokens(
|
|
388
|
+
catch { }
|
|
389
|
+
for (const p of parts) {
|
|
390
|
+
if (p.type === "text" && !p.synthetic && !p.ignored)
|
|
391
|
+
dist.user += estimateTokens(p.text);
|
|
392
|
+
else if (p.type === "file") {
|
|
393
|
+
const fp = p;
|
|
394
|
+
if (fp.source?.text?.value)
|
|
395
|
+
dist.user += estimateTokens(fp.source.text.value);
|
|
396
|
+
}
|
|
417
397
|
}
|
|
418
398
|
}
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
// Tool call input (params)
|
|
432
|
-
let rawInput = "";
|
|
433
|
-
try {
|
|
434
|
-
rawInput = tp.state.raw ?? JSON.stringify(tp.state.input);
|
|
435
|
-
}
|
|
436
|
-
catch {
|
|
399
|
+
else if (msg.role === "assistant") {
|
|
400
|
+
const am = msg;
|
|
401
|
+
dist.output += num(am.tokens?.output);
|
|
402
|
+
let parts = [];
|
|
403
|
+
try {
|
|
404
|
+
parts = props.api.state.part(msg.id);
|
|
405
|
+
}
|
|
406
|
+
catch { }
|
|
407
|
+
for (const p of parts) {
|
|
408
|
+
if (p.type === "tool") {
|
|
409
|
+
const tp = p;
|
|
410
|
+
let rawInput = "";
|
|
437
411
|
try {
|
|
438
|
-
rawInput = JSON.stringify(tp.state);
|
|
412
|
+
rawInput = tp.state.raw ?? JSON.stringify(tp.state.input);
|
|
413
|
+
}
|
|
414
|
+
catch {
|
|
415
|
+
try {
|
|
416
|
+
rawInput = JSON.stringify(tp.state);
|
|
417
|
+
}
|
|
418
|
+
catch { }
|
|
419
|
+
}
|
|
420
|
+
if (rawInput)
|
|
421
|
+
dist.toolCall += estimateTokens(rawInput);
|
|
422
|
+
if (tp.state.status === "completed") {
|
|
423
|
+
const c = tp.state;
|
|
424
|
+
if (c.output)
|
|
425
|
+
dist.toolResult += estimateTokens(c.output);
|
|
426
|
+
}
|
|
427
|
+
else if (tp.state.status === "error") {
|
|
428
|
+
const e = tp.state;
|
|
429
|
+
if (e.error)
|
|
430
|
+
dist.toolResult += estimateTokens(e.error);
|
|
439
431
|
}
|
|
440
|
-
catch { }
|
|
441
432
|
}
|
|
442
|
-
if (
|
|
443
|
-
dist.
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
if (completed.output)
|
|
448
|
-
dist.toolResult += estimateTokens(completed.output);
|
|
433
|
+
else if (p.type === "reasoning")
|
|
434
|
+
dist.agent += estimateTokens(p.text);
|
|
435
|
+
else if (p.type === "subtask") {
|
|
436
|
+
const sub = p;
|
|
437
|
+
dist.agent += estimateTokens(sub.prompt || sub.description || "");
|
|
449
438
|
}
|
|
450
|
-
else if (
|
|
451
|
-
const
|
|
452
|
-
|
|
453
|
-
|
|
439
|
+
else if (p.type === "step-finish") {
|
|
440
|
+
const sf = p;
|
|
441
|
+
dist.apiInput += sf.tokens?.input ?? 0;
|
|
442
|
+
dist.apiOutput += sf.tokens?.output ?? 0;
|
|
454
443
|
}
|
|
455
444
|
}
|
|
456
|
-
else if (p.type === "reasoning") {
|
|
457
|
-
dist.agent += estimateTokens(p.text);
|
|
458
|
-
}
|
|
459
|
-
else if (p.type === "subtask") {
|
|
460
|
-
const sub = p;
|
|
461
|
-
dist.agent += estimateTokens(sub.prompt || sub.description || "");
|
|
462
|
-
}
|
|
463
|
-
else if (p.type === "step-finish") {
|
|
464
|
-
// StepFinishPart carries API-exact per-call token counts.
|
|
465
|
-
// Sum across all step-finish parts (one per API call in tool loops).
|
|
466
|
-
const sf = p;
|
|
467
|
-
dist.apiInput += sf.tokens?.input ?? 0;
|
|
468
|
-
dist.apiOutput += sf.tokens?.output ?? 0;
|
|
469
|
-
}
|
|
470
445
|
}
|
|
471
446
|
}
|
|
447
|
+
const totalInput = dist.system + dist.user + dist.agent + dist.toolCall + dist.toolResult;
|
|
448
|
+
const overhead = Math.max(0, dist.apiInput - totalInput);
|
|
449
|
+
if (overhead >= 50)
|
|
450
|
+
dist.system += overhead;
|
|
451
|
+
hasDistData = totalInput > 0 || dist.apiOutput > 0 || dist.apiInput > 0;
|
|
472
452
|
}
|
|
473
|
-
|
|
474
|
-
const
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
// system bucket rather than replacing the local estimate.
|
|
481
|
-
const overhead = Math.max(0, apiTotalInput - totalInput);
|
|
482
|
-
if (overhead >= 50) {
|
|
483
|
-
dist.system += overhead;
|
|
484
|
-
}
|
|
485
|
-
hasDistData = totalInput > 0 || finalOutput > 0 || apiTotalInput > 0;
|
|
486
|
-
}
|
|
487
|
-
catch {
|
|
488
|
-
// Graceful degradation — dist stays at zeroes
|
|
489
|
-
}
|
|
490
|
-
// Fall back to last known-good distribution while api.state.part()
|
|
491
|
-
// is re-hydrating after a view switch.
|
|
492
|
-
const finalDist = hasDistData ? dist : lastDist();
|
|
493
|
-
const finalHasDist = hasDistData || lastHasDist();
|
|
494
|
-
return {
|
|
495
|
-
hitRate, read, write, freshInput: input, output,
|
|
496
|
-
cost, saved, model, inputRate, cacheReadRate, cacheWriteRate, hasPricing,
|
|
453
|
+
catch { }
|
|
454
|
+
const finalDist = hasDistData ? dist : lastDist(), finalHasDist = hasDistData || lastHasDist();
|
|
455
|
+
return { finalDist, finalHasDist };
|
|
456
|
+
});
|
|
457
|
+
setDataSignal({
|
|
458
|
+
hitRate, read, write, freshInput: input, output, cost, saved, model,
|
|
459
|
+
inputRate, cacheReadRate, cacheWriteRate, hasPricing,
|
|
497
460
|
hasData: read > 0 || write > 0 || input > 0 || output > 0 || cost > 0,
|
|
498
|
-
trend, hasTrendData,
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
461
|
+
trend, hasTrendData, providerName, sessionHitRate,
|
|
462
|
+
dist: distData.finalDist, hasDistData: distData.finalHasDist,
|
|
463
|
+
});
|
|
464
|
+
});
|
|
465
|
+
const data = createMemo(() => {
|
|
466
|
+
return dataSignal();
|
|
504
467
|
});
|
|
505
468
|
// Persist the last valid distribution so that data() can fall back
|
|
506
469
|
// to it while api.state.part() is re-hydrating after a view switch.
|
|
@@ -600,8 +563,9 @@ function TokenCachePanel(props) {
|
|
|
600
563
|
clearTimeout(partTimer);
|
|
601
564
|
partTimer = setTimeout(() => setPartVersion((v) => v + 1), 100);
|
|
602
565
|
};
|
|
603
|
-
const unsubPart = props.api.event.on("message.part.updated", bumpPartVersion);
|
|
604
|
-
const unsubMsg = props.api.event.on("message.updated", bumpPartVersion);
|
|
566
|
+
const unsubPart = props.api.event.on("message.part.updated", () => { bumpPartVersion(); setRefreshTick(v => v + 1); });
|
|
567
|
+
const unsubMsg = props.api.event.on("message.updated", () => { bumpPartVersion(); setRefreshTick(v => v + 1); });
|
|
568
|
+
setRefreshTick(v => v + 1);
|
|
605
569
|
onCleanup(() => { clearTimeout(partTimer); unsubPart(); unsubMsg(); });
|
|
606
570
|
});
|
|
607
571
|
// ── colours ──
|
package/package.json
CHANGED
package/src/_version.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
// auto-generated
|
|
2
|
-
export const PLUGIN_VERSION="1.2.9
|
|
2
|
+
export const PLUGIN_VERSION="1.2.9";
|