codiedev 0.9.7-beta.0 → 0.9.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/hook.js +12 -0
- package/dist/utils.d.ts +22 -0
- package/dist/utils.js +145 -0
- package/package.json +1 -1
package/dist/hook.js
CHANGED
|
@@ -256,6 +256,11 @@ async function main() {
|
|
|
256
256
|
// server-side against a merged MR/PR's source_branch / commits for AI attribution.
|
|
257
257
|
const branch = (0, utils_1.getGitBranch)(workspaceRoot);
|
|
258
258
|
const headSha = (0, utils_1.getGitHeadSha)(workspaceRoot);
|
|
259
|
+
// Cost/velocity/steering signals parsed straight from the transcript we're
|
|
260
|
+
// already holding — no extra hooks, no extra git/process calls.
|
|
261
|
+
const usage = (0, utils_1.parseUsage)(transcriptContent, mode);
|
|
262
|
+
const sessionStartedAt = (0, utils_1.parseSessionStart)(transcriptContent);
|
|
263
|
+
const steering = (0, utils_1.parseSteering)(transcriptContent, mode);
|
|
259
264
|
const payload = (0, utils_1.buildCapturePayload)({
|
|
260
265
|
sessionId: session_id,
|
|
261
266
|
transcriptBase64,
|
|
@@ -268,6 +273,13 @@ async function main() {
|
|
|
268
273
|
source: mode,
|
|
269
274
|
branch,
|
|
270
275
|
headSha,
|
|
276
|
+
inputTokens: usage.inputTokens,
|
|
277
|
+
outputTokens: usage.outputTokens,
|
|
278
|
+
cacheTokens: usage.cacheTokens,
|
|
279
|
+
model: usage.model,
|
|
280
|
+
sessionStartedAt,
|
|
281
|
+
interruptCount: steering.interrupts,
|
|
282
|
+
rejectionCount: steering.rejections,
|
|
271
283
|
});
|
|
272
284
|
// Fire the upload from a fully detached child and exit immediately. Awaiting
|
|
273
285
|
// it inline let Claude Code's SessionEnd shutdown window elapse and kill the
|
package/dist/utils.d.ts
CHANGED
|
@@ -41,6 +41,13 @@ export interface CapturePayloadInput {
|
|
|
41
41
|
source: string;
|
|
42
42
|
branch?: string | null;
|
|
43
43
|
headSha?: string | null;
|
|
44
|
+
inputTokens?: number;
|
|
45
|
+
outputTokens?: number;
|
|
46
|
+
cacheTokens?: number;
|
|
47
|
+
model?: string | null;
|
|
48
|
+
sessionStartedAt?: number | null;
|
|
49
|
+
interruptCount?: number;
|
|
50
|
+
rejectionCount?: number;
|
|
44
51
|
}
|
|
45
52
|
export declare function buildCapturePayload(input: CapturePayloadInput): Record<string, unknown>;
|
|
46
53
|
export declare function hashToken(token: string): string;
|
|
@@ -119,6 +126,21 @@ export interface ParsedStats {
|
|
|
119
126
|
messageCount: number;
|
|
120
127
|
toolCallCount: number;
|
|
121
128
|
}
|
|
129
|
+
export interface SteeringStats {
|
|
130
|
+
interrupts: number;
|
|
131
|
+
rejections: number;
|
|
132
|
+
}
|
|
133
|
+
export declare function parseClaudeSteering(content: string): SteeringStats;
|
|
134
|
+
export declare function parseCodexSteering(content: string): SteeringStats;
|
|
135
|
+
export declare function parseSteering(content: string, mode: "claude-code" | "codex" | "cursor"): SteeringStats;
|
|
136
|
+
export interface UsageStats {
|
|
137
|
+
inputTokens: number;
|
|
138
|
+
outputTokens: number;
|
|
139
|
+
cacheTokens: number;
|
|
140
|
+
model: string | null;
|
|
141
|
+
}
|
|
142
|
+
export declare function parseUsage(content: string, mode: "claude-code" | "codex" | "cursor"): UsageStats;
|
|
143
|
+
export declare function parseSessionStart(content: string): number | null;
|
|
122
144
|
export declare function parseClaudeCodeStats(content: string): ParsedStats;
|
|
123
145
|
export declare function parseCodexStats(content: string): ParsedStats;
|
|
124
146
|
export declare function extractCodexRepoUrl(content: string): string | null;
|
package/dist/utils.js
CHANGED
|
@@ -65,6 +65,11 @@ exports.cleanupCodexMcp = cleanupCodexMcp;
|
|
|
65
65
|
exports.installCodexMcp = installCodexMcp;
|
|
66
66
|
exports.installCodexHook = installCodexHook;
|
|
67
67
|
exports.installCursorHook = installCursorHook;
|
|
68
|
+
exports.parseClaudeSteering = parseClaudeSteering;
|
|
69
|
+
exports.parseCodexSteering = parseCodexSteering;
|
|
70
|
+
exports.parseSteering = parseSteering;
|
|
71
|
+
exports.parseUsage = parseUsage;
|
|
72
|
+
exports.parseSessionStart = parseSessionStart;
|
|
68
73
|
exports.parseClaudeCodeStats = parseClaudeCodeStats;
|
|
69
74
|
exports.parseCodexStats = parseCodexStats;
|
|
70
75
|
exports.extractCodexRepoUrl = extractCodexRepoUrl;
|
|
@@ -349,6 +354,15 @@ function buildCapturePayload(input) {
|
|
|
349
354
|
toolCallCount: input.toolCallCount,
|
|
350
355
|
capturedAt: input.capturedAt,
|
|
351
356
|
source: input.source,
|
|
357
|
+
// New capture signals (cost / velocity / steering). Optional — omitted
|
|
358
|
+
// (undefined) keys drop out of the JSON body, so older servers ignore them.
|
|
359
|
+
inputTokens: input.inputTokens,
|
|
360
|
+
outputTokens: input.outputTokens,
|
|
361
|
+
cacheTokens: input.cacheTokens,
|
|
362
|
+
model: input.model ?? undefined,
|
|
363
|
+
sessionStartedAt: input.sessionStartedAt ?? undefined,
|
|
364
|
+
interruptCount: input.interruptCount,
|
|
365
|
+
rejectionCount: input.rejectionCount,
|
|
352
366
|
};
|
|
353
367
|
}
|
|
354
368
|
function hashToken(token) {
|
|
@@ -1032,6 +1046,137 @@ function installCursorHook() {
|
|
|
1032
1046
|
hooks.sessionEnd = filtered;
|
|
1033
1047
|
fs.writeFileSync(CURSOR_HOOKS_PATH, JSON.stringify(hooksFile, null, 2), "utf8");
|
|
1034
1048
|
}
|
|
1049
|
+
function parseClaudeSteering(content) {
|
|
1050
|
+
let interrupts = 0;
|
|
1051
|
+
let rejections = 0;
|
|
1052
|
+
for (const line of content.split("\n")) {
|
|
1053
|
+
if (!line.trim())
|
|
1054
|
+
continue;
|
|
1055
|
+
// "[Request interrupted by user]" / "...for tool use" — Claude Code markers
|
|
1056
|
+
if (line.includes("[Request interrupted by user"))
|
|
1057
|
+
interrupts++;
|
|
1058
|
+
// Standard Claude Code tool-rejection sentinels in tool_result content
|
|
1059
|
+
if (line.includes("doesn't want to take this action") ||
|
|
1060
|
+
line.includes("user doesn't want to proceed"))
|
|
1061
|
+
rejections++;
|
|
1062
|
+
}
|
|
1063
|
+
return { interrupts, rejections };
|
|
1064
|
+
}
|
|
1065
|
+
function parseCodexSteering(content) {
|
|
1066
|
+
let interrupts = 0;
|
|
1067
|
+
let rejections = 0;
|
|
1068
|
+
for (const line of content.split("\n")) {
|
|
1069
|
+
if (!line.trim())
|
|
1070
|
+
continue;
|
|
1071
|
+
try {
|
|
1072
|
+
const e = JSON.parse(line);
|
|
1073
|
+
const pt = e?.payload?.type;
|
|
1074
|
+
if (e?.type === "event_msg" && pt === "turn_aborted")
|
|
1075
|
+
interrupts++;
|
|
1076
|
+
if (e?.type === "response_item" && pt === "function_call_output") {
|
|
1077
|
+
const out = String(e.payload?.output ?? "");
|
|
1078
|
+
if (/\b(rejected|denied|not approved)\b/i.test(out))
|
|
1079
|
+
rejections++;
|
|
1080
|
+
}
|
|
1081
|
+
}
|
|
1082
|
+
catch {
|
|
1083
|
+
// skip malformed
|
|
1084
|
+
}
|
|
1085
|
+
}
|
|
1086
|
+
return { interrupts, rejections };
|
|
1087
|
+
}
|
|
1088
|
+
function parseSteering(content, mode) {
|
|
1089
|
+
return mode === "codex"
|
|
1090
|
+
? parseCodexSteering(content)
|
|
1091
|
+
: parseClaudeSteering(content);
|
|
1092
|
+
}
|
|
1093
|
+
function parseClaudeUsage(content) {
|
|
1094
|
+
// Claude Code logs per-turn usage; sum across the session. `input_tokens` is
|
|
1095
|
+
// already fresh (cache read/creation are reported separately).
|
|
1096
|
+
let inputTokens = 0;
|
|
1097
|
+
let outputTokens = 0;
|
|
1098
|
+
let cacheTokens = 0;
|
|
1099
|
+
let model = null;
|
|
1100
|
+
for (const line of content.split("\n")) {
|
|
1101
|
+
if (!line.trim())
|
|
1102
|
+
continue;
|
|
1103
|
+
try {
|
|
1104
|
+
const e = JSON.parse(line);
|
|
1105
|
+
const m = e?.message;
|
|
1106
|
+
const u = m?.usage;
|
|
1107
|
+
if (u) {
|
|
1108
|
+
inputTokens += u.input_tokens ?? 0;
|
|
1109
|
+
outputTokens += u.output_tokens ?? 0;
|
|
1110
|
+
cacheTokens += (u.cache_creation_input_tokens ?? 0) + (u.cache_read_input_tokens ?? 0);
|
|
1111
|
+
}
|
|
1112
|
+
if (typeof m?.model === "string" && m.model)
|
|
1113
|
+
model = m.model;
|
|
1114
|
+
}
|
|
1115
|
+
catch {
|
|
1116
|
+
// skip malformed
|
|
1117
|
+
}
|
|
1118
|
+
}
|
|
1119
|
+
return { inputTokens, outputTokens, cacheTokens, model };
|
|
1120
|
+
}
|
|
1121
|
+
function parseCodexUsage(content) {
|
|
1122
|
+
// Codex reports a CUMULATIVE token_count per turn — the last one is the
|
|
1123
|
+
// session total. `input_tokens` includes the cached portion, so subtract it.
|
|
1124
|
+
let model = null;
|
|
1125
|
+
let last = null;
|
|
1126
|
+
for (const line of content.split("\n")) {
|
|
1127
|
+
if (!line.trim())
|
|
1128
|
+
continue;
|
|
1129
|
+
try {
|
|
1130
|
+
const e = JSON.parse(line);
|
|
1131
|
+
if (e?.type === "turn_context" && typeof e.payload?.model === "string" && e.payload.model) {
|
|
1132
|
+
model = e.payload.model;
|
|
1133
|
+
}
|
|
1134
|
+
if (e?.type === "event_msg" && e.payload?.type === "token_count") {
|
|
1135
|
+
const t = e.payload?.info?.total_token_usage;
|
|
1136
|
+
if (t)
|
|
1137
|
+
last = t;
|
|
1138
|
+
}
|
|
1139
|
+
}
|
|
1140
|
+
catch {
|
|
1141
|
+
// skip malformed
|
|
1142
|
+
}
|
|
1143
|
+
}
|
|
1144
|
+
if (!last)
|
|
1145
|
+
return { inputTokens: 0, outputTokens: 0, cacheTokens: 0, model };
|
|
1146
|
+
const cached = last.cached_input_tokens ?? 0;
|
|
1147
|
+
const input = last.input_tokens ?? 0;
|
|
1148
|
+
return {
|
|
1149
|
+
inputTokens: Math.max(0, input - cached),
|
|
1150
|
+
outputTokens: (last.output_tokens ?? 0) + (last.reasoning_output_tokens ?? 0),
|
|
1151
|
+
cacheTokens: cached,
|
|
1152
|
+
model,
|
|
1153
|
+
};
|
|
1154
|
+
}
|
|
1155
|
+
function parseUsage(content, mode) {
|
|
1156
|
+
return mode === "codex" ? parseCodexUsage(content) : parseClaudeUsage(content);
|
|
1157
|
+
}
|
|
1158
|
+
// Lead-time start proxy: the earliest top-level `timestamp` in the transcript.
|
|
1159
|
+
// Both Claude Code and Codex stamp every line, so this is mode-agnostic.
|
|
1160
|
+
function parseSessionStart(content) {
|
|
1161
|
+
let min = null;
|
|
1162
|
+
for (const line of content.split("\n")) {
|
|
1163
|
+
if (!line.trim())
|
|
1164
|
+
continue;
|
|
1165
|
+
try {
|
|
1166
|
+
const e = JSON.parse(line);
|
|
1167
|
+
const ts = e?.timestamp;
|
|
1168
|
+
if (typeof ts === "string") {
|
|
1169
|
+
const ms = Date.parse(ts);
|
|
1170
|
+
if (!Number.isNaN(ms) && (min === null || ms < min))
|
|
1171
|
+
min = ms;
|
|
1172
|
+
}
|
|
1173
|
+
}
|
|
1174
|
+
catch {
|
|
1175
|
+
// skip malformed
|
|
1176
|
+
}
|
|
1177
|
+
}
|
|
1178
|
+
return min;
|
|
1179
|
+
}
|
|
1035
1180
|
function parseClaudeCodeStats(content) {
|
|
1036
1181
|
let messageCount = 0;
|
|
1037
1182
|
let toolCallCount = 0;
|
package/package.json
CHANGED