@jagit/hook-copilot 0.0.4 → 0.0.5
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/index.d.ts +9 -2
- package/dist/index.js +56 -13
- package/package.json +2 -2
package/dist/index.d.ts
CHANGED
|
@@ -65,6 +65,7 @@ interface ModelUsageBucket {
|
|
|
65
65
|
cachedInputTokens: number;
|
|
66
66
|
outputTokens: number;
|
|
67
67
|
totalTokens: number;
|
|
68
|
+
costUsd: number;
|
|
68
69
|
observations: number;
|
|
69
70
|
}
|
|
70
71
|
interface CopilotDebugUsage {
|
|
@@ -75,11 +76,17 @@ interface CopilotDebugUsage {
|
|
|
75
76
|
cachedInputTokens: number;
|
|
76
77
|
outputTokens: number;
|
|
77
78
|
totalTokens: number;
|
|
79
|
+
costUsd: number | null;
|
|
78
80
|
sourcePath: string;
|
|
79
81
|
modelUsage: Record<string, ModelUsageBucket>;
|
|
80
82
|
}
|
|
81
83
|
export declare function inferWorkspaceIdBySession(sessionId: string, hookTimestamp?: string, baseDir?: string): WorkspaceCandidate | undefined;
|
|
82
|
-
|
|
84
|
+
interface TranscriptPathLocation {
|
|
85
|
+
workspaceId: string;
|
|
86
|
+
sessionId: string;
|
|
87
|
+
}
|
|
88
|
+
export declare function parseTranscriptPathLocation(transcriptPath: string | undefined): TranscriptPathLocation | undefined;
|
|
89
|
+
export declare function resolveDebugUsageBySession(sessionId: string | undefined, hookTimestamp?: string, baseDir?: string, workspaceIdFromTranscript?: string): CopilotDebugUsage | undefined;
|
|
83
90
|
/**
|
|
84
91
|
* Build payload from a real VS Code Copilot agent Stop-hook stdin.
|
|
85
92
|
*
|
|
@@ -88,7 +95,7 @@ export declare function resolveDebugUsageBySession(sessionId: string | undefined
|
|
|
88
95
|
* VS Code Copilot transcript — Copilot uses seat-based billing and does not
|
|
89
96
|
* expose per-call telemetry. These fields are reported as 0/null/"copilot".
|
|
90
97
|
*/
|
|
91
|
-
export declare function buildPayloadFromStdin(stdin: CopilotStopStdin, read?: (path: string) => CopilotTranscriptEntry[], resolveUsage?: (sessionId: string | undefined, hookTimestamp?: string) => CopilotDebugUsage | undefined): AgentSessionPayload;
|
|
98
|
+
export declare function buildPayloadFromStdin(stdin: CopilotStopStdin, read?: (path: string) => CopilotTranscriptEntry[], resolveUsage?: (sessionId: string | undefined, hookTimestamp?: string, baseDir?: string, workspaceIdFromTranscript?: string) => CopilotDebugUsage | undefined): AgentSessionPayload;
|
|
92
99
|
/**
|
|
93
100
|
* Build payload in legacy mode (no stdin) — used when hook-copilot is invoked
|
|
94
101
|
* via the old shell-wrapper pattern around the Copilot CLI. Token counts are
|
package/dist/index.js
CHANGED
|
@@ -93,6 +93,23 @@ export function inferWorkspaceIdBySession(sessionId, hookTimestamp, baseDir = WO
|
|
|
93
93
|
}
|
|
94
94
|
return candidates[0];
|
|
95
95
|
}
|
|
96
|
+
export function parseTranscriptPathLocation(transcriptPath) {
|
|
97
|
+
if (!transcriptPath) {
|
|
98
|
+
return undefined;
|
|
99
|
+
}
|
|
100
|
+
const normalized = transcriptPath.replace(/\\/g, "/");
|
|
101
|
+
const match = normalized.match(/\/workspaceStorage\/([^/]+)\/GitHub\.copilot-chat\/transcripts\/([^/]+)\.jsonl$/);
|
|
102
|
+
if (!match) {
|
|
103
|
+
return undefined;
|
|
104
|
+
}
|
|
105
|
+
return {
|
|
106
|
+
workspaceId: match[1],
|
|
107
|
+
sessionId: match[2],
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
function buildMainLogPath(baseDir, workspaceId, sessionId) {
|
|
111
|
+
return join(baseDir, workspaceId, "GitHub.copilot-chat", "debug-logs", sessionId, "main.jsonl");
|
|
112
|
+
}
|
|
96
113
|
function resolveModelFromObject(obj, fallbackModel) {
|
|
97
114
|
const keys = ["model", "modelName", "model_name", "resolvedModel", "deployment", "engine"];
|
|
98
115
|
for (const key of keys) {
|
|
@@ -108,6 +125,7 @@ function extractTokensFromObject(obj) {
|
|
|
108
125
|
let cachedInputTokens = 0;
|
|
109
126
|
let outputTokens = 0;
|
|
110
127
|
let totalTokens = 0;
|
|
128
|
+
let costUsd = 0;
|
|
111
129
|
let foundAny = false;
|
|
112
130
|
for (const [rawKey, rawValue] of Object.entries(obj)) {
|
|
113
131
|
const key = normalizeKey(rawKey);
|
|
@@ -135,6 +153,11 @@ function extractTokensFromObject(obj) {
|
|
|
135
153
|
foundAny = true;
|
|
136
154
|
continue;
|
|
137
155
|
}
|
|
156
|
+
if (["costusd", "cost", "usd", "usdcost"].includes(key)) {
|
|
157
|
+
costUsd += value;
|
|
158
|
+
foundAny = true;
|
|
159
|
+
continue;
|
|
160
|
+
}
|
|
138
161
|
}
|
|
139
162
|
if (!foundAny) {
|
|
140
163
|
return null;
|
|
@@ -142,7 +165,7 @@ function extractTokensFromObject(obj) {
|
|
|
142
165
|
if (totalTokens === 0) {
|
|
143
166
|
totalTokens = inputTokens + outputTokens;
|
|
144
167
|
}
|
|
145
|
-
return { inputTokens, cachedInputTokens, outputTokens, totalTokens };
|
|
168
|
+
return { inputTokens, cachedInputTokens, outputTokens, totalTokens, costUsd };
|
|
146
169
|
}
|
|
147
170
|
function collectModelUsage(value, usageByModel, currentModel = "copilot") {
|
|
148
171
|
if (Array.isArray(value)) {
|
|
@@ -162,12 +185,14 @@ function collectModelUsage(value, usageByModel, currentModel = "copilot") {
|
|
|
162
185
|
cachedInputTokens: 0,
|
|
163
186
|
outputTokens: 0,
|
|
164
187
|
totalTokens: 0,
|
|
188
|
+
costUsd: 0,
|
|
165
189
|
observations: 0,
|
|
166
190
|
};
|
|
167
191
|
existing.inputTokens += tokens.inputTokens;
|
|
168
192
|
existing.cachedInputTokens += tokens.cachedInputTokens;
|
|
169
193
|
existing.outputTokens += tokens.outputTokens;
|
|
170
194
|
existing.totalTokens += tokens.totalTokens;
|
|
195
|
+
existing.costUsd += tokens.costUsd;
|
|
171
196
|
existing.observations += 1;
|
|
172
197
|
usageByModel.set(resolvedModel, existing);
|
|
173
198
|
}
|
|
@@ -175,17 +200,29 @@ function collectModelUsage(value, usageByModel, currentModel = "copilot") {
|
|
|
175
200
|
collectModelUsage(child, usageByModel, resolvedModel);
|
|
176
201
|
}
|
|
177
202
|
}
|
|
178
|
-
export function resolveDebugUsageBySession(sessionId, hookTimestamp, baseDir = WORKSPACE_STORAGE_DIR) {
|
|
203
|
+
export function resolveDebugUsageBySession(sessionId, hookTimestamp, baseDir = WORKSPACE_STORAGE_DIR, workspaceIdFromTranscript) {
|
|
179
204
|
if (!sessionId) {
|
|
180
205
|
return undefined;
|
|
181
206
|
}
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
207
|
+
let workspaceId = workspaceIdFromTranscript;
|
|
208
|
+
let mainJsonlPath;
|
|
209
|
+
if (workspaceId) {
|
|
210
|
+
const pathFromTranscript = buildMainLogPath(baseDir, workspaceId, sessionId);
|
|
211
|
+
if (existsSync(pathFromTranscript)) {
|
|
212
|
+
mainJsonlPath = pathFromTranscript;
|
|
213
|
+
}
|
|
185
214
|
}
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
215
|
+
if (!mainJsonlPath) {
|
|
216
|
+
const workspace = inferWorkspaceIdBySession(sessionId, hookTimestamp, baseDir);
|
|
217
|
+
if (!workspace) {
|
|
218
|
+
return undefined;
|
|
219
|
+
}
|
|
220
|
+
workspaceId = workspace.workspaceId;
|
|
221
|
+
const fallbackPath = buildMainLogPath(baseDir, workspace.workspaceId, sessionId);
|
|
222
|
+
if (!existsSync(fallbackPath)) {
|
|
223
|
+
return undefined;
|
|
224
|
+
}
|
|
225
|
+
mainJsonlPath = fallbackPath;
|
|
189
226
|
}
|
|
190
227
|
const usageByModel = new Map();
|
|
191
228
|
const lines = readFileSync(mainJsonlPath, "utf-8").split("\n");
|
|
@@ -205,12 +242,13 @@ export function resolveDebugUsageBySession(sessionId, hookTimestamp, baseDir = W
|
|
|
205
242
|
if (usageByModel.size === 0) {
|
|
206
243
|
return {
|
|
207
244
|
sessionId,
|
|
208
|
-
workspaceId:
|
|
245
|
+
workspaceId: workspaceId ?? "unknown",
|
|
209
246
|
model: "copilot",
|
|
210
247
|
inputTokens: 0,
|
|
211
248
|
cachedInputTokens: 0,
|
|
212
249
|
outputTokens: 0,
|
|
213
250
|
totalTokens: 0,
|
|
251
|
+
costUsd: null,
|
|
214
252
|
sourcePath: mainJsonlPath,
|
|
215
253
|
modelUsage: {},
|
|
216
254
|
};
|
|
@@ -222,11 +260,13 @@ export function resolveDebugUsageBySession(sessionId, hookTimestamp, baseDir = W
|
|
|
222
260
|
let cachedInputTokens = 0;
|
|
223
261
|
let outputTokens = 0;
|
|
224
262
|
let totalTokens = 0;
|
|
263
|
+
let totalCostUsd = 0;
|
|
225
264
|
for (const [model, usage] of usageByModel.entries()) {
|
|
226
265
|
inputTokens += usage.inputTokens;
|
|
227
266
|
cachedInputTokens += usage.cachedInputTokens;
|
|
228
267
|
outputTokens += usage.outputTokens;
|
|
229
268
|
totalTokens += usage.totalTokens;
|
|
269
|
+
totalCostUsd += usage.costUsd;
|
|
230
270
|
if (usage.totalTokens > dominantTotal || (usage.totalTokens === dominantTotal && usage.observations > dominantObs)) {
|
|
231
271
|
dominantModel = model;
|
|
232
272
|
dominantTotal = usage.totalTokens;
|
|
@@ -236,12 +276,13 @@ export function resolveDebugUsageBySession(sessionId, hookTimestamp, baseDir = W
|
|
|
236
276
|
const modelUsage = Object.fromEntries(usageByModel.entries());
|
|
237
277
|
return {
|
|
238
278
|
sessionId,
|
|
239
|
-
workspaceId:
|
|
279
|
+
workspaceId: workspaceId ?? "unknown",
|
|
240
280
|
model: dominantModel,
|
|
241
281
|
inputTokens,
|
|
242
282
|
cachedInputTokens,
|
|
243
283
|
outputTokens,
|
|
244
284
|
totalTokens,
|
|
285
|
+
costUsd: totalCostUsd > 0 ? totalCostUsd : null,
|
|
245
286
|
sourcePath: mainJsonlPath,
|
|
246
287
|
modelUsage,
|
|
247
288
|
};
|
|
@@ -296,11 +337,13 @@ export function buildPayloadFromStdin(stdin, read = readTranscript, resolveUsage
|
|
|
296
337
|
})() : [];
|
|
297
338
|
const toolCallCount = countToolCalls(entries);
|
|
298
339
|
const startedAt = extractStartTime(entries) ?? stdin.timestamp ?? new Date().toISOString();
|
|
299
|
-
const
|
|
340
|
+
const parsedLocation = parseTranscriptPathLocation(stdin.transcript_path);
|
|
341
|
+
const sessionId = parsedLocation?.sessionId ?? stdin.session_id;
|
|
342
|
+
const usage = resolveUsage(sessionId, stdin.timestamp, WORKSPACE_STORAGE_DIR, parsedLocation?.workspaceId);
|
|
300
343
|
return {
|
|
301
344
|
tool: "copilot",
|
|
302
345
|
// session_id is optional per VS Code spec; synthesize a fallback if absent
|
|
303
|
-
sessionId:
|
|
346
|
+
sessionId: sessionId ?? `copilot-${Date.now()}-${process.pid}`,
|
|
304
347
|
gitUsername: resolveGitUsername(stdin.cwd),
|
|
305
348
|
// Model/tokens are inferred from debug logs by session_id; falls back safely.
|
|
306
349
|
model: usage?.model ?? "copilot",
|
|
@@ -308,7 +351,7 @@ export function buildPayloadFromStdin(stdin, read = readTranscript, resolveUsage
|
|
|
308
351
|
cachedInputTokens: usage?.cachedInputTokens ?? 0,
|
|
309
352
|
cacheCreationInputTokens: 0,
|
|
310
353
|
outputTokens: usage?.outputTokens ?? 0,
|
|
311
|
-
costUsd: null,
|
|
354
|
+
costUsd: usage?.costUsd ?? null,
|
|
312
355
|
toolCallCount,
|
|
313
356
|
startedAt,
|
|
314
357
|
rawPayload: usage ? {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jagit/hook-copilot",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.5",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"bin": {
|
|
6
6
|
"jagit-hook-copilot": "dist/index.js"
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
"dist"
|
|
10
10
|
],
|
|
11
11
|
"dependencies": {
|
|
12
|
-
"@jagit/agent-reporter": "0.0.
|
|
12
|
+
"@jagit/agent-reporter": "0.0.5"
|
|
13
13
|
},
|
|
14
14
|
"devDependencies": {
|
|
15
15
|
"@types/node": "^25.9.3",
|