opencode-hud 0.1.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.
- package/README.md +108 -0
- package/dist/config.d.ts +9 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +38 -0
- package/dist/config.js.map +1 -0
- package/dist/display.d.ts +5 -0
- package/dist/display.d.ts.map +1 -0
- package/dist/display.js +11 -0
- package/dist/display.js.map +1 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +131 -0
- package/dist/index.js.map +1 -0
- package/dist/logger.d.ts +2 -0
- package/dist/logger.d.ts.map +1 -0
- package/dist/logger.js +20 -0
- package/dist/logger.js.map +1 -0
- package/dist/metrics.d.ts +11 -0
- package/dist/metrics.d.ts.map +1 -0
- package/dist/metrics.js +23 -0
- package/dist/metrics.js.map +1 -0
- package/dist/types.d.ts +8 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/package.json +50 -0
package/README.md
ADDED
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
# OpenCode HUD Plugin
|
|
2
|
+
|
|
3
|
+
A plugin for [OpenCode](https://opencode.ai) that displays token streaming metrics at the end of each conversation.
|
|
4
|
+
|
|
5
|
+
## Metrics Displayed
|
|
6
|
+
|
|
7
|
+
| Metric | Description |
|
|
8
|
+
|--------|-------------|
|
|
9
|
+
| ⚡ Avg TPS | Average tokens per second (includes reasoning tokens) |
|
|
10
|
+
| TTFT | Time To First Token — latency from request to first response token |
|
|
11
|
+
| Total tokens | Cumulative token count from API (output + reasoning) |
|
|
12
|
+
| Elapsed time | Wall-clock time since the first token |
|
|
13
|
+
|
|
14
|
+
**Example toast:**
|
|
15
|
+
```
|
|
16
|
+
⚡ 42.5 t/s TTFT 312ms [639 tok / 15.0s]
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Installation
|
|
20
|
+
|
|
21
|
+
### From npm
|
|
22
|
+
|
|
23
|
+
Add the plugin to your `.opencode/opencode.json`:
|
|
24
|
+
|
|
25
|
+
```json
|
|
26
|
+
{
|
|
27
|
+
"plugin": ["opencode-hud@latest"]
|
|
28
|
+
}
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
### Local Development
|
|
32
|
+
|
|
33
|
+
This plugin auto-loads when you run `opencode` from this directory, because it lives in `.opencode/plugins/`.
|
|
34
|
+
|
|
35
|
+
## Configuration
|
|
36
|
+
|
|
37
|
+
Create a config file at `~/.config/opencode/opencode-hud.json`:
|
|
38
|
+
|
|
39
|
+
```json
|
|
40
|
+
{
|
|
41
|
+
"enableLogging": true,
|
|
42
|
+
"logFilePath": ".opencode/hud-debug.log"
|
|
43
|
+
}
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
| Option | Type | Default | Description |
|
|
47
|
+
|--------|------|---------|-------------|
|
|
48
|
+
| `enableLogging` | boolean | `false` | Enable debug logging |
|
|
49
|
+
| `logFilePath` | string | `.opencode/hud-debug.log` | Path to log file |
|
|
50
|
+
|
|
51
|
+
## Development
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
# Install dependencies (Bun required)
|
|
55
|
+
bun install
|
|
56
|
+
|
|
57
|
+
# Run tests
|
|
58
|
+
bun test
|
|
59
|
+
|
|
60
|
+
# Type-check
|
|
61
|
+
bun run typecheck
|
|
62
|
+
|
|
63
|
+
# Build
|
|
64
|
+
bun run build
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
## Project Structure
|
|
68
|
+
|
|
69
|
+
```
|
|
70
|
+
opencode-hud/
|
|
71
|
+
├── .opencode/
|
|
72
|
+
│ ├── opencode.json # OpenCode project config
|
|
73
|
+
│ └── plugins/
|
|
74
|
+
│ └── hud.ts # Plugin entry (auto-loaded by OpenCode)
|
|
75
|
+
├── src/
|
|
76
|
+
│ ├── index.ts # Plugin main entry — event handlers
|
|
77
|
+
│ ├── config.ts # Configuration management
|
|
78
|
+
│ ├── logger.ts # Configurable logging
|
|
79
|
+
│ ├── types.ts # TypeScript interfaces
|
|
80
|
+
│ ├── metrics.ts # Token estimation, duration formatting
|
|
81
|
+
│ └── display.ts # Toast formatting and emission
|
|
82
|
+
├── tests/
|
|
83
|
+
│ ├── metrics.test.ts # Unit tests
|
|
84
|
+
│ └── integration.test.ts # Event flow tests
|
|
85
|
+
├── dist/ # Build output
|
|
86
|
+
├── package.json
|
|
87
|
+
└── tsconfig.json
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
## Architecture
|
|
91
|
+
|
|
92
|
+
The plugin listens to OpenCode events and displays metrics when a conversation ends (`session.idle`).
|
|
93
|
+
|
|
94
|
+
**Event Flow:**
|
|
95
|
+
1. `message.updated` (user) → Record request start time
|
|
96
|
+
2. `message.updated` (assistant) → Store message with tokens info
|
|
97
|
+
3. `message.part.updated` → Track first token time, count tokens
|
|
98
|
+
4. `session.idle` → Calculate metrics and show toast
|
|
99
|
+
|
|
100
|
+
**Token Counting:**
|
|
101
|
+
- Primary: Uses API-provided `tokens.output + tokens.reasoning`
|
|
102
|
+
- Fallback: Estimates from text length (`length / 3`)
|
|
103
|
+
|
|
104
|
+
**Key Design Decisions:**
|
|
105
|
+
- `requestStartTime` set on user message to capture full TTFT
|
|
106
|
+
- `streamingStartTime` set on first assistant token (lazy init)
|
|
107
|
+
- Token count includes reasoning tokens for accurate TPS
|
|
108
|
+
- Uses `performance.now()` for high-precision timing
|
package/dist/config.d.ts
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export interface HudConfig {
|
|
2
|
+
enableLogging: boolean;
|
|
3
|
+
logFilePath?: string;
|
|
4
|
+
}
|
|
5
|
+
export declare function loadConfigFromFile(): void;
|
|
6
|
+
export declare function getConfig(): HudConfig;
|
|
7
|
+
export declare function setConfig(config: Partial<HudConfig>): void;
|
|
8
|
+
export declare function resetConfig(): void;
|
|
9
|
+
//# sourceMappingURL=config.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAIA,MAAM,WAAW,SAAS;IACxB,aAAa,EAAE,OAAO,CAAA;IACtB,WAAW,CAAC,EAAE,MAAM,CAAA;CACrB;AAkBD,wBAAgB,kBAAkB,IAAI,IAAI,CAWzC;AAED,wBAAgB,SAAS,IAAI,SAAS,CAErC;AAED,wBAAgB,SAAS,CAAC,MAAM,EAAE,OAAO,CAAC,SAAS,CAAC,GAAG,IAAI,CAE1D;AAED,wBAAgB,WAAW,IAAI,IAAI,CAElC"}
|
package/dist/config.js
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from "fs";
|
|
2
|
+
import { homedir } from "os";
|
|
3
|
+
import { join } from "path";
|
|
4
|
+
const CONFIG_FILE_NAME = "opencode-hud.json";
|
|
5
|
+
const DEFAULT_CONFIG = {
|
|
6
|
+
enableLogging: false,
|
|
7
|
+
logFilePath: ".opencode/hud-debug.log",
|
|
8
|
+
};
|
|
9
|
+
let currentConfig = { ...DEFAULT_CONFIG };
|
|
10
|
+
function getConfigPaths() {
|
|
11
|
+
return [
|
|
12
|
+
join(homedir(), ".config", "opencode", CONFIG_FILE_NAME),
|
|
13
|
+
join(process.cwd(), ".opencode", CONFIG_FILE_NAME),
|
|
14
|
+
];
|
|
15
|
+
}
|
|
16
|
+
export function loadConfigFromFile() {
|
|
17
|
+
for (const configPath of getConfigPaths()) {
|
|
18
|
+
if (existsSync(configPath)) {
|
|
19
|
+
try {
|
|
20
|
+
const content = readFileSync(configPath, "utf-8");
|
|
21
|
+
const parsed = JSON.parse(content);
|
|
22
|
+
currentConfig = { ...DEFAULT_CONFIG, ...parsed };
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
catch { }
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
export function getConfig() {
|
|
30
|
+
return currentConfig;
|
|
31
|
+
}
|
|
32
|
+
export function setConfig(config) {
|
|
33
|
+
currentConfig = { ...currentConfig, ...config };
|
|
34
|
+
}
|
|
35
|
+
export function resetConfig() {
|
|
36
|
+
currentConfig = { ...DEFAULT_CONFIG };
|
|
37
|
+
}
|
|
38
|
+
//# sourceMappingURL=config.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,IAAI,CAAA;AAC7C,OAAO,EAAE,OAAO,EAAE,MAAM,IAAI,CAAA;AAC5B,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAA;AAO3B,MAAM,gBAAgB,GAAG,mBAAmB,CAAA;AAE5C,MAAM,cAAc,GAAc;IAChC,aAAa,EAAE,KAAK;IACpB,WAAW,EAAE,yBAAyB;CACvC,CAAA;AAED,IAAI,aAAa,GAAc,EAAE,GAAG,cAAc,EAAE,CAAA;AAEpD,SAAS,cAAc;IACrB,OAAO;QACL,IAAI,CAAC,OAAO,EAAE,EAAE,SAAS,EAAE,UAAU,EAAE,gBAAgB,CAAC;QACxD,IAAI,CAAC,OAAO,CAAC,GAAG,EAAE,EAAE,WAAW,EAAE,gBAAgB,CAAC;KACnD,CAAA;AACH,CAAC;AAED,MAAM,UAAU,kBAAkB;IAChC,KAAK,MAAM,UAAU,IAAI,cAAc,EAAE,EAAE,CAAC;QAC1C,IAAI,UAAU,CAAC,UAAU,CAAC,EAAE,CAAC;YAC3B,IAAI,CAAC;gBACH,MAAM,OAAO,GAAG,YAAY,CAAC,UAAU,EAAE,OAAO,CAAC,CAAA;gBACjD,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,CAAuB,CAAA;gBACxD,aAAa,GAAG,EAAE,GAAG,cAAc,EAAE,GAAG,MAAM,EAAE,CAAA;gBAChD,OAAM;YACR,CAAC;YAAC,MAAM,CAAC,CAAA,CAAC;QACZ,CAAC;IACH,CAAC;AACH,CAAC;AAED,MAAM,UAAU,SAAS;IACvB,OAAO,aAAa,CAAA;AACtB,CAAC;AAED,MAAM,UAAU,SAAS,CAAC,MAA0B;IAClD,aAAa,GAAG,EAAE,GAAG,aAAa,EAAE,GAAG,MAAM,EAAE,CAAA;AACjD,CAAC;AAED,MAAM,UAAU,WAAW;IACzB,aAAa,GAAG,EAAE,GAAG,cAAc,EAAE,CAAA;AACvC,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"display.d.ts","sourceRoot":"","sources":["../src/display.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAA;AAItD,wBAAsB,OAAO,CAAC,MAAM,EAAE,cAAc,EAAE,OAAO,EAAE;IAAE,OAAO,EAAE,MAAM,CAAA;CAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAQjG"}
|
package/dist/display.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"display.js","sourceRoot":"","sources":["../src/display.ts"],"names":[],"mappings":"AAEA,MAAM,iBAAiB,GAAG,IAAI,CAAA;AAE9B,MAAM,CAAC,KAAK,UAAU,OAAO,CAAC,MAAsB,EAAE,OAA4B;IAChF,MAAM,MAAM,CAAC,GAAG,CAAC,SAAS,CAAC;QACzB,IAAI,EAAE;YACJ,OAAO,EAAE,OAAO,CAAC,OAAO;YACxB,OAAO,EAAE,MAAM;YACf,QAAQ,EAAE,iBAAiB;SAC5B;KACF,CAAC,CAAA;AACJ,CAAC"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,qBAAqB,CAAA;AAQjD,OAAO,EAAE,SAAS,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,aAAa,CAAA;AAC/D,YAAY,EAAE,SAAS,EAAE,MAAM,aAAa,CAAA;AAE5C,eAAO,MAAM,SAAS,EAAE,MAiJvB,CAAA"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import { showHud } from "./display.js";
|
|
2
|
+
import { createFreshMetrics, formatDuration, now, estimateTokens } from "./metrics.js";
|
|
3
|
+
import { log } from "./logger.js";
|
|
4
|
+
import { loadConfigFromFile } from "./config.js";
|
|
5
|
+
export { setConfig, getConfig, resetConfig } from "./config.js";
|
|
6
|
+
export const HudPlugin = async ({ client }) => {
|
|
7
|
+
loadConfigFromFile();
|
|
8
|
+
const sessions = new Map();
|
|
9
|
+
const messageRoles = new Map();
|
|
10
|
+
const assistantMessages = new Map();
|
|
11
|
+
function isTextPart(part) {
|
|
12
|
+
return part.type === "text" && "text" in part;
|
|
13
|
+
}
|
|
14
|
+
function getOrCreate(sessionId) {
|
|
15
|
+
let m = sessions.get(sessionId);
|
|
16
|
+
if (!m) {
|
|
17
|
+
m = createFreshMetrics();
|
|
18
|
+
sessions.set(sessionId, m);
|
|
19
|
+
}
|
|
20
|
+
return m;
|
|
21
|
+
}
|
|
22
|
+
return {
|
|
23
|
+
event: async ({ event }) => {
|
|
24
|
+
switch (event.type) {
|
|
25
|
+
case "session.created": {
|
|
26
|
+
const session = event.properties.info;
|
|
27
|
+
log(`session.created: id=${session.id}`);
|
|
28
|
+
sessions.set(session.id, createFreshMetrics());
|
|
29
|
+
break;
|
|
30
|
+
}
|
|
31
|
+
case "message.updated": {
|
|
32
|
+
const msg = event.properties.info;
|
|
33
|
+
log(`message.updated: id=${msg.id} role=${msg.role} sessionID=${msg.sessionID}`);
|
|
34
|
+
messageRoles.set(msg.id, msg.role);
|
|
35
|
+
if (msg.role === "user") {
|
|
36
|
+
const metrics = getOrCreate(msg.sessionID);
|
|
37
|
+
metrics.requestStartTime = now();
|
|
38
|
+
}
|
|
39
|
+
else if (msg.role === "assistant") {
|
|
40
|
+
const assistantMsg = msg;
|
|
41
|
+
assistantMessages.set(msg.id, assistantMsg);
|
|
42
|
+
log(` assistant tokens: output=${assistantMsg.tokens?.output} reasoning=${assistantMsg.tokens?.reasoning}`);
|
|
43
|
+
}
|
|
44
|
+
break;
|
|
45
|
+
}
|
|
46
|
+
case "message.part.updated": {
|
|
47
|
+
const part = event.properties.part;
|
|
48
|
+
if (!isTextPart(part))
|
|
49
|
+
break;
|
|
50
|
+
const sessionId = part.sessionID;
|
|
51
|
+
const messageId = part.messageID;
|
|
52
|
+
log(`message.part.updated: sessionId=${sessionId} messageId=${messageId} msgRole=${messageRoles.get(messageId)}`);
|
|
53
|
+
if (messageRoles.get(messageId) !== "assistant") {
|
|
54
|
+
log(` SKIP: not assistant`);
|
|
55
|
+
break;
|
|
56
|
+
}
|
|
57
|
+
const metrics = getOrCreate(sessionId);
|
|
58
|
+
log(` metrics: totalTokens=${metrics.totalTokens} streamingStartTime=${metrics.streamingStartTime}`);
|
|
59
|
+
if (messageId !== metrics.currentMessageId) {
|
|
60
|
+
metrics.streamingStartTime = null;
|
|
61
|
+
metrics.totalTokens = 0;
|
|
62
|
+
metrics.currentMessageId = messageId;
|
|
63
|
+
}
|
|
64
|
+
const assistantMsg = assistantMessages.get(messageId);
|
|
65
|
+
if (assistantMsg?.tokens?.output !== undefined && assistantMsg.tokens.output > 0) {
|
|
66
|
+
const output = assistantMsg.tokens.output;
|
|
67
|
+
const reasoning = assistantMsg.tokens.reasoning ?? 0;
|
|
68
|
+
metrics.totalTokens = output + reasoning;
|
|
69
|
+
log(` using API tokens: output=${output} reasoning=${reasoning} total=${metrics.totalTokens}`);
|
|
70
|
+
}
|
|
71
|
+
else {
|
|
72
|
+
metrics.totalTokens = estimateTokens(part.text);
|
|
73
|
+
log(` using estimated tokens: ${metrics.totalTokens}`);
|
|
74
|
+
}
|
|
75
|
+
if (metrics.streamingStartTime === null) {
|
|
76
|
+
metrics.streamingStartTime = now();
|
|
77
|
+
}
|
|
78
|
+
break;
|
|
79
|
+
}
|
|
80
|
+
case "session.idle": {
|
|
81
|
+
const sessionId = event.properties.sessionID;
|
|
82
|
+
log(`session.idle: sessionId=${sessionId}`);
|
|
83
|
+
const metrics = sessions.get(sessionId);
|
|
84
|
+
log(` metrics: ${JSON.stringify(metrics)}`);
|
|
85
|
+
if (!metrics) {
|
|
86
|
+
log(` SKIP: no metrics`);
|
|
87
|
+
break;
|
|
88
|
+
}
|
|
89
|
+
if (metrics.streamingStartTime === null) {
|
|
90
|
+
log(` SKIP: streamingStartTime is null`);
|
|
91
|
+
break;
|
|
92
|
+
}
|
|
93
|
+
const assistantMsg = metrics.currentMessageId
|
|
94
|
+
? assistantMessages.get(metrics.currentMessageId)
|
|
95
|
+
: undefined;
|
|
96
|
+
if (assistantMsg?.tokens?.output !== undefined) {
|
|
97
|
+
const output = assistantMsg.tokens.output;
|
|
98
|
+
const reasoning = assistantMsg.tokens.reasoning ?? 0;
|
|
99
|
+
metrics.totalTokens = output + reasoning;
|
|
100
|
+
log(` final tokens from API: output=${output} reasoning=${reasoning} total=${metrics.totalTokens}`);
|
|
101
|
+
}
|
|
102
|
+
if (metrics.totalTokens === 0) {
|
|
103
|
+
log(` SKIP: totalTokens is 0`);
|
|
104
|
+
break;
|
|
105
|
+
}
|
|
106
|
+
metrics.completionTime = now();
|
|
107
|
+
const ttft = metrics.requestStartTime !== null && metrics.streamingStartTime !== null
|
|
108
|
+
? metrics.streamingStartTime - metrics.requestStartTime
|
|
109
|
+
: null;
|
|
110
|
+
const elapsedMs = metrics.completionTime - metrics.streamingStartTime;
|
|
111
|
+
const elapsedSec = elapsedMs / 1000;
|
|
112
|
+
const avgTps = elapsedSec > 0 ? (metrics.totalTokens / elapsedSec) : 0;
|
|
113
|
+
const message = `⚡ ${avgTps.toFixed(1)} t/s TTFT ${ttft !== null ? formatDuration(ttft) : "--"} [${metrics.totalTokens} tok / ${elapsedSec.toFixed(1)}s]`;
|
|
114
|
+
log(` SHOWING: ${message}`);
|
|
115
|
+
await showHud(client, { message });
|
|
116
|
+
break;
|
|
117
|
+
}
|
|
118
|
+
case "message.removed": {
|
|
119
|
+
messageRoles.delete(event.properties.messageID);
|
|
120
|
+
assistantMessages.delete(event.properties.messageID);
|
|
121
|
+
break;
|
|
122
|
+
}
|
|
123
|
+
case "session.deleted": {
|
|
124
|
+
sessions.delete(event.properties.info.id);
|
|
125
|
+
break;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
},
|
|
129
|
+
};
|
|
130
|
+
};
|
|
131
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
package/dist/logger.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"logger.d.ts","sourceRoot":"","sources":["../src/logger.ts"],"names":[],"mappings":"AAIA,wBAAgB,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAcrC"}
|
package/dist/logger.js
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { appendFileSync, mkdirSync, existsSync } from "fs";
|
|
2
|
+
import { dirname } from "path";
|
|
3
|
+
import { getConfig } from "./config.js";
|
|
4
|
+
export function log(msg) {
|
|
5
|
+
const config = getConfig();
|
|
6
|
+
if (!config.enableLogging)
|
|
7
|
+
return;
|
|
8
|
+
const logFile = config.logFilePath;
|
|
9
|
+
if (!logFile)
|
|
10
|
+
return;
|
|
11
|
+
const timestamp = new Date().toISOString().slice(11, 23);
|
|
12
|
+
try {
|
|
13
|
+
if (!existsSync(logFile)) {
|
|
14
|
+
mkdirSync(dirname(logFile), { recursive: true });
|
|
15
|
+
}
|
|
16
|
+
appendFileSync(logFile, `[${timestamp}] ${msg}\n`);
|
|
17
|
+
}
|
|
18
|
+
catch { }
|
|
19
|
+
}
|
|
20
|
+
//# sourceMappingURL=logger.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"logger.js","sourceRoot":"","sources":["../src/logger.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,IAAI,CAAA;AAC1D,OAAO,EAAE,OAAO,EAAE,MAAM,MAAM,CAAA;AAC9B,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAA;AAEvC,MAAM,UAAU,GAAG,CAAC,GAAW;IAC7B,MAAM,MAAM,GAAG,SAAS,EAAE,CAAA;IAC1B,IAAI,CAAC,MAAM,CAAC,aAAa;QAAE,OAAM;IAEjC,MAAM,OAAO,GAAG,MAAM,CAAC,WAAW,CAAA;IAClC,IAAI,CAAC,OAAO;QAAE,OAAM;IAEpB,MAAM,SAAS,GAAG,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC,KAAK,CAAC,EAAE,EAAE,EAAE,CAAC,CAAA;IACxD,IAAI,CAAC;QACH,IAAI,CAAC,UAAU,CAAC,OAAO,CAAC,EAAE,CAAC;YACzB,SAAS,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAA;QAClD,CAAC;QACD,cAAc,CAAC,OAAO,EAAE,IAAI,SAAS,KAAK,GAAG,IAAI,CAAC,CAAA;IACpD,CAAC;IAAC,MAAM,CAAC,CAAA,CAAC;AACZ,CAAC"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export declare function now(): number;
|
|
2
|
+
export declare function formatDuration(ms: number): string;
|
|
3
|
+
export declare function estimateTokens(text: string): number;
|
|
4
|
+
export declare function createFreshMetrics(): {
|
|
5
|
+
requestStartTime: number | null;
|
|
6
|
+
streamingStartTime: number | null;
|
|
7
|
+
completionTime: number | null;
|
|
8
|
+
totalTokens: number;
|
|
9
|
+
currentMessageId: string | null;
|
|
10
|
+
};
|
|
11
|
+
//# sourceMappingURL=metrics.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"metrics.d.ts","sourceRoot":"","sources":["../src/metrics.ts"],"names":[],"mappings":"AAAA,wBAAgB,GAAG,IAAI,MAAM,CAE5B;AAED,wBAAgB,cAAc,CAAC,EAAE,EAAE,MAAM,GAAG,MAAM,CAGjD;AAED,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAGnD;AAED,wBAAgB,kBAAkB;sBAEJ,MAAM,GAAG,IAAI;wBACX,MAAM,GAAG,IAAI;oBACjB,MAAM,GAAG,IAAI;;sBAEX,MAAM,GAAG,IAAI;EAE1C"}
|
package/dist/metrics.js
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
export function now() {
|
|
2
|
+
return performance.now();
|
|
3
|
+
}
|
|
4
|
+
export function formatDuration(ms) {
|
|
5
|
+
if (ms < 1000)
|
|
6
|
+
return `${Math.round(ms)}ms`;
|
|
7
|
+
return `${(ms / 1000).toFixed(2)}s`;
|
|
8
|
+
}
|
|
9
|
+
export function estimateTokens(text) {
|
|
10
|
+
if (!text)
|
|
11
|
+
return 0;
|
|
12
|
+
return Math.round(text.length / 3);
|
|
13
|
+
}
|
|
14
|
+
export function createFreshMetrics() {
|
|
15
|
+
return {
|
|
16
|
+
requestStartTime: null,
|
|
17
|
+
streamingStartTime: null,
|
|
18
|
+
completionTime: null,
|
|
19
|
+
totalTokens: 0,
|
|
20
|
+
currentMessageId: null,
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
//# sourceMappingURL=metrics.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"metrics.js","sourceRoot":"","sources":["../src/metrics.ts"],"names":[],"mappings":"AAAA,MAAM,UAAU,GAAG;IACjB,OAAO,WAAW,CAAC,GAAG,EAAE,CAAA;AAC1B,CAAC;AAED,MAAM,UAAU,cAAc,CAAC,EAAU;IACvC,IAAI,EAAE,GAAG,IAAI;QAAE,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,IAAI,CAAA;IAC3C,OAAO,GAAG,CAAC,EAAE,GAAG,IAAI,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,CAAA;AACrC,CAAC;AAED,MAAM,UAAU,cAAc,CAAC,IAAY;IACzC,IAAI,CAAC,IAAI;QAAE,OAAO,CAAC,CAAA;IACnB,OAAO,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC,CAAA;AACpC,CAAC;AAED,MAAM,UAAU,kBAAkB;IAChC,OAAO;QACL,gBAAgB,EAAE,IAAqB;QACvC,kBAAkB,EAAE,IAAqB;QACzC,cAAc,EAAE,IAAqB;QACrC,WAAW,EAAE,CAAC;QACd,gBAAgB,EAAE,IAAqB;KACxC,CAAA;AACH,CAAC"}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,cAAc;IAC7B,gBAAgB,EAAE,MAAM,GAAG,IAAI,CAAA;IAC/B,kBAAkB,EAAE,MAAM,GAAG,IAAI,CAAA;IACjC,cAAc,EAAE,MAAM,GAAG,IAAI,CAAA;IAC7B,WAAW,EAAE,MAAM,CAAA;IACnB,gBAAgB,EAAE,MAAM,GAAG,IAAI,CAAA;CAChC"}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":""}
|
package/package.json
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "opencode-hud",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"description": "OpenCode plugin that displays TPS, TTFT, and token metrics at conversation end.",
|
|
6
|
+
"main": "dist/index.js",
|
|
7
|
+
"module": "dist/index.js",
|
|
8
|
+
"types": "dist/index.d.ts",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"import": "./dist/index.js",
|
|
12
|
+
"types": "./dist/index.d.ts"
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
"files": [
|
|
16
|
+
"dist",
|
|
17
|
+
"README.md"
|
|
18
|
+
],
|
|
19
|
+
"scripts": {
|
|
20
|
+
"test": "bun test",
|
|
21
|
+
"typecheck": "tsc --noEmit",
|
|
22
|
+
"build": "tsc",
|
|
23
|
+
"prepublishOnly": "bun run typecheck && bun test && bun run build"
|
|
24
|
+
},
|
|
25
|
+
"keywords": [
|
|
26
|
+
"opencode",
|
|
27
|
+
"opencode-plugin",
|
|
28
|
+
"tps",
|
|
29
|
+
"ttft",
|
|
30
|
+
"metrics",
|
|
31
|
+
"ai",
|
|
32
|
+
"llm"
|
|
33
|
+
],
|
|
34
|
+
"license": "MIT",
|
|
35
|
+
"peerDependencies": {
|
|
36
|
+
"@opencode-ai/plugin": ">=0.1.0",
|
|
37
|
+
"@opencode-ai/sdk": ">=0.1.0"
|
|
38
|
+
},
|
|
39
|
+
"devDependencies": {
|
|
40
|
+
"@opencode-ai/plugin": "latest",
|
|
41
|
+
"@opencode-ai/sdk": "latest",
|
|
42
|
+
"typescript": "^5.0.0",
|
|
43
|
+
"bun-types": "latest",
|
|
44
|
+
"@types/bun": "latest"
|
|
45
|
+
},
|
|
46
|
+
"repository": {
|
|
47
|
+
"type": "git",
|
|
48
|
+
"url": "https://github.com/Alaye-Dong/opencode-hud.git"
|
|
49
|
+
}
|
|
50
|
+
}
|