@llblab/pi-telegram 0.2.9 → 0.3.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 +40 -26
- package/docs/architecture.md +62 -35
- package/index.ts +388 -1936
- package/lib/api.ts +647 -76
- package/lib/attachments.ts +128 -16
- package/lib/commands.ts +721 -0
- package/lib/config.ts +157 -0
- package/lib/media.ts +211 -36
- package/lib/menu.ts +920 -338
- package/lib/model.ts +647 -0
- package/lib/pi.ts +80 -0
- package/lib/polling.ts +264 -18
- package/lib/preview.ts +451 -29
- package/lib/queue.ts +1134 -110
- package/lib/registration.ts +127 -28
- package/lib/rendering.ts +575 -281
- package/lib/replies.ts +198 -8
- package/lib/runtime.ts +475 -0
- package/lib/setup.ts +129 -1
- package/lib/status.ts +428 -13
- package/lib/turns.ts +207 -17
- package/lib/updates.ts +392 -99
- package/package.json +18 -3
- package/AGENTS.md +0 -91
- package/BACKLOG.md +0 -5
- package/CHANGELOG.md +0 -23
- package/lib/model-switch.ts +0 -62
- package/tests/api.test.ts +0 -89
- package/tests/attachments.test.ts +0 -132
- package/tests/config.test.ts +0 -80
- package/tests/media.test.ts +0 -77
- package/tests/menu.test.ts +0 -676
- package/tests/polling.test.ts +0 -129
- package/tests/preview.test.ts +0 -441
- package/tests/queue.test.ts +0 -3245
- package/tests/registration.test.ts +0 -268
- package/tests/rendering.test.ts +0 -475
- package/tests/replies.test.ts +0 -142
- package/tests/turns.test.ts +0 -132
- package/tests/updates.test.ts +0 -357
package/lib/status.ts
CHANGED
|
@@ -3,8 +3,7 @@
|
|
|
3
3
|
* Builds usage, cost, and context summaries for the interactive Telegram status view
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
|
|
7
|
-
import type { ExtensionContext } from "@mariozechner/pi-coding-agent";
|
|
6
|
+
export type TelegramStatusQueueLane = "control" | "priority" | "default";
|
|
8
7
|
|
|
9
8
|
export interface TelegramUsageStats {
|
|
10
9
|
totalInput: number;
|
|
@@ -14,6 +13,417 @@ export interface TelegramUsageStats {
|
|
|
14
13
|
totalCost: number;
|
|
15
14
|
}
|
|
16
15
|
|
|
16
|
+
interface TelegramUsageMessage {
|
|
17
|
+
role: string;
|
|
18
|
+
usage?: {
|
|
19
|
+
input: number;
|
|
20
|
+
output: number;
|
|
21
|
+
cacheRead: number;
|
|
22
|
+
cacheWrite: number;
|
|
23
|
+
cost: { total: number };
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
interface TelegramStatusSessionEntry {
|
|
28
|
+
type: string;
|
|
29
|
+
message?: TelegramUsageMessage;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
interface TelegramContextUsage {
|
|
33
|
+
contextWindow?: number;
|
|
34
|
+
percent: number | null;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export interface TelegramStatusActiveModel {
|
|
38
|
+
contextWindow?: number;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export interface TelegramStatusContext {
|
|
42
|
+
sessionManager: { getEntries(): TelegramStatusSessionEntry[] };
|
|
43
|
+
getContextUsage(): TelegramContextUsage | undefined;
|
|
44
|
+
modelRegistry: {
|
|
45
|
+
isUsingOAuth(model: TelegramStatusActiveModel): boolean;
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export type TelegramRuntimeEventDetailValue = string | number | boolean | null;
|
|
50
|
+
|
|
51
|
+
const MAX_RECENT_TELEGRAM_RUNTIME_EVENTS = 10;
|
|
52
|
+
|
|
53
|
+
export interface TelegramRuntimeEvent {
|
|
54
|
+
at: number;
|
|
55
|
+
category: string;
|
|
56
|
+
message: string;
|
|
57
|
+
details?: Record<string, TelegramRuntimeEventDetailValue>;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export interface TelegramRuntimeEventInput {
|
|
61
|
+
category: string;
|
|
62
|
+
error?: unknown;
|
|
63
|
+
message?: string;
|
|
64
|
+
details?: Record<string, unknown>;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export interface TelegramRuntimeEventRecorder {
|
|
68
|
+
record: (
|
|
69
|
+
category: string,
|
|
70
|
+
error: unknown,
|
|
71
|
+
details?: Record<string, unknown>,
|
|
72
|
+
) => void;
|
|
73
|
+
getEvents: () => TelegramRuntimeEvent[];
|
|
74
|
+
clear: () => void;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export interface TelegramRuntimeEventRecorderOptions {
|
|
78
|
+
getBotToken: () => string | undefined;
|
|
79
|
+
maxEvents?: number;
|
|
80
|
+
now?: () => number;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export interface TelegramBridgeStatusLineState {
|
|
84
|
+
botUsername?: string;
|
|
85
|
+
allowedUserId?: number;
|
|
86
|
+
pollingActive: boolean;
|
|
87
|
+
lastUpdateId?: number;
|
|
88
|
+
activeSourceMessageIds?: number[];
|
|
89
|
+
pendingDispatch: boolean;
|
|
90
|
+
compactionInProgress: boolean;
|
|
91
|
+
activeToolExecutions: number;
|
|
92
|
+
pendingModelSwitch: boolean;
|
|
93
|
+
queuedItems: Array<{ queueLane: TelegramStatusQueueLane }>;
|
|
94
|
+
recentRuntimeEvents: TelegramRuntimeEvent[];
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export interface TelegramStatusBarTheme {
|
|
98
|
+
fg: (
|
|
99
|
+
token: "accent" | "error" | "muted" | "warning" | "success",
|
|
100
|
+
text: string,
|
|
101
|
+
) => string;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
export interface TelegramStatusBarState {
|
|
105
|
+
hasBotToken: boolean;
|
|
106
|
+
pollingActive: boolean;
|
|
107
|
+
paired: boolean;
|
|
108
|
+
compactionInProgress: boolean;
|
|
109
|
+
processing: boolean;
|
|
110
|
+
queuedStatus: string;
|
|
111
|
+
error?: string;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
export interface TelegramStatusRuntimeContext {
|
|
115
|
+
ui: {
|
|
116
|
+
theme: TelegramStatusBarTheme;
|
|
117
|
+
setStatus: (key: string, text: string) => void;
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
export interface TelegramStatusRuntimeDeps<
|
|
122
|
+
TContext extends TelegramStatusRuntimeContext,
|
|
123
|
+
> {
|
|
124
|
+
statusKey?: string;
|
|
125
|
+
getStatusBarState: (ctx: TContext, error?: string) => TelegramStatusBarState;
|
|
126
|
+
getBridgeStatusLineState: () => TelegramBridgeStatusLineState;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
export interface TelegramBridgeStatusConfig {
|
|
130
|
+
botToken?: string;
|
|
131
|
+
botUsername?: string;
|
|
132
|
+
allowedUserId?: number;
|
|
133
|
+
lastUpdateId?: number;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
export interface TelegramBridgeStatusRuntimeDeps<
|
|
137
|
+
TQueueItem extends { queueLane: TelegramStatusQueueLane },
|
|
138
|
+
> {
|
|
139
|
+
statusKey?: string;
|
|
140
|
+
getConfig: () => TelegramBridgeStatusConfig;
|
|
141
|
+
isPollingActive: () => boolean;
|
|
142
|
+
getActiveSourceMessageIds: () => number[] | undefined;
|
|
143
|
+
hasActiveTurn: () => boolean;
|
|
144
|
+
hasDispatchPending: () => boolean;
|
|
145
|
+
isCompactionInProgress: () => boolean;
|
|
146
|
+
getActiveToolExecutions: () => number;
|
|
147
|
+
hasPendingModelSwitch: () => boolean;
|
|
148
|
+
getQueuedItems: () => TQueueItem[];
|
|
149
|
+
formatQueuedStatus: (items: TQueueItem[]) => string;
|
|
150
|
+
getRecentRuntimeEvents: () => TelegramRuntimeEvent[];
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
export interface TelegramStatusRuntime<
|
|
154
|
+
TContext extends TelegramStatusRuntimeContext,
|
|
155
|
+
> {
|
|
156
|
+
updateStatus: (ctx: TContext, error?: string) => void;
|
|
157
|
+
getStatusLines: () => string[];
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
export function redactTelegramRuntimeMessage(
|
|
161
|
+
message: string,
|
|
162
|
+
botToken: string | undefined,
|
|
163
|
+
): string {
|
|
164
|
+
if (!botToken) return message;
|
|
165
|
+
return message.split(botToken).join("<redacted-token>");
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
function normalizeTelegramRuntimeEventDetails(
|
|
169
|
+
details: Record<string, unknown> | undefined,
|
|
170
|
+
botToken: string | undefined,
|
|
171
|
+
): Record<string, TelegramRuntimeEventDetailValue> | undefined {
|
|
172
|
+
if (!details) return undefined;
|
|
173
|
+
const normalized: Record<string, TelegramRuntimeEventDetailValue> = {};
|
|
174
|
+
for (const [key, value] of Object.entries(details)) {
|
|
175
|
+
if (value === undefined) continue;
|
|
176
|
+
if (typeof value === "string") {
|
|
177
|
+
normalized[key] = redactTelegramRuntimeMessage(value, botToken);
|
|
178
|
+
continue;
|
|
179
|
+
}
|
|
180
|
+
if (typeof value === "number" || typeof value === "boolean") {
|
|
181
|
+
normalized[key] = value;
|
|
182
|
+
continue;
|
|
183
|
+
}
|
|
184
|
+
if (value === null) {
|
|
185
|
+
normalized[key] = null;
|
|
186
|
+
continue;
|
|
187
|
+
}
|
|
188
|
+
normalized[key] = redactTelegramRuntimeMessage(String(value), botToken);
|
|
189
|
+
}
|
|
190
|
+
return Object.keys(normalized).length > 0 ? normalized : undefined;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
function getTelegramRuntimeEventMessage(
|
|
194
|
+
input: TelegramRuntimeEventInput,
|
|
195
|
+
): string {
|
|
196
|
+
if (input.message !== undefined) return input.message;
|
|
197
|
+
if (input.error instanceof Error) return input.error.message;
|
|
198
|
+
return String(input.error);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
export function recordStructuredTelegramRuntimeEvent(
|
|
202
|
+
events: TelegramRuntimeEvent[],
|
|
203
|
+
input: TelegramRuntimeEventInput,
|
|
204
|
+
options: { botToken?: string; maxEvents: number; now?: number },
|
|
205
|
+
): void {
|
|
206
|
+
const details = normalizeTelegramRuntimeEventDetails(
|
|
207
|
+
input.details,
|
|
208
|
+
options.botToken,
|
|
209
|
+
);
|
|
210
|
+
events.push({
|
|
211
|
+
at: options.now ?? Date.now(),
|
|
212
|
+
category: input.category,
|
|
213
|
+
message: redactTelegramRuntimeMessage(
|
|
214
|
+
getTelegramRuntimeEventMessage(input),
|
|
215
|
+
options.botToken,
|
|
216
|
+
),
|
|
217
|
+
...(details ? { details } : {}),
|
|
218
|
+
});
|
|
219
|
+
while (events.length > options.maxEvents) {
|
|
220
|
+
events.shift();
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
export function recordTelegramRuntimeEvent(
|
|
225
|
+
events: TelegramRuntimeEvent[],
|
|
226
|
+
category: string,
|
|
227
|
+
error: unknown,
|
|
228
|
+
options: { botToken?: string; maxEvents: number; now?: number },
|
|
229
|
+
): void {
|
|
230
|
+
recordStructuredTelegramRuntimeEvent(events, { category, error }, options);
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
export function createTelegramRuntimeEventRecorder(
|
|
234
|
+
options: TelegramRuntimeEventRecorderOptions,
|
|
235
|
+
): TelegramRuntimeEventRecorder {
|
|
236
|
+
const events: TelegramRuntimeEvent[] = [];
|
|
237
|
+
return {
|
|
238
|
+
record: (category, error, details) => {
|
|
239
|
+
recordStructuredTelegramRuntimeEvent(
|
|
240
|
+
events,
|
|
241
|
+
{ category, error, details },
|
|
242
|
+
{
|
|
243
|
+
botToken: options.getBotToken(),
|
|
244
|
+
maxEvents: options.maxEvents ?? MAX_RECENT_TELEGRAM_RUNTIME_EVENTS,
|
|
245
|
+
now: options.now?.(),
|
|
246
|
+
},
|
|
247
|
+
);
|
|
248
|
+
},
|
|
249
|
+
getEvents: () => events,
|
|
250
|
+
clear: () => {
|
|
251
|
+
events.length = 0;
|
|
252
|
+
},
|
|
253
|
+
};
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
function formatTelegramRuntimeEventCategory(
|
|
257
|
+
event: TelegramRuntimeEvent,
|
|
258
|
+
): string {
|
|
259
|
+
const method = event.details?.method;
|
|
260
|
+
return typeof method === "string"
|
|
261
|
+
? `${event.category}:${method}`
|
|
262
|
+
: event.category;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
function formatTelegramRuntimeEventDetails(
|
|
266
|
+
event: TelegramRuntimeEvent,
|
|
267
|
+
): string {
|
|
268
|
+
if (!event.details) return "";
|
|
269
|
+
const details = Object.entries(event.details)
|
|
270
|
+
.filter(([key]) => key !== "method")
|
|
271
|
+
.map(([key, value]) => `${key}=${JSON.stringify(value)}`);
|
|
272
|
+
return details.length > 0 ? ` (${details.join(", ")})` : "";
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
function formatTelegramRuntimeEventSummary(
|
|
276
|
+
event: TelegramRuntimeEvent,
|
|
277
|
+
): string {
|
|
278
|
+
return `${formatTelegramRuntimeEventCategory(event)}: ${event.message}${formatTelegramRuntimeEventDetails(event)}`;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
function formatTelegramRuntimeEvent(event: TelegramRuntimeEvent): string {
|
|
282
|
+
return `${new Date(event.at).toISOString()} ${formatTelegramRuntimeEventSummary(event)}`;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
export function buildTelegramRuntimeEventLines(
|
|
286
|
+
events: TelegramRuntimeEvent[],
|
|
287
|
+
): string[] {
|
|
288
|
+
if (events.length === 0) return ["recent runtime events: none"];
|
|
289
|
+
return [
|
|
290
|
+
"recent runtime events:",
|
|
291
|
+
...events
|
|
292
|
+
.slice()
|
|
293
|
+
.reverse()
|
|
294
|
+
.map((event) => `- ${formatTelegramRuntimeEvent(event)}`),
|
|
295
|
+
];
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
export function createTelegramStatusHtmlBuilder<TContext>(deps: {
|
|
299
|
+
getActiveModel: (ctx: TContext) => TelegramStatusActiveModel | undefined;
|
|
300
|
+
}): (ctx: TContext & TelegramStatusContext) => string {
|
|
301
|
+
return (ctx) => buildStatusHtml(ctx, deps.getActiveModel(ctx));
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
export function createTelegramStatusRuntime<
|
|
305
|
+
TContext extends TelegramStatusRuntimeContext,
|
|
306
|
+
>(deps: TelegramStatusRuntimeDeps<TContext>): TelegramStatusRuntime<TContext> {
|
|
307
|
+
const statusKey = deps.statusKey ?? "telegram";
|
|
308
|
+
return {
|
|
309
|
+
updateStatus: (ctx, error) => {
|
|
310
|
+
ctx.ui.setStatus(
|
|
311
|
+
statusKey,
|
|
312
|
+
buildTelegramStatusBarText(
|
|
313
|
+
ctx.ui.theme,
|
|
314
|
+
deps.getStatusBarState(ctx, error),
|
|
315
|
+
),
|
|
316
|
+
);
|
|
317
|
+
},
|
|
318
|
+
getStatusLines: () =>
|
|
319
|
+
buildTelegramBridgeStatusLines(deps.getBridgeStatusLineState()),
|
|
320
|
+
};
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
export function createTelegramBridgeStatusRuntime<
|
|
324
|
+
TContext extends TelegramStatusRuntimeContext,
|
|
325
|
+
TQueueItem extends { queueLane: TelegramStatusQueueLane },
|
|
326
|
+
>(
|
|
327
|
+
deps: TelegramBridgeStatusRuntimeDeps<TQueueItem>,
|
|
328
|
+
): TelegramStatusRuntime<TContext> {
|
|
329
|
+
return createTelegramStatusRuntime({
|
|
330
|
+
statusKey: deps.statusKey,
|
|
331
|
+
getStatusBarState: (_ctx, error) => {
|
|
332
|
+
const config = deps.getConfig();
|
|
333
|
+
const queuedItems = deps.getQueuedItems();
|
|
334
|
+
const compactionInProgress = deps.isCompactionInProgress();
|
|
335
|
+
return {
|
|
336
|
+
hasBotToken: !!config.botToken,
|
|
337
|
+
pollingActive: deps.isPollingActive(),
|
|
338
|
+
paired: !!config.allowedUserId,
|
|
339
|
+
compactionInProgress,
|
|
340
|
+
processing:
|
|
341
|
+
deps.hasActiveTurn() ||
|
|
342
|
+
deps.hasDispatchPending() ||
|
|
343
|
+
queuedItems.length > 0,
|
|
344
|
+
queuedStatus: deps.formatQueuedStatus(queuedItems),
|
|
345
|
+
error,
|
|
346
|
+
};
|
|
347
|
+
},
|
|
348
|
+
getBridgeStatusLineState: () => {
|
|
349
|
+
const config = deps.getConfig();
|
|
350
|
+
return {
|
|
351
|
+
botUsername: config.botUsername,
|
|
352
|
+
allowedUserId: config.allowedUserId,
|
|
353
|
+
pollingActive: deps.isPollingActive(),
|
|
354
|
+
lastUpdateId: config.lastUpdateId,
|
|
355
|
+
activeSourceMessageIds: deps.getActiveSourceMessageIds(),
|
|
356
|
+
pendingDispatch: deps.hasDispatchPending(),
|
|
357
|
+
compactionInProgress: deps.isCompactionInProgress(),
|
|
358
|
+
activeToolExecutions: deps.getActiveToolExecutions(),
|
|
359
|
+
pendingModelSwitch: deps.hasPendingModelSwitch(),
|
|
360
|
+
queuedItems: deps.getQueuedItems(),
|
|
361
|
+
recentRuntimeEvents: deps.getRecentRuntimeEvents(),
|
|
362
|
+
};
|
|
363
|
+
},
|
|
364
|
+
});
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
export function buildTelegramStatusBarText(
|
|
368
|
+
theme: TelegramStatusBarTheme,
|
|
369
|
+
state: TelegramStatusBarState,
|
|
370
|
+
): string {
|
|
371
|
+
const label = theme.fg("accent", "telegram");
|
|
372
|
+
if (state.error) {
|
|
373
|
+
return `${label} ${theme.fg("error", "error")} ${theme.fg("muted", state.error)}`;
|
|
374
|
+
}
|
|
375
|
+
if (!state.hasBotToken)
|
|
376
|
+
return `${label} ${theme.fg("muted", "not configured")}`;
|
|
377
|
+
if (!state.pollingActive)
|
|
378
|
+
return `${label} ${theme.fg("muted", "disconnected")}`;
|
|
379
|
+
if (!state.paired)
|
|
380
|
+
return `${label} ${theme.fg("warning", "awaiting pairing")}`;
|
|
381
|
+
const queued = theme.fg("muted", state.queuedStatus);
|
|
382
|
+
if (state.compactionInProgress) {
|
|
383
|
+
return `${label} ${theme.fg("accent", "compacting")}${queued}`;
|
|
384
|
+
}
|
|
385
|
+
if (state.processing) {
|
|
386
|
+
return `${label} ${theme.fg("accent", "processing")}${queued}`;
|
|
387
|
+
}
|
|
388
|
+
return `${label} ${theme.fg("success", "connected")}`;
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
export function buildTelegramBridgeStatusLines(
|
|
392
|
+
state: TelegramBridgeStatusLineState,
|
|
393
|
+
): string[] {
|
|
394
|
+
const controlQueueCount = state.queuedItems.filter(
|
|
395
|
+
(item) => item.queueLane === "control",
|
|
396
|
+
).length;
|
|
397
|
+
const priorityQueueCount = state.queuedItems.filter(
|
|
398
|
+
(item) => item.queueLane === "priority",
|
|
399
|
+
).length;
|
|
400
|
+
const defaultQueueCount = state.queuedItems.filter(
|
|
401
|
+
(item) => item.queueLane === "default",
|
|
402
|
+
).length;
|
|
403
|
+
return [
|
|
404
|
+
"connection:",
|
|
405
|
+
`- bot: ${state.botUsername ? `@${state.botUsername}` : "not configured"}`,
|
|
406
|
+
`- allowed user: ${state.allowedUserId ?? "not paired"}`,
|
|
407
|
+
"",
|
|
408
|
+
"polling:",
|
|
409
|
+
`- state: ${state.pollingActive ? "running" : "stopped"}`,
|
|
410
|
+
`- last update id: ${state.lastUpdateId ?? "none"}`,
|
|
411
|
+
"",
|
|
412
|
+
"execution:",
|
|
413
|
+
`- active turn: ${state.activeSourceMessageIds?.join(",") || "no"}`,
|
|
414
|
+
`- pending dispatch: ${state.pendingDispatch ? "yes" : "no"}`,
|
|
415
|
+
`- compaction: ${state.compactionInProgress ? "running" : "idle"}`,
|
|
416
|
+
`- active tools: ${state.activeToolExecutions}`,
|
|
417
|
+
`- pending model switch: ${state.pendingModelSwitch ? "yes" : "no"}`,
|
|
418
|
+
"",
|
|
419
|
+
"queue:",
|
|
420
|
+
`- queued turns: ${state.queuedItems.length}`,
|
|
421
|
+
`- lanes: control=${controlQueueCount}, priority=${priorityQueueCount}, default=${defaultQueueCount}`,
|
|
422
|
+
"",
|
|
423
|
+
...buildTelegramRuntimeEventLines(state.recentRuntimeEvents),
|
|
424
|
+
];
|
|
425
|
+
}
|
|
426
|
+
|
|
17
427
|
function escapeHtml(text: string): string {
|
|
18
428
|
return text
|
|
19
429
|
.replace(/&/g, "&")
|
|
@@ -29,7 +439,7 @@ function formatTokens(count: number): string {
|
|
|
29
439
|
return `${Math.round(count / 1000000)}M`;
|
|
30
440
|
}
|
|
31
441
|
|
|
32
|
-
|
|
442
|
+
function collectUsageStats(ctx: TelegramStatusContext): TelegramUsageStats {
|
|
33
443
|
const stats: TelegramUsageStats = {
|
|
34
444
|
totalInput: 0,
|
|
35
445
|
totalOutput: 0,
|
|
@@ -38,14 +448,19 @@ export function collectUsageStats(ctx: ExtensionContext): TelegramUsageStats {
|
|
|
38
448
|
totalCost: 0,
|
|
39
449
|
};
|
|
40
450
|
for (const entry of ctx.sessionManager.getEntries()) {
|
|
41
|
-
|
|
451
|
+
const usage = entry.message?.usage;
|
|
452
|
+
if (
|
|
453
|
+
entry.type !== "message" ||
|
|
454
|
+
entry.message?.role !== "assistant" ||
|
|
455
|
+
!usage
|
|
456
|
+
) {
|
|
42
457
|
continue;
|
|
43
458
|
}
|
|
44
|
-
stats.totalInput +=
|
|
45
|
-
stats.totalOutput +=
|
|
46
|
-
stats.totalCacheRead +=
|
|
47
|
-
stats.totalCacheWrite +=
|
|
48
|
-
stats.totalCost +=
|
|
459
|
+
stats.totalInput += usage.input;
|
|
460
|
+
stats.totalOutput += usage.output;
|
|
461
|
+
stats.totalCacheRead += usage.cacheRead;
|
|
462
|
+
stats.totalCacheWrite += usage.cacheWrite;
|
|
463
|
+
stats.totalCost += usage.cost.total;
|
|
49
464
|
}
|
|
50
465
|
return stats;
|
|
51
466
|
}
|
|
@@ -74,8 +489,8 @@ function buildCostSummary(
|
|
|
74
489
|
}
|
|
75
490
|
|
|
76
491
|
function buildContextSummary(
|
|
77
|
-
ctx:
|
|
78
|
-
activeModel:
|
|
492
|
+
ctx: TelegramStatusContext,
|
|
493
|
+
activeModel: TelegramStatusActiveModel | undefined,
|
|
79
494
|
): string {
|
|
80
495
|
const usage = ctx.getContextUsage();
|
|
81
496
|
if (!usage) return "unknown";
|
|
@@ -85,8 +500,8 @@ function buildContextSummary(
|
|
|
85
500
|
}
|
|
86
501
|
|
|
87
502
|
export function buildStatusHtml(
|
|
88
|
-
ctx:
|
|
89
|
-
activeModel:
|
|
503
|
+
ctx: TelegramStatusContext,
|
|
504
|
+
activeModel: TelegramStatusActiveModel | undefined,
|
|
90
505
|
): string {
|
|
91
506
|
const stats = collectUsageStats(ctx);
|
|
92
507
|
const usesSubscription = activeModel
|