portable-agent-layer 0.104.0 → 0.104.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/package.json
CHANGED
package/src/cli/usage-render.ts
CHANGED
|
@@ -4,7 +4,6 @@ import {
|
|
|
4
4
|
fmt,
|
|
5
5
|
fmtCost,
|
|
6
6
|
grandBucket,
|
|
7
|
-
inferenceLabel,
|
|
8
7
|
modelLabel,
|
|
9
8
|
modelsByCost,
|
|
10
9
|
projectsByCost,
|
|
@@ -100,7 +99,7 @@ function projectSection(style: Style, agents: AgentReport[]): string[] {
|
|
|
100
99
|
|
|
101
100
|
function inferenceSections(style: Style, data: UsageData): string[] {
|
|
102
101
|
return usedInferenceModels(data.pal).flatMap(([model, buckets]) => [
|
|
103
|
-
subtle(style, "PAL inference",
|
|
102
|
+
subtle(style, "PAL inference", modelLabel(model)),
|
|
104
103
|
...windowRows(style, buckets),
|
|
105
104
|
"",
|
|
106
105
|
]);
|
|
@@ -34,10 +34,12 @@ interface PreserveLocations {
|
|
|
34
34
|
|
|
35
35
|
type Manifest = Record<string, string>;
|
|
36
36
|
|
|
37
|
+
export const transcriptArchive = () => resolve(paths.content(), "claude");
|
|
38
|
+
|
|
37
39
|
function defaultLocations(): PreserveLocations {
|
|
38
40
|
return {
|
|
39
41
|
source: resolve(platform.claudeDir(), "projects"),
|
|
40
|
-
target:
|
|
42
|
+
target: transcriptArchive(),
|
|
41
43
|
manifest: resolve(paths.state(), "transcript-copies.json"),
|
|
42
44
|
};
|
|
43
45
|
}
|
|
@@ -105,9 +105,9 @@ function byCost<T>(entries: [string, T][], costOf: (value: T) => number): [strin
|
|
|
105
105
|
return entries.sort((a, b) => costOf(b[1]) - costOf(a[1]));
|
|
106
106
|
}
|
|
107
107
|
|
|
108
|
-
/** Drops the vendor's prefix
|
|
108
|
+
/** Drops the vendor's prefix, any provider path and a release date, so `accounts/x/models/kimi` reads `kimi`. */
|
|
109
109
|
export function modelLabel(model: string): string {
|
|
110
|
-
return (model.split("/").at(-1) ?? model).replace("claude-", "");
|
|
110
|
+
return (model.split("/").at(-1) ?? model).replace("claude-", "").replace(/-\d{8}$/, "");
|
|
111
111
|
}
|
|
112
112
|
|
|
113
113
|
export const usedAgents = (agents: AgentReport[]) =>
|
|
@@ -160,18 +160,12 @@ function byProjectLines(agents: AgentReport[]): string[] {
|
|
|
160
160
|
];
|
|
161
161
|
}
|
|
162
162
|
|
|
163
|
-
export function inferenceLabel(model: string): string {
|
|
164
|
-
if (model.includes("haiku")) return "Haiku";
|
|
165
|
-
if (model.includes("sonnet")) return "Sonnet";
|
|
166
|
-
return model.replace("claude-", "");
|
|
167
|
-
}
|
|
168
|
-
|
|
169
163
|
export const usedInferenceModels = (pal: PalInferenceUsage) =>
|
|
170
164
|
Object.entries(pal.byModel).filter(([, buckets]) => buckets.total.calls > 0);
|
|
171
165
|
|
|
172
166
|
function palInferenceLines(pal: PalInferenceUsage): string[] {
|
|
173
167
|
return usedInferenceModels(pal).flatMap(([model, buckets]) => [
|
|
174
|
-
`\n PAL Inference (${
|
|
168
|
+
`\n PAL Inference (${modelLabel(model)})\n`,
|
|
175
169
|
...windowLines(buckets),
|
|
176
170
|
]);
|
|
177
171
|
}
|
|
@@ -4,8 +4,8 @@
|
|
|
4
4
|
* rather than finding it themselves, which is what makes them testable.
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
import { existsSync, readdirSync, readFileSync } from "node:fs";
|
|
8
|
-
import { resolve } from "node:path";
|
|
7
|
+
import { type Dirent, existsSync, readdirSync, readFileSync } from "node:fs";
|
|
8
|
+
import { relative, resolve } from "node:path";
|
|
9
9
|
import type { LoggedEvent } from "../../hooks/lib/events";
|
|
10
10
|
import { costOfUsage } from "../../hooks/lib/models";
|
|
11
11
|
import { cacheWritesOf, finalPerMessage, type TranscriptUsage } from "./transcript-usage";
|
|
@@ -194,10 +194,18 @@ interface TranscriptLine {
|
|
|
194
194
|
* wrote — those sit one level down and are billed to the same project, so
|
|
195
195
|
* missing them undercounts every session that spawned one.
|
|
196
196
|
*/
|
|
197
|
-
function
|
|
197
|
+
function entriesOf(dir: string): Dirent[] {
|
|
198
|
+
try {
|
|
199
|
+
return readdirSync(dir, { withFileTypes: true });
|
|
200
|
+
} catch {
|
|
201
|
+
return [];
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
function transcriptsIn(projPath: string, extension = ".jsonl"): string[] {
|
|
198
206
|
const files: string[] = [];
|
|
199
|
-
for (const entry of
|
|
200
|
-
if (entry.isFile() && entry.name.endsWith(
|
|
207
|
+
for (const entry of entriesOf(projPath)) {
|
|
208
|
+
if (entry.isFile() && entry.name.endsWith(extension)) {
|
|
201
209
|
files.push(resolve(projPath, entry.name));
|
|
202
210
|
continue;
|
|
203
211
|
}
|
|
@@ -205,7 +213,7 @@ function transcriptsIn(projPath: string): string[] {
|
|
|
205
213
|
const subagentsDir = resolve(projPath, entry.name, "subagents");
|
|
206
214
|
try {
|
|
207
215
|
for (const sub of readdirSync(subagentsDir)) {
|
|
208
|
-
if (sub.endsWith(
|
|
216
|
+
if (sub.endsWith(extension)) files.push(resolve(subagentsDir, sub));
|
|
209
217
|
}
|
|
210
218
|
} catch {
|
|
211
219
|
/* no subagents dir */
|
|
@@ -256,30 +264,64 @@ function pricedCallOf(line: string): PricedCall | null {
|
|
|
256
264
|
};
|
|
257
265
|
}
|
|
258
266
|
|
|
267
|
+
const projectDirsIn = (dir: string) =>
|
|
268
|
+
entriesOf(dir)
|
|
269
|
+
.filter((entry) => entry.isDirectory())
|
|
270
|
+
.map((entry) => entry.name);
|
|
271
|
+
|
|
272
|
+
const GZIP = ".gz";
|
|
273
|
+
|
|
274
|
+
/** Claude Code deletes a transcript after cleanupPeriodDays; PAL's gzipped copy outlives it. */
|
|
275
|
+
function deletedTranscriptsIn(archivedProject: string, liveProject: string): string[] {
|
|
276
|
+
return transcriptsIn(archivedProject, `.jsonl${GZIP}`).filter((copy) => {
|
|
277
|
+
const original = relative(archivedProject, copy).slice(0, -GZIP.length);
|
|
278
|
+
return !existsSync(resolve(liveProject, original));
|
|
279
|
+
});
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
function transcriptText(filepath: string): string | null {
|
|
283
|
+
try {
|
|
284
|
+
const raw = readFileSync(filepath);
|
|
285
|
+
return filepath.endsWith(GZIP)
|
|
286
|
+
? new TextDecoder().decode(Bun.gunzipSync(raw))
|
|
287
|
+
: raw.toString("utf-8");
|
|
288
|
+
} catch {
|
|
289
|
+
return null;
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
function projectTranscripts(
|
|
294
|
+
claudeDir: string,
|
|
295
|
+
archiveDir: string | undefined,
|
|
296
|
+
dirName: string
|
|
297
|
+
) {
|
|
298
|
+
const live = resolve(claudeDir, dirName);
|
|
299
|
+
return [
|
|
300
|
+
...transcriptsIn(live),
|
|
301
|
+
...(archiveDir ? deletedTranscriptsIn(resolve(archiveDir, dirName), live) : []),
|
|
302
|
+
];
|
|
303
|
+
}
|
|
304
|
+
|
|
259
305
|
export function readClaudeCode(
|
|
260
306
|
claudeDir: string,
|
|
261
307
|
projectFilter?: string,
|
|
262
|
-
now: Date = new Date()
|
|
308
|
+
now: Date = new Date(),
|
|
309
|
+
archiveDir?: string
|
|
263
310
|
): AgentUsage {
|
|
264
311
|
const tally = usageTally(projectFilter, now);
|
|
265
|
-
if (!existsSync(claudeDir)) return tally.usage;
|
|
266
312
|
const calls: (PricedCall & { project: string })[] = [];
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
313
|
+
const projectDirs = new Set([
|
|
314
|
+
...projectDirsIn(claudeDir),
|
|
315
|
+
...(archiveDir ? projectDirsIn(archiveDir) : []),
|
|
316
|
+
]);
|
|
271
317
|
|
|
272
318
|
for (const dirName of projectDirs) {
|
|
273
319
|
const projName = projectNameOf(dirName);
|
|
274
320
|
if (typeof projectFilter === "string" && !projName.includes(projectFilter)) continue;
|
|
275
321
|
|
|
276
|
-
for (const filepath of
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
content = readFileSync(filepath, "utf-8");
|
|
280
|
-
} catch {
|
|
281
|
-
continue;
|
|
282
|
-
}
|
|
322
|
+
for (const filepath of projectTranscripts(claudeDir, archiveDir, dirName)) {
|
|
323
|
+
const content = transcriptText(filepath);
|
|
324
|
+
if (content === null) continue;
|
|
283
325
|
|
|
284
326
|
for (const line of content.split("\n")) {
|
|
285
327
|
// Cheap reject before the parse: most lines are not model turns.
|
package/src/tools/token-cost.ts
CHANGED
|
@@ -8,6 +8,7 @@ import { resolve } from "node:path";
|
|
|
8
8
|
import { AGENT_REGISTRY, type AgentName } from "../hooks/lib/agent-registry";
|
|
9
9
|
import { eventsSince } from "../hooks/lib/events";
|
|
10
10
|
import { platform } from "../hooks/lib/paths";
|
|
11
|
+
import { transcriptArchive } from "../hooks/lib/transcript-preserve";
|
|
11
12
|
import { findBinaryOnPath } from "../hooks/lib/which";
|
|
12
13
|
import {
|
|
13
14
|
opencodeDatabase,
|
|
@@ -32,7 +33,12 @@ function agentReports(project: string | undefined): AgentReport[] {
|
|
|
32
33
|
});
|
|
33
34
|
return [
|
|
34
35
|
report("claude", () =>
|
|
35
|
-
readClaudeCode(
|
|
36
|
+
readClaudeCode(
|
|
37
|
+
resolve(platform.claudeDir(), "projects"),
|
|
38
|
+
project,
|
|
39
|
+
new Date(),
|
|
40
|
+
transcriptArchive()
|
|
41
|
+
)
|
|
36
42
|
),
|
|
37
43
|
report("codex", () => readCodex(platform.codexDir(), project)),
|
|
38
44
|
report("opencode", () => readOpencode(opencodeDatabase(), project)),
|