agy-insights 0.8.0 → 0.8.2
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.es.md +1 -1
- package/README.md +1 -1
- package/agy-insights.js +4 -3
- package/docs/how-it-works.md +1 -1
- package/lib/findings.js +3 -2
- package/lib/history.js +78 -10
- package/lib/i18n.js +4 -4
- package/lib/report.js +7 -4
- package/package.json +1 -1
package/README.es.md
CHANGED
|
@@ -36,7 +36,7 @@ npx agy-insights
|
|
|
36
36
|
npx agy-insights@latest --text
|
|
37
37
|
```
|
|
38
38
|
|
|
39
|
-
`@latest` hace que npx compruebe cada vez si hay una versión nueva. Sin él, una copia instalada globalmente (`npm install -g`) o en el proyecto actual tiene prioridad y no se actualiza nunca. Para fijar una versión concreta: `npx agy-insights@0.8.
|
|
39
|
+
`@latest` hace que npx compruebe cada vez si hay una versión nueva. Sin él, una copia instalada globalmente (`npm install -g`) o en el proyecto actual tiene prioridad y no se actualiza nunca. Para fijar una versión concreta: `npx agy-insights@0.8.2`.
|
|
40
40
|
|
|
41
41
|
**Instalándolo globalmente:**
|
|
42
42
|
|
package/README.md
CHANGED
|
@@ -36,7 +36,7 @@ npx agy-insights
|
|
|
36
36
|
npx agy-insights@latest --text
|
|
37
37
|
```
|
|
38
38
|
|
|
39
|
-
`@latest` makes npx check for the newest version every time. Without it, a copy installed globally (`npm install -g`) or in the current project takes precedence and is never updated. To pin a version instead: `npx agy-insights@0.8.
|
|
39
|
+
`@latest` makes npx check for the newest version every time. Without it, a copy installed globally (`npm install -g`) or in the current project takes precedence and is never updated. To pin a version instead: `npx agy-insights@0.8.2`.
|
|
40
40
|
|
|
41
41
|
**Globally:**
|
|
42
42
|
|
package/agy-insights.js
CHANGED
|
@@ -86,14 +86,15 @@ function sinceTime({ since, days }) {
|
|
|
86
86
|
// Tab-separated table of where each conversation's call models came from, for bug reports.
|
|
87
87
|
function debugModels(r) {
|
|
88
88
|
const d = t.debugModels;
|
|
89
|
-
const sum = { rows: 0, rowsWithModel: 0, calls: 0, fromTranscript: 0, unknown: 0 };
|
|
89
|
+
const sum = { rows: 0, rowsWithModel: 0, calls: 0, fromTranscript: 0, unknown: 0, unmetered: 0 };
|
|
90
90
|
const lines = [d.header.join('\t')];
|
|
91
91
|
for (const s of r.sessions) {
|
|
92
92
|
const m = s.modelSources;
|
|
93
93
|
for (const k of Object.keys(sum)) sum[k] += m[k];
|
|
94
|
-
lines.push([s.id.slice(0, 8), m.calls, m.rows, m.rowsWithModel, m.fromTranscript, m.unknown, s.model ? `${s.model.model} (${s.model.effort})` : '', m.unknown ? m.candidates.join(' | ') : ''
|
|
94
|
+
lines.push([s.id.slice(0, 8), m.calls, m.rows, m.rowsWithModel, m.fromTranscript, m.unknown, s.model ? `${s.model.model} (${s.model.effort})` : '', m.unknown ? m.candidates.join(' | ') : '',
|
|
95
|
+
m.unmetered || '', m.unmetered ? m.unmeteredKeys.join(' ') : ''].join('\t'));
|
|
95
96
|
}
|
|
96
|
-
lines.push([d.total, sum.calls, sum.rows, sum.rowsWithModel, sum.fromTranscript, sum.unknown, '', ''].join('\t'), '', d.note, '');
|
|
97
|
+
lines.push([d.total, sum.calls, sum.rows, sum.rowsWithModel, sum.fromTranscript, sum.unknown, '', '', sum.unmetered || '', ''].join('\t'), '', d.note, '');
|
|
97
98
|
return lines.join('\n');
|
|
98
99
|
}
|
|
99
100
|
|
package/docs/how-it-works.md
CHANGED
|
@@ -35,7 +35,7 @@ It is undocumented; this is what has been observed in `agy` 1.4.x.
|
|
|
35
35
|
- Table `steps`: one row per step. `step_payload` is a protobuf message. Tool calls store the name (`view_file`, `run_command`, `grep_search`…) followed by their JSON arguments, for example `{"AbsolutePath":"C:\\dev\\...\\X.cs","StartLine":301,"EndLine":380}`, preceded by a `call_NNN` id.
|
|
36
36
|
- Each call takes two steps with the same id: the model response that requests it (`step_type` 15) and the result (`step_type` 132), which repeats name and arguments and adds the file content or command output. `lib/history.js` counts the call once per id and adds the size of both steps, which approximates what entered the context.
|
|
37
37
|
- Table `gen_metadata`: one row per model call, in the same order as the transcript's `PLANNER_RESPONSE` lines. Each row holds the model's display name with the effort level in brackets (`Gemini 3.8 Flash (High)`) and its id (`gemini-3.8-flash-high`). `modelOf()` takes the model and effort from whole strings only, so prose in the system prompt that mentions a model is ignored, and the n-th row is matched to the n-th call to get tokens per model and effort. That pairing is only trusted when the counts match. Otherwise each call takes the model selected in the transcript (`USER_INPUT` lines carry "changed setting `Model Selection` from X to Y"), or the conversation's only model; what is left is "unknown". `modelKey()` merges the two spellings (`Gemini 3.8 Flash` / `gemini-3.8-flash`, `High` / `high`). `--debug-models` prints, per conversation, how many calls got their model from each source: counts only, safe to paste into an issue.
|
|
38
|
-
- Next to the history, `../brain/<id>/.system_generated/logs/transcript.jsonl`: one JSON line per step. `PLANNER_RESPONSE` lines are model calls and carry `input_tokens`, `cache_read_tokens` and `output_tokens`; failed steps have `status: "ERROR"`. Subagents have their own conversation and transcript. `realUsage()` in `lib/history.js` adds those figures up (it only reads the file). The total printed by `agy -p --output-format json` does not include subagents; this one does.
|
|
38
|
+
- Next to the history, `../brain/<id>/.system_generated/logs/transcript.jsonl`: one JSON line per step. `PLANNER_RESPONSE` lines are model calls and carry `input_tokens`, `cache_read_tokens` and `output_tokens`; failed steps have `status: "ERROR"`. Subagents have their own conversation and transcript. `realUsage()` in `lib/history.js` adds those figures up (it only reads the file). The total printed by `agy -p --output-format json` does not include subagents; this one does. Some logs (seen with the VS Code plugin) have `PLANNER_RESPONSE` lines without any token field; those are not counted as 0 tokens. The same figures are also in each `gen_metadata` row, at protobuf field 1 › 4 (2 = input, 3 = output, 5 = cache read; the model's display name is at 1 › 21). When the transcript has no counts, or there is no transcript, `usageFromMeta()` takes real usage from there, keeping the day and selected model of the transcript's n-th call when there is one.
|
|
39
39
|
|
|
40
40
|
Each `transcript.jsonl` line also has `created_at` (ISO time). A conversation is dated by the first and last of those; without a transcript, by the `.db` file's last write. `--since` / `--days` keep a conversation when its last activity falls inside the period, and skip the rest before opening them. `../conversation_summaries.db` has one row per conversation: its short `title` (or `preview` in older ones) names each session in the report instead of its id; `last_modified_time` is not used. If the file is missing, sessions are shown by id. Each model call's `created_at` also puts its tokens on a local day (`r.daily`), which feeds the usage chart.
|
|
41
41
|
|
package/lib/findings.js
CHANGED
|
@@ -10,7 +10,7 @@ const DEFAULTS = { crossSessionMin: 3, heavyOutputTokens: 2000, longSessionToken
|
|
|
10
10
|
// Suggested .antigravityignore patterns for the built-in non-source types.
|
|
11
11
|
const KIND_PATTERNS = {
|
|
12
12
|
'node_modules': ['node_modules/'],
|
|
13
|
-
'build': ['bin/', 'obj/', 'dist/', 'build/', 'out/', 'target/', 'coverage/'],
|
|
13
|
+
'build': ['bin/', 'obj/', 'dist/', 'build/', 'out/', 'target/', 'coverage/', 'allure-results/', 'allure-report/'],
|
|
14
14
|
'lockfiles': ['*.lock', 'package-lock.json', 'pnpm-lock.yaml', 'packages.lock.json'],
|
|
15
15
|
'logs': ['*.log', '*.map', '*.min.js', '*.min.css'],
|
|
16
16
|
'generated': ['*.designer.cs', '*.g.cs', 'Migrations/'],
|
|
@@ -126,8 +126,9 @@ function findings(r, opts = {}) {
|
|
|
126
126
|
const size = (s) => (s.usage ? s.usage.input + s.usage.output : s.tokens);
|
|
127
127
|
const long = r.sessions.filter((s) => size(s) >= o.longSessionTokens);
|
|
128
128
|
if (long.length) {
|
|
129
|
+
// `exact`: every one has real usage, so the header shows the same figures as its items, as a share of real usage.
|
|
129
130
|
out.push({
|
|
130
|
-
id: 'long-sessions', level: 'info', title: f.longTitle, tokens: sum(long, (s) => s.
|
|
131
|
+
id: 'long-sessions', level: 'info', title: f.longTitle, tokens: sum(long, size), exact: long.every((s) => s.usage),
|
|
131
132
|
why: f.longWhy(long.length, num(o.longSessionTokens)), suggestion: f.longSuggestion,
|
|
132
133
|
items: long.slice(0, 5).map((s) => ({
|
|
133
134
|
// Real tokens when agy logged them (`exact`: shown without "~"), else the size estimate.
|
package/lib/history.js
CHANGED
|
@@ -114,6 +114,39 @@ function collect(blob) {
|
|
|
114
114
|
return out;
|
|
115
115
|
}
|
|
116
116
|
|
|
117
|
+
// The top-level fields of one protobuf message: tag -> varint value or bytes (last one wins). Null if malformed.
|
|
118
|
+
function fields(buf) {
|
|
119
|
+
const out = new Map();
|
|
120
|
+
let i = 0;
|
|
121
|
+
const varint = () => {
|
|
122
|
+
let v = 0, s = 0, b;
|
|
123
|
+
do { if (i >= buf.length) throw 0; b = buf[i++]; v += (b & 0x7f) * 2 ** s; s += 7; } while (b & 0x80);
|
|
124
|
+
return v;
|
|
125
|
+
};
|
|
126
|
+
try {
|
|
127
|
+
while (i < buf.length) {
|
|
128
|
+
const k = varint(), tag = Math.floor(k / 8), wt = k & 7;
|
|
129
|
+
if (wt === 0) out.set(tag, varint());
|
|
130
|
+
else if (wt === 1) i += 8;
|
|
131
|
+
else if (wt === 5) i += 4;
|
|
132
|
+
else if (wt === 2) { const len = varint(); if (i + len > buf.length) throw 0; out.set(tag, buf.subarray(i, i + len)); i += len; } else throw 0;
|
|
133
|
+
}
|
|
134
|
+
return out;
|
|
135
|
+
} catch { return null; }
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// Token counts of one gen_metadata row (one model call): field 1 › 4 holds 2 = input, 3 = output,
|
|
139
|
+
// 5 = cache read, the same figures the transcript logs. Null when the row has none: some logs (seen
|
|
140
|
+
// with the VS Code plugin) carry no token counts, and this is then the only place that has them.
|
|
141
|
+
function metaUsage(blob) {
|
|
142
|
+
const top = blob && fields(Buffer.from(blob));
|
|
143
|
+
const call = Buffer.isBuffer(top?.get(1)) && fields(top.get(1));
|
|
144
|
+
const u = Buffer.isBuffer(call?.get(4)) && fields(call.get(4));
|
|
145
|
+
if (!u || !(u.has(2) || u.has(3))) return null;
|
|
146
|
+
const n = (k) => (typeof u.get(k) === 'number' ? u.get(k) : 0);
|
|
147
|
+
return { input: n(2), output: n(3), cache: n(5) };
|
|
148
|
+
}
|
|
149
|
+
|
|
117
150
|
// A tool call is stored as the tool name followed (within a few strings) by its JSON arguments,
|
|
118
151
|
// e.g. "view_file" + {"AbsolutePath":"C:\\dev\\...\\X.cs","StartLine":301,"EndLine":380}.
|
|
119
152
|
// The call id ("call_5859111") just before the name identifies the call: agy stores each call twice,
|
|
@@ -154,7 +187,7 @@ function kind(p, { inRepo = false } = {}) {
|
|
|
154
187
|
// are never reported as generated or proposed for exclusion, whatever their names look like.
|
|
155
188
|
if (/(^|\/)\.gemini\//i.test(p)) return 'agent';
|
|
156
189
|
if (/(^|\/)node_modules\//.test(p)) return 'node_modules';
|
|
157
|
-
if (!inRepo && /(^|\/)(dist|build|out|target|coverage|\.next|bin|obj)\//i.test(p)) return 'build';
|
|
190
|
+
if (!inRepo && /(^|\/)(dist|build|out|target|coverage|allure-results|allure-report|\.next|bin|obj)\//i.test(p)) return 'build';
|
|
158
191
|
if (/(package-lock\.json|yarn\.lock|pnpm-lock\.yaml|packages\.lock\.json|\.lock)$/.test(p)) return 'lockfiles';
|
|
159
192
|
if (/\.(log|map)$|\.min\.(js|css)$/.test(p)) return 'logs';
|
|
160
193
|
if (/(^|\/)(tests?|__tests__|specs?)\//i.test(p) || /\.(test|spec|tests?)\.\w+$/i.test(p) || /Tests?\//.test(p)) return 'tests';
|
|
@@ -221,7 +254,10 @@ function realUsage(dir, id) {
|
|
|
221
254
|
try { text = fs.readFileSync(file, 'utf8'); } catch { return null; }
|
|
222
255
|
// `days`: the same figures per local day of each model call (calls without a time go under null).
|
|
223
256
|
// `perCall`: each call's figures, in order, and the model selected in the transcript when it was made.
|
|
224
|
-
|
|
257
|
+
// `calls` counts only the calls that carry token counts; some logs (seen with the VS Code plugin) list
|
|
258
|
+
// calls without them, and counting those as 0 tokens would drag every per-call figure down.
|
|
259
|
+
// `unmetered`: those other calls, and `unmeteredKeys` the field names they do have, for --debug-models.
|
|
260
|
+
const u = { calls: 0, input: 0, cache: 0, output: 0, errors: 0, start: null, end: null, days: new Map(), perCall: [], unmetered: 0, unmeteredKeys: new Set() };
|
|
225
261
|
let selected = null;
|
|
226
262
|
u.unrecognized = new Set();
|
|
227
263
|
for (const line of text.split('\n')) {
|
|
@@ -236,11 +272,17 @@ function realUsage(dir, id) {
|
|
|
236
272
|
for (const m of String(e.content).matchAll(MODEL_SETTING_RE)) if (!modelOf([m[1].trim()]).model) u.unrecognized.add(m[1].trim().slice(0, 60));
|
|
237
273
|
}
|
|
238
274
|
if (e.type !== 'PLANNER_RESPONSE') continue;
|
|
275
|
+
if (!['input_tokens', 'cache_read_tokens', 'output_tokens'].some((k) => k in e)) {
|
|
276
|
+
u.unmetered++;
|
|
277
|
+
for (const k of Object.keys(e)) if (k.length < 40) u.unmeteredKeys.add(k);
|
|
278
|
+
u.perCall.push({ metered: false, model: selected, at });
|
|
279
|
+
continue;
|
|
280
|
+
}
|
|
239
281
|
u.calls++;
|
|
240
282
|
u.input += Number(e.input_tokens) || 0;
|
|
241
283
|
u.cache += Number(e.cache_read_tokens) || 0;
|
|
242
284
|
u.output += Number(e.output_tokens) || 0;
|
|
243
|
-
u.perCall.push({ input: Number(e.input_tokens) || 0, cache: Number(e.cache_read_tokens) || 0, output: Number(e.output_tokens) || 0, model: selected });
|
|
285
|
+
u.perCall.push({ metered: true, input: Number(e.input_tokens) || 0, cache: Number(e.cache_read_tokens) || 0, output: Number(e.output_tokens) || 0, model: selected });
|
|
244
286
|
const k = at ? dayKey(at) : null;
|
|
245
287
|
const d = u.days.get(k) || { calls: 0, input: 0, cache: 0, output: 0 };
|
|
246
288
|
d.calls++;
|
|
@@ -252,6 +294,27 @@ function realUsage(dir, id) {
|
|
|
252
294
|
return u;
|
|
253
295
|
}
|
|
254
296
|
|
|
297
|
+
// Real usage rebuilt from gen_metadata, one row per model call, for conversations whose log has no token
|
|
298
|
+
// counts (or no log). Each row's call keeps the model and day of the log's n-th call when there is one.
|
|
299
|
+
function usageFromMeta(blobs, log, end) {
|
|
300
|
+
const rows = blobs.map(metaUsage);
|
|
301
|
+
if (!rows.some(Boolean)) return null;
|
|
302
|
+
const u = { calls: 0, input: 0, cache: 0, output: 0, errors: log?.errors || 0, start: log?.start ?? null, end: log?.end ?? null,
|
|
303
|
+
days: new Map(), perCall: [], unmetered: 0, unmeteredKeys: new Set(), unrecognized: log?.unrecognized || new Set(), fromMeta: true };
|
|
304
|
+
rows.forEach((x, i) => {
|
|
305
|
+
const from = log?.perCall[i];
|
|
306
|
+
if (!x) { u.perCall.push({ metered: false, model: from?.model || null }); return; }
|
|
307
|
+
u.calls++;
|
|
308
|
+
u.input += x.input; u.cache += x.cache; u.output += x.output;
|
|
309
|
+
u.perCall.push({ metered: true, ...x, model: from?.model || null });
|
|
310
|
+
const k = dayKey(from?.at || log?.end || end);
|
|
311
|
+
const d = u.days.get(k) || { calls: 0, input: 0, cache: 0, output: 0 };
|
|
312
|
+
d.calls++; d.input += x.input; d.cache += x.cache; d.output += x.output;
|
|
313
|
+
u.days.set(k, d);
|
|
314
|
+
});
|
|
315
|
+
return u;
|
|
316
|
+
}
|
|
317
|
+
|
|
255
318
|
// --- Titles ---
|
|
256
319
|
|
|
257
320
|
// The short title agy gives each conversation ("Analyzing Package Lock Dependencies"), from
|
|
@@ -317,9 +380,11 @@ async function analyze({ dir = DEFAULT_DIR, workspaces = [], customKinds = {}, s
|
|
|
317
380
|
for (const f of dbs) {
|
|
318
381
|
// When it happened: the transcript's step times, else the database's last write.
|
|
319
382
|
const id = f.replace(/\.db$/, '');
|
|
320
|
-
const
|
|
321
|
-
const end =
|
|
322
|
-
const start =
|
|
383
|
+
const log = realUsage(dir, id);
|
|
384
|
+
const end = log?.end ?? fs.statSync(path.join(dir, f)).mtimeMs;
|
|
385
|
+
const start = log?.start ?? end;
|
|
386
|
+
// Real usage from the log when it has token counts; else from gen_metadata (below), else the estimate.
|
|
387
|
+
let usage = log?.calls ? log : null;
|
|
323
388
|
if (since && end < since) { r.olderSkipped++; continue; }
|
|
324
389
|
let db;
|
|
325
390
|
try { db = await openDb(path.join(dir, f), tmp); } catch { r.unreadable++; continue; }
|
|
@@ -379,6 +444,8 @@ async function analyze({ dir = DEFAULT_DIR, workspaces = [], customKinds = {}, s
|
|
|
379
444
|
files.add(key(file), { ...fields, convs: once(seenFiles, key(file)) }, { abs: file, rel, root, project, kind: k });
|
|
380
445
|
});
|
|
381
446
|
}
|
|
447
|
+
const metaBlobs = db.all('select data from gen_metadata order by idx').map(([blob]) => blob);
|
|
448
|
+
if (!usage) usage = usageFromMeta(metaBlobs, log, end);
|
|
382
449
|
if (usage) {
|
|
383
450
|
r.usage.conversations++;
|
|
384
451
|
for (const k of ['calls', 'input', 'cache', 'output', 'errors']) r.usage[k] += usage[k];
|
|
@@ -393,9 +460,9 @@ async function analyze({ dir = DEFAULT_DIR, workspaces = [], customKinds = {}, s
|
|
|
393
460
|
// Model of each call. gen_metadata usually has one row per call, in the transcript's order; when the
|
|
394
461
|
// counts match, the n-th row is the n-th call. When they don't, pairing by position would mislabel
|
|
395
462
|
// calls, so the model selected in the transcript is used instead, or the conversation's only model.
|
|
396
|
-
const metaStrs =
|
|
463
|
+
const metaStrs = metaBlobs.map(collect);
|
|
397
464
|
const metaRows = metaStrs.map(modelOf);
|
|
398
|
-
const calls = usage ? usage.perCall : metaRows.map(() => null);
|
|
465
|
+
const calls = usage?.fromMeta ? usage.perCall : log ? log.perCall : metaRows.map(() => null);
|
|
399
466
|
const aligned = metaRows.length === calls.length;
|
|
400
467
|
const rowModels = new Set(metaRows.filter((m) => m.model).map(modelKey));
|
|
401
468
|
const onlyModel = rowModels.size === 1 ? metaRows.find((m) => m.model) : null;
|
|
@@ -412,7 +479,7 @@ async function analyze({ dir = DEFAULT_DIR, workspaces = [], customKinds = {}, s
|
|
|
412
479
|
e.calls++;
|
|
413
480
|
if (!seenModels.has(k)) { seenModels.add(k); e.convs++; }
|
|
414
481
|
const c = calls[i];
|
|
415
|
-
if (c) { e.known++; e.input += c.input; e.cache += c.cache; e.output += c.output; }
|
|
482
|
+
if (c?.metered) { e.known++; e.input += c.input; e.cache += c.cache; e.output += c.output; }
|
|
416
483
|
models.set(k, e);
|
|
417
484
|
if (m.model) counts.set(k, (counts.get(k) || 0) + 1);
|
|
418
485
|
});
|
|
@@ -426,7 +493,8 @@ async function analyze({ dir = DEFAULT_DIR, workspaces = [], customKinds = {}, s
|
|
|
426
493
|
// the short strings that look like a model id (to spot a new format). Never prompts, paths or code.
|
|
427
494
|
modelSources: { rows: metaRows.length, rowsWithModel: metaRows.filter((m) => m.model).length, calls: calls.length,
|
|
428
495
|
fromTranscript: calls.filter((c) => c?.model).length, unknown: callModels.filter((m) => !m.model).length,
|
|
429
|
-
|
|
496
|
+
unmetered: log?.unmetered || 0, unmeteredKeys: [...(log?.unmeteredKeys || [])].sort(),
|
|
497
|
+
candidates: [...new Set([...(log?.unrecognized || []),
|
|
430
498
|
...metaStrs.filter((_, i) => !metaRows[i].model).flat().filter(looksLikeModel)])].slice(0, 10) },
|
|
431
499
|
});
|
|
432
500
|
r.tokens += convBytes / BYTES_PER_TOKEN;
|
package/lib/i18n.js
CHANGED
|
@@ -35,7 +35,7 @@ Options:
|
|
|
35
35
|
mapDone: (tokens, file) => `\n~${tokens} tokens on every request. Paste it into ${file} if you like it; agy-insights does not write it.`,
|
|
36
36
|
reportName: 'report',
|
|
37
37
|
debugModels: {
|
|
38
|
-
header: ['conversation', 'calls', 'gen_metadata rows', 'rows with model', 'calls with model in transcript', 'unknown', 'main model', 'unrecognized model ids'],
|
|
38
|
+
header: ['conversation', 'calls', 'gen_metadata rows', 'rows with model', 'calls with model in transcript', 'unknown', 'main model', 'unrecognized model ids', 'calls without tokens', 'fields of those calls'],
|
|
39
39
|
total: 'total',
|
|
40
40
|
note: 'Only counts and model names or ids: no paths, prompts or code. Paste it into an issue if models show up as unknown.',
|
|
41
41
|
},
|
|
@@ -162,7 +162,7 @@ Options:
|
|
|
162
162
|
since: (date) => `active since ${date}`,
|
|
163
163
|
olderSkipped: (n) => `${plural(n, 'older conversation', 'older conversations')} left out by the date filter`,
|
|
164
164
|
periodLine: (period, since) => `Period: ${period}${since ? ` (filter: ${since})` : ''}`,
|
|
165
|
-
footerReal: (missing) => `Real tokens: the ones Antigravity records in each conversation's log (subagents included)${missing ? `; ${plural(missing, 'conversation has', 'conversations have')} no log and ${missing === 1 ? 'is' : 'are'} not counted` : ''}.`,
|
|
165
|
+
footerReal: (missing) => `Real tokens: the ones Antigravity records in each conversation's log (subagents included)${missing ? `; ${plural(missing, 'conversation has', 'conversations have')} no token counts in its log and ${missing === 1 ? 'is' : 'are'} not counted` : ''}.`,
|
|
166
166
|
footerCache: 'It is unknown whether the quota counts cache the same as new input, so both figures are shown.',
|
|
167
167
|
footerEstimate: '~tokens = stored size of each step / 4: useful to compare files and folders with each other.',
|
|
168
168
|
footerPrivacy: 'The history was read from a copy and was not modified. This page contains paths from your projects: review it before sharing.',
|
|
@@ -211,7 +211,7 @@ Opciones:
|
|
|
211
211
|
mapDone: (tokens, file) => `\n~${tokens} tokens en cada petición. Pégalo en ${file} si te convence; agy-insights no lo escribe.`,
|
|
212
212
|
reportName: 'informe',
|
|
213
213
|
debugModels: {
|
|
214
|
-
header: ['conversación', 'llamadas', 'filas gen_metadata', 'filas con modelo', 'llamadas con modelo en transcript', 'desconocidas', 'modelo principal', 'ids de modelo no reconocidos'],
|
|
214
|
+
header: ['conversación', 'llamadas', 'filas gen_metadata', 'filas con modelo', 'llamadas con modelo en transcript', 'desconocidas', 'modelo principal', 'ids de modelo no reconocidos', 'llamadas sin tokens', 'campos de esas llamadas'],
|
|
215
215
|
total: 'total',
|
|
216
216
|
note: 'Solo recuentos y nombres o ids de modelo: ni rutas, ni prompts, ni código. Pégalo en una incidencia si salen modelos desconocidos.',
|
|
217
217
|
},
|
|
@@ -338,7 +338,7 @@ Opciones:
|
|
|
338
338
|
since: (date) => `con actividad desde el ${date}`,
|
|
339
339
|
olderSkipped: (n) => `${plural(n, 'conversación anterior queda fuera', 'conversaciones anteriores quedan fuera')} por el filtro de fechas`,
|
|
340
340
|
periodLine: (period, since) => `Periodo: ${period}${since ? ` (filtro: ${since})` : ''}`,
|
|
341
|
-
footerReal: (missing) => `Tokens reales: los que Antigravity anota en el registro de cada conversación (incluidos subagentes)${missing ? `; ${missing === 1 ? '1 conversación no tiene registro y no cuenta' : `${missing} conversaciones no tienen registro y no cuentan`}` : ''}.`,
|
|
341
|
+
footerReal: (missing) => `Tokens reales: los que Antigravity anota en el registro de cada conversación (incluidos subagentes)${missing ? `; ${missing === 1 ? '1 conversación no tiene tokens en su registro y no cuenta' : `${missing} conversaciones no tienen tokens en su registro y no cuentan`}` : ''}.`,
|
|
342
342
|
footerCache: 'No se sabe si la cuota cuenta la caché igual que la entrada nueva: por eso se muestran las dos cifras.',
|
|
343
343
|
footerEstimate: '~tokens = tamaño guardado de cada paso / 4: sirve para comparar ficheros y carpetas entre sí.',
|
|
344
344
|
footerPrivacy: 'El historial se ha leído desde una copia y no se ha modificado. Esta página contiene rutas de tus proyectos: no la compartas sin revisarla.',
|
package/lib/report.js
CHANGED
|
@@ -38,9 +38,12 @@ function item(it) {
|
|
|
38
38
|
<span class="num">${it.tokens ? `${it.exact ? '' : '~'}${fmt(it.tokens)}` : ''}</span></div>${extra}</li>`;
|
|
39
39
|
}
|
|
40
40
|
|
|
41
|
-
|
|
41
|
+
// A finding's share: of real usage when its figure is real (`exact`), else of the estimated context.
|
|
42
|
+
const share = (f, r) => (f.exact ? billed(r.usage) : r.tokens);
|
|
43
|
+
|
|
44
|
+
function finding(f, r) {
|
|
42
45
|
return `<section class="finding ${f.level}">
|
|
43
|
-
<h3>${esc(f.title)}${f.tokens ? ` <span class="tok"
|
|
46
|
+
<h3>${esc(f.title)}${f.tokens ? ` <span class="tok">${f.exact ? '' : '~'}${fmt(f.tokens)} tokens · ${pct(f.tokens, share(f, r))}</span>` : ''}</h3>
|
|
44
47
|
<p>${esc(f.why)}</p><p class="hint">${esc(f.suggestion)}</p>
|
|
45
48
|
${f.items.length ? `<ul class="items">${f.items.map(item).join('')}</ul>` : ''}
|
|
46
49
|
</section>`;
|
|
@@ -188,7 +191,7 @@ function body(r, found, scope) {
|
|
|
188
191
|
${modelsTable(r)}
|
|
189
192
|
<h2>${t.report.happening}</h2>
|
|
190
193
|
<p class="hint">${t.report.happeningHint}</p>
|
|
191
|
-
${found.length ? found.map((f) => finding(f, r
|
|
194
|
+
${found.length ? found.map((f) => finding(f, r)).join('') : `<p>${t.report.nothing}</p>`}
|
|
192
195
|
<details class="detail-all"><summary><h2>${t.report.details}</h2></summary>
|
|
193
196
|
${sessionsTable(r)}
|
|
194
197
|
${table(t.report.byProject, r.projects, r.tokens, (x) => x.project || t.report.outsideGit)}
|
|
@@ -358,7 +361,7 @@ function text(r, found, { scope, paths }) {
|
|
|
358
361
|
}
|
|
359
362
|
}
|
|
360
363
|
for (const f of found) {
|
|
361
|
-
out.push('', `${f.level === 'warn' ? '⚠' : '•'} ${f.title}${f.tokens ? ` (
|
|
364
|
+
out.push('', `${f.level === 'warn' ? '⚠' : '•'} ${f.title}${f.tokens ? ` (${f.exact ? '' : '~'}${fmt(f.tokens)} tokens, ${pct(f.tokens, share(f, r))})` : ''}`,
|
|
362
365
|
` ${f.why}`, ` → ${f.suggestion}`);
|
|
363
366
|
// Only file and folder labels are paths; commands and session ids are always shown.
|
|
364
367
|
const hasPaths = ['rereads', 'non-source', 'cross-session'].includes(f.id);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "agy-insights",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.2",
|
|
4
4
|
"description": "Measures Antigravity CLI token usage and shows which context is sent repeatedly or needlessly, in English or Spanish. Run: npx agy-insights@latest",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"keywords": [
|