@juvio15/pi-cliproxyapi-provider 0.0.0-stage → 0.1.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.
@@ -0,0 +1,215 @@
1
+ import type {
2
+ ExtensionAPI,
3
+ ExtensionContext,
4
+ } from "@earendil-works/pi-coding-agent";
5
+
6
+ import type { PauseController } from "./pause.ts";
7
+
8
+ const STATUS_KEY = "tps";
9
+ const REFRESH_INTERVAL_MS = 1000;
10
+
11
+ const primaryUi = (ctx: ExtensionContext): boolean =>
12
+ ctx.hasUI && ctx.mode === "tui";
13
+
14
+ const formatElapsed = (totalSeconds: number): string => {
15
+ const safe = Math.max(0, Math.floor(totalSeconds));
16
+
17
+ const units = [
18
+ { suffix: "d", value: Math.floor(safe / 86_400) },
19
+ { suffix: "h", value: Math.floor((safe % 86_400) / 3600) },
20
+ { suffix: "m", value: Math.floor((safe % 3600) / 60) },
21
+ { suffix: "s", value: safe % 60 },
22
+ ];
23
+
24
+ const first = units.findIndex(
25
+ (unit, index) => unit.value > 0 || index === units.length - 1
26
+ );
27
+
28
+ return units
29
+ .slice(first)
30
+ .map((unit) => `${unit.value}${unit.suffix}`)
31
+ .join(" ");
32
+ };
33
+
34
+ export interface TpsUsage {
35
+ cacheRead: number;
36
+ cacheWrite: number;
37
+ input: number;
38
+ output: number;
39
+ total: number;
40
+ }
41
+
42
+ const PERCENT = 100;
43
+ const MS_PER_SECOND = 1000;
44
+
45
+ const count = (value: number): string => value.toLocaleString("en-US");
46
+
47
+ const seconds = (ms: number): string => (ms / MS_PER_SECOND).toFixed(1);
48
+
49
+ /** The run summary: output speed over generation time, run length, and cache hit rate. */
50
+ export const formatTpsSummary = (
51
+ usage: TpsUsage,
52
+ generationMs: number,
53
+ wallMs: number
54
+ ): string => {
55
+ const tps =
56
+ generationMs > 0
57
+ ? (usage.output / (generationMs / MS_PER_SECOND)).toFixed(1)
58
+ : "--";
59
+
60
+ const prompt = usage.input + usage.cacheRead;
61
+
62
+ const cacheHit =
63
+ prompt > 0 ? `${Math.round((usage.cacheRead / prompt) * PERCENT)}%` : "--";
64
+
65
+ return `TPS ${tps} tok/s (${seconds(generationMs)}s generating of ${seconds(wallMs)}s). out ${count(usage.output)}, in ${count(usage.input)}, cache hit ${cacheHit} (r/w ${count(usage.cacheRead)}/${count(usage.cacheWrite)}), total ${count(usage.total)}`;
66
+ };
67
+
68
+ const emptyUsage = (): TpsUsage => ({
69
+ cacheRead: 0,
70
+ cacheWrite: 0,
71
+ input: 0,
72
+ output: 0,
73
+ total: 0,
74
+ });
75
+
76
+ const isProviderAssistant = (
77
+ message: { role: string; provider?: unknown },
78
+ providerId: string
79
+ ): boolean => message.role === "assistant" && message.provider === providerId;
80
+
81
+ export const registerTpsFooter = (
82
+ pi: ExtensionAPI,
83
+ pause: PauseController,
84
+ providerId: string
85
+ ): void => {
86
+ let startedAt: number | undefined;
87
+ let pausedAtStart = 0;
88
+ let pauseAtStart = false;
89
+ let timer: ReturnType<typeof setInterval> | undefined;
90
+ let statusContext: ExtensionContext | undefined;
91
+ let usage = emptyUsage();
92
+ let generationMs = 0;
93
+ let generationStartedAt: number | undefined;
94
+
95
+ const clearTimer = (): void => {
96
+ if (timer) {
97
+ clearInterval(timer);
98
+ timer = undefined;
99
+ }
100
+ };
101
+
102
+ const elapsed = (): number => {
103
+ if (startedAt === undefined) {
104
+ return 0;
105
+ }
106
+
107
+ return pauseAtStart
108
+ ? pause.getElapsed(startedAt, pausedAtStart)
109
+ : Math.max(0, Date.now() - startedAt);
110
+ };
111
+
112
+ const showElapsed = (ctx: ExtensionContext): void => {
113
+ if (primaryUi(ctx)) {
114
+ ctx.ui.setStatus(
115
+ STATUS_KEY,
116
+ ctx.ui.theme.fg("dim", `Elapsed ${formatElapsed(elapsed() / 1000)}`)
117
+ );
118
+ }
119
+ };
120
+
121
+ pi.on("before_agent_start", (_event, ctx) => {
122
+ if (!primaryUi(ctx)) {
123
+ return;
124
+ }
125
+
126
+ statusContext = ctx;
127
+
128
+ if (startedAt !== undefined) {
129
+ return;
130
+ }
131
+
132
+ startedAt = Date.now();
133
+ pauseAtStart = pause.isEnabled();
134
+ pausedAtStart = pause.getPausedDuration(startedAt);
135
+ usage = emptyUsage();
136
+ generationMs = 0;
137
+ generationStartedAt = undefined;
138
+ showElapsed(ctx);
139
+ clearTimer();
140
+ timer = setInterval(() => {
141
+ if (statusContext) {
142
+ showElapsed(statusContext);
143
+ }
144
+ }, REFRESH_INTERVAL_MS);
145
+ timer.unref();
146
+ });
147
+
148
+ pi.on("message_start", (event) => {
149
+ if (
150
+ startedAt !== undefined &&
151
+ isProviderAssistant(event.message, providerId)
152
+ ) {
153
+ generationStartedAt = Date.now();
154
+ }
155
+ });
156
+
157
+ pi.on("message_end", (event) => {
158
+ if (
159
+ generationStartedAt === undefined ||
160
+ !isProviderAssistant(event.message, providerId)
161
+ ) {
162
+ return;
163
+ }
164
+
165
+ generationMs += Math.max(0, Date.now() - generationStartedAt);
166
+ generationStartedAt = undefined;
167
+ });
168
+
169
+ pi.on("agent_end", (event, ctx) => {
170
+ if (startedAt === undefined || !primaryUi(ctx)) {
171
+ return;
172
+ }
173
+
174
+ for (const message of event.messages) {
175
+ if (message.role !== "assistant" || message.provider !== providerId) {
176
+ continue;
177
+ }
178
+
179
+ usage.input += message.usage.input;
180
+ usage.output += message.usage.output;
181
+ usage.cacheRead += message.usage.cacheRead;
182
+ usage.cacheWrite += message.usage.cacheWrite;
183
+ usage.total += message.usage.totalTokens;
184
+ }
185
+ });
186
+
187
+ pi.on("agent_settled", (_event, ctx) => {
188
+ if (startedAt === undefined || !primaryUi(ctx)) {
189
+ return;
190
+ }
191
+
192
+ const elapsedMs = elapsed();
193
+ showElapsed(ctx);
194
+ startedAt = undefined;
195
+ clearTimer();
196
+ statusContext = ctx;
197
+
198
+ if (elapsedMs <= 0) {
199
+ return;
200
+ }
201
+
202
+ ctx.ui.notify(formatTpsSummary(usage, generationMs, elapsedMs), "info");
203
+ });
204
+
205
+ pi.on("session_shutdown", (_event, ctx) => {
206
+ clearTimer();
207
+
208
+ if (primaryUi(ctx)) {
209
+ ctx.ui.setStatus(STATUS_KEY, undefined);
210
+ }
211
+
212
+ startedAt = undefined;
213
+ statusContext = undefined;
214
+ });
215
+ };