@rejacky/opencode-insights 0.1.9 → 0.1.11
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/README.md +22 -1
- package/dist/capture-BIiGg2nW.d.ts +148 -0
- package/dist/{chunk-FGTKNB7T.js → chunk-3YLLHABZ.js} +307 -0
- package/dist/{chunk-7M32TU5P.js → chunk-O5FFLPEQ.js} +0 -152
- package/dist/cli.d.ts +1 -1
- package/dist/cli.js +28 -12
- package/dist/index.d.ts +2 -48
- package/dist/index.js +22 -8
- package/dist/tui.js +186 -48
- package/package.json +2 -1
- package/dist/capture-BMWWI5GR.d.ts +0 -61
package/dist/cli.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import {
|
|
3
3
|
openDatabase,
|
|
4
4
|
resolveCapturePath
|
|
5
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-3YLLHABZ.js";
|
|
6
6
|
|
|
7
7
|
// src/cli.ts
|
|
8
8
|
import { execFile as execFile2 } from "child_process";
|
|
@@ -12,6 +12,7 @@ import { dirname, join, resolve } from "path";
|
|
|
12
12
|
import { homedir } from "os";
|
|
13
13
|
import { pathToFileURL } from "url";
|
|
14
14
|
import { promisify as promisify2 } from "util";
|
|
15
|
+
import { applyEdits, modify, parse } from "jsonc-parser";
|
|
15
16
|
|
|
16
17
|
// src/inspect.ts
|
|
17
18
|
import { readFile } from "fs/promises";
|
|
@@ -1727,8 +1728,10 @@ async function configureOpenCodeDebug(options) {
|
|
|
1727
1728
|
if (!existsSync2(localServerEntry) || !existsSync2(localTuiEntry)) {
|
|
1728
1729
|
throw new Error("Missing dist output. Run npm run build before opencode-insights debug.");
|
|
1729
1730
|
}
|
|
1730
|
-
const
|
|
1731
|
-
const
|
|
1731
|
+
const opencodeSource = await readJsonConfigSource(opencodePath);
|
|
1732
|
+
const tuiSource = await readJsonConfigSource(tuiPath);
|
|
1733
|
+
const opencodeConfig = await readJsonConfig(opencodePath, { plugin: [] }, opencodeSource);
|
|
1734
|
+
const tuiConfig = await readJsonConfig(tuiPath, { plugin: [] }, tuiSource);
|
|
1732
1735
|
setSinglePluginSpec(opencodeConfig, SERVER_PLUGIN_SPEC, [localServerEntry, debugServerOptions(options)], localServerEntry);
|
|
1733
1736
|
setSinglePluginSpec(tuiConfig, TUI_PLUGIN_SPEC, localTuiEntry);
|
|
1734
1737
|
removePlugin(tuiConfig, SUBPATH_TUI_PLUGIN_SPEC);
|
|
@@ -1745,8 +1748,8 @@ async function configureOpenCodeDebug(options) {
|
|
|
1745
1748
|
return lines.join("\n");
|
|
1746
1749
|
}
|
|
1747
1750
|
await mkdir(configDir, { recursive: true });
|
|
1748
|
-
await writeJsonConfig(opencodePath, opencodeConfig);
|
|
1749
|
-
await writeJsonConfig(tuiPath, tuiConfig);
|
|
1751
|
+
await writeJsonConfig(opencodePath, opencodeConfig, opencodeSource);
|
|
1752
|
+
await writeJsonConfig(tuiPath, tuiConfig, tuiSource);
|
|
1750
1753
|
lines.push("Debug configuration written. Restart OpenCode to load the local build.");
|
|
1751
1754
|
return lines.join("\n");
|
|
1752
1755
|
}
|
|
@@ -1763,18 +1766,23 @@ function resolveOpenCodeConfigPath(configDir) {
|
|
|
1763
1766
|
if (existsSync2(jsonPath)) return jsonPath;
|
|
1764
1767
|
return jsonPath;
|
|
1765
1768
|
}
|
|
1766
|
-
async function readJsonConfig(path, fallback) {
|
|
1767
|
-
|
|
1768
|
-
|
|
1769
|
+
async function readJsonConfig(path, fallback, source) {
|
|
1770
|
+
const content = source ?? (existsSync2(path) ? await readFile2(path, "utf8") : void 0);
|
|
1771
|
+
if (content === void 0) return { ...fallback };
|
|
1769
1772
|
const trimmed = content.trim();
|
|
1770
1773
|
if (!trimmed) return { ...fallback };
|
|
1771
1774
|
try {
|
|
1772
|
-
const
|
|
1775
|
+
const parseErrors = [];
|
|
1776
|
+
const parsed = parse(trimmed, parseErrors, { allowTrailingComma: true });
|
|
1777
|
+
if (parseErrors.length > 0) throw new Error("invalid JSONC syntax");
|
|
1773
1778
|
return isJsonObject(parsed) ? parsed : { ...fallback };
|
|
1774
1779
|
} catch (error) {
|
|
1775
1780
|
throw new Error(`Could not parse ${path}: ${error instanceof Error ? error.message : String(error)}`);
|
|
1776
1781
|
}
|
|
1777
1782
|
}
|
|
1783
|
+
async function readJsonConfigSource(path) {
|
|
1784
|
+
return existsSync2(path) ? readFile2(path, "utf8") : void 0;
|
|
1785
|
+
}
|
|
1778
1786
|
function stripJsonCommentsAndTrailingCommas(input) {
|
|
1779
1787
|
let output = "";
|
|
1780
1788
|
let inString = false;
|
|
@@ -1888,11 +1896,12 @@ async function uninstallOpenCode(options) {
|
|
|
1888
1896
|
}
|
|
1889
1897
|
async function removePluginFromConfig(path, plugin, options) {
|
|
1890
1898
|
if (!existsSync2(path)) return "config not found";
|
|
1891
|
-
const
|
|
1899
|
+
const source = await readJsonConfigSource(path);
|
|
1900
|
+
const config = await readJsonConfig(path, { plugin: [] }, source);
|
|
1892
1901
|
const changed = removePlugin(config, plugin);
|
|
1893
1902
|
if (!changed) return `not present (${plugin})`;
|
|
1894
1903
|
if (options.dryRun) return `would remove (${plugin})`;
|
|
1895
|
-
await writeJsonConfig(path, config);
|
|
1904
|
+
await writeJsonConfig(path, config, source);
|
|
1896
1905
|
return `removed (${plugin})`;
|
|
1897
1906
|
}
|
|
1898
1907
|
async function removeDataFiles(paths, options) {
|
|
@@ -1904,8 +1913,15 @@ async function removeDataFiles(paths, options) {
|
|
|
1904
1913
|
}
|
|
1905
1914
|
return existing;
|
|
1906
1915
|
}
|
|
1907
|
-
async function writeJsonConfig(path, config) {
|
|
1916
|
+
async function writeJsonConfig(path, config, source) {
|
|
1908
1917
|
await mkdir(dirname(path), { recursive: true });
|
|
1918
|
+
if (source !== void 0) {
|
|
1919
|
+
const edits = modify(source, ["plugin"], config.plugin, {
|
|
1920
|
+
formattingOptions: { insertSpaces: true, tabSize: 2, eol: "\n" }
|
|
1921
|
+
});
|
|
1922
|
+
await writeFile(path, applyEdits(source, edits), "utf8");
|
|
1923
|
+
return;
|
|
1924
|
+
}
|
|
1909
1925
|
await writeFile(path, `${JSON.stringify(config, null, 2)}
|
|
1910
1926
|
`, "utf8");
|
|
1911
1927
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,52 +1,6 @@
|
|
|
1
1
|
import { Plugin } from '@opencode-ai/plugin';
|
|
2
2
|
import { TuiPlugin } from '@opencode-ai/plugin/tui';
|
|
3
|
-
export { a as CaptureKind, C as CaptureRecord, b as CaptureStore, I as InsightsOptions, J as JsonlCaptureStore, S as SqliteCaptureStore,
|
|
4
|
-
|
|
5
|
-
type StreamSample = {
|
|
6
|
-
at: number;
|
|
7
|
-
tokens: number;
|
|
8
|
-
};
|
|
9
|
-
type MessageTiming = {
|
|
10
|
-
sessionID: string;
|
|
11
|
-
requestStartAt: number;
|
|
12
|
-
firstResponseAt?: number | undefined;
|
|
13
|
-
firstTokenAt?: number | undefined;
|
|
14
|
-
lastTokenAt?: number | undefined;
|
|
15
|
-
lastToolCallAt?: number | undefined;
|
|
16
|
-
};
|
|
17
|
-
type SessionAverage = {
|
|
18
|
-
totalTokens: number;
|
|
19
|
-
totalDurationMs: number;
|
|
20
|
-
totalTtftMs: number;
|
|
21
|
-
messageCount: number;
|
|
22
|
-
};
|
|
23
|
-
type MetricsState = {
|
|
24
|
-
streamSamplesBySession: Record<string, StreamSample[]>;
|
|
25
|
-
messageTimingByID: Record<string, MessageTiming>;
|
|
26
|
-
sessionAverageByID: Record<string, SessionAverage>;
|
|
27
|
-
};
|
|
28
|
-
declare function createMetricsState(): MetricsState;
|
|
29
|
-
declare function estimateStreamTokens(delta: string): number;
|
|
30
|
-
declare function recordAssistantMessage(state: MetricsState, input: {
|
|
31
|
-
sessionID: string;
|
|
32
|
-
messageID: string;
|
|
33
|
-
createdAt: number;
|
|
34
|
-
completedAt?: number;
|
|
35
|
-
outputTokens?: number;
|
|
36
|
-
reasoningTokens?: number;
|
|
37
|
-
finish?: string;
|
|
38
|
-
}): void;
|
|
39
|
-
declare function recordAssistantDelta(state: MetricsState, input: {
|
|
40
|
-
sessionID: string;
|
|
41
|
-
messageID: string;
|
|
42
|
-
delta: string;
|
|
43
|
-
at: number;
|
|
44
|
-
}): void;
|
|
45
|
-
declare function recordToolActivity(state: MetricsState, sessionID: string, messageID: string, at?: number): void;
|
|
46
|
-
declare function renderMetricsText(state: MetricsState, sessionID: string, options?: {
|
|
47
|
-
now?: number;
|
|
48
|
-
idle?: boolean;
|
|
49
|
-
}): string;
|
|
3
|
+
export { A as AssistantResponseUsage, a as CaptureKind, C as CaptureRecord, b as CaptureStore, D as DEFAULT_PROMPT_RIGHT_METRICS, I as InsightsConfig, c as InsightsOptions, J as JsonlCaptureStore, M as MessageTiming, d as MetricsState, P as PromptRightMetric, S as SessionAverage, e as SessionTokenUsage, f as SqliteCaptureStore, g as SqliteDb, h as StreamSample, i as createCaptureStore, j as createMetricsState, k as defaultDataDir, l as estimateStreamTokens, m as extractEventType, n as getSessionTokenUsage, o as normalizeChatHeadersCapture, p as normalizeChatMessageCapture, q as normalizeChatParamsCapture, r as normalizeEventCapture, s as normalizeExperimentalChatMessagesTransformCapture, t as normalizeExperimentalChatSystemTransformCapture, u as normalizeToolCapture, v as openDatabase, w as readInsightsConfig, x as recordAssistantDelta, y as recordAssistantMessage, z as recordToolActivity, B as renderMetricsText, E as renderPromptRightMetricsText, F as renderResponseMetricsText, G as renderSessionTokenUsage, H as resolveCapturePath, K as resolveInsightsConfigPath, L as resolveRetentionDays } from './capture-BIiGg2nW.js';
|
|
50
4
|
|
|
51
5
|
type SubagentStatus = "running" | "done" | "error";
|
|
52
6
|
type SubagentInfo = {
|
|
@@ -112,4 +66,4 @@ declare const _default: {
|
|
|
112
66
|
server: Plugin;
|
|
113
67
|
};
|
|
114
68
|
|
|
115
|
-
export {
|
|
69
|
+
export { OpenCodeInsights, type SubagentInfo, type SubagentSidebarModel, type SubagentSidebarRow, type SubagentState, type SubagentStatus, applySubagentEvent, createSubagentState, _default as default, getSubagentItems, getSubagentSidebarModel, getSubagentSidebarRowAtLine, id, pruneStaleSubagents, renderSubagentFooter, renderSubagentSidebar, renderSubagentStatus, server, rootTui as tui };
|
package/dist/index.js
CHANGED
|
@@ -1,26 +1,24 @@
|
|
|
1
1
|
import {
|
|
2
2
|
applySubagentEvent,
|
|
3
|
-
createMetricsState,
|
|
4
3
|
createSubagentState,
|
|
5
|
-
estimateStreamTokens,
|
|
6
4
|
getSubagentItems,
|
|
7
5
|
getSubagentSidebarModel,
|
|
8
6
|
getSubagentSidebarRowAtLine,
|
|
9
7
|
pruneStaleSubagents,
|
|
10
|
-
recordAssistantDelta,
|
|
11
|
-
recordAssistantMessage,
|
|
12
|
-
recordToolActivity,
|
|
13
|
-
renderMetricsText,
|
|
14
8
|
renderSubagentFooter,
|
|
15
9
|
renderSubagentSidebar,
|
|
16
10
|
renderSubagentStatus
|
|
17
|
-
} from "./chunk-
|
|
11
|
+
} from "./chunk-O5FFLPEQ.js";
|
|
18
12
|
import {
|
|
13
|
+
DEFAULT_PROMPT_RIGHT_METRICS,
|
|
19
14
|
JsonlCaptureStore,
|
|
20
15
|
SqliteCaptureStore,
|
|
21
16
|
createCaptureStore,
|
|
17
|
+
createMetricsState,
|
|
22
18
|
defaultDataDir,
|
|
19
|
+
estimateStreamTokens,
|
|
23
20
|
extractEventType,
|
|
21
|
+
getSessionTokenUsage,
|
|
24
22
|
normalizeChatHeadersCapture,
|
|
25
23
|
normalizeChatMessageCapture,
|
|
26
24
|
normalizeChatParamsCapture,
|
|
@@ -29,9 +27,18 @@ import {
|
|
|
29
27
|
normalizeExperimentalChatSystemTransformCapture,
|
|
30
28
|
normalizeToolCapture,
|
|
31
29
|
openDatabase,
|
|
30
|
+
readInsightsConfig,
|
|
31
|
+
recordAssistantDelta,
|
|
32
|
+
recordAssistantMessage,
|
|
33
|
+
recordToolActivity,
|
|
34
|
+
renderMetricsText,
|
|
35
|
+
renderPromptRightMetricsText,
|
|
36
|
+
renderResponseMetricsText,
|
|
37
|
+
renderSessionTokenUsage,
|
|
32
38
|
resolveCapturePath,
|
|
39
|
+
resolveInsightsConfigPath,
|
|
33
40
|
resolveRetentionDays
|
|
34
|
-
} from "./chunk-
|
|
41
|
+
} from "./chunk-3YLLHABZ.js";
|
|
35
42
|
|
|
36
43
|
// src/cli-shim.ts
|
|
37
44
|
import { existsSync } from "fs";
|
|
@@ -133,6 +140,7 @@ var rootTui = async (...args) => {
|
|
|
133
140
|
var id = "opencode-insights";
|
|
134
141
|
var src_default = { id, server };
|
|
135
142
|
export {
|
|
143
|
+
DEFAULT_PROMPT_RIGHT_METRICS,
|
|
136
144
|
JsonlCaptureStore,
|
|
137
145
|
OpenCodeInsights,
|
|
138
146
|
SqliteCaptureStore,
|
|
@@ -144,6 +152,7 @@ export {
|
|
|
144
152
|
defaultDataDir,
|
|
145
153
|
estimateStreamTokens,
|
|
146
154
|
extractEventType,
|
|
155
|
+
getSessionTokenUsage,
|
|
147
156
|
getSubagentItems,
|
|
148
157
|
getSubagentSidebarModel,
|
|
149
158
|
getSubagentSidebarRowAtLine,
|
|
@@ -157,14 +166,19 @@ export {
|
|
|
157
166
|
normalizeToolCapture,
|
|
158
167
|
openDatabase,
|
|
159
168
|
pruneStaleSubagents,
|
|
169
|
+
readInsightsConfig,
|
|
160
170
|
recordAssistantDelta,
|
|
161
171
|
recordAssistantMessage,
|
|
162
172
|
recordToolActivity,
|
|
163
173
|
renderMetricsText,
|
|
174
|
+
renderPromptRightMetricsText,
|
|
175
|
+
renderResponseMetricsText,
|
|
176
|
+
renderSessionTokenUsage,
|
|
164
177
|
renderSubagentFooter,
|
|
165
178
|
renderSubagentSidebar,
|
|
166
179
|
renderSubagentStatus,
|
|
167
180
|
resolveCapturePath,
|
|
181
|
+
resolveInsightsConfigPath,
|
|
168
182
|
resolveRetentionDays,
|
|
169
183
|
server,
|
|
170
184
|
rootTui as tui
|
package/dist/tui.js
CHANGED
|
@@ -1,34 +1,67 @@
|
|
|
1
1
|
import {
|
|
2
2
|
applySubagentEvent,
|
|
3
|
-
createMetricsState,
|
|
4
3
|
createSubagentState,
|
|
5
4
|
getSubagentSidebarModel,
|
|
6
|
-
getSubagentSidebarRowAtLine
|
|
5
|
+
getSubagentSidebarRowAtLine
|
|
6
|
+
} from "./chunk-O5FFLPEQ.js";
|
|
7
|
+
import {
|
|
8
|
+
createMetricsState,
|
|
9
|
+
readInsightsConfig,
|
|
7
10
|
recordAssistantDelta,
|
|
8
11
|
recordAssistantMessage,
|
|
9
12
|
recordToolActivity,
|
|
10
|
-
|
|
11
|
-
|
|
13
|
+
renderPromptRightMetricsText,
|
|
14
|
+
renderSessionTokenUsage
|
|
15
|
+
} from "./chunk-3YLLHABZ.js";
|
|
12
16
|
|
|
13
17
|
// src/tui.tsx
|
|
14
18
|
import { createTextAttributes, StyledText } from "@opentui/core";
|
|
15
19
|
import { createSignal, onCleanup } from "solid-js";
|
|
16
|
-
|
|
20
|
+
|
|
21
|
+
// src/listeners.ts
|
|
22
|
+
function createListenerRegistry() {
|
|
23
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
24
|
+
return {
|
|
25
|
+
notify() {
|
|
26
|
+
for (const listener of listeners) listener();
|
|
27
|
+
},
|
|
28
|
+
subscribe(listener) {
|
|
29
|
+
listeners.add(listener);
|
|
30
|
+
return () => listeners.delete(listener);
|
|
31
|
+
}
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// src/render-state.ts
|
|
36
|
+
function hasRenderStateChanged(previous, next) {
|
|
37
|
+
return !previous || previous.content !== next.content || previous.visible !== next.visible || previous.height !== next.height;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// src/tui.tsx
|
|
41
|
+
import { Fragment, jsx, jsxs } from "@opentui/solid/jsx-runtime";
|
|
17
42
|
function isSessionID(value) {
|
|
18
43
|
return typeof value === "string" && value.startsWith("ses");
|
|
19
44
|
}
|
|
20
|
-
function
|
|
45
|
+
function PromptRightMetrics(props) {
|
|
21
46
|
let text;
|
|
47
|
+
let previous;
|
|
22
48
|
const sync = () => {
|
|
23
49
|
if (!text) return;
|
|
24
50
|
const content = props.text();
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
51
|
+
const next = { content, visible: content.length > 0, height: content.length > 0 ? 1 : 0 };
|
|
52
|
+
if (!hasRenderStateChanged(previous, next)) return;
|
|
53
|
+
previous = next;
|
|
54
|
+
text.content = next.content;
|
|
55
|
+
text.visible = next.visible;
|
|
56
|
+
text.height = next.height;
|
|
28
57
|
props.api.renderer.requestRender();
|
|
29
58
|
};
|
|
30
59
|
const unsubscribe = props.subscribe(sync);
|
|
31
|
-
|
|
60
|
+
const timer = setInterval(sync, 1e3);
|
|
61
|
+
onCleanup(() => {
|
|
62
|
+
unsubscribe();
|
|
63
|
+
clearInterval(timer);
|
|
64
|
+
});
|
|
32
65
|
return /* @__PURE__ */ jsx(
|
|
33
66
|
"text",
|
|
34
67
|
{
|
|
@@ -37,19 +70,65 @@ function PromptRight(props) {
|
|
|
37
70
|
sync();
|
|
38
71
|
},
|
|
39
72
|
fg: props.api.theme.current.textMuted,
|
|
73
|
+
height: 1,
|
|
74
|
+
wrapMode: "none",
|
|
75
|
+
truncate: true,
|
|
76
|
+
overflow: "hidden",
|
|
40
77
|
children: props.text()
|
|
41
78
|
}
|
|
42
79
|
);
|
|
43
80
|
}
|
|
81
|
+
function TokenUsageSidebar(props) {
|
|
82
|
+
let text;
|
|
83
|
+
let previous;
|
|
84
|
+
const [collapsed, setCollapsed] = createSignal(false);
|
|
85
|
+
const titleAttributes = createTextAttributes({ bold: true });
|
|
86
|
+
const toggleTokenUsage = (event) => {
|
|
87
|
+
if (!text || event.y !== text.y) return;
|
|
88
|
+
setCollapsed((prev) => !prev);
|
|
89
|
+
sync();
|
|
90
|
+
};
|
|
91
|
+
const sync = () => {
|
|
92
|
+
if (!text) return;
|
|
93
|
+
const content = renderSessionTokenUsage(props.state, props.sessionID);
|
|
94
|
+
const next = {
|
|
95
|
+
content: `${collapsed()}|${content}`,
|
|
96
|
+
visible: content.length > 0,
|
|
97
|
+
height: content.length > 0 ? "auto" : 0
|
|
98
|
+
};
|
|
99
|
+
if (!hasRenderStateChanged(previous, next)) return;
|
|
100
|
+
previous = next;
|
|
101
|
+
text.visible = next.visible;
|
|
102
|
+
text.height = next.height;
|
|
103
|
+
text.content = content ? renderTokenUsageSidebar(content, props.api, titleAttributes, collapsed()) : "";
|
|
104
|
+
props.api.renderer.requestRender();
|
|
105
|
+
};
|
|
106
|
+
const unsubscribe = props.subscribe(sync);
|
|
107
|
+
onCleanup(unsubscribe);
|
|
108
|
+
props.hydrate();
|
|
109
|
+
return /* @__PURE__ */ jsx(
|
|
110
|
+
"text",
|
|
111
|
+
{
|
|
112
|
+
ref: (ref) => {
|
|
113
|
+
text = ref;
|
|
114
|
+
sync();
|
|
115
|
+
},
|
|
116
|
+
onMouseDown: toggleTokenUsage,
|
|
117
|
+
fg: props.api.theme.current.textMuted,
|
|
118
|
+
children: ""
|
|
119
|
+
}
|
|
120
|
+
);
|
|
121
|
+
}
|
|
44
122
|
function SubagentSidebar(props) {
|
|
45
123
|
let text;
|
|
46
124
|
const [collapsed, setCollapsed] = createSignal(false);
|
|
47
125
|
const [hoveredRowID, setHoveredRowID] = createSignal();
|
|
48
126
|
const titleAttributes = createTextAttributes({ bold: true });
|
|
127
|
+
let previous;
|
|
49
128
|
const toggle = (event) => {
|
|
50
129
|
if (!text || event.y !== text.y) return;
|
|
51
130
|
setCollapsed((prev) => !prev);
|
|
52
|
-
|
|
131
|
+
sync();
|
|
53
132
|
};
|
|
54
133
|
const openSubagent = (event) => {
|
|
55
134
|
if (!text || collapsed()) return;
|
|
@@ -76,9 +155,17 @@ function SubagentSidebar(props) {
|
|
|
76
155
|
const sync = () => {
|
|
77
156
|
if (!text) return;
|
|
78
157
|
const model = getSubagentSidebarModel(props.state, props.sessionID);
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
158
|
+
const content = model ? renderSubagentStyledSidebar(model, props.api, titleAttributes, collapsed(), hoveredRowID()) : "";
|
|
159
|
+
const next = {
|
|
160
|
+
content: model ? contentSignature(model, collapsed(), hoveredRowID()) : "",
|
|
161
|
+
visible: !!model,
|
|
162
|
+
height: model ? "auto" : 0
|
|
163
|
+
};
|
|
164
|
+
if (!hasRenderStateChanged(previous, next)) return;
|
|
165
|
+
previous = next;
|
|
166
|
+
text.visible = next.visible;
|
|
167
|
+
text.height = next.height;
|
|
168
|
+
text.content = model ? content : "";
|
|
82
169
|
props.api.renderer.requestRender();
|
|
83
170
|
};
|
|
84
171
|
const unsubscribe = props.subscribe(sync);
|
|
@@ -103,9 +190,7 @@ function SubagentSidebar(props) {
|
|
|
103
190
|
}
|
|
104
191
|
);
|
|
105
192
|
}
|
|
106
|
-
function renderSubagentStyledSidebar(
|
|
107
|
-
const model = getSubagentSidebarModel(state, sessionID);
|
|
108
|
-
if (!model) return "";
|
|
193
|
+
function renderSubagentStyledSidebar(model, api, titleAttributes, collapsed, hoveredRowID) {
|
|
109
194
|
const indicator = collapsed ? "\u25B6 " : "\u25BC ";
|
|
110
195
|
const chunks = [
|
|
111
196
|
textChunk(`${indicator}${model.title}
|
|
@@ -126,6 +211,18 @@ function renderSubagentStyledSidebar(state, sessionID, api, titleAttributes, col
|
|
|
126
211
|
}
|
|
127
212
|
return new StyledText(chunks);
|
|
128
213
|
}
|
|
214
|
+
function renderTokenUsageSidebar(content, api, titleAttributes, collapsed) {
|
|
215
|
+
const [title, ...details] = content.split("\n");
|
|
216
|
+
const visibleDetails = collapsed ? details.slice(0, 1) : details;
|
|
217
|
+
return new StyledText([
|
|
218
|
+
textChunk(`${collapsed ? "\u25B6" : "\u25BC"} ${title}
|
|
219
|
+
`, api.theme.current.text, titleAttributes),
|
|
220
|
+
textChunk(visibleDetails.join("\n"), api.theme.current.textMuted)
|
|
221
|
+
]);
|
|
222
|
+
}
|
|
223
|
+
function contentSignature(model, collapsed, hoveredRowID) {
|
|
224
|
+
return JSON.stringify({ collapsed, hoveredRowID, title: model.title, summary: model.summary, rows: model.rows });
|
|
225
|
+
}
|
|
129
226
|
function textChunk(text, fg, attributes, bg) {
|
|
130
227
|
return {
|
|
131
228
|
__isChunk: true,
|
|
@@ -135,16 +232,39 @@ function textChunk(text, fg, attributes, bg) {
|
|
|
135
232
|
...bg === void 0 ? {} : { bg }
|
|
136
233
|
};
|
|
137
234
|
}
|
|
138
|
-
var tui = async (api) => {
|
|
235
|
+
var tui = async (api, options) => {
|
|
236
|
+
const config = await readInsightsConfig(options ?? {});
|
|
139
237
|
const metrics = createMetricsState();
|
|
140
238
|
const subagents = createSubagentState();
|
|
141
|
-
const
|
|
142
|
-
const
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
239
|
+
const metricListeners = createListenerRegistry();
|
|
240
|
+
const subagentListeners = createListenerRegistry();
|
|
241
|
+
const hydratedSessions = /* @__PURE__ */ new Set();
|
|
242
|
+
const hydrateSessionMetrics = async (sessionID) => {
|
|
243
|
+
if (!isSessionID(sessionID) || hydratedSessions.has(sessionID)) return;
|
|
244
|
+
hydratedSessions.add(sessionID);
|
|
245
|
+
try {
|
|
246
|
+
const response = await api.client.session.messages({ sessionID });
|
|
247
|
+
const messages = response.data ?? [];
|
|
248
|
+
for (const message of messages) {
|
|
249
|
+
const info = message.info;
|
|
250
|
+
if (info.role !== "assistant" || typeof info.time.completed !== "number") continue;
|
|
251
|
+
const input = {
|
|
252
|
+
sessionID: info.sessionID,
|
|
253
|
+
messageID: info.id,
|
|
254
|
+
createdAt: info.time.created,
|
|
255
|
+
completedAt: info.time.completed,
|
|
256
|
+
inputTokens: info.tokens.input,
|
|
257
|
+
outputTokens: info.tokens.output,
|
|
258
|
+
reasoningTokens: info.tokens.reasoning,
|
|
259
|
+
cacheReadTokens: info.tokens.cache.read,
|
|
260
|
+
cacheWriteTokens: info.tokens.cache.write
|
|
261
|
+
};
|
|
262
|
+
recordAssistantMessage(metrics, typeof info.finish === "string" ? { ...input, finish: info.finish } : input);
|
|
263
|
+
}
|
|
264
|
+
metricListeners.notify();
|
|
265
|
+
} catch {
|
|
266
|
+
hydratedSessions.delete(sessionID);
|
|
267
|
+
}
|
|
148
268
|
};
|
|
149
269
|
const offDelta = api.event.on("message.part.delta", (evt) => {
|
|
150
270
|
if (evt.properties.field !== "text") return;
|
|
@@ -154,71 +274,89 @@ var tui = async (api) => {
|
|
|
154
274
|
delta: evt.properties.delta,
|
|
155
275
|
at: Date.now()
|
|
156
276
|
});
|
|
157
|
-
|
|
277
|
+
metricListeners.notify();
|
|
158
278
|
});
|
|
159
279
|
const offMessage = api.event.on("message.updated", (evt) => {
|
|
160
280
|
const info = evt.properties.info;
|
|
161
|
-
if (applySubagentEvent(subagents, evt))
|
|
281
|
+
if (applySubagentEvent(subagents, evt)) subagentListeners.notify();
|
|
162
282
|
if (info.role !== "assistant") return;
|
|
163
283
|
const messageInput = {
|
|
164
284
|
sessionID: info.sessionID ?? evt.properties.sessionID,
|
|
165
285
|
messageID: info.id,
|
|
166
286
|
createdAt: info.time.created,
|
|
287
|
+
inputTokens: info.tokens.input,
|
|
167
288
|
outputTokens: info.tokens.output,
|
|
168
|
-
reasoningTokens: info.tokens.reasoning
|
|
289
|
+
reasoningTokens: info.tokens.reasoning,
|
|
290
|
+
cacheReadTokens: info.tokens.cache?.read,
|
|
291
|
+
cacheWriteTokens: info.tokens.cache?.write
|
|
169
292
|
};
|
|
170
293
|
if (typeof info.time.completed === "number") messageInput.completedAt = info.time.completed;
|
|
171
294
|
if (typeof info.finish === "string") messageInput.finish = info.finish;
|
|
172
295
|
recordAssistantMessage(metrics, messageInput);
|
|
173
|
-
|
|
296
|
+
metricListeners.notify();
|
|
174
297
|
});
|
|
175
298
|
const offPart = api.event.on("message.part.updated", (evt) => {
|
|
176
299
|
const part = evt.properties.part;
|
|
177
300
|
if (part.type === "tool") {
|
|
178
301
|
recordToolActivity(metrics, part.sessionID ?? evt.properties.sessionID, part.messageID, Date.now());
|
|
179
302
|
}
|
|
180
|
-
applySubagentEvent(subagents, evt);
|
|
181
|
-
|
|
303
|
+
if (applySubagentEvent(subagents, evt)) subagentListeners.notify();
|
|
304
|
+
metricListeners.notify();
|
|
182
305
|
});
|
|
183
306
|
const offSessionCreated = api.event.on("session.created", (evt) => {
|
|
184
|
-
if (applySubagentEvent(subagents, evt))
|
|
307
|
+
if (applySubagentEvent(subagents, evt)) subagentListeners.notify();
|
|
185
308
|
});
|
|
186
309
|
const offSessionUpdated = api.event.on("session.updated", (evt) => {
|
|
187
|
-
if (applySubagentEvent(subagents, evt))
|
|
310
|
+
if (applySubagentEvent(subagents, evt)) subagentListeners.notify();
|
|
188
311
|
});
|
|
189
312
|
const offSessionStatus = api.event.on("session.status", (evt) => {
|
|
190
|
-
if (applySubagentEvent(subagents, evt))
|
|
313
|
+
if (applySubagentEvent(subagents, evt)) subagentListeners.notify();
|
|
191
314
|
});
|
|
192
315
|
const offSessionIdle = api.event.on("session.idle", (evt) => {
|
|
193
|
-
if (applySubagentEvent(subagents, evt))
|
|
316
|
+
if (applySubagentEvent(subagents, evt)) subagentListeners.notify();
|
|
194
317
|
});
|
|
195
318
|
const offSessionError = api.event.on("session.error", (evt) => {
|
|
196
|
-
if (applySubagentEvent(subagents, evt))
|
|
319
|
+
if (applySubagentEvent(subagents, evt)) subagentListeners.notify();
|
|
197
320
|
});
|
|
198
321
|
const offSlots = api.slots.register({
|
|
199
322
|
slots: {
|
|
200
323
|
session_prompt_right: (_ctx, props) => /* @__PURE__ */ jsx(
|
|
201
|
-
|
|
324
|
+
PromptRightMetrics,
|
|
202
325
|
{
|
|
203
326
|
api,
|
|
204
327
|
sessionID: props.session_id,
|
|
205
|
-
subscribe,
|
|
328
|
+
subscribe: metricListeners.subscribe,
|
|
206
329
|
text: () => {
|
|
207
330
|
if (!isSessionID(props.session_id)) return "";
|
|
208
331
|
const status = api.state.session.status(props.session_id);
|
|
209
|
-
return
|
|
332
|
+
return renderPromptRightMetricsText(metrics, props.session_id, {
|
|
333
|
+
idle: status?.type === "idle",
|
|
334
|
+
metrics: config.promptRightMetrics
|
|
335
|
+
});
|
|
210
336
|
}
|
|
211
337
|
}
|
|
212
338
|
),
|
|
213
|
-
sidebar_content: (_ctx, props) => /* @__PURE__ */
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
339
|
+
sidebar_content: (_ctx, props) => /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
340
|
+
/* @__PURE__ */ jsx(
|
|
341
|
+
TokenUsageSidebar,
|
|
342
|
+
{
|
|
343
|
+
api,
|
|
344
|
+
sessionID: props.session_id,
|
|
345
|
+
state: metrics,
|
|
346
|
+
subscribe: metricListeners.subscribe,
|
|
347
|
+
hydrate: () => void hydrateSessionMetrics(props.session_id)
|
|
348
|
+
}
|
|
349
|
+
),
|
|
350
|
+
/* @__PURE__ */ jsx(
|
|
351
|
+
SubagentSidebar,
|
|
352
|
+
{
|
|
353
|
+
api,
|
|
354
|
+
sessionID: props.session_id,
|
|
355
|
+
state: subagents,
|
|
356
|
+
subscribe: subagentListeners.subscribe
|
|
357
|
+
}
|
|
358
|
+
)
|
|
359
|
+
] })
|
|
222
360
|
}
|
|
223
361
|
});
|
|
224
362
|
api.lifecycle.onDispose(() => {
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "https://json.schemastore.org/package.json",
|
|
3
3
|
"name": "@rejacky/opencode-insights",
|
|
4
|
-
"version": "0.1.
|
|
4
|
+
"version": "0.1.11",
|
|
5
5
|
"description": "OpenCode plugin for local request capture, TPS metrics, and subagent status visibility.",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"author": "opencode-insights contributors",
|
|
@@ -86,6 +86,7 @@
|
|
|
86
86
|
},
|
|
87
87
|
"dependencies": {
|
|
88
88
|
"better-sqlite3": "^12.11.1",
|
|
89
|
+
"jsonc-parser": "^3.3.1",
|
|
89
90
|
"sql.js": "^1.14.1"
|
|
90
91
|
}
|
|
91
92
|
}
|
|
@@ -1,61 +0,0 @@
|
|
|
1
|
-
type CaptureKind = "chat.message" | "chat.params" | "chat.headers" | "experimental.chat.messages.transform" | "experimental.chat.system.transform" | "event" | "tool.execute.before" | "tool.execute.after";
|
|
2
|
-
type CaptureRecord = {
|
|
3
|
-
id: string;
|
|
4
|
-
kind: CaptureKind;
|
|
5
|
-
timestamp: number;
|
|
6
|
-
sessionID?: string | undefined;
|
|
7
|
-
messageID?: string | undefined;
|
|
8
|
-
providerID?: string | undefined;
|
|
9
|
-
modelID?: string | undefined;
|
|
10
|
-
payload: Record<string, unknown>;
|
|
11
|
-
};
|
|
12
|
-
type CaptureStore = {
|
|
13
|
-
initialize?(): Promise<void>;
|
|
14
|
-
append(record: CaptureRecord): Promise<void>;
|
|
15
|
-
close?(): Promise<void>;
|
|
16
|
-
};
|
|
17
|
-
type InsightsOptions = {
|
|
18
|
-
dataDir?: unknown;
|
|
19
|
-
dbPath?: unknown;
|
|
20
|
-
retentionDays?: unknown;
|
|
21
|
-
};
|
|
22
|
-
declare function defaultDataDir(): string;
|
|
23
|
-
declare function resolveCapturePath(options?: InsightsOptions): string;
|
|
24
|
-
declare function normalizeChatMessageCapture(input: unknown, output: unknown, timestamp?: number): CaptureRecord;
|
|
25
|
-
declare function normalizeChatParamsCapture(input: unknown, output: unknown, timestamp?: number): CaptureRecord;
|
|
26
|
-
declare function normalizeChatHeadersCapture(input: unknown, output: unknown, timestamp?: number): CaptureRecord;
|
|
27
|
-
declare function normalizeExperimentalChatMessagesTransformCapture(input: unknown, output: unknown, timestamp?: number): CaptureRecord;
|
|
28
|
-
declare function normalizeExperimentalChatSystemTransformCapture(input: unknown, output: unknown, timestamp?: number): CaptureRecord;
|
|
29
|
-
declare function normalizeEventCapture(event: unknown, timestamp?: number): CaptureRecord;
|
|
30
|
-
declare function normalizeToolCapture(kind: "tool.execute.before" | "tool.execute.after", input: unknown, output: unknown, timestamp?: number): CaptureRecord;
|
|
31
|
-
declare class JsonlCaptureStore implements CaptureStore {
|
|
32
|
-
private readonly path;
|
|
33
|
-
private readonly retentionMs;
|
|
34
|
-
constructor(path: string, retentionMs?: number | undefined);
|
|
35
|
-
initialize(): Promise<void>;
|
|
36
|
-
append(record: CaptureRecord): Promise<void>;
|
|
37
|
-
private pruneExpired;
|
|
38
|
-
}
|
|
39
|
-
interface SqliteDb {
|
|
40
|
-
all(sql: string, ...params: unknown[]): Record<string, unknown>[];
|
|
41
|
-
run(sql: string, ...params: unknown[]): void;
|
|
42
|
-
sync(): void;
|
|
43
|
-
close(): void;
|
|
44
|
-
}
|
|
45
|
-
declare function openDatabase(path: string, readonly?: boolean): Promise<SqliteDb | undefined>;
|
|
46
|
-
declare function extractEventType(payload: Record<string, unknown>): string | null;
|
|
47
|
-
declare class SqliteCaptureStore implements CaptureStore {
|
|
48
|
-
private readonly path;
|
|
49
|
-
private readonly retentionMs;
|
|
50
|
-
private db;
|
|
51
|
-
private fallbackStore;
|
|
52
|
-
constructor(path: string, retentionMs?: number | undefined);
|
|
53
|
-
initialize(): Promise<void>;
|
|
54
|
-
append(record: CaptureRecord): Promise<void>;
|
|
55
|
-
close(): Promise<void>;
|
|
56
|
-
private pruneExpired;
|
|
57
|
-
}
|
|
58
|
-
declare function createCaptureStore(options?: InsightsOptions): CaptureStore;
|
|
59
|
-
declare function resolveRetentionDays(value: unknown): number;
|
|
60
|
-
|
|
61
|
-
export { type CaptureRecord as C, type InsightsOptions as I, JsonlCaptureStore as J, SqliteCaptureStore as S, type CaptureKind as a, type CaptureStore as b, type SqliteDb as c, createCaptureStore as d, defaultDataDir as e, extractEventType as f, normalizeChatMessageCapture as g, normalizeChatParamsCapture as h, normalizeEventCapture as i, normalizeExperimentalChatMessagesTransformCapture as j, normalizeExperimentalChatSystemTransformCapture as k, normalizeToolCapture as l, resolveRetentionDays as m, normalizeChatHeadersCapture as n, openDatabase as o, resolveCapturePath as r };
|