Skip to content

Commit 7c0759e

Browse files
fix provider budget accounting
1 parent f5c4da9 commit 7c0759e

1 file changed

Lines changed: 21 additions & 6 deletions

File tree

global-template/docgen/lib/provider.mjs

Lines changed: 21 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import fs from 'node:fs';
22
import path from 'node:path';
33
import { spawn } from 'node:child_process';
4-
import { appendJsonl, commandExists, ensureDir, estimateTokens, loadConfig, now, projectPaths, readJson, sha256, sleep, writeJson } from './core.mjs';
4+
import { appendJsonl, commandExists, ensureDir, estimateTokens, loadConfig, now, projectPaths, sha256, sleep, writeJson } from './core.mjs';
55

66
function executable(config) {
77
if (process.env.DOCGEN_COMMAND_CODE_BIN) return process.env.DOCGEN_COMMAND_CODE_BIN;
@@ -38,16 +38,27 @@ export function telemetry(root) {
3838
return fs.readFileSync(file, 'utf8').split(/\r?\n/).filter(Boolean).map((line) => JSON.parse(line));
3939
}
4040

41+
function completedRuns(root) {
42+
const terminal = new Map();
43+
for (const record of telemetry(root)) if (record.status === 'completed' || record.status === 'failed') terminal.set(record.runId, record);
44+
return [...terminal.values()];
45+
}
46+
4147
export function budgetReport(root) {
42-
const paths = projectPaths(root); const config = loadConfig(root); const limits = budgetConfig(config); const runs = telemetry(root);
48+
const paths = projectPaths(root); const limits = budgetConfig(loadConfig(root)); const runs = completedRuns(root);
4349
const usage = {
44-
providerCalls: runs.filter((x) => x.status === 'completed' || x.status === 'failed').length,
50+
providerCalls: runs.length,
4551
estimatedInputTokens: runs.reduce((n, x) => n + Number(x.estimatedInputTokens ?? 0), 0),
4652
estimatedOutputTokens: runs.reduce((n, x) => n + Number(x.estimatedOutputTokens ?? 0), 0),
4753
cacheHits: runs.filter((x) => x.cacheHit).length
4854
};
4955
const remaining = { providerCalls: limits.maxProviderCalls - usage.providerCalls, estimatedInputTokens: limits.maxEstimatedInputTokens - usage.estimatedInputTokens, estimatedOutputTokens: limits.maxEstimatedOutputTokens - usage.estimatedOutputTokens };
50-
const report = { schemaVersion: '2.0', generatedAt: now(), limits, usage, remaining, exceeded: Object.values(remaining).some((x) => x < 0), stages: Object.fromEntries([...new Set(runs.map((x) => x.stage))].map((stage) => [stage, { calls: runs.filter((x) => x.stage === stage).length, inputTokens: runs.filter((x) => x.stage === stage).reduce((n, x) => n + Number(x.estimatedInputTokens ?? 0), 0), outputTokens: runs.filter((x) => x.stage === stage).reduce((n, x) => n + Number(x.estimatedOutputTokens ?? 0), 0) }])) };
56+
const stages = {};
57+
for (const run of runs) {
58+
const stage = stages[run.stage] ??= { calls: 0, inputTokens: 0, outputTokens: 0 };
59+
stage.calls++; stage.inputTokens += Number(run.estimatedInputTokens ?? 0); stage.outputTokens += Number(run.estimatedOutputTokens ?? 0);
60+
}
61+
const report = { schemaVersion: '2.0', generatedAt: now(), limits, usage, remaining, exceeded: Object.values(remaining).some((x) => x < 0), stages };
5162
writeJson(paths.budget, report); return report;
5263
}
5364

@@ -66,10 +77,14 @@ function runOnce(root, stage, target, prompt, attempt, maxAttempts) {
6677
const record = { schemaVersion: '2.0', runId, stage, target: target || null, attempt, maxAttempts, startedAt, estimatedInputTokens: inputTokens, promptHash: sha256(prompt), model: process.env.DOCGEN_MODEL || config.commandCode?.stageModels?.[stage] || config.commandCode?.model || null, status: 'running' };
6778
appendJsonl(path.join(paths.telemetry, 'provider-runs.jsonl'), record);
6879
return new Promise((resolve, reject) => {
80+
let settled = false;
6981
const child = spawn(bin, args, { cwd: root, env: { ...process.env, DOCGEN_MODE: '1', DOCGEN_STAGE: stage, DOCGEN_TARGET: target, DOCGEN_CONTEXT_ONLY: '1' }, shell: process.platform === 'win32', stdio: ['pipe', 'pipe', 'pipe'], windowsHide: true });
70-
let stdout = ''; let stderr = ''; child.stdout.on('data', (chunk) => { stdout += chunk; fs.appendFileSync(stdoutFile, chunk); }); child.stderr.on('data', (chunk) => { stderr += chunk; fs.appendFileSync(stderrFile, chunk); });
71-
child.on('error', reject);
82+
let stdout = ''; let stderr = '';
83+
child.stdout.on('data', (chunk) => { stdout += chunk; fs.appendFileSync(stdoutFile, chunk); });
84+
child.stderr.on('data', (chunk) => { stderr += chunk; fs.appendFileSync(stderrFile, chunk); });
85+
child.on('error', (error) => { if (!settled) { settled = true; reject(error); } });
7286
child.on('close', (code) => {
87+
if (settled) return; settled = true;
7388
const completed = { ...record, finishedAt: now(), exitCode: code ?? 1, status: code === 0 ? 'completed' : 'failed', estimatedOutputTokens: estimateTokens(stdout + stderr), stdoutFile: path.relative(root, stdoutFile).replaceAll('\\', '/'), stderrFile: path.relative(root, stderrFile).replaceAll('\\', '/') };
7489
appendJsonl(path.join(paths.telemetry, 'provider-runs.jsonl'), completed); budgetReport(root);
7590
if (code === 0) resolve(completed); else reject(Object.assign(new Error(`${stage}${target ? `:${target}` : ''} failed with exit ${code}. ${stderr.slice(-2000)}`), { exitCode: code ?? 1, stdout, stderr }));

0 commit comments

Comments
 (0)