@agent-finops/core 0.8.1 → 0.9.0
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/README.md +5 -3
- package/dist/actionPlanner.d.ts +140 -0
- package/dist/actionPlanner.js +938 -0
- package/dist/actionVerification.d.ts +1240 -0
- package/dist/actionVerification.js +1028 -0
- package/dist/activitySnapshot.d.ts +142 -50
- package/dist/activitySnapshot.js +145 -6
- package/dist/activitySnapshotCache.d.ts +8 -1
- package/dist/activitySnapshotCache.js +103 -7
- package/dist/agentEconomicsReceipt.d.ts +74 -74
- package/dist/glance.d.ts +27 -1
- package/dist/glance.js +151 -12
- package/dist/index.d.ts +11 -2
- package/dist/index.js +10 -1
- package/dist/localAgentFormats/gemini.js +2 -2
- package/dist/localAgentFormats/registry.js +6 -2
- package/dist/localAgentFormats/runtimeRegistry.js +5 -2
- package/dist/localAgentFormats/types.d.ts +2 -1
- package/dist/localAgentLogs.d.ts +362 -3
- package/dist/localAgentLogs.js +1964 -165
- package/dist/modelPricing.d.ts +1 -1
- package/dist/modelPricing.js +1 -1
- package/dist/projectEconomics.d.ts +617 -0
- package/dist/projectEconomics.js +620 -0
- package/dist/projectEconomicsBuilder.d.ts +89 -0
- package/dist/projectEconomicsBuilder.js +473 -0
- package/dist/projectIndexStore.d.ts +545 -0
- package/dist/projectIndexStore.js +606 -0
- package/dist/providerConnectors.d.ts +59 -1
- package/dist/providerConnectors.js +175 -11
- package/dist/qualitativeIndexCache.d.ts +494 -0
- package/dist/qualitativeIndexCache.js +930 -0
- package/dist/resultCard.d.ts +350 -0
- package/dist/resultCard.js +604 -0
- package/dist/runtimeCommands.d.ts +21 -0
- package/dist/runtimeCommands.js +27 -0
- package/dist/scanGuard.d.ts +3 -1
- package/dist/scanGuard.js +164 -4
- package/dist/schema.d.ts +31 -31
- package/dist/sessionVitals.d.ts +145 -0
- package/dist/sessionVitals.js +521 -0
- package/dist/toolInvocations.d.ts +40 -1
- package/dist/toolInvocations.js +101 -20
- package/package.json +1 -1
package/dist/scanGuard.js
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import { homedir } from "node:os";
|
|
2
2
|
import { constants, realpathSync } from "node:fs";
|
|
3
|
+
import { execFile as execFileCallback } from "node:child_process";
|
|
3
4
|
import { chmod, lstat, mkdir, open, realpath, rename, stat, unlink } from "node:fs/promises";
|
|
4
5
|
import { randomUUID } from "node:crypto";
|
|
5
|
-
import { basename, join, resolve, sep } from "node:path";
|
|
6
|
+
import { basename, dirname, isAbsolute, join, relative, resolve, sep } from "node:path";
|
|
7
|
+
import { promisify } from "node:util";
|
|
6
8
|
/**
|
|
7
9
|
* Shared unsafe-scan-root policy for EVERY scan entrypoint (CLI `scan`, MCP
|
|
8
10
|
* `scan_ai_spend`, and any future surface). Scanning the home directory, the
|
|
@@ -29,6 +31,7 @@ const systemRootDirectories = new Set([
|
|
|
29
31
|
"/sys",
|
|
30
32
|
"/dev"
|
|
31
33
|
]);
|
|
34
|
+
const execFile = promisify(execFileCallback);
|
|
32
35
|
export class UnsafeScanRootError extends Error {
|
|
33
36
|
rootPath;
|
|
34
37
|
constructor(rootPath, reason) {
|
|
@@ -127,9 +130,11 @@ export async function resolveSafeStateDirectory(rootPath, options = {}) {
|
|
|
127
130
|
return undefined;
|
|
128
131
|
throw error;
|
|
129
132
|
});
|
|
133
|
+
let createdStateDirectory = false;
|
|
130
134
|
if (!stateInfo && options.create) {
|
|
131
135
|
try {
|
|
132
136
|
await mkdir(statePath, { mode: 0o700 });
|
|
137
|
+
createdStateDirectory = true;
|
|
133
138
|
}
|
|
134
139
|
catch (error) {
|
|
135
140
|
// A concurrent creator is safe only after the same lstat checks below.
|
|
@@ -153,17 +158,145 @@ export async function resolveSafeStateDirectory(rootPath, options = {}) {
|
|
|
153
158
|
if (canonicalState !== statePath) {
|
|
154
159
|
throw new UnsafeStateDirectoryError(statePath, ".ai-spend-agent does not resolve directly inside the approved root");
|
|
155
160
|
}
|
|
156
|
-
|
|
161
|
+
// Project state can contain private receipts and experiment evidence. A
|
|
162
|
+
// repository-authored marker or tracked state is not a trust boundary: the
|
|
163
|
+
// exact marker, private permissions, untracked status, and effective Git
|
|
164
|
+
// ignore rule must all be proven before either reads or writes proceed.
|
|
165
|
+
await assertPrivateStateGitBoundary(canonicalRoot, canonicalState, options.create === true);
|
|
166
|
+
const directoryMode = stateInfo.mode & 0o777;
|
|
167
|
+
if ((directoryMode & 0o077) !== 0) {
|
|
168
|
+
if (options.create !== true) {
|
|
169
|
+
throw new UnsafeStateDirectoryError(statePath, ".ai-spend-agent permissions expose private state metadata to other users");
|
|
170
|
+
}
|
|
171
|
+
// Explicit create/write calls may safely migrate a legacy aibill-owned
|
|
172
|
+
// directory only after the Git privacy boundary above proved that neither
|
|
173
|
+
// it nor a child is tracked and that the exact private marker is effective.
|
|
174
|
+
// Read-only callers never change permissions implicitly.
|
|
175
|
+
await chmod(statePath, 0o700);
|
|
176
|
+
const securedInfo = await lstat(statePath);
|
|
177
|
+
if (!securedInfo.isDirectory() || securedInfo.isSymbolicLink() ||
|
|
178
|
+
securedInfo.dev !== stateInfo.dev || securedInfo.ino !== stateInfo.ino ||
|
|
179
|
+
(securedInfo.mode & 0o077) !== 0) {
|
|
180
|
+
throw new UnsafeStateDirectoryError(statePath, ".ai-spend-agent permissions could not be secured without changing directory identity");
|
|
181
|
+
}
|
|
182
|
+
stateInfo = securedInfo;
|
|
183
|
+
}
|
|
184
|
+
else if (options.create && createdStateDirectory && directoryMode !== 0o700) {
|
|
185
|
+
// An unusually restrictive umask is still normalized for stable private
|
|
186
|
+
// access only on the directory this call created.
|
|
157
187
|
await chmod(statePath, 0o700);
|
|
158
188
|
}
|
|
159
189
|
return statePath;
|
|
160
190
|
}
|
|
191
|
+
async function assertPrivateStateGitBoundary(canonicalRoot, statePath, create) {
|
|
192
|
+
const gitEntryRoot = await findEnclosingGitEntryRoot(canonicalRoot);
|
|
193
|
+
const gitRoot = await resolveGitRoot(canonicalRoot, gitEntryRoot);
|
|
194
|
+
// Outside a Git worktree, the state files' own 0600 permissions and safe
|
|
195
|
+
// no-follow I/O are the privacy boundary. The ignore marker is specifically
|
|
196
|
+
// a repository-leak guard and is required only when Git can see this root.
|
|
197
|
+
if (!gitRoot)
|
|
198
|
+
return;
|
|
199
|
+
const relativeState = relative(gitRoot, statePath);
|
|
200
|
+
if (!relativeState || relativeState === ".." || relativeState.startsWith(`..${sep}`) ||
|
|
201
|
+
isAbsolute(relativeState)) {
|
|
202
|
+
throw new UnsafeStateDirectoryError(statePath, ".ai-spend-agent is outside the resolved Git worktree");
|
|
203
|
+
}
|
|
204
|
+
const tracked = await execFile("git", ["-C", gitRoot, "ls-files", "--", relativeState], { encoding: "utf8", maxBuffer: 64 * 1024 }).then(({ stdout }) => stdout.trim()).catch(() => {
|
|
205
|
+
throw new UnsafeStateDirectoryError(statePath, "Git tracking status for .ai-spend-agent could not be verified");
|
|
206
|
+
});
|
|
207
|
+
if (tracked) {
|
|
208
|
+
throw new UnsafeStateDirectoryError(statePath, ".ai-spend-agent or one of its children is already tracked by Git");
|
|
209
|
+
}
|
|
210
|
+
const markerPath = join(statePath, ".gitignore");
|
|
211
|
+
let handle;
|
|
212
|
+
try {
|
|
213
|
+
handle = await open(markerPath, constants.O_RDONLY | noFollowFlag());
|
|
214
|
+
}
|
|
215
|
+
catch (error) {
|
|
216
|
+
if (!isNodeError(error, "ENOENT") || !create) {
|
|
217
|
+
if (isNodeError(error, "ELOOP")) {
|
|
218
|
+
throw new UnsafeStateDirectoryError(statePath, ".ai-spend-agent/.gitignore is a symbolic link");
|
|
219
|
+
}
|
|
220
|
+
throw error;
|
|
221
|
+
}
|
|
222
|
+
let writer;
|
|
223
|
+
try {
|
|
224
|
+
writer = await open(markerPath, constants.O_WRONLY | constants.O_CREAT | constants.O_EXCL | noFollowFlag(), 0o600);
|
|
225
|
+
await writer.writeFile("*\n", "utf8");
|
|
226
|
+
await writer.sync();
|
|
227
|
+
}
|
|
228
|
+
catch (createError) {
|
|
229
|
+
if (isNodeError(createError, "ELOOP")) {
|
|
230
|
+
throw new UnsafeStateDirectoryError(statePath, ".ai-spend-agent/.gitignore is a symbolic link");
|
|
231
|
+
}
|
|
232
|
+
if (!isNodeError(createError, "EEXIST"))
|
|
233
|
+
throw createError;
|
|
234
|
+
}
|
|
235
|
+
finally {
|
|
236
|
+
await writer?.close().catch(() => undefined);
|
|
237
|
+
}
|
|
238
|
+
handle = await open(markerPath, constants.O_RDONLY | noFollowFlag());
|
|
239
|
+
}
|
|
240
|
+
try {
|
|
241
|
+
const info = await handle.stat();
|
|
242
|
+
if (!info.isFile() || (info.mode & 0o077) !== 0 || info.size !== 2) {
|
|
243
|
+
throw new UnsafeStateDirectoryError(statePath, ".ai-spend-agent/.gitignore is not the private aibill marker");
|
|
244
|
+
}
|
|
245
|
+
const bytes = Buffer.alloc(2);
|
|
246
|
+
const { bytesRead } = await handle.read(bytes, 0, 2, 0);
|
|
247
|
+
if (bytesRead !== 2 || bytes.toString("utf8") !== "*\n") {
|
|
248
|
+
throw new UnsafeStateDirectoryError(statePath, ".ai-spend-agent/.gitignore is not the exact private aibill marker");
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
finally {
|
|
252
|
+
await handle.close().catch(() => undefined);
|
|
253
|
+
}
|
|
254
|
+
const relativeProbe = join(relativeState, "privacy-probe.json");
|
|
255
|
+
const ignored = await execFile("git", ["-C", gitRoot, "check-ignore", "--quiet", "--no-index", "--", relativeProbe], { maxBuffer: 64 * 1024 }).then(() => true).catch(() => false);
|
|
256
|
+
if (!ignored) {
|
|
257
|
+
throw new UnsafeStateDirectoryError(statePath, ".ai-spend-agent is not proven ignored by Git");
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
async function findEnclosingGitEntryRoot(path) {
|
|
261
|
+
let current = resolve(path);
|
|
262
|
+
while (true) {
|
|
263
|
+
const entry = await lstat(join(current, ".git")).catch((error) => {
|
|
264
|
+
if (isNodeError(error, "ENOENT") || isNodeError(error, "ENOTDIR"))
|
|
265
|
+
return undefined;
|
|
266
|
+
throw error;
|
|
267
|
+
});
|
|
268
|
+
if (entry)
|
|
269
|
+
return current;
|
|
270
|
+
const parent = dirname(current);
|
|
271
|
+
if (parent === current)
|
|
272
|
+
return undefined;
|
|
273
|
+
current = parent;
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
async function resolveGitRoot(canonicalRoot, gitEntryRoot) {
|
|
277
|
+
try {
|
|
278
|
+
const { stdout } = await execFile("git", ["-C", canonicalRoot, "rev-parse", "--show-toplevel"], { encoding: "utf8", maxBuffer: 64 * 1024 });
|
|
279
|
+
const reported = stdout.trim();
|
|
280
|
+
if (!reported || !isAbsolute(reported)) {
|
|
281
|
+
throw new UnsafeStateDirectoryError(canonicalRoot, "Git returned an invalid worktree root");
|
|
282
|
+
}
|
|
283
|
+
return await realpath(reported);
|
|
284
|
+
}
|
|
285
|
+
catch (error) {
|
|
286
|
+
if (error instanceof UnsafeStateDirectoryError)
|
|
287
|
+
throw error;
|
|
288
|
+
if (gitEntryRoot) {
|
|
289
|
+
throw new UnsafeStateDirectoryError(canonicalRoot, "the enclosing Git worktree could not be verified");
|
|
290
|
+
}
|
|
291
|
+
return undefined;
|
|
292
|
+
}
|
|
293
|
+
}
|
|
161
294
|
/**
|
|
162
295
|
* Read one regular child file without following a symbolic link. State can sit
|
|
163
296
|
* inside a cloned repository, so validating only the parent directory is not
|
|
164
297
|
* enough: a committed child symlink must never expose an arbitrary local file.
|
|
165
298
|
*/
|
|
166
|
-
export async function readSafeStateText(stateDir, fileName) {
|
|
299
|
+
export async function readSafeStateText(stateDir, fileName, options = {}) {
|
|
167
300
|
const filePath = await resolveSafeStateChild(stateDir, fileName, false);
|
|
168
301
|
let handle;
|
|
169
302
|
try {
|
|
@@ -172,7 +305,34 @@ export async function readSafeStateText(stateDir, fileName) {
|
|
|
172
305
|
if (!info.isFile()) {
|
|
173
306
|
throw new UnsafeStateFileError(filePath, "the state entry is not a regular file");
|
|
174
307
|
}
|
|
175
|
-
|
|
308
|
+
const maxBytes = options.maxBytes;
|
|
309
|
+
if (maxBytes === undefined)
|
|
310
|
+
return await handle.readFile("utf8");
|
|
311
|
+
if (!Number.isSafeInteger(maxBytes) || maxBytes < 0) {
|
|
312
|
+
throw new RangeError("maxBytes must be a non-negative safe integer");
|
|
313
|
+
}
|
|
314
|
+
if (info.size > maxBytes) {
|
|
315
|
+
throw new UnsafeStateFileError(filePath, `the state entry exceeds ${maxBytes} bytes`);
|
|
316
|
+
}
|
|
317
|
+
// Never use readFile for attacker-authored bounded state: a same-inode
|
|
318
|
+
// growth race could otherwise allocate the new whole size after fstat.
|
|
319
|
+
const buffer = Buffer.alloc(Math.min(maxBytes + 1, Math.max(1, info.size + 1)));
|
|
320
|
+
let offset = 0;
|
|
321
|
+
while (offset < buffer.length) {
|
|
322
|
+
const { bytesRead } = await handle.read(buffer, offset, buffer.length - offset, offset);
|
|
323
|
+
if (bytesRead === 0)
|
|
324
|
+
break;
|
|
325
|
+
offset += bytesRead;
|
|
326
|
+
}
|
|
327
|
+
const after = await handle.stat();
|
|
328
|
+
if (offset > maxBytes || after.size > maxBytes) {
|
|
329
|
+
throw new UnsafeStateFileError(filePath, `the state entry exceeds ${maxBytes} bytes`);
|
|
330
|
+
}
|
|
331
|
+
if (after.size !== info.size || after.mtimeMs !== info.mtimeMs ||
|
|
332
|
+
after.ctimeMs !== info.ctimeMs) {
|
|
333
|
+
throw new UnsafeStateFileError(filePath, "the state entry changed while it was read");
|
|
334
|
+
}
|
|
335
|
+
return buffer.subarray(0, offset).toString("utf8");
|
|
176
336
|
}
|
|
177
337
|
catch (error) {
|
|
178
338
|
if (isNodeError(error, "ELOOP")) {
|
package/dist/schema.d.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
export declare const costConfidenceValues: readonly ["verified", "estimated", "detected_unverified", "missing"];
|
|
3
3
|
export declare const costConfidenceSchema: z.ZodEnum<{
|
|
4
|
-
verified: "verified";
|
|
5
4
|
estimated: "estimated";
|
|
5
|
+
verified: "verified";
|
|
6
6
|
detected_unverified: "detected_unverified";
|
|
7
7
|
missing: "missing";
|
|
8
8
|
}>;
|
|
@@ -12,8 +12,8 @@ export declare const spendSourceSchema: z.ZodObject<{
|
|
|
12
12
|
name: z.ZodString;
|
|
13
13
|
provider: z.ZodString;
|
|
14
14
|
confidence: z.ZodEnum<{
|
|
15
|
-
verified: "verified";
|
|
16
15
|
estimated: "estimated";
|
|
16
|
+
verified: "verified";
|
|
17
17
|
detected_unverified: "detected_unverified";
|
|
18
18
|
missing: "missing";
|
|
19
19
|
}>;
|
|
@@ -30,9 +30,9 @@ export type SpendSource = z.infer<typeof spendSourceSchema>;
|
|
|
30
30
|
*/
|
|
31
31
|
export declare const usageGranularityValues: readonly ["call", "invocation", "session", "daily_aggregate", "usage_bucket", "billing_bucket", "seat", "user_aggregate"];
|
|
32
32
|
export declare const usageGranularitySchema: z.ZodEnum<{
|
|
33
|
+
session: "session";
|
|
33
34
|
call: "call";
|
|
34
35
|
invocation: "invocation";
|
|
35
|
-
session: "session";
|
|
36
36
|
daily_aggregate: "daily_aggregate";
|
|
37
37
|
usage_bucket: "usage_bucket";
|
|
38
38
|
billing_bucket: "billing_bucket";
|
|
@@ -59,8 +59,8 @@ export declare const usageRecordSchema: z.ZodObject<{
|
|
|
59
59
|
name: z.ZodString;
|
|
60
60
|
provider: z.ZodString;
|
|
61
61
|
confidence: z.ZodEnum<{
|
|
62
|
-
verified: "verified";
|
|
63
62
|
estimated: "estimated";
|
|
63
|
+
verified: "verified";
|
|
64
64
|
detected_unverified: "detected_unverified";
|
|
65
65
|
missing: "missing";
|
|
66
66
|
}>;
|
|
@@ -89,8 +89,8 @@ export declare const usageRecordSchema: z.ZodObject<{
|
|
|
89
89
|
sourceVersions: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
90
90
|
amountUsd: z.ZodNullable<z.ZodNumber>;
|
|
91
91
|
costConfidence: z.ZodEnum<{
|
|
92
|
-
verified: "verified";
|
|
93
92
|
estimated: "estimated";
|
|
93
|
+
verified: "verified";
|
|
94
94
|
detected_unverified: "detected_unverified";
|
|
95
95
|
missing: "missing";
|
|
96
96
|
}>;
|
|
@@ -104,9 +104,9 @@ export declare const usageRecordSchema: z.ZodObject<{
|
|
|
104
104
|
agentId: z.ZodOptional<z.ZodString>;
|
|
105
105
|
operation: z.ZodOptional<z.ZodString>;
|
|
106
106
|
usageGranularity: z.ZodOptional<z.ZodEnum<{
|
|
107
|
+
session: "session";
|
|
107
108
|
call: "call";
|
|
108
109
|
invocation: "invocation";
|
|
109
|
-
session: "session";
|
|
110
110
|
daily_aggregate: "daily_aggregate";
|
|
111
111
|
usage_bucket: "usage_bucket";
|
|
112
112
|
billing_bucket: "billing_bucket";
|
|
@@ -152,10 +152,10 @@ export declare function downgradeSampleUsageEvidence(records: UsageRecord[]): Us
|
|
|
152
152
|
export declare function spendComparisonKey(record: UsageRecord): string | undefined;
|
|
153
153
|
export declare const attributionCandidateSchema: z.ZodObject<{
|
|
154
154
|
entityType: z.ZodEnum<{
|
|
155
|
-
|
|
155
|
+
user: "user";
|
|
156
156
|
project: "project";
|
|
157
157
|
agent: "agent";
|
|
158
|
-
|
|
158
|
+
client: "client";
|
|
159
159
|
workspace: "workspace";
|
|
160
160
|
api_key: "api_key";
|
|
161
161
|
}>;
|
|
@@ -168,10 +168,10 @@ export declare const attributionMappingSchema: z.ZodObject<{
|
|
|
168
168
|
usageRecordId: z.ZodString;
|
|
169
169
|
candidates: z.ZodArray<z.ZodObject<{
|
|
170
170
|
entityType: z.ZodEnum<{
|
|
171
|
-
|
|
171
|
+
user: "user";
|
|
172
172
|
project: "project";
|
|
173
173
|
agent: "agent";
|
|
174
|
-
|
|
174
|
+
client: "client";
|
|
175
175
|
workspace: "workspace";
|
|
176
176
|
api_key: "api_key";
|
|
177
177
|
}>;
|
|
@@ -181,10 +181,10 @@ export declare const attributionMappingSchema: z.ZodObject<{
|
|
|
181
181
|
}, z.core.$strip>>;
|
|
182
182
|
selected: z.ZodOptional<z.ZodObject<{
|
|
183
183
|
entityType: z.ZodEnum<{
|
|
184
|
-
|
|
184
|
+
user: "user";
|
|
185
185
|
project: "project";
|
|
186
186
|
agent: "agent";
|
|
187
|
-
|
|
187
|
+
client: "client";
|
|
188
188
|
workspace: "workspace";
|
|
189
189
|
api_key: "api_key";
|
|
190
190
|
}>;
|
|
@@ -206,8 +206,8 @@ export declare const spendBreakdownEntrySchema: z.ZodObject<{
|
|
|
206
206
|
amountUsd: z.ZodNumber;
|
|
207
207
|
recordCount: z.ZodNumber;
|
|
208
208
|
confidence: z.ZodEnum<{
|
|
209
|
-
verified: "verified";
|
|
210
209
|
estimated: "estimated";
|
|
210
|
+
verified: "verified";
|
|
211
211
|
detected_unverified: "detected_unverified";
|
|
212
212
|
missing: "missing";
|
|
213
213
|
}>;
|
|
@@ -224,8 +224,8 @@ export declare const spendAnomalySchema: z.ZodObject<{
|
|
|
224
224
|
currentAmountUsd: z.ZodNumber;
|
|
225
225
|
multiplier: z.ZodNumber;
|
|
226
226
|
confidence: z.ZodEnum<{
|
|
227
|
-
verified: "verified";
|
|
228
227
|
estimated: "estimated";
|
|
228
|
+
verified: "verified";
|
|
229
229
|
detected_unverified: "detected_unverified";
|
|
230
230
|
missing: "missing";
|
|
231
231
|
}>;
|
|
@@ -241,8 +241,8 @@ export declare const workflowWatchEntrySchema: z.ZodObject<{
|
|
|
241
241
|
shareOfSpend: z.ZodNumber;
|
|
242
242
|
recordCount: z.ZodNumber;
|
|
243
243
|
confidence: z.ZodEnum<{
|
|
244
|
-
verified: "verified";
|
|
245
244
|
estimated: "estimated";
|
|
245
|
+
verified: "verified";
|
|
246
246
|
detected_unverified: "detected_unverified";
|
|
247
247
|
missing: "missing";
|
|
248
248
|
}>;
|
|
@@ -266,8 +266,8 @@ export declare const recommendationSchema: z.ZodObject<{
|
|
|
266
266
|
}>;
|
|
267
267
|
estimatedImpactUsd: z.ZodNumber;
|
|
268
268
|
confidence: z.ZodEnum<{
|
|
269
|
-
verified: "verified";
|
|
270
269
|
estimated: "estimated";
|
|
270
|
+
verified: "verified";
|
|
271
271
|
detected_unverified: "detected_unverified";
|
|
272
272
|
missing: "missing";
|
|
273
273
|
}>;
|
|
@@ -311,8 +311,8 @@ export declare const spendInsightSchema: z.ZodObject<{
|
|
|
311
311
|
affectedModels: z.ZodArray<z.ZodString>;
|
|
312
312
|
estimatedImpactUsd: z.ZodNumber;
|
|
313
313
|
confidence: z.ZodEnum<{
|
|
314
|
-
verified: "verified";
|
|
315
314
|
estimated: "estimated";
|
|
315
|
+
verified: "verified";
|
|
316
316
|
detected_unverified: "detected_unverified";
|
|
317
317
|
missing: "missing";
|
|
318
318
|
}>;
|
|
@@ -324,14 +324,14 @@ export declare const spendSummarySchema: z.ZodObject<{
|
|
|
324
324
|
totalUsd: z.ZodNumber;
|
|
325
325
|
recordCount: z.ZodNumber;
|
|
326
326
|
confidence: z.ZodEnum<{
|
|
327
|
-
verified: "verified";
|
|
328
327
|
estimated: "estimated";
|
|
328
|
+
verified: "verified";
|
|
329
329
|
detected_unverified: "detected_unverified";
|
|
330
330
|
missing: "missing";
|
|
331
331
|
}>;
|
|
332
332
|
confidenceBreakdown: z.ZodRecord<z.ZodEnum<{
|
|
333
|
-
verified: "verified";
|
|
334
333
|
estimated: "estimated";
|
|
334
|
+
verified: "verified";
|
|
335
335
|
detected_unverified: "detected_unverified";
|
|
336
336
|
missing: "missing";
|
|
337
337
|
}>, z.ZodNumber>;
|
|
@@ -340,8 +340,8 @@ export declare const spendSummarySchema: z.ZodObject<{
|
|
|
340
340
|
amountUsd: z.ZodNumber;
|
|
341
341
|
recordCount: z.ZodNumber;
|
|
342
342
|
confidence: z.ZodEnum<{
|
|
343
|
-
verified: "verified";
|
|
344
343
|
estimated: "estimated";
|
|
344
|
+
verified: "verified";
|
|
345
345
|
detected_unverified: "detected_unverified";
|
|
346
346
|
missing: "missing";
|
|
347
347
|
}>;
|
|
@@ -351,8 +351,8 @@ export declare const spendSummarySchema: z.ZodObject<{
|
|
|
351
351
|
amountUsd: z.ZodNumber;
|
|
352
352
|
recordCount: z.ZodNumber;
|
|
353
353
|
confidence: z.ZodEnum<{
|
|
354
|
-
verified: "verified";
|
|
355
354
|
estimated: "estimated";
|
|
355
|
+
verified: "verified";
|
|
356
356
|
detected_unverified: "detected_unverified";
|
|
357
357
|
missing: "missing";
|
|
358
358
|
}>;
|
|
@@ -362,8 +362,8 @@ export declare const spendSummarySchema: z.ZodObject<{
|
|
|
362
362
|
amountUsd: z.ZodNumber;
|
|
363
363
|
recordCount: z.ZodNumber;
|
|
364
364
|
confidence: z.ZodEnum<{
|
|
365
|
-
verified: "verified";
|
|
366
365
|
estimated: "estimated";
|
|
366
|
+
verified: "verified";
|
|
367
367
|
detected_unverified: "detected_unverified";
|
|
368
368
|
missing: "missing";
|
|
369
369
|
}>;
|
|
@@ -373,8 +373,8 @@ export declare const spendSummarySchema: z.ZodObject<{
|
|
|
373
373
|
amountUsd: z.ZodNumber;
|
|
374
374
|
recordCount: z.ZodNumber;
|
|
375
375
|
confidence: z.ZodEnum<{
|
|
376
|
-
verified: "verified";
|
|
377
376
|
estimated: "estimated";
|
|
377
|
+
verified: "verified";
|
|
378
378
|
detected_unverified: "detected_unverified";
|
|
379
379
|
missing: "missing";
|
|
380
380
|
}>;
|
|
@@ -384,8 +384,8 @@ export declare const spendSummarySchema: z.ZodObject<{
|
|
|
384
384
|
amountUsd: z.ZodNumber;
|
|
385
385
|
recordCount: z.ZodNumber;
|
|
386
386
|
confidence: z.ZodEnum<{
|
|
387
|
-
verified: "verified";
|
|
388
387
|
estimated: "estimated";
|
|
388
|
+
verified: "verified";
|
|
389
389
|
detected_unverified: "detected_unverified";
|
|
390
390
|
missing: "missing";
|
|
391
391
|
}>;
|
|
@@ -395,8 +395,8 @@ export declare const spendSummarySchema: z.ZodObject<{
|
|
|
395
395
|
amountUsd: z.ZodNumber;
|
|
396
396
|
recordCount: z.ZodNumber;
|
|
397
397
|
confidence: z.ZodEnum<{
|
|
398
|
-
verified: "verified";
|
|
399
398
|
estimated: "estimated";
|
|
399
|
+
verified: "verified";
|
|
400
400
|
detected_unverified: "detected_unverified";
|
|
401
401
|
missing: "missing";
|
|
402
402
|
}>;
|
|
@@ -406,8 +406,8 @@ export declare const spendSummarySchema: z.ZodObject<{
|
|
|
406
406
|
amountUsd: z.ZodNumber;
|
|
407
407
|
recordCount: z.ZodNumber;
|
|
408
408
|
confidence: z.ZodEnum<{
|
|
409
|
-
verified: "verified";
|
|
410
409
|
estimated: "estimated";
|
|
410
|
+
verified: "verified";
|
|
411
411
|
detected_unverified: "detected_unverified";
|
|
412
412
|
missing: "missing";
|
|
413
413
|
}>;
|
|
@@ -417,8 +417,8 @@ export declare const spendSummarySchema: z.ZodObject<{
|
|
|
417
417
|
amountUsd: z.ZodNumber;
|
|
418
418
|
recordCount: z.ZodNumber;
|
|
419
419
|
confidence: z.ZodEnum<{
|
|
420
|
-
verified: "verified";
|
|
421
420
|
estimated: "estimated";
|
|
421
|
+
verified: "verified";
|
|
422
422
|
detected_unverified: "detected_unverified";
|
|
423
423
|
missing: "missing";
|
|
424
424
|
}>;
|
|
@@ -433,8 +433,8 @@ export declare const spendSummarySchema: z.ZodObject<{
|
|
|
433
433
|
shareOfSpend: z.ZodNumber;
|
|
434
434
|
recordCount: z.ZodNumber;
|
|
435
435
|
confidence: z.ZodEnum<{
|
|
436
|
-
verified: "verified";
|
|
437
436
|
estimated: "estimated";
|
|
437
|
+
verified: "verified";
|
|
438
438
|
detected_unverified: "detected_unverified";
|
|
439
439
|
missing: "missing";
|
|
440
440
|
}>;
|
|
@@ -455,8 +455,8 @@ export declare const spendSummarySchema: z.ZodObject<{
|
|
|
455
455
|
currentAmountUsd: z.ZodNumber;
|
|
456
456
|
multiplier: z.ZodNumber;
|
|
457
457
|
confidence: z.ZodEnum<{
|
|
458
|
-
verified: "verified";
|
|
459
458
|
estimated: "estimated";
|
|
459
|
+
verified: "verified";
|
|
460
460
|
detected_unverified: "detected_unverified";
|
|
461
461
|
missing: "missing";
|
|
462
462
|
}>;
|
|
@@ -474,8 +474,8 @@ export declare const spendSummarySchema: z.ZodObject<{
|
|
|
474
474
|
}>;
|
|
475
475
|
estimatedImpactUsd: z.ZodNumber;
|
|
476
476
|
confidence: z.ZodEnum<{
|
|
477
|
-
verified: "verified";
|
|
478
477
|
estimated: "estimated";
|
|
478
|
+
verified: "verified";
|
|
479
479
|
detected_unverified: "detected_unverified";
|
|
480
480
|
missing: "missing";
|
|
481
481
|
}>;
|
|
@@ -512,8 +512,8 @@ export declare const spendSummarySchema: z.ZodObject<{
|
|
|
512
512
|
affectedModels: z.ZodArray<z.ZodString>;
|
|
513
513
|
estimatedImpactUsd: z.ZodNumber;
|
|
514
514
|
confidence: z.ZodEnum<{
|
|
515
|
-
verified: "verified";
|
|
516
515
|
estimated: "estimated";
|
|
516
|
+
verified: "verified";
|
|
517
517
|
detected_unverified: "detected_unverified";
|
|
518
518
|
missing: "missing";
|
|
519
519
|
}>;
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
import { type LocalAgentActivity, type LocalAgentCall, type LocalAgentRateLimitWindow, type LocalAgentTurnUsage } from "./localAgentLogs.js";
|
|
2
|
+
/**
|
|
3
|
+
* Additive, privacy-reduced session evidence for future before/after tests.
|
|
4
|
+
*
|
|
5
|
+
* V0 deliberately accepts already-parsed LocalAgentCall values rather than
|
|
6
|
+
* reading transcripts itself. It never carries prompts, responses, absolute
|
|
7
|
+
* paths, filenames, or raw provider session identifiers.
|
|
8
|
+
*/
|
|
9
|
+
export type SessionVitalsAgentV0 = "claude-code" | "codex";
|
|
10
|
+
export type SessionVitalsTokenEvidenceV0 = {
|
|
11
|
+
status: "observed";
|
|
12
|
+
basis: "turn_sum" | "session_cumulative";
|
|
13
|
+
inputTokens: number;
|
|
14
|
+
outputTokens: number;
|
|
15
|
+
cacheReadTokens?: number;
|
|
16
|
+
cacheWrite5mTokens?: number;
|
|
17
|
+
cacheWrite1hTokens?: number;
|
|
18
|
+
thoughtTokens?: number;
|
|
19
|
+
toolTokens?: number;
|
|
20
|
+
componentTotalTokens: number;
|
|
21
|
+
reportedTotalTokens?: number;
|
|
22
|
+
componentEvidence: {
|
|
23
|
+
inputTokens: "observed";
|
|
24
|
+
outputTokens: "observed";
|
|
25
|
+
cacheReadTokens: "observed" | "not_separately_reported";
|
|
26
|
+
cacheWriteTokens: "observed" | "partial" | "not_separately_reported";
|
|
27
|
+
thoughtTokens: "observed" | "not_separately_reported";
|
|
28
|
+
toolTokens: "observed" | "not_separately_reported";
|
|
29
|
+
componentTotalTokens: "calculated_complete" | "calculated_partial";
|
|
30
|
+
reportedTotalTokens: "provider_reported" | "not_reported";
|
|
31
|
+
};
|
|
32
|
+
} | {
|
|
33
|
+
status: "missing";
|
|
34
|
+
reason: "unsupported_token_shape" | "mixed_usage_scope" | "invalid_token_evidence";
|
|
35
|
+
};
|
|
36
|
+
export type SessionVitalsLatestTurnV0 = {
|
|
37
|
+
inputTokens: number;
|
|
38
|
+
outputTokens: number;
|
|
39
|
+
cacheReadTokens?: number;
|
|
40
|
+
cacheWrite5mTokens?: number;
|
|
41
|
+
cacheWrite1hTokens?: number;
|
|
42
|
+
thoughtTokens?: number;
|
|
43
|
+
toolTokens?: number;
|
|
44
|
+
contextTokens: number;
|
|
45
|
+
totalTokens: number;
|
|
46
|
+
source: LocalAgentTurnUsage["source"];
|
|
47
|
+
};
|
|
48
|
+
export type SessionVitalsRateLimitWindowV0 = {
|
|
49
|
+
kind: LocalAgentRateLimitWindow["kind"];
|
|
50
|
+
name: string;
|
|
51
|
+
usedPercent: number;
|
|
52
|
+
windowMinutes: number;
|
|
53
|
+
resetsAt: string;
|
|
54
|
+
};
|
|
55
|
+
export type SessionVitalsCompletionV0 = {
|
|
56
|
+
status: "completed";
|
|
57
|
+
/**
|
|
58
|
+
* A completed session snapshot, not permanent transcript closure.
|
|
59
|
+
* `claude_task_result` is the host-recorded Task tool result
|
|
60
|
+
* (`status: "completed"`) written into the owning transcript when a
|
|
61
|
+
* subagent run finishes — the only explicit completion marker Claude
|
|
62
|
+
* Code produces for subagent transcript files.
|
|
63
|
+
*/
|
|
64
|
+
evidence: "claude_turn_duration" | "codex_task_complete" | "claude_task_result";
|
|
65
|
+
observedAt: string;
|
|
66
|
+
} | {
|
|
67
|
+
status: "missing";
|
|
68
|
+
evidence: "missing";
|
|
69
|
+
reason: "completion_marker_not_observed" | "inconsistent_completion_evidence";
|
|
70
|
+
};
|
|
71
|
+
export type SessionVitalV0 = {
|
|
72
|
+
/** Stable AV-compatible pseudonym. The raw transcript session id is never returned. */
|
|
73
|
+
sessionRef: string;
|
|
74
|
+
/**
|
|
75
|
+
* Pseudonym of the owning parent session, present only on rows split out of
|
|
76
|
+
* a shared-sessionId subagent transcript. Matches the parent row's
|
|
77
|
+
* `sessionRef`; raw identifiers are never returned.
|
|
78
|
+
*/
|
|
79
|
+
parentSessionRef?: string;
|
|
80
|
+
agent: SessionVitalsAgentV0;
|
|
81
|
+
/** Unknown remains ineligible for automatic before/after cohort matching. */
|
|
82
|
+
sessionType: "parent" | "subagent" | "unknown";
|
|
83
|
+
/** Existing privacy-reduced project label; paths and suspicious values are omitted. */
|
|
84
|
+
project?: string;
|
|
85
|
+
/** Stable opaque identity derived from one consistent native working directory. */
|
|
86
|
+
projectRef?: string;
|
|
87
|
+
models: string[];
|
|
88
|
+
/** Empty means the parser did not report a safe version; never inferred. */
|
|
89
|
+
sourceVersions: string[];
|
|
90
|
+
observedFrom: string;
|
|
91
|
+
observedTo: string;
|
|
92
|
+
/** Omitted unless a distinct, valid start and end are present. */
|
|
93
|
+
observedDurationMs?: number;
|
|
94
|
+
/** Explicit host completion boundary for this snapshot; inactivity is never treated as completion. */
|
|
95
|
+
completion: SessionVitalsCompletionV0;
|
|
96
|
+
tokenEvidence: SessionVitalsTokenEvidenceV0;
|
|
97
|
+
latestTurn?: SessionVitalsLatestTurnV0;
|
|
98
|
+
activity?: {
|
|
99
|
+
kind: LocalAgentActivity["kind"];
|
|
100
|
+
action: LocalAgentActivity["action"];
|
|
101
|
+
promptCount: number;
|
|
102
|
+
toolCallCount: number;
|
|
103
|
+
};
|
|
104
|
+
rateLimits?: {
|
|
105
|
+
observedAt: string;
|
|
106
|
+
planType?: string;
|
|
107
|
+
windows: SessionVitalsRateLimitWindowV0[];
|
|
108
|
+
};
|
|
109
|
+
provenance: {
|
|
110
|
+
source: "parsed_local_agent_calls";
|
|
111
|
+
confidence: "observed";
|
|
112
|
+
uploaded: false;
|
|
113
|
+
};
|
|
114
|
+
};
|
|
115
|
+
export type SessionVitalsV0 = {
|
|
116
|
+
schemaVersion: 0;
|
|
117
|
+
sessions: SessionVitalV0[];
|
|
118
|
+
coverage: {
|
|
119
|
+
inputCalls: number;
|
|
120
|
+
deduplicatedCalls: number;
|
|
121
|
+
eligibleCalls: number;
|
|
122
|
+
emittedSessions: number;
|
|
123
|
+
sessionsWithObservedTokens: number;
|
|
124
|
+
sessionsWithMissingTokens: number;
|
|
125
|
+
excludedCalls: {
|
|
126
|
+
unsupportedAgent: number;
|
|
127
|
+
missingSessionIdentity: number;
|
|
128
|
+
invalidTimestamp: number;
|
|
129
|
+
};
|
|
130
|
+
};
|
|
131
|
+
privacy: {
|
|
132
|
+
rawSessionIds: false;
|
|
133
|
+
promptOrResponseText: false;
|
|
134
|
+
absolutePaths: false;
|
|
135
|
+
uploaded: false;
|
|
136
|
+
};
|
|
137
|
+
};
|
|
138
|
+
/**
|
|
139
|
+
* Build one deterministic V0 row per Claude Code or Codex session.
|
|
140
|
+
*
|
|
141
|
+
* Token evidence fails closed at the session boundary: one unsupported,
|
|
142
|
+
* invalid, or mixed-scope call prevents a partial sum from looking complete.
|
|
143
|
+
*/
|
|
144
|
+
export declare function extractSessionVitalsV0(calls: readonly LocalAgentCall[]): SessionVitalsV0;
|
|
145
|
+
//# sourceMappingURL=sessionVitals.d.ts.map
|