mirror of
https://github.com/garrytan/gstack.git
synced 2026-11-04 15:57:33 +01:00
fix: transcript pricing never throws from a PTY ledger writer
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
This commit is contained in:
1 parent
55bd791f32
commit
876cf1a8d3
2 files changed
+22
-21
No files matched your search
@@ -45,6 +45,7 @@ describe('ptyTranscriptCostUsd', () => {
|
||||
const unpriced = fixture(project => fs.writeFileSync(path.join(project, `${SID}.jsonl`),
|
||||
assistant('msg_a', 'claude-opus-5-5', { output_tokens: 1 }) + '\n' + assistant('msg_b', 'some-unpriced-model', { output_tokens: 1 }) + '\n'));
|
||||
const empty = fixture(project => fs.writeFileSync(path.join(project, `${SID}.jsonl`), JSON.stringify({ type: 'user', sessionId: SID }) + '\n'));
|
||||
const unreadable = fixture(project => fs.mkdirSync(path.join(project, `${SID}.jsonl`)));
|
||||
try {
|
||||
expect(ptyTranscriptCostUsd(unpriced, SID)).toBeUndefined();
|
||||
expect(ptyTranscriptCostUsd(empty, SID)).toBeUndefined();
|
||||
@@ -53,6 +54,7 @@ describe('ptyTranscriptCostUsd', () => {
|
||||
expect(ptyTranscriptCostUsd(null, SID)).toBeUndefined();
|
||||
expect(ptyTranscriptCostUsd(path.join(empty, 'missing'), SID)).toBeUndefined();
|
||||
expect(pricedTranscript(undefined)).toEqual({});
|
||||
} finally { for (const dir of [unpriced, empty]) fs.rmSync(dir, { recursive: true, force: true }); }
|
||||
expect(ptyTranscriptCostUsd(unreadable, SID)).toBeUndefined();
|
||||
} finally { for (const dir of [unpriced, empty, unreadable]) fs.rmSync(dir, { recursive: true, force: true }); }
|
||||
});
|
||||
});
|
||||
@@ -12,31 +12,30 @@ export function pricedTranscript(session: { hermeticConfigDir?: string | null; s
|
||||
* Price one PTY session from the transcripts Claude Code wrote under its owned config dir:
|
||||
* `projects/<dir>/<sessionId>.jsonl` (inline sidechains included) and `projects/<dir>/<sessionId>/subagents/*.jsonl`,
|
||||
* rows deduplicated by message id. Undefined (cost-unknown, never $0) without a config dir, session id, transcript or
|
||||
* usage, or when a message's model has no price row.
|
||||
* usage, when a message's model has no price row, or when any read fails: it runs in ledger writers before close and
|
||||
* cleanup, so it never throws.
|
||||
*/
|
||||
export function ptyTranscriptCostUsd(configDir: string | null | undefined, sessionId: string | undefined): number | undefined {
|
||||
if (!configDir || !sessionId) return undefined;
|
||||
const projects = path.join(configDir, 'projects');
|
||||
let dirs: fs.Dirent[];
|
||||
try {
|
||||
dirs = fs.readdirSync(projects, { withFileTypes: true });
|
||||
const projects = path.join(configDir, 'projects');
|
||||
const files: string[] = [];
|
||||
for (const dir of fs.readdirSync(projects, { withFileTypes: true })) {
|
||||
if (!dir.isDirectory()) continue;
|
||||
const main = path.join(projects, dir.name, `${sessionId}.jsonl`);
|
||||
if (fs.existsSync(main)) files.push(main);
|
||||
const subagents = path.join(projects, dir.name, sessionId, 'subagents');
|
||||
if (fs.existsSync(subagents)) files.push(...fs.readdirSync(subagents).filter(f => f.endsWith('.jsonl')).map(f => path.join(subagents, f)));
|
||||
}
|
||||
const rows = files.flatMap(file => fs.readFileSync(file, 'utf8').split('\n').flatMap(line => {
|
||||
try {
|
||||
return [JSON.parse(line)];
|
||||
} catch {
|
||||
return [];
|
||||
}
|
||||
}));
|
||||
return streamedUsageCostUsd(rows);
|
||||
} catch {
|
||||
return undefined;
|
||||
}
|
||||
const files: string[] = [];
|
||||
for (const dir of dirs) {
|
||||
if (!dir.isDirectory()) continue;
|
||||
const main = path.join(projects, dir.name, `${sessionId}.jsonl`);
|
||||
if (fs.existsSync(main)) files.push(main);
|
||||
const subagents = path.join(projects, dir.name, sessionId, 'subagents');
|
||||
if (fs.existsSync(subagents)) files.push(...fs.readdirSync(subagents).filter(f => f.endsWith('.jsonl')).map(f => path.join(subagents, f)));
|
||||
}
|
||||
const rows = files.flatMap(file => fs.readFileSync(file, 'utf8').split('\n').flatMap(line => {
|
||||
try {
|
||||
return [JSON.parse(line)];
|
||||
} catch {
|
||||
return [];
|
||||
}
|
||||
}));
|
||||
return streamedUsageCostUsd(rows);
|
||||
}
|
||||
Reference in new issue
Block a user