Showing the changes to apps/server/src/usage/usageTranscripts.test.ts (1 of 3 files). Show all
Unified · Split
| @@ -134,6 +134,106 @@ describe("parseCodexLine", () => { · 133 unmodified lines | |||
| 134 | parseCodexLine(turnContext, state); | 134 | parseCodexLine(turnContext, state); |
| 135 | expect(parseCodexLine(tokenCount(100, 0, 10, 0), state)).not.toBeNull(); | 135 | expect(parseCodexLine(tokenCount(100, 0, 10, 0), state)).not.toBeNull(); |
| 136 | }); | 136 | }); |
| 137 | |||
| 138 | // A forked/subagent rollout opens with the parent's history copied in and | ||
| 139 | // every line re-stamped to the fork instant, then the ancestors' session | ||
| 140 | // metas. Counting those again multiplied usage ~1.85x on real data (#5758). | ||
| 141 | describe("forked rollouts", () => { | ||
| 142 | const meta = (overrides: { | ||
| 143 | id: string; | ||
| 144 | timestamp: string; | ||
| 145 | forkedFromId?: string; | ||
| 146 | spawnParentId?: string; | ||
| 147 | }) => | ||
| 148 | JSON.stringify({ | ||
| 149 | type: "session_meta", | ||
| 150 | timestamp: overrides.timestamp, | ||
| 151 | payload: { | ||
| 152 | type: "session_meta", | ||
| 153 | id: overrides.id, | ||
| 154 | ...(overrides.forkedFromId === undefined | ||
| 155 | ? {} | ||
| 156 | : { forked_from_id: overrides.forkedFromId }), | ||
| 157 | ...(overrides.spawnParentId === undefined | ||
| 158 | ? {} | ||
| 159 | : { | ||
| 160 | source: { | ||
| 161 | subagent: { thread_spawn: { parent_thread_id: overrides.spawnParentId } }, | ||
| 162 | }, | ||
| 163 | }), | ||
| 164 | }, | ||
| 165 | }); | ||
| 166 | const stamped = (timestamp: string, line: string) => { | ||
| 167 | const parsed = JSON.parse(line) as { timestamp: string }; | ||
| 168 | parsed.timestamp = timestamp; | ||
| 169 | return JSON.stringify(parsed); | ||
| 170 | }; | ||
| 171 | |||
| 172 | it("keeps the child session id over copied ancestor metas", () => { | ||
| 173 | const state = initialCodexScanState(); | ||
| 174 | parseCodexLine(meta({ id: "child", timestamp: "2026-08-01T05:00:00.000Z" }), state); | ||
| 175 | parseCodexLine(meta({ id: "parent", timestamp: "2026-08-01T05:00:00.000Z" }), state); | ||
| 176 | parseCodexLine(turnContext, state); | ||
| 177 | const record = parseCodexLine(tokenCount(100, 0, 10, 0), state); | ||
| 178 | |||
| 179 | expect(record?.sessionId).toBe("child"); | ||
| 180 | }); | ||
| 181 | |||
| 182 | it("drops the re-stamped copied burst and keeps the first real event", () => { | ||
| 183 | const state = initialCodexScanState(); | ||
| 184 | const forkInstant = "2026-08-01T05:00:00.000Z"; | ||
| 185 | parseCodexLine(meta({ id: "child", timestamp: forkInstant, forkedFromId: "parent" }), state); | ||
| 186 | parseCodexLine(meta({ id: "parent", timestamp: forkInstant }), state); | ||
| 187 | parseCodexLine(stamped(forkInstant, turnContext), state); | ||
| 188 | |||
| 189 | // Copied history: written in one burst at the fork instant. | ||
| 190 | expect( | ||
| 191 | parseCodexLine(stamped("2026-08-01T05:00:00.001Z", tokenCount(100, 0, 10, 0)), state), | ||
| 192 | ).toBeNull(); | ||
| 193 | expect( | ||
| 194 | parseCodexLine(stamped("2026-08-01T05:00:00.002Z", tokenCount(200, 0, 20, 0)), state), | ||
| 195 | ).toBeNull(); | ||
| 196 | |||
| 197 | // The child's first genuine turn lands seconds later and must count. | ||
| 198 | const real = parseCodexLine( | ||
| 199 | stamped("2026-08-01T05:00:06.000Z", tokenCount(300, 0, 30, 0)), | ||
| 200 | state, | ||
| 201 | ); | ||
| 202 | expect(real).not.toBeNull(); | ||
| 203 | expect(real?.totals.outputTokens).toBe(30); | ||
| 204 | |||
| 205 | // Suppression never restarts, even for closely spaced later events. | ||
| 206 | const next = parseCodexLine( | ||
| 207 | stamped("2026-08-01T05:00:06.100Z", tokenCount(400, 0, 40, 0)), | ||
| 208 | state, | ||
| 209 | ); | ||
| 210 | expect(next).not.toBeNull(); | ||
| 211 | }); | ||
| 212 | |||
| 213 | it("recognizes subagent spawns without forked_from_id", () => { | ||
| 214 | const state = initialCodexScanState(); | ||
| 215 | const spawnInstant = "2026-08-01T05:00:00.000Z"; | ||
| 216 | parseCodexLine( | ||
| 217 | meta({ id: "child", timestamp: spawnInstant, spawnParentId: "parent" }), | ||
| 218 | state, | ||
| 219 | ); | ||
| 220 | parseCodexLine(stamped(spawnInstant, turnContext), state); | ||
| 221 | expect( | ||
| 222 | parseCodexLine(stamped("2026-08-01T05:00:00.001Z", tokenCount(100, 0, 10, 0)), state), | ||
| 223 | ).toBeNull(); | ||
| 224 | }); | ||
| 225 | |||
| 226 | it("does not suppress anything in a rollout that is not a fork", () => { | ||
| 227 | const state = initialCodexScanState(); | ||
| 228 | parseCodexLine(meta({ id: "root", timestamp: "2026-08-01T05:00:00.000Z" }), state); | ||
| 229 | parseCodexLine(stamped("2026-08-01T05:00:00.100Z", turnContext), state); | ||
| 230 | const record = parseCodexLine( | ||
| 231 | stamped("2026-08-01T05:00:00.200Z", tokenCount(100, 0, 10, 0)), | ||
| 232 | state, | ||
| 233 | ); | ||
| 234 | expect(record).not.toBeNull(); | ||
| 235 | }); | ||
| 236 | }); | ||
| 137 | }); | 237 | }); |
| 138 | 238 | ||
| 139 | describe("totalTokens", () => { | 239 | describe("totalTokens", () => { |