@roberttlange/agentlens 0.2.2 → 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/dist/browser.js +154 -20
- package/dist/browser.js.map +1 -1
- package/dist/main.test.js +138 -1
- package/dist/main.test.js.map +1 -1
- package/node_modules/@agentlens/contracts/dist/index.d.ts +120 -0
- package/node_modules/@agentlens/core/dist/__tests__/config.test.js +67 -2
- package/node_modules/@agentlens/core/dist/__tests__/config.test.js.map +1 -1
- package/node_modules/@agentlens/core/dist/__tests__/index.test.js +590 -2
- package/node_modules/@agentlens/core/dist/__tests__/index.test.js.map +1 -1
- package/node_modules/@agentlens/core/dist/config.js +95 -5
- package/node_modules/@agentlens/core/dist/config.js.map +1 -1
- package/node_modules/@agentlens/core/dist/generatedPricing.d.ts +3 -0
- package/node_modules/@agentlens/core/dist/generatedPricing.js +131 -0
- package/node_modules/@agentlens/core/dist/generatedPricing.js.map +1 -0
- package/node_modules/@agentlens/core/dist/metrics.d.ts +13 -0
- package/node_modules/@agentlens/core/dist/metrics.js +227 -54
- package/node_modules/@agentlens/core/dist/metrics.js.map +1 -1
- package/node_modules/@agentlens/core/dist/pricing.d.ts +15 -0
- package/node_modules/@agentlens/core/dist/pricing.js +133 -0
- package/node_modules/@agentlens/core/dist/pricing.js.map +1 -0
- package/node_modules/@agentlens/core/dist/pricing.test.d.ts +1 -0
- package/node_modules/@agentlens/core/dist/pricing.test.js +109 -0
- package/node_modules/@agentlens/core/dist/pricing.test.js.map +1 -0
- package/node_modules/@agentlens/core/dist/sourceProfiles.js +7 -67
- package/node_modules/@agentlens/core/dist/sourceProfiles.js.map +1 -1
- package/node_modules/@agentlens/core/dist/traceIndex.d.ts +34 -1
- package/node_modules/@agentlens/core/dist/traceIndex.js +374 -15
- package/node_modules/@agentlens/core/dist/traceIndex.js.map +1 -1
- package/node_modules/@agentlens/server/dist/activity-cache.d.ts +32 -0
- package/node_modules/@agentlens/server/dist/activity-cache.js +63 -0
- package/node_modules/@agentlens/server/dist/activity-cache.js.map +1 -0
- package/node_modules/@agentlens/server/dist/activity-cache.test.d.ts +1 -0
- package/node_modules/@agentlens/server/dist/activity-cache.test.js +170 -0
- package/node_modules/@agentlens/server/dist/activity-cache.test.js.map +1 -0
- package/node_modules/@agentlens/server/dist/activity.d.ts +31 -1
- package/node_modules/@agentlens/server/dist/activity.js +532 -34
- package/node_modules/@agentlens/server/dist/activity.js.map +1 -1
- package/node_modules/@agentlens/server/dist/app.d.ts +4 -2
- package/node_modules/@agentlens/server/dist/app.js +248 -5
- package/node_modules/@agentlens/server/dist/app.js.map +1 -1
- package/node_modules/@agentlens/server/dist/app.test.js +670 -9
- package/node_modules/@agentlens/server/dist/app.test.js.map +1 -1
- package/node_modules/@agentlens/server/dist/web/assets/index-CTFOBaBt.css +1 -0
- package/node_modules/@agentlens/server/dist/web/assets/index-CVf00w06.js +52 -0
- package/node_modules/@agentlens/server/dist/web/index.html +2 -2
- package/package.json +1 -1
- package/node_modules/@agentlens/server/dist/web/assets/index-Ci8okH8M.js +0 -52
- package/node_modules/@agentlens/server/dist/web/assets/index-Cj3kmsFf.css +0 -1
|
@@ -1,12 +1,49 @@
|
|
|
1
|
-
import { appendFile, mkdtemp, mkdir, writeFile } from "node:fs/promises";
|
|
1
|
+
import { appendFile, mkdtemp, mkdir, utimes, writeFile } from "node:fs/promises";
|
|
2
2
|
import os from "node:os";
|
|
3
3
|
import path from "node:path";
|
|
4
|
-
import { describe, expect, it } from "vitest";
|
|
4
|
+
import { describe, expect, it, vi } from "vitest";
|
|
5
5
|
import { mergeConfig } from "../config.js";
|
|
6
6
|
import { TraceIndex } from "../traceIndex.js";
|
|
7
7
|
async function createTempRoot() {
|
|
8
8
|
return mkdtemp(path.join(os.tmpdir(), "agentlens-core-"));
|
|
9
9
|
}
|
|
10
|
+
function buildCodexTraceLog(sessionId, sequence) {
|
|
11
|
+
const firstTs = new Date(Date.UTC(2026, 1, 11, 10, 0, sequence)).toISOString();
|
|
12
|
+
const secondTs = new Date(Date.UTC(2026, 1, 11, 10, 0, sequence + 1)).toISOString();
|
|
13
|
+
return [
|
|
14
|
+
JSON.stringify({
|
|
15
|
+
timestamp: firstTs,
|
|
16
|
+
type: "session_meta",
|
|
17
|
+
payload: { id: sessionId, cwd: "/tmp/project", cli_version: "0.1.0" },
|
|
18
|
+
}),
|
|
19
|
+
JSON.stringify({
|
|
20
|
+
timestamp: secondTs,
|
|
21
|
+
type: "response_item",
|
|
22
|
+
payload: {
|
|
23
|
+
type: "message",
|
|
24
|
+
role: "assistant",
|
|
25
|
+
content: [{ type: "output_text", text: `trace ${sequence}` }],
|
|
26
|
+
},
|
|
27
|
+
}),
|
|
28
|
+
].join("\n");
|
|
29
|
+
}
|
|
30
|
+
async function waitForCondition(predicate, options = {}) {
|
|
31
|
+
const timeoutMs = options.timeoutMs ?? 5_000;
|
|
32
|
+
const intervalMs = options.intervalMs ?? 25;
|
|
33
|
+
const deadline = Date.now() + timeoutMs;
|
|
34
|
+
for (;;) {
|
|
35
|
+
try {
|
|
36
|
+
predicate();
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
catch (error) {
|
|
40
|
+
if (Date.now() >= deadline) {
|
|
41
|
+
throw error;
|
|
42
|
+
}
|
|
43
|
+
await new Promise((resolve) => setTimeout(resolve, intervalMs));
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
10
47
|
describe("trace index", () => {
|
|
11
48
|
it("defaults sessionLogDirectories to common agent homes with explicit log types", () => {
|
|
12
49
|
const config = mergeConfig();
|
|
@@ -48,6 +85,113 @@ describe("trace index", () => {
|
|
|
48
85
|
{ directory: "~/.pi", logType: "pi" },
|
|
49
86
|
]);
|
|
50
87
|
});
|
|
88
|
+
it("arms the watcher before bootstrap hydration during start", async () => {
|
|
89
|
+
const baseConfig = mergeConfig({ sessionLogDirectories: [] });
|
|
90
|
+
const config = {
|
|
91
|
+
...baseConfig,
|
|
92
|
+
sessionLogDirectories: [],
|
|
93
|
+
sources: Object.fromEntries(Object.entries(baseConfig.sources).map(([key, value]) => [
|
|
94
|
+
key,
|
|
95
|
+
{
|
|
96
|
+
...value,
|
|
97
|
+
enabled: false,
|
|
98
|
+
roots: [],
|
|
99
|
+
},
|
|
100
|
+
])),
|
|
101
|
+
};
|
|
102
|
+
const index = new TraceIndex(config);
|
|
103
|
+
const internal = index;
|
|
104
|
+
const calls = [];
|
|
105
|
+
const bootstrapSpy = vi.spyOn(internal, "bootstrapRecentTraces").mockImplementation(async () => {
|
|
106
|
+
calls.push("bootstrap");
|
|
107
|
+
});
|
|
108
|
+
const restartWatcherSpy = vi.spyOn(internal, "restartWatcher").mockImplementation(async () => {
|
|
109
|
+
calls.push("watcher");
|
|
110
|
+
});
|
|
111
|
+
try {
|
|
112
|
+
await index.start();
|
|
113
|
+
expect(calls).toEqual(["watcher", "bootstrap"]);
|
|
114
|
+
}
|
|
115
|
+
finally {
|
|
116
|
+
bootstrapSpy.mockRestore();
|
|
117
|
+
restartWatcherSpy.mockRestore();
|
|
118
|
+
index.stop();
|
|
119
|
+
}
|
|
120
|
+
});
|
|
121
|
+
it("becomes inspector-ready before full hydration finishes during start", async () => {
|
|
122
|
+
const root = await createTempRoot();
|
|
123
|
+
const codexDir = path.join(root, ".codex", "sessions", "2026", "02", "11");
|
|
124
|
+
await mkdir(codexDir, { recursive: true });
|
|
125
|
+
for (let traceIndex = 0; traceIndex < 130; traceIndex += 1) {
|
|
126
|
+
const tracePath = path.join(codexDir, `bootstrap-${String(traceIndex).padStart(3, "0")}.jsonl`);
|
|
127
|
+
await writeFile(tracePath, buildCodexTraceLog(`bootstrap-${traceIndex}`, traceIndex), "utf8");
|
|
128
|
+
const mtime = new Date(Date.UTC(2026, 1, 11, 10, 0, 0) + (130 - traceIndex) * 1_000);
|
|
129
|
+
await utimes(tracePath, mtime, mtime);
|
|
130
|
+
}
|
|
131
|
+
const config = mergeConfig({
|
|
132
|
+
sessionLogDirectories: [],
|
|
133
|
+
sources: {
|
|
134
|
+
codex_home: {
|
|
135
|
+
name: "codex_home",
|
|
136
|
+
enabled: true,
|
|
137
|
+
roots: [path.join(root, ".codex", "sessions")],
|
|
138
|
+
includeGlobs: ["**/*.jsonl"],
|
|
139
|
+
excludeGlobs: [],
|
|
140
|
+
maxDepth: 8,
|
|
141
|
+
agentHint: "codex",
|
|
142
|
+
},
|
|
143
|
+
claude_projects: {
|
|
144
|
+
name: "claude_projects",
|
|
145
|
+
enabled: false,
|
|
146
|
+
roots: [],
|
|
147
|
+
includeGlobs: ["**/*.jsonl"],
|
|
148
|
+
excludeGlobs: [],
|
|
149
|
+
maxDepth: 8,
|
|
150
|
+
agentHint: "claude",
|
|
151
|
+
},
|
|
152
|
+
claude_history: {
|
|
153
|
+
name: "claude_history",
|
|
154
|
+
enabled: false,
|
|
155
|
+
roots: [],
|
|
156
|
+
includeGlobs: ["history.jsonl"],
|
|
157
|
+
excludeGlobs: [],
|
|
158
|
+
maxDepth: 8,
|
|
159
|
+
agentHint: "claude",
|
|
160
|
+
},
|
|
161
|
+
},
|
|
162
|
+
});
|
|
163
|
+
const index = new TraceIndex(config);
|
|
164
|
+
try {
|
|
165
|
+
await index.start();
|
|
166
|
+
const startup = index.getStartupState();
|
|
167
|
+
expect(startup.inspectorReady).toBe(true);
|
|
168
|
+
expect(startup.fullReady).toBe(false);
|
|
169
|
+
expect(startup.isPartial).toBe(true);
|
|
170
|
+
expect(startup.discoveredTraceCount).toBe(130);
|
|
171
|
+
expect(startup.hydratedTraceCount).toBeLessThan(130);
|
|
172
|
+
expect(index.getSummaries()).toHaveLength(startup.hydratedTraceCount);
|
|
173
|
+
const allProgress = index.getHydrationProgress(0);
|
|
174
|
+
expect(allProgress).toEqual({
|
|
175
|
+
ready: false,
|
|
176
|
+
relevantDiscoveredCount: 130,
|
|
177
|
+
relevantHydratedCount: startup.hydratedTraceCount,
|
|
178
|
+
percent: Math.round((startup.hydratedTraceCount / 130) * 100),
|
|
179
|
+
});
|
|
180
|
+
const newestHydratedMtime = Math.min(...index.getSummaries().map((summary) => summary.mtimeMs));
|
|
181
|
+
const newestWindowProgress = index.getHydrationProgress(newestHydratedMtime);
|
|
182
|
+
expect(newestWindowProgress.ready).toBe(true);
|
|
183
|
+
await waitForCondition(() => {
|
|
184
|
+
const ready = index.getStartupState();
|
|
185
|
+
expect(ready.fullReady).toBe(true);
|
|
186
|
+
expect(ready.isPartial).toBe(false);
|
|
187
|
+
expect(ready.hydratedTraceCount).toBe(130);
|
|
188
|
+
});
|
|
189
|
+
expect(index.getSummaries()).toHaveLength(130);
|
|
190
|
+
}
|
|
191
|
+
finally {
|
|
192
|
+
index.stop();
|
|
193
|
+
}
|
|
194
|
+
});
|
|
51
195
|
it("parses codex home session files and computes overview", async () => {
|
|
52
196
|
const root = await createTempRoot();
|
|
53
197
|
const codexDir = path.join(root, ".codex", "sessions", "2026", "02", "11");
|
|
@@ -1713,6 +1857,135 @@ describe("trace index", () => {
|
|
|
1713
1857
|
expect(summary?.costEstimateUsd).not.toBeNull();
|
|
1714
1858
|
expect((summary?.costEstimateUsd ?? 0) > 0).toBe(true);
|
|
1715
1859
|
});
|
|
1860
|
+
it("derives timestamped codex usage points from token_count rows", async () => {
|
|
1861
|
+
const root = await createTempRoot();
|
|
1862
|
+
const codexDir = path.join(root, ".codex", "sessions", "2026", "02", "13");
|
|
1863
|
+
await mkdir(codexDir, { recursive: true });
|
|
1864
|
+
const codexPath = path.join(codexDir, "rollout-usage-points.jsonl");
|
|
1865
|
+
await writeFile(codexPath, [
|
|
1866
|
+
JSON.stringify({
|
|
1867
|
+
timestamp: "2026-02-13T23:59:00.000Z",
|
|
1868
|
+
type: "session_meta",
|
|
1869
|
+
payload: { id: "sess-usage-points", cwd: "/tmp/project", cli_version: "0.1.0" },
|
|
1870
|
+
}),
|
|
1871
|
+
JSON.stringify({
|
|
1872
|
+
timestamp: "2026-02-13T23:59:01.000Z",
|
|
1873
|
+
type: "turn_context",
|
|
1874
|
+
payload: { model: "gpt-5.3-codex" },
|
|
1875
|
+
}),
|
|
1876
|
+
JSON.stringify({
|
|
1877
|
+
timestamp: "2026-02-13T23:59:02.000Z",
|
|
1878
|
+
type: "event_msg",
|
|
1879
|
+
payload: {
|
|
1880
|
+
type: "token_count",
|
|
1881
|
+
info: {
|
|
1882
|
+
total_token_usage: {
|
|
1883
|
+
input_tokens: 100,
|
|
1884
|
+
cached_input_tokens: 20,
|
|
1885
|
+
output_tokens: 10,
|
|
1886
|
+
reasoning_output_tokens: 0,
|
|
1887
|
+
total_tokens: 130,
|
|
1888
|
+
},
|
|
1889
|
+
last_token_usage: {
|
|
1890
|
+
input_tokens: 100,
|
|
1891
|
+
cached_input_tokens: 20,
|
|
1892
|
+
output_tokens: 10,
|
|
1893
|
+
reasoning_output_tokens: 0,
|
|
1894
|
+
total_tokens: 130,
|
|
1895
|
+
},
|
|
1896
|
+
},
|
|
1897
|
+
},
|
|
1898
|
+
}),
|
|
1899
|
+
JSON.stringify({
|
|
1900
|
+
timestamp: "2026-02-14T00:00:01.000Z",
|
|
1901
|
+
type: "event_msg",
|
|
1902
|
+
payload: {
|
|
1903
|
+
type: "token_count",
|
|
1904
|
+
info: {
|
|
1905
|
+
total_token_usage: {
|
|
1906
|
+
input_tokens: 140,
|
|
1907
|
+
cached_input_tokens: 30,
|
|
1908
|
+
output_tokens: 40,
|
|
1909
|
+
reasoning_output_tokens: 5,
|
|
1910
|
+
total_tokens: 215,
|
|
1911
|
+
},
|
|
1912
|
+
last_token_usage: {
|
|
1913
|
+
input_tokens: 40,
|
|
1914
|
+
cached_input_tokens: 10,
|
|
1915
|
+
output_tokens: 30,
|
|
1916
|
+
reasoning_output_tokens: 5,
|
|
1917
|
+
total_tokens: 85,
|
|
1918
|
+
},
|
|
1919
|
+
},
|
|
1920
|
+
},
|
|
1921
|
+
}),
|
|
1922
|
+
].join("\n"), "utf8");
|
|
1923
|
+
const config = mergeConfig({
|
|
1924
|
+
sessionLogDirectories: [],
|
|
1925
|
+
cost: {
|
|
1926
|
+
enabled: true,
|
|
1927
|
+
currency: "USD",
|
|
1928
|
+
unknownModelPolicy: "n_a",
|
|
1929
|
+
modelRates: [
|
|
1930
|
+
{
|
|
1931
|
+
model: "gpt-5.3-codex",
|
|
1932
|
+
inputPer1MUsd: 1,
|
|
1933
|
+
outputPer1MUsd: 1,
|
|
1934
|
+
cachedReadPer1MUsd: 1,
|
|
1935
|
+
cachedCreatePer1MUsd: 1,
|
|
1936
|
+
reasoningOutputPer1MUsd: 1,
|
|
1937
|
+
},
|
|
1938
|
+
],
|
|
1939
|
+
},
|
|
1940
|
+
sources: {
|
|
1941
|
+
codex_home: {
|
|
1942
|
+
name: "codex_home",
|
|
1943
|
+
enabled: true,
|
|
1944
|
+
roots: [path.join(root, ".codex", "sessions")],
|
|
1945
|
+
includeGlobs: ["**/*.jsonl"],
|
|
1946
|
+
excludeGlobs: [],
|
|
1947
|
+
maxDepth: 8,
|
|
1948
|
+
agentHint: "codex",
|
|
1949
|
+
},
|
|
1950
|
+
claude_projects: {
|
|
1951
|
+
name: "claude_projects",
|
|
1952
|
+
enabled: false,
|
|
1953
|
+
roots: [],
|
|
1954
|
+
includeGlobs: ["**/*.jsonl"],
|
|
1955
|
+
excludeGlobs: [],
|
|
1956
|
+
maxDepth: 8,
|
|
1957
|
+
agentHint: "claude",
|
|
1958
|
+
},
|
|
1959
|
+
claude_history: {
|
|
1960
|
+
name: "claude_history",
|
|
1961
|
+
enabled: false,
|
|
1962
|
+
roots: [],
|
|
1963
|
+
includeGlobs: ["history.jsonl"],
|
|
1964
|
+
excludeGlobs: [],
|
|
1965
|
+
maxDepth: 8,
|
|
1966
|
+
agentHint: "claude",
|
|
1967
|
+
},
|
|
1968
|
+
},
|
|
1969
|
+
});
|
|
1970
|
+
const index = new TraceIndex(config);
|
|
1971
|
+
await index.refresh();
|
|
1972
|
+
const summary = index.getSummaries()[0];
|
|
1973
|
+
const usageArtifacts = index.getSessionUsageArtifacts(summary.id);
|
|
1974
|
+
expect(usageArtifacts.usagePoints).toHaveLength(2);
|
|
1975
|
+
expect(usageArtifacts.usagePoints[0]).toMatchObject({
|
|
1976
|
+
timestampMs: Date.UTC(2026, 1, 13, 23, 59, 2),
|
|
1977
|
+
inputTokens: 100,
|
|
1978
|
+
cachedReadTokens: 20,
|
|
1979
|
+
outputTokens: 10,
|
|
1980
|
+
});
|
|
1981
|
+
expect(usageArtifacts.usagePoints[1]).toMatchObject({
|
|
1982
|
+
timestampMs: Date.UTC(2026, 1, 14, 0, 0, 1),
|
|
1983
|
+
inputTokens: 40,
|
|
1984
|
+
cachedReadTokens: 10,
|
|
1985
|
+
outputTokens: 30,
|
|
1986
|
+
reasoningOutputTokens: 5,
|
|
1987
|
+
});
|
|
1988
|
+
});
|
|
1716
1989
|
it("does not double bill codex cached input tokens in cost estimate", async () => {
|
|
1717
1990
|
const root = await createTempRoot();
|
|
1718
1991
|
const codexDir = path.join(root, ".codex", "sessions", "2026", "02", "13");
|
|
@@ -1806,6 +2079,132 @@ describe("trace index", () => {
|
|
|
1806
2079
|
const summary = index.getSummaries()[0];
|
|
1807
2080
|
expect(summary?.costEstimateUsd).toBe(0.0001);
|
|
1808
2081
|
});
|
|
2082
|
+
it("applies GPT-5.4 long-context pricing per codex token-count row", async () => {
|
|
2083
|
+
const root = await createTempRoot();
|
|
2084
|
+
const codexDir = path.join(root, ".codex", "sessions", "2026", "02", "13");
|
|
2085
|
+
await mkdir(codexDir, { recursive: true });
|
|
2086
|
+
const codexPath = path.join(codexDir, "rollout-gpt54-tiered.jsonl");
|
|
2087
|
+
await writeFile(codexPath, [
|
|
2088
|
+
JSON.stringify({
|
|
2089
|
+
timestamp: "2026-02-13T12:00:00.000Z",
|
|
2090
|
+
type: "session_meta",
|
|
2091
|
+
payload: { id: "sess-gpt54-tiered", cwd: "/tmp/project", cli_version: "0.1.0" },
|
|
2092
|
+
}),
|
|
2093
|
+
JSON.stringify({
|
|
2094
|
+
timestamp: "2026-02-13T12:00:01.000Z",
|
|
2095
|
+
type: "turn_context",
|
|
2096
|
+
payload: { model: "gpt-5.4-2026-02-28" },
|
|
2097
|
+
}),
|
|
2098
|
+
JSON.stringify({
|
|
2099
|
+
timestamp: "2026-02-13T12:00:02.000Z",
|
|
2100
|
+
type: "event_msg",
|
|
2101
|
+
payload: {
|
|
2102
|
+
type: "token_count",
|
|
2103
|
+
info: {
|
|
2104
|
+
total_token_usage: {
|
|
2105
|
+
input_tokens: 100_000,
|
|
2106
|
+
cached_input_tokens: 10_000,
|
|
2107
|
+
output_tokens: 10_000,
|
|
2108
|
+
reasoning_output_tokens: 0,
|
|
2109
|
+
total_tokens: 120_000,
|
|
2110
|
+
},
|
|
2111
|
+
last_token_usage: {
|
|
2112
|
+
input_tokens: 100_000,
|
|
2113
|
+
cached_input_tokens: 10_000,
|
|
2114
|
+
output_tokens: 10_000,
|
|
2115
|
+
reasoning_output_tokens: 0,
|
|
2116
|
+
total_tokens: 120_000,
|
|
2117
|
+
},
|
|
2118
|
+
model_context_window: 1_050_000,
|
|
2119
|
+
},
|
|
2120
|
+
},
|
|
2121
|
+
}),
|
|
2122
|
+
JSON.stringify({
|
|
2123
|
+
timestamp: "2026-02-13T12:00:03.000Z",
|
|
2124
|
+
type: "event_msg",
|
|
2125
|
+
payload: {
|
|
2126
|
+
type: "token_count",
|
|
2127
|
+
info: {
|
|
2128
|
+
total_token_usage: {
|
|
2129
|
+
input_tokens: 400_000,
|
|
2130
|
+
cached_input_tokens: 30_000,
|
|
2131
|
+
output_tokens: 20_000,
|
|
2132
|
+
reasoning_output_tokens: 0,
|
|
2133
|
+
total_tokens: 450_000,
|
|
2134
|
+
},
|
|
2135
|
+
last_token_usage: {
|
|
2136
|
+
input_tokens: 300_000,
|
|
2137
|
+
cached_input_tokens: 20_000,
|
|
2138
|
+
output_tokens: 10_000,
|
|
2139
|
+
reasoning_output_tokens: 0,
|
|
2140
|
+
total_tokens: 330_000,
|
|
2141
|
+
},
|
|
2142
|
+
model_context_window: 1_050_000,
|
|
2143
|
+
},
|
|
2144
|
+
},
|
|
2145
|
+
}),
|
|
2146
|
+
].join("\n"), "utf8");
|
|
2147
|
+
const config = mergeConfig({
|
|
2148
|
+
sessionLogDirectories: [],
|
|
2149
|
+
cost: {
|
|
2150
|
+
enabled: true,
|
|
2151
|
+
currency: "USD",
|
|
2152
|
+
unknownModelPolicy: "n_a",
|
|
2153
|
+
modelRates: [
|
|
2154
|
+
{
|
|
2155
|
+
model: "gpt-5.4",
|
|
2156
|
+
inputPer1MUsd: 1.25,
|
|
2157
|
+
outputPer1MUsd: 7.5,
|
|
2158
|
+
cachedReadPer1MUsd: 0.125,
|
|
2159
|
+
cachedCreatePer1MUsd: 0,
|
|
2160
|
+
reasoningOutputPer1MUsd: 0,
|
|
2161
|
+
longContextThresholdTokens: 272_000,
|
|
2162
|
+
longContextInputPer1MUsd: 2.5,
|
|
2163
|
+
longContextOutputPer1MUsd: 11.25,
|
|
2164
|
+
contextWindowTokens: 1_050_000,
|
|
2165
|
+
},
|
|
2166
|
+
],
|
|
2167
|
+
},
|
|
2168
|
+
models: {
|
|
2169
|
+
defaultContextWindowTokens: 200_000,
|
|
2170
|
+
contextWindows: [{ model: "gpt-5.4", contextWindowTokens: 1_050_000 }],
|
|
2171
|
+
},
|
|
2172
|
+
sources: {
|
|
2173
|
+
codex_home: {
|
|
2174
|
+
name: "codex_home",
|
|
2175
|
+
enabled: true,
|
|
2176
|
+
roots: [path.join(root, ".codex", "sessions")],
|
|
2177
|
+
includeGlobs: ["**/*.jsonl"],
|
|
2178
|
+
excludeGlobs: [],
|
|
2179
|
+
maxDepth: 8,
|
|
2180
|
+
agentHint: "codex",
|
|
2181
|
+
},
|
|
2182
|
+
claude_projects: {
|
|
2183
|
+
name: "claude_projects",
|
|
2184
|
+
enabled: false,
|
|
2185
|
+
roots: [],
|
|
2186
|
+
includeGlobs: ["**/*.jsonl"],
|
|
2187
|
+
excludeGlobs: [],
|
|
2188
|
+
maxDepth: 8,
|
|
2189
|
+
agentHint: "claude",
|
|
2190
|
+
},
|
|
2191
|
+
claude_history: {
|
|
2192
|
+
name: "claude_history",
|
|
2193
|
+
enabled: false,
|
|
2194
|
+
roots: [],
|
|
2195
|
+
includeGlobs: ["history.jsonl"],
|
|
2196
|
+
excludeGlobs: [],
|
|
2197
|
+
maxDepth: 8,
|
|
2198
|
+
agentHint: "claude",
|
|
2199
|
+
},
|
|
2200
|
+
},
|
|
2201
|
+
});
|
|
2202
|
+
const index = new TraceIndex(config);
|
|
2203
|
+
await index.refresh();
|
|
2204
|
+
const summary = index.getSummaries()[0];
|
|
2205
|
+
expect(summary?.contextWindowPct).toBeCloseTo(30.47619, 5);
|
|
2206
|
+
expect(summary?.costEstimateUsd).toBe(1.06625);
|
|
2207
|
+
});
|
|
1809
2208
|
it("deduplicates repeated claude usage rows by request id for cost estimation", async () => {
|
|
1810
2209
|
const root = await createTempRoot();
|
|
1811
2210
|
const claudeDir = path.join(root, ".claude", "projects", "proj");
|
|
@@ -1925,6 +2324,100 @@ describe("trace index", () => {
|
|
|
1925
2324
|
const summary = index.getSummaries()[0];
|
|
1926
2325
|
expect(summary?.costEstimateUsd).toBe(0.001065);
|
|
1927
2326
|
});
|
|
2327
|
+
it("applies Claude long-context pricing and alias normalization from assistant usage rows", async () => {
|
|
2328
|
+
const root = await createTempRoot();
|
|
2329
|
+
const claudeDir = path.join(root, ".claude", "projects", "proj");
|
|
2330
|
+
await mkdir(claudeDir, { recursive: true });
|
|
2331
|
+
const claudePath = path.join(claudeDir, "session-cost-tiered.jsonl");
|
|
2332
|
+
await writeFile(claudePath, [
|
|
2333
|
+
JSON.stringify({
|
|
2334
|
+
type: "assistant",
|
|
2335
|
+
sessionId: "claude-tiered-sess",
|
|
2336
|
+
uuid: "u1",
|
|
2337
|
+
requestId: "req_tiered_1",
|
|
2338
|
+
message: {
|
|
2339
|
+
model: "claude-sonnet-4-6-20260219",
|
|
2340
|
+
id: "msg_1",
|
|
2341
|
+
type: "message",
|
|
2342
|
+
role: "assistant",
|
|
2343
|
+
content: [{ type: "text", text: "tiered" }],
|
|
2344
|
+
usage: {
|
|
2345
|
+
input_tokens: 100_000,
|
|
2346
|
+
cache_creation_input_tokens: 10_000,
|
|
2347
|
+
cache_read_input_tokens: 120_000,
|
|
2348
|
+
cache_creation: { ephemeral_5m_input_tokens: 5_000, ephemeral_1h_input_tokens: 5_000 },
|
|
2349
|
+
output_tokens: 50_000,
|
|
2350
|
+
},
|
|
2351
|
+
},
|
|
2352
|
+
}),
|
|
2353
|
+
].join("\n"), "utf8");
|
|
2354
|
+
const config = mergeConfig({
|
|
2355
|
+
sessionLogDirectories: [],
|
|
2356
|
+
cost: {
|
|
2357
|
+
enabled: true,
|
|
2358
|
+
currency: "USD",
|
|
2359
|
+
unknownModelPolicy: "n_a",
|
|
2360
|
+
modelRates: [
|
|
2361
|
+
{
|
|
2362
|
+
model: "claude-sonnet-4.6",
|
|
2363
|
+
inputPer1MUsd: 3,
|
|
2364
|
+
outputPer1MUsd: 15,
|
|
2365
|
+
cachedReadPer1MUsd: 0.3,
|
|
2366
|
+
cachedCreatePer1MUsd: 3.75,
|
|
2367
|
+
cachedCreate5mPer1MUsd: 3.75,
|
|
2368
|
+
cachedCreate1hPer1MUsd: 6,
|
|
2369
|
+
reasoningOutputPer1MUsd: 0,
|
|
2370
|
+
longContextThresholdTokens: 200_000,
|
|
2371
|
+
longContextInputPer1MUsd: 6,
|
|
2372
|
+
longContextOutputPer1MUsd: 22.5,
|
|
2373
|
+
longContextCachedReadPer1MUsd: 0.6,
|
|
2374
|
+
longContextCachedCreatePer1MUsd: 7.5,
|
|
2375
|
+
longContextCachedCreate5mPer1MUsd: 7.5,
|
|
2376
|
+
longContextCachedCreate1hPer1MUsd: 12,
|
|
2377
|
+
contextWindowTokens: 1_000_000,
|
|
2378
|
+
},
|
|
2379
|
+
],
|
|
2380
|
+
},
|
|
2381
|
+
models: {
|
|
2382
|
+
defaultContextWindowTokens: 200_000,
|
|
2383
|
+
contextWindows: [{ model: "claude-sonnet-4.6", contextWindowTokens: 1_000_000 }],
|
|
2384
|
+
},
|
|
2385
|
+
sources: {
|
|
2386
|
+
claude_projects: {
|
|
2387
|
+
name: "claude_projects",
|
|
2388
|
+
enabled: true,
|
|
2389
|
+
roots: [path.join(root, ".claude", "projects")],
|
|
2390
|
+
includeGlobs: ["**/*.jsonl"],
|
|
2391
|
+
excludeGlobs: [],
|
|
2392
|
+
maxDepth: 8,
|
|
2393
|
+
agentHint: "claude",
|
|
2394
|
+
},
|
|
2395
|
+
codex_home: {
|
|
2396
|
+
name: "codex_home",
|
|
2397
|
+
enabled: false,
|
|
2398
|
+
roots: [],
|
|
2399
|
+
includeGlobs: ["**/*.jsonl"],
|
|
2400
|
+
excludeGlobs: [],
|
|
2401
|
+
maxDepth: 8,
|
|
2402
|
+
agentHint: "codex",
|
|
2403
|
+
},
|
|
2404
|
+
claude_history: {
|
|
2405
|
+
name: "claude_history",
|
|
2406
|
+
enabled: false,
|
|
2407
|
+
roots: [],
|
|
2408
|
+
includeGlobs: ["history.jsonl"],
|
|
2409
|
+
excludeGlobs: [],
|
|
2410
|
+
maxDepth: 8,
|
|
2411
|
+
agentHint: "claude",
|
|
2412
|
+
},
|
|
2413
|
+
},
|
|
2414
|
+
});
|
|
2415
|
+
const index = new TraceIndex(config);
|
|
2416
|
+
await index.refresh();
|
|
2417
|
+
const summary = index.getSummaries()[0];
|
|
2418
|
+
expect(summary?.contextWindowPct).toBe(23);
|
|
2419
|
+
expect(summary?.costEstimateUsd).toBe(1.8945);
|
|
2420
|
+
});
|
|
1928
2421
|
it("redacts secret-like values from event previews and raw payloads", async () => {
|
|
1929
2422
|
const root = await createTempRoot();
|
|
1930
2423
|
const codexDir = path.join(root, ".codex", "sessions", "2026", "02", "13");
|
|
@@ -2108,6 +2601,101 @@ describe("trace index", () => {
|
|
|
2108
2601
|
index.stop();
|
|
2109
2602
|
}
|
|
2110
2603
|
});
|
|
2604
|
+
it("serves activity artifacts for cold traces without materializing full events", async () => {
|
|
2605
|
+
const root = await createTempRoot();
|
|
2606
|
+
const codexDir = path.join(root, ".codex", "sessions", "2026", "02", "13");
|
|
2607
|
+
await mkdir(codexDir, { recursive: true });
|
|
2608
|
+
for (const [index, filename] of ["rollout-a.jsonl", "rollout-b.jsonl", "rollout-c.jsonl"].entries()) {
|
|
2609
|
+
await writeFile(path.join(codexDir, filename), [
|
|
2610
|
+
JSON.stringify({
|
|
2611
|
+
timestamp: `2026-02-13T10:00:0${index}.000Z`,
|
|
2612
|
+
type: "session_meta",
|
|
2613
|
+
payload: { id: `sess-${index}`, cwd: "/tmp/project", cli_version: "0.1.0" },
|
|
2614
|
+
}),
|
|
2615
|
+
JSON.stringify({
|
|
2616
|
+
timestamp: `2026-02-13T10:00:1${index}.000Z`,
|
|
2617
|
+
type: "response_item",
|
|
2618
|
+
payload: {
|
|
2619
|
+
type: "message",
|
|
2620
|
+
role: "assistant",
|
|
2621
|
+
content: [{ type: "output_text", text: `trace-${index}` }],
|
|
2622
|
+
},
|
|
2623
|
+
}),
|
|
2624
|
+
].join("\n") + "\n", "utf8");
|
|
2625
|
+
}
|
|
2626
|
+
const config = mergeConfig({
|
|
2627
|
+
retention: {
|
|
2628
|
+
strategy: "aggressive_recency",
|
|
2629
|
+
hotTraceCount: 1,
|
|
2630
|
+
warmTraceCount: 0,
|
|
2631
|
+
maxResidentEventsPerHotTrace: 1000,
|
|
2632
|
+
maxResidentEventsPerWarmTrace: 50,
|
|
2633
|
+
detailLoadMode: "lazy_from_disk",
|
|
2634
|
+
},
|
|
2635
|
+
sessionLogDirectories: [],
|
|
2636
|
+
sources: {
|
|
2637
|
+
codex_home: {
|
|
2638
|
+
name: "codex_home",
|
|
2639
|
+
enabled: true,
|
|
2640
|
+
roots: [path.join(root, ".codex", "sessions")],
|
|
2641
|
+
includeGlobs: ["**/*.jsonl"],
|
|
2642
|
+
excludeGlobs: [],
|
|
2643
|
+
maxDepth: 8,
|
|
2644
|
+
agentHint: "codex",
|
|
2645
|
+
},
|
|
2646
|
+
claude_projects: {
|
|
2647
|
+
name: "claude_projects",
|
|
2648
|
+
enabled: false,
|
|
2649
|
+
roots: [],
|
|
2650
|
+
includeGlobs: ["**/*.jsonl"],
|
|
2651
|
+
excludeGlobs: [],
|
|
2652
|
+
maxDepth: 8,
|
|
2653
|
+
agentHint: "claude",
|
|
2654
|
+
},
|
|
2655
|
+
claude_history: {
|
|
2656
|
+
name: "claude_history",
|
|
2657
|
+
enabled: false,
|
|
2658
|
+
roots: [],
|
|
2659
|
+
includeGlobs: ["history.jsonl"],
|
|
2660
|
+
excludeGlobs: [],
|
|
2661
|
+
maxDepth: 8,
|
|
2662
|
+
agentHint: "claude",
|
|
2663
|
+
},
|
|
2664
|
+
},
|
|
2665
|
+
});
|
|
2666
|
+
const index = new TraceIndex(config);
|
|
2667
|
+
await index.start();
|
|
2668
|
+
try {
|
|
2669
|
+
const summaries = index.getSummaries();
|
|
2670
|
+
const coldTraceId = summaries[2]?.id;
|
|
2671
|
+
expect(coldTraceId).toBeTruthy();
|
|
2672
|
+
if (!coldTraceId) {
|
|
2673
|
+
throw new Error("missing cold trace");
|
|
2674
|
+
}
|
|
2675
|
+
const internal = index;
|
|
2676
|
+
const entry = internal.entries.get(coldTraceId);
|
|
2677
|
+
expect(entry?.cachedFullEvents).toBeNull();
|
|
2678
|
+
expect(entry?.cachedRawEvents).toBeNull();
|
|
2679
|
+
expect(entry?.activityArtifacts).toMatchObject({
|
|
2680
|
+
eventCount: 2,
|
|
2681
|
+
eventTimestamps: [],
|
|
2682
|
+
});
|
|
2683
|
+
const parseFileSyncSpy = vi.spyOn(internal.parserRegistry, "parseFileSync");
|
|
2684
|
+
const artifacts = index.getSessionActivityArtifacts(coldTraceId);
|
|
2685
|
+
expect(artifacts.eventCount).toBe(2);
|
|
2686
|
+
expect(artifacts.eventTimestamps).toEqual([]);
|
|
2687
|
+
expect(entry?.cachedFullEvents).toBeNull();
|
|
2688
|
+
expect(entry?.cachedRawEvents).toBeNull();
|
|
2689
|
+
expect(entry?.activityArtifacts).toMatchObject({
|
|
2690
|
+
eventCount: 2,
|
|
2691
|
+
eventTimestamps: [],
|
|
2692
|
+
});
|
|
2693
|
+
expect(parseFileSyncSpy).not.toHaveBeenCalled();
|
|
2694
|
+
}
|
|
2695
|
+
finally {
|
|
2696
|
+
index.stop();
|
|
2697
|
+
}
|
|
2698
|
+
});
|
|
2111
2699
|
it("keeps codex model and cost metrics after appends with strict redaction", async () => {
|
|
2112
2700
|
const root = await createTempRoot();
|
|
2113
2701
|
const codexDir = path.join(root, ".codex", "sessions", "2026", "02", "13");
|