@bubblebrain-ai/bubble 0.0.13 → 0.0.15
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/agent/execution-governor.js +1 -1
- package/dist/agent/tool-intent.js +1 -0
- package/dist/agent.d.ts +2 -0
- package/dist/agent.js +589 -316
- package/dist/approval/controller.d.ts +1 -0
- package/dist/approval/controller.js +20 -3
- package/dist/approval/tool-helper.js +2 -0
- package/dist/approval/types.d.ts +14 -1
- package/dist/cli.d.ts +3 -1
- package/dist/cli.js +12 -0
- package/dist/context/compact.js +9 -3
- package/dist/context/projector.js +27 -12
- package/dist/debug-trace.d.ts +27 -0
- package/dist/debug-trace.js +385 -0
- package/dist/feishu/agent-host/approval-card.js +9 -0
- package/dist/feishu/serve.js +7 -1
- package/dist/main.js +41 -0
- package/dist/model-catalog.js +1 -0
- package/dist/orchestrator/default-hooks.js +19 -8
- package/dist/orchestrator/hooks.d.ts +1 -0
- package/dist/prompt/environment.js +2 -0
- package/dist/prompt/reminders.d.ts +5 -6
- package/dist/prompt/reminders.js +8 -9
- package/dist/prompt/runtime.js +2 -2
- package/dist/provider-openai-codex.d.ts +7 -0
- package/dist/provider-openai-codex.js +265 -124
- package/dist/provider-registry.d.ts +2 -0
- package/dist/provider-registry.js +58 -9
- package/dist/provider.d.ts +3 -0
- package/dist/provider.js +5 -1
- package/dist/session-log.js +13 -1
- package/dist/slash-commands/commands.js +12 -0
- package/dist/slash-commands/types.d.ts +2 -0
- package/dist/stats/usage.d.ts +52 -0
- package/dist/stats/usage.js +414 -0
- package/dist/tools/apply-patch.d.ts +9 -0
- package/dist/tools/apply-patch.js +330 -0
- package/dist/tools/bash.js +205 -44
- package/dist/tools/edit-apply.d.ts +5 -2
- package/dist/tools/edit-apply.js +221 -31
- package/dist/tools/edit.js +12 -3
- package/dist/tools/file-mutation-queue.d.ts +1 -0
- package/dist/tools/file-mutation-queue.js +12 -1
- package/dist/tools/index.d.ts +2 -0
- package/dist/tools/index.js +7 -1
- package/dist/tools/patch-apply.d.ts +41 -0
- package/dist/tools/patch-apply.js +312 -0
- package/dist/tools/server-manager.d.ts +36 -0
- package/dist/tools/server-manager.js +234 -0
- package/dist/tools/server.d.ts +6 -0
- package/dist/tools/server.js +245 -0
- package/dist/tools/write.d.ts +3 -6
- package/dist/tools/write.js +26 -46
- package/dist/tui/display-history.d.ts +1 -0
- package/dist/tui/display-history.js +5 -4
- package/dist/tui/edit-diff.js +6 -1
- package/dist/tui/model-picker-data.d.ts +10 -0
- package/dist/tui/model-picker-data.js +32 -0
- package/dist/tui/run.d.ts +2 -0
- package/dist/tui/run.js +717 -122
- package/dist/tui/tool-renderers/fallback.js +1 -1
- package/dist/tui/tool-renderers/write-preview.js +2 -0
- package/dist/tui/trace-groups.js +10 -3
- package/dist/tui-ink/app.js +1 -4
- package/dist/tui-ink/approval/approval-dialog.js +7 -1
- package/dist/tui-ink/display-history.d.ts +1 -0
- package/dist/tui-ink/display-history.js +5 -4
- package/dist/tui-ink/message-list.js +14 -8
- package/dist/tui-ink/trace-groups.js +1 -1
- package/dist/tui-opentui/app.js +2 -0
- package/dist/tui-opentui/approval/approval-dialog.js +7 -1
- package/dist/tui-opentui/display-history.d.ts +1 -0
- package/dist/tui-opentui/display-history.js +5 -4
- package/dist/tui-opentui/edit-diff.js +6 -1
- package/dist/tui-opentui/message-list.js +6 -3
- package/dist/tui-opentui/trace-groups.js +10 -3
- package/dist/types.d.ts +12 -2
- package/dist/update/index.d.ts +46 -0
- package/dist/update/index.js +240 -0
- package/package.json +1 -1
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
export type StatsRange = "7d" | "30d";
|
|
2
|
+
export interface DailyUsage {
|
|
3
|
+
date: string;
|
|
4
|
+
active: boolean;
|
|
5
|
+
tokens: number;
|
|
6
|
+
hasPreciseUsage: boolean;
|
|
7
|
+
}
|
|
8
|
+
export interface HeatmapColumn {
|
|
9
|
+
label: string;
|
|
10
|
+
cells: Array<DailyUsage | undefined>;
|
|
11
|
+
}
|
|
12
|
+
export interface ModelUsageStats {
|
|
13
|
+
model: string;
|
|
14
|
+
displayName: string;
|
|
15
|
+
providerId?: string;
|
|
16
|
+
modelId?: string;
|
|
17
|
+
turns: number;
|
|
18
|
+
promptTokens: number;
|
|
19
|
+
completionTokens: number;
|
|
20
|
+
promptCacheHitTokens: number;
|
|
21
|
+
promptCacheMissTokens: number;
|
|
22
|
+
reasoningTokens: number;
|
|
23
|
+
totalTokens: number;
|
|
24
|
+
cost?: number;
|
|
25
|
+
}
|
|
26
|
+
export interface UsageStats {
|
|
27
|
+
range: StatsRange;
|
|
28
|
+
days: number;
|
|
29
|
+
startDate: string;
|
|
30
|
+
endDate: string;
|
|
31
|
+
daily: DailyUsage[];
|
|
32
|
+
heatmap: HeatmapColumn[];
|
|
33
|
+
models: ModelUsageStats[];
|
|
34
|
+
totalTokens: number;
|
|
35
|
+
trackedCost?: number;
|
|
36
|
+
activeDays: number;
|
|
37
|
+
sessionsScanned: number;
|
|
38
|
+
sessionsWithoutTokenData: number;
|
|
39
|
+
}
|
|
40
|
+
export interface UsageStatsBundle {
|
|
41
|
+
generatedAt: Date;
|
|
42
|
+
ranges: Record<StatsRange, UsageStats>;
|
|
43
|
+
}
|
|
44
|
+
export declare function collectUsageStatsBundle(options?: {
|
|
45
|
+
now?: Date;
|
|
46
|
+
sessionsRoot?: string;
|
|
47
|
+
}): UsageStatsBundle;
|
|
48
|
+
export declare function formatStatsText(bundle: UsageStatsBundle, range?: StatsRange, width?: number): string;
|
|
49
|
+
export declare function formatStatsPanelBody(stats: UsageStats, width?: number): string;
|
|
50
|
+
export declare function rangeLabel(range: StatsRange): string;
|
|
51
|
+
export declare function formatCompactNumber(value: number): string;
|
|
52
|
+
export declare function formatCurrency(value: number): string;
|
|
@@ -0,0 +1,414 @@
|
|
|
1
|
+
import { existsSync, readdirSync, readFileSync, statSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { getBubbleHome } from "../bubble-home.js";
|
|
4
|
+
import { calculateUsageCost } from "../model-pricing.js";
|
|
5
|
+
import { decodeModel } from "../provider-registry.js";
|
|
6
|
+
const RANGES = [
|
|
7
|
+
{ range: "7d", days: 7 },
|
|
8
|
+
{ range: "30d", days: 30 },
|
|
9
|
+
];
|
|
10
|
+
const WEEKDAY_LABELS = ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"];
|
|
11
|
+
const EMPTY_CELL = " ";
|
|
12
|
+
const HEAT_LEVELS = [".", "o", "O", "@"];
|
|
13
|
+
const MAX_MODEL_ROWS = 5;
|
|
14
|
+
export function collectUsageStatsBundle(options = {}) {
|
|
15
|
+
const now = options.now ?? new Date();
|
|
16
|
+
const sessionsRoot = options.sessionsRoot ?? join(getBubbleHome(), "sessions");
|
|
17
|
+
const accumulators = Object.fromEntries(RANGES.map(({ range, days }) => [range, createAccumulator(range, days, now)]));
|
|
18
|
+
for (const file of listSessionFiles(sessionsRoot)) {
|
|
19
|
+
processSessionFile(file, accumulators);
|
|
20
|
+
}
|
|
21
|
+
return {
|
|
22
|
+
generatedAt: now,
|
|
23
|
+
ranges: {
|
|
24
|
+
"7d": finalizeAccumulator(accumulators["7d"]),
|
|
25
|
+
"30d": finalizeAccumulator(accumulators["30d"]),
|
|
26
|
+
},
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
export function formatStatsText(bundle, range = "30d", width = 78) {
|
|
30
|
+
const stats = bundle.ranges[range];
|
|
31
|
+
return [
|
|
32
|
+
`Bubble Stats · ${rangeLabel(range)}`,
|
|
33
|
+
"",
|
|
34
|
+
formatStatsPanelBody(stats, width),
|
|
35
|
+
].join("\n");
|
|
36
|
+
}
|
|
37
|
+
export function formatStatsPanelBody(stats, width = 72) {
|
|
38
|
+
const bodyWidth = Math.max(48, width);
|
|
39
|
+
const lines = [];
|
|
40
|
+
lines.push("Activity");
|
|
41
|
+
lines.push(...formatHeatmapLines(stats));
|
|
42
|
+
lines.push("");
|
|
43
|
+
lines.push("Model usage");
|
|
44
|
+
lines.push(...formatModelUsageLines(stats, bodyWidth));
|
|
45
|
+
lines.push("");
|
|
46
|
+
lines.push("Summary");
|
|
47
|
+
lines.push(...formatSummaryLines(stats, bodyWidth));
|
|
48
|
+
return lines.join("\n");
|
|
49
|
+
}
|
|
50
|
+
export function rangeLabel(range) {
|
|
51
|
+
return range === "7d" ? "Last 7 days" : "Last 30 days";
|
|
52
|
+
}
|
|
53
|
+
export function formatCompactNumber(value) {
|
|
54
|
+
const abs = Math.abs(value);
|
|
55
|
+
if (abs >= 1_000_000_000)
|
|
56
|
+
return `${trimFixed(value / 1_000_000_000, 1)}B`;
|
|
57
|
+
if (abs >= 1_000_000)
|
|
58
|
+
return `${trimFixed(value / 1_000_000, 2)}M`;
|
|
59
|
+
if (abs >= 1_000)
|
|
60
|
+
return `${trimFixed(value / 1_000, 1)}k`;
|
|
61
|
+
return String(Math.round(value));
|
|
62
|
+
}
|
|
63
|
+
export function formatCurrency(value) {
|
|
64
|
+
if (value >= 1)
|
|
65
|
+
return `$${value.toFixed(2)}`;
|
|
66
|
+
if (value >= 0.01)
|
|
67
|
+
return `$${value.toFixed(3)}`;
|
|
68
|
+
return `$${value.toFixed(4)}`;
|
|
69
|
+
}
|
|
70
|
+
function createAccumulator(range, days, now) {
|
|
71
|
+
const end = startOfLocalDay(now);
|
|
72
|
+
const start = addDays(end, -(days - 1));
|
|
73
|
+
const daily = new Map();
|
|
74
|
+
for (let i = 0; i < days; i += 1) {
|
|
75
|
+
const date = localDateKey(addDays(start, i));
|
|
76
|
+
daily.set(date, {
|
|
77
|
+
date,
|
|
78
|
+
active: false,
|
|
79
|
+
tokens: 0,
|
|
80
|
+
hasPreciseUsage: false,
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
return {
|
|
84
|
+
range,
|
|
85
|
+
days,
|
|
86
|
+
start,
|
|
87
|
+
end,
|
|
88
|
+
daily,
|
|
89
|
+
modelUsage: new Map(),
|
|
90
|
+
sessionsScanned: 0,
|
|
91
|
+
sessionsWithoutTokenData: 0,
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
function processSessionFile(file, accumulators) {
|
|
95
|
+
let entries;
|
|
96
|
+
try {
|
|
97
|
+
const content = readFileSync(file, "utf-8");
|
|
98
|
+
entries = content
|
|
99
|
+
.split("\n")
|
|
100
|
+
.filter((line) => line.trim())
|
|
101
|
+
.map((line) => JSON.parse(line));
|
|
102
|
+
}
|
|
103
|
+
catch {
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
const sessionFlags = Object.fromEntries(RANGES.map(({ range }) => [range, { active: false, hasUsage: false }]));
|
|
107
|
+
let currentModel;
|
|
108
|
+
for (const entry of entries) {
|
|
109
|
+
if (entry.type === "metadata") {
|
|
110
|
+
currentModel = entry.metadata?.model ?? currentModel;
|
|
111
|
+
continue;
|
|
112
|
+
}
|
|
113
|
+
if (entry.type === "marker" && entry.kind === "model_switch") {
|
|
114
|
+
currentModel = typeof entry.value === "string" ? entry.value : currentModel;
|
|
115
|
+
}
|
|
116
|
+
const timestamp = normalizeTimestamp(entry.timestamp);
|
|
117
|
+
if (!timestamp)
|
|
118
|
+
continue;
|
|
119
|
+
const message = assistantPayload(entry);
|
|
120
|
+
const model = resolveEntryModel(entry, message, currentModel);
|
|
121
|
+
const usage = message ? normalizeUsage(message.usage) : undefined;
|
|
122
|
+
for (const accumulator of Object.values(accumulators)) {
|
|
123
|
+
if (!isWithinRange(timestamp, accumulator))
|
|
124
|
+
continue;
|
|
125
|
+
const flags = sessionFlags[accumulator.range];
|
|
126
|
+
flags.active = true;
|
|
127
|
+
markActiveDay(accumulator, timestamp, usage);
|
|
128
|
+
if (message && usage && model) {
|
|
129
|
+
flags.hasUsage = true;
|
|
130
|
+
addModelUsage(accumulator, model, message, usage);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
for (const accumulator of Object.values(accumulators)) {
|
|
135
|
+
const flags = sessionFlags[accumulator.range];
|
|
136
|
+
if (!flags.active)
|
|
137
|
+
continue;
|
|
138
|
+
accumulator.sessionsScanned += 1;
|
|
139
|
+
if (!flags.hasUsage)
|
|
140
|
+
accumulator.sessionsWithoutTokenData += 1;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
function finalizeAccumulator(accumulator) {
|
|
144
|
+
const daily = [...accumulator.daily.values()];
|
|
145
|
+
const models = [...accumulator.modelUsage.values()]
|
|
146
|
+
.filter((model) => model.totalTokens > 0)
|
|
147
|
+
.sort((a, b) => b.totalTokens - a.totalTokens);
|
|
148
|
+
const totalTokens = models.reduce((sum, model) => sum + model.totalTokens, 0);
|
|
149
|
+
const trackedCost = models.reduce((sum, model) => sum + (model.cost ?? 0), 0);
|
|
150
|
+
return {
|
|
151
|
+
range: accumulator.range,
|
|
152
|
+
days: accumulator.days,
|
|
153
|
+
startDate: localDateKey(accumulator.start),
|
|
154
|
+
endDate: localDateKey(accumulator.end),
|
|
155
|
+
daily,
|
|
156
|
+
heatmap: buildHeatmap(daily),
|
|
157
|
+
models,
|
|
158
|
+
totalTokens,
|
|
159
|
+
trackedCost: trackedCost > 0 ? trackedCost : undefined,
|
|
160
|
+
activeDays: daily.filter((day) => day.active).length,
|
|
161
|
+
sessionsScanned: accumulator.sessionsScanned,
|
|
162
|
+
sessionsWithoutTokenData: accumulator.sessionsWithoutTokenData,
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
function addModelUsage(accumulator, model, message, usage) {
|
|
166
|
+
const decoded = decodeModel(model);
|
|
167
|
+
const providerId = typeof message.providerId === "string" ? message.providerId : decoded.providerId;
|
|
168
|
+
const modelId = typeof message.modelId === "string" ? message.modelId : decoded.modelId;
|
|
169
|
+
const key = providerId ? `${providerId}:${modelId}` : model;
|
|
170
|
+
const existing = accumulator.modelUsage.get(key) ?? {
|
|
171
|
+
model: key,
|
|
172
|
+
displayName: key,
|
|
173
|
+
providerId,
|
|
174
|
+
modelId,
|
|
175
|
+
turns: 0,
|
|
176
|
+
promptTokens: 0,
|
|
177
|
+
completionTokens: 0,
|
|
178
|
+
promptCacheHitTokens: 0,
|
|
179
|
+
promptCacheMissTokens: 0,
|
|
180
|
+
reasoningTokens: 0,
|
|
181
|
+
totalTokens: 0,
|
|
182
|
+
};
|
|
183
|
+
existing.turns += 1;
|
|
184
|
+
existing.promptTokens += usage.promptTokens;
|
|
185
|
+
existing.completionTokens += usage.completionTokens;
|
|
186
|
+
existing.promptCacheHitTokens += usage.promptCacheHitTokens ?? 0;
|
|
187
|
+
existing.promptCacheMissTokens += usage.promptCacheMissTokens ?? 0;
|
|
188
|
+
existing.reasoningTokens += usage.reasoningTokens ?? 0;
|
|
189
|
+
existing.totalTokens += tokenTotal(usage);
|
|
190
|
+
if (providerId && modelId) {
|
|
191
|
+
const cost = calculateUsageCost(providerId, modelId, usage);
|
|
192
|
+
if (cost)
|
|
193
|
+
existing.cost = (existing.cost ?? 0) + cost.cost;
|
|
194
|
+
}
|
|
195
|
+
accumulator.modelUsage.set(key, existing);
|
|
196
|
+
}
|
|
197
|
+
function markActiveDay(accumulator, timestamp, usage) {
|
|
198
|
+
const key = localDateKey(timestamp);
|
|
199
|
+
const day = accumulator.daily.get(key);
|
|
200
|
+
if (!day)
|
|
201
|
+
return;
|
|
202
|
+
day.active = true;
|
|
203
|
+
if (usage) {
|
|
204
|
+
day.hasPreciseUsage = true;
|
|
205
|
+
day.tokens += tokenTotal(usage);
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
function buildHeatmap(daily) {
|
|
209
|
+
const byWeek = new Map();
|
|
210
|
+
for (const day of daily) {
|
|
211
|
+
const date = parseLocalDate(day.date);
|
|
212
|
+
const weekStart = mondayOfWeek(date);
|
|
213
|
+
const weekKey = localDateKey(weekStart);
|
|
214
|
+
if (!byWeek.has(weekKey)) {
|
|
215
|
+
byWeek.set(weekKey, {
|
|
216
|
+
label: formatMonthDay(weekStart),
|
|
217
|
+
cells: Array.from({ length: 7 }, () => undefined),
|
|
218
|
+
});
|
|
219
|
+
}
|
|
220
|
+
byWeek.get(weekKey).cells[weekdayIndex(date)] = day;
|
|
221
|
+
}
|
|
222
|
+
return [...byWeek.values()];
|
|
223
|
+
}
|
|
224
|
+
function formatHeatmapLines(stats) {
|
|
225
|
+
if (stats.activeDays === 0)
|
|
226
|
+
return [" No activity in this range."];
|
|
227
|
+
const columns = stats.heatmap;
|
|
228
|
+
const maxTokens = Math.max(0, ...stats.daily.map((day) => day.tokens));
|
|
229
|
+
const lines = [
|
|
230
|
+
` ${columns.map((column) => column.label.padStart(5, " ")).join(" ")}`,
|
|
231
|
+
];
|
|
232
|
+
for (let row = 0; row < 7; row += 1) {
|
|
233
|
+
const cells = columns.map((column) => {
|
|
234
|
+
const day = column.cells[row];
|
|
235
|
+
return heatmapCell(day, maxTokens).padStart(5, " ");
|
|
236
|
+
});
|
|
237
|
+
lines.push(`${WEEKDAY_LABELS[row]} ${cells.join(" ")}`);
|
|
238
|
+
}
|
|
239
|
+
lines.push(` Less ${HEAT_LEVELS.join(" ")} More`);
|
|
240
|
+
return lines;
|
|
241
|
+
}
|
|
242
|
+
function formatModelUsageLines(stats, width) {
|
|
243
|
+
if (stats.models.length === 0) {
|
|
244
|
+
return [" No precise token usage recorded yet."];
|
|
245
|
+
}
|
|
246
|
+
const showCost = stats.models.some((model) => model.cost !== undefined);
|
|
247
|
+
const overhead = showCost ? 29 : 21;
|
|
248
|
+
const minBarWidth = 6;
|
|
249
|
+
const labelWidth = Math.max(12, Math.min(28, Math.floor((width - overhead - minBarWidth) * 0.65)));
|
|
250
|
+
const barWidth = Math.max(minBarWidth, Math.min(20, width - labelWidth - overhead));
|
|
251
|
+
const rows = stats.models.slice(0, MAX_MODEL_ROWS).map((model) => {
|
|
252
|
+
const percent = stats.totalTokens > 0 ? model.totalTokens / stats.totalTokens : 0;
|
|
253
|
+
const bar = usageBar(percent, barWidth);
|
|
254
|
+
const percentText = `${Math.round(percent * 100)}%`.padStart(4, " ");
|
|
255
|
+
const tokenText = formatCompactNumber(model.totalTokens).padStart(6, " ");
|
|
256
|
+
const turnsText = `${model.turns}t`.padStart(4, " ");
|
|
257
|
+
const costText = showCost ? ` ${(model.cost !== undefined ? formatCurrency(model.cost) : "").padStart(7, " ")}` : "";
|
|
258
|
+
return ` ${truncate(model.displayName, labelWidth).padEnd(labelWidth, " ")} ${bar} ${percentText} ${tokenText} ${turnsText}${costText}`.trimEnd();
|
|
259
|
+
});
|
|
260
|
+
if (stats.models.length > MAX_MODEL_ROWS) {
|
|
261
|
+
rows.push(` ...and ${stats.models.length - MAX_MODEL_ROWS} more model${stats.models.length - MAX_MODEL_ROWS === 1 ? "" : "s"}`);
|
|
262
|
+
}
|
|
263
|
+
return rows;
|
|
264
|
+
}
|
|
265
|
+
function formatSummaryLines(stats, width) {
|
|
266
|
+
const favorite = stats.models[0]?.displayName;
|
|
267
|
+
const lines = [
|
|
268
|
+
` Active days ${stats.activeDays}/${stats.days} · Total ${formatCompactNumber(stats.totalTokens)} tokens`,
|
|
269
|
+
];
|
|
270
|
+
if (favorite) {
|
|
271
|
+
lines.push(` Favorite model ${truncate(favorite, Math.max(12, width - 17))}`);
|
|
272
|
+
}
|
|
273
|
+
if (stats.trackedCost !== undefined)
|
|
274
|
+
lines.push(` Tracked cost ${formatCurrency(stats.trackedCost)}`);
|
|
275
|
+
lines.push(` Sessions scanned ${stats.sessionsScanned}`);
|
|
276
|
+
if (stats.sessionsWithoutTokenData > 0) {
|
|
277
|
+
lines.push(` Sessions without token data ${stats.sessionsWithoutTokenData}`);
|
|
278
|
+
}
|
|
279
|
+
return lines;
|
|
280
|
+
}
|
|
281
|
+
function heatmapCell(day, maxTokens) {
|
|
282
|
+
if (!day)
|
|
283
|
+
return " ";
|
|
284
|
+
if (!day.active)
|
|
285
|
+
return EMPTY_CELL;
|
|
286
|
+
if (!day.hasPreciseUsage || maxTokens <= 0)
|
|
287
|
+
return HEAT_LEVELS[0];
|
|
288
|
+
const ratio = day.tokens / maxTokens;
|
|
289
|
+
if (ratio <= 0.25)
|
|
290
|
+
return HEAT_LEVELS[0];
|
|
291
|
+
if (ratio <= 0.5)
|
|
292
|
+
return HEAT_LEVELS[1];
|
|
293
|
+
if (ratio <= 0.75)
|
|
294
|
+
return HEAT_LEVELS[2];
|
|
295
|
+
return HEAT_LEVELS[3];
|
|
296
|
+
}
|
|
297
|
+
function usageBar(percent, width) {
|
|
298
|
+
if (width <= 0)
|
|
299
|
+
return "";
|
|
300
|
+
let filled = Math.round(percent * width);
|
|
301
|
+
if (percent > 0 && filled === 0)
|
|
302
|
+
filled = 1;
|
|
303
|
+
filled = Math.max(0, Math.min(width, filled));
|
|
304
|
+
return "█".repeat(filled) + "░".repeat(width - filled);
|
|
305
|
+
}
|
|
306
|
+
function assistantPayload(entry) {
|
|
307
|
+
if (entry.type === "assistant_message")
|
|
308
|
+
return entry.message;
|
|
309
|
+
if (entry.type === "message" && entry.data?.role === "assistant")
|
|
310
|
+
return entry.data;
|
|
311
|
+
return undefined;
|
|
312
|
+
}
|
|
313
|
+
function resolveEntryModel(entry, message, currentModel) {
|
|
314
|
+
const model = message?.model ?? entry.model ?? currentModel;
|
|
315
|
+
return typeof model === "string" && model.trim() ? model : undefined;
|
|
316
|
+
}
|
|
317
|
+
function normalizeUsage(raw) {
|
|
318
|
+
if (!raw || typeof raw !== "object")
|
|
319
|
+
return undefined;
|
|
320
|
+
const value = raw;
|
|
321
|
+
const promptTokens = numberValue(value.promptTokens) ?? numberValue(value.input_tokens);
|
|
322
|
+
const completionTokens = numberValue(value.completionTokens) ?? numberValue(value.output_tokens);
|
|
323
|
+
if (promptTokens === undefined || completionTokens === undefined)
|
|
324
|
+
return undefined;
|
|
325
|
+
return {
|
|
326
|
+
promptTokens,
|
|
327
|
+
completionTokens,
|
|
328
|
+
promptCacheHitTokens: numberValue(value.promptCacheHitTokens) ?? numberValue(value.cache_read_input_tokens),
|
|
329
|
+
promptCacheMissTokens: numberValue(value.promptCacheMissTokens) ?? numberValue(value.cache_creation_input_tokens),
|
|
330
|
+
reasoningTokens: numberValue(value.reasoningTokens),
|
|
331
|
+
totalTokens: numberValue(value.totalTokens) ?? numberValue(value.total_tokens),
|
|
332
|
+
};
|
|
333
|
+
}
|
|
334
|
+
function tokenTotal(usage) {
|
|
335
|
+
return usage.totalTokens ?? (usage.promptTokens + usage.completionTokens);
|
|
336
|
+
}
|
|
337
|
+
function listSessionFiles(root) {
|
|
338
|
+
if (!existsSync(root))
|
|
339
|
+
return [];
|
|
340
|
+
const files = [];
|
|
341
|
+
for (const projectDir of safeReadDir(root)) {
|
|
342
|
+
const projectPath = join(root, projectDir);
|
|
343
|
+
try {
|
|
344
|
+
if (!statSync(projectPath).isDirectory())
|
|
345
|
+
continue;
|
|
346
|
+
}
|
|
347
|
+
catch {
|
|
348
|
+
continue;
|
|
349
|
+
}
|
|
350
|
+
for (const file of safeReadDir(projectPath)) {
|
|
351
|
+
if (file.endsWith(".jsonl"))
|
|
352
|
+
files.push(join(projectPath, file));
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
return files;
|
|
356
|
+
}
|
|
357
|
+
function safeReadDir(path) {
|
|
358
|
+
try {
|
|
359
|
+
return readdirSync(path);
|
|
360
|
+
}
|
|
361
|
+
catch {
|
|
362
|
+
return [];
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
function normalizeTimestamp(value) {
|
|
366
|
+
if (typeof value !== "number" && typeof value !== "string")
|
|
367
|
+
return undefined;
|
|
368
|
+
const date = new Date(value);
|
|
369
|
+
return Number.isFinite(date.getTime()) ? date : undefined;
|
|
370
|
+
}
|
|
371
|
+
function isWithinRange(date, accumulator) {
|
|
372
|
+
const day = startOfLocalDay(date);
|
|
373
|
+
return day.getTime() >= accumulator.start.getTime() && day.getTime() <= accumulator.end.getTime();
|
|
374
|
+
}
|
|
375
|
+
function numberValue(value) {
|
|
376
|
+
return typeof value === "number" && Number.isFinite(value) ? value : undefined;
|
|
377
|
+
}
|
|
378
|
+
function startOfLocalDay(date) {
|
|
379
|
+
return new Date(date.getFullYear(), date.getMonth(), date.getDate());
|
|
380
|
+
}
|
|
381
|
+
function addDays(date, days) {
|
|
382
|
+
const next = new Date(date);
|
|
383
|
+
next.setDate(next.getDate() + days);
|
|
384
|
+
return next;
|
|
385
|
+
}
|
|
386
|
+
function localDateKey(date) {
|
|
387
|
+
const y = date.getFullYear();
|
|
388
|
+
const m = String(date.getMonth() + 1).padStart(2, "0");
|
|
389
|
+
const d = String(date.getDate()).padStart(2, "0");
|
|
390
|
+
return `${y}-${m}-${d}`;
|
|
391
|
+
}
|
|
392
|
+
function parseLocalDate(value) {
|
|
393
|
+
const [y, m, d] = value.split("-").map(Number);
|
|
394
|
+
return new Date(y, (m ?? 1) - 1, d ?? 1);
|
|
395
|
+
}
|
|
396
|
+
function formatMonthDay(date) {
|
|
397
|
+
return `${String(date.getMonth() + 1).padStart(2, "0")}/${String(date.getDate()).padStart(2, "0")}`;
|
|
398
|
+
}
|
|
399
|
+
function mondayOfWeek(date) {
|
|
400
|
+
return addDays(startOfLocalDay(date), -weekdayIndex(date));
|
|
401
|
+
}
|
|
402
|
+
function weekdayIndex(date) {
|
|
403
|
+
return (date.getDay() + 6) % 7;
|
|
404
|
+
}
|
|
405
|
+
function trimFixed(value, fractionDigits) {
|
|
406
|
+
return value.toFixed(fractionDigits).replace(/\.0+$/, "").replace(/(\.\d*[1-9])0+$/, "$1");
|
|
407
|
+
}
|
|
408
|
+
function truncate(value, width) {
|
|
409
|
+
if (value.length <= width)
|
|
410
|
+
return value;
|
|
411
|
+
if (width <= 1)
|
|
412
|
+
return value.slice(0, width);
|
|
413
|
+
return `${value.slice(0, width - 1)}…`;
|
|
414
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { ApprovalController } from "../approval/types.js";
|
|
2
|
+
import { type LspService } from "../lsp/index.js";
|
|
3
|
+
import type { ToolRegistryEntry } from "../types.js";
|
|
4
|
+
import { type FileStateTracker } from "./file-state.js";
|
|
5
|
+
export interface ApplyPatchArgs {
|
|
6
|
+
patch?: string;
|
|
7
|
+
patchText?: string;
|
|
8
|
+
}
|
|
9
|
+
export declare function createApplyPatchTool(cwd: string, approval?: ApprovalController, lsp?: LspService, fileState?: FileStateTracker): ToolRegistryEntry;
|