11import fs from 'node:fs' ;
22import path from 'node:path' ;
33import { 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
66function 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+
4147export 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