feat(observability): opt-in token capture in Stop hook (VOYAGE_TOKEN_METER)
This commit is contained in:
parent
62ebc28e3f
commit
46d51f8088
3 changed files with 243 additions and 2 deletions
|
|
@ -17,6 +17,7 @@
|
|||
// - All stderr prefixed with [voyage].
|
||||
// - EXDEV mitigation: tmp file in same dir as target (do NOT use atomicWriteJson).
|
||||
|
||||
import { stdin } from 'node:process';
|
||||
import { readFileSync, existsSync, writeFileSync, renameSync } from 'node:fs';
|
||||
import { join, dirname } from 'node:path';
|
||||
import { transformToPrometheus } from '../../lib/exporters/textfile-format.mjs';
|
||||
|
|
@ -24,6 +25,13 @@ import { transformToOtlpJson } from '../../lib/exporters/otlp-format.mjs';
|
|||
import { validateTextfilePath } from '../../lib/exporters/path-validator.mjs';
|
||||
import { validateOtlpEndpoint } from '../../lib/exporters/endpoint-validator.mjs';
|
||||
import { applyFieldAllowlist } from '../../lib/exporters/field-allowlist.mjs';
|
||||
import { captureTokenUsage } from '../../lib/stats/token-usage.mjs';
|
||||
|
||||
async function readStdin() {
|
||||
let data = '';
|
||||
for await (const chunk of stdin) data += chunk;
|
||||
return data;
|
||||
}
|
||||
|
||||
const VALID_MODES = new Set(['off', 'textfile', 'otlp']);
|
||||
const TEXTFILE_NAME = 'voyage.prom';
|
||||
|
|
@ -38,6 +46,7 @@ const STATS_FILES = [
|
|||
{ file: 'trekexecute-stats.jsonl', schema: 'trekexecute' },
|
||||
{ file: 'trekreview-stats.jsonl', schema: 'trekreview' },
|
||||
{ file: 'trekcontinue-stats.jsonl', schema: 'trekcontinue' },
|
||||
{ file: 'token-usage-stats.jsonl', schema: 'token-usage' },
|
||||
];
|
||||
|
||||
function loadAndAllowlist(dataDir) {
|
||||
|
|
@ -134,6 +143,26 @@ async function exportOtlp(records, env) {
|
|||
(async () => {
|
||||
try {
|
||||
const env = process.env;
|
||||
|
||||
// SKAL-2: opt-in main-context token/cost capture (default off → zero added
|
||||
// latency). Rides this existing Stop hook rather than a new hook script —
|
||||
// avoids a second-Stop-hook race and keeps the README hook-count pin intact.
|
||||
// Runs BEFORE the export-mode gate so capture is independent of export.
|
||||
// Fail-open: any error is swallowed; capture must never block Stop.
|
||||
if (env.VOYAGE_TOKEN_METER) {
|
||||
try {
|
||||
const raw = await readStdin();
|
||||
if (raw.trim()) {
|
||||
const payload = JSON.parse(raw);
|
||||
captureTokenUsage({
|
||||
transcriptPath: payload.transcript_path,
|
||||
sessionId: payload.session_id,
|
||||
dataDir: env.CLAUDE_PLUGIN_DATA,
|
||||
});
|
||||
}
|
||||
} catch { /* fail-open: never block Stop, never throw */ }
|
||||
}
|
||||
|
||||
const mode = (env.VOYAGE_EXPORT_MODE || 'off').toLowerCase();
|
||||
|
||||
if (mode === 'off') return;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue