@monotykamary/pi-ledger 0.2.0 → 0.4.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 +6 -2
- package/extensions/pi-ledger/__tests__/helpers.ts +8 -1
- package/extensions/pi-ledger/__tests__/in-process-session-observer.test.ts +555 -0
- package/extensions/pi-ledger/__tests__/ledger.test.ts +125 -0
- package/extensions/pi-ledger/__tests__/nested-agent-telemetry.test.ts +867 -0
- package/extensions/pi-ledger/__tests__/subprocess-observer.test.ts +364 -0
- package/extensions/pi-ledger/in-process-session-observer.ts +890 -0
- package/extensions/pi-ledger/index.ts +121 -1
- package/extensions/pi-ledger/nested-agent-telemetry.ts +1271 -0
- package/extensions/pi-ledger/subprocess-observer.ts +694 -0
- package/package.json +1 -1
|
@@ -0,0 +1,1271 @@
|
|
|
1
|
+
import type { ExtensionAPI } from '@earendil-works/pi-coding-agent';
|
|
2
|
+
|
|
3
|
+
export const NESTED_AGENT_TELEMETRY_EVENT = 'pi-ledger:nested-agent-telemetry';
|
|
4
|
+
|
|
5
|
+
export const NESTED_AGENT_TELEMETRY_LIMITS = Object.freeze({
|
|
6
|
+
maxDepth: 12,
|
|
7
|
+
maxNodes: 2_048,
|
|
8
|
+
maxArrayItems: 64,
|
|
9
|
+
maxObjectKeys: 96,
|
|
10
|
+
maxObservations: 128,
|
|
11
|
+
maxStringBytes: 512,
|
|
12
|
+
maxPathBytes: 4_096,
|
|
13
|
+
maxIdentityCandidates: 12,
|
|
14
|
+
maxDedupeEntries: 2_048,
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
export interface NestedAgentTelemetryLimits {
|
|
18
|
+
readonly maxDepth: number;
|
|
19
|
+
readonly maxNodes: number;
|
|
20
|
+
readonly maxArrayItems: number;
|
|
21
|
+
readonly maxObjectKeys: number;
|
|
22
|
+
readonly maxObservations: number;
|
|
23
|
+
readonly maxStringBytes: number;
|
|
24
|
+
readonly maxPathBytes: number;
|
|
25
|
+
readonly maxIdentityCandidates: number;
|
|
26
|
+
readonly maxDedupeEntries: number;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export type NestedAgentTelemetrySource =
|
|
30
|
+
| 'tool_execution_update.partialResult'
|
|
31
|
+
| 'tool_execution_end.result'
|
|
32
|
+
| 'tool_result.details'
|
|
33
|
+
| 'tool_result_message.details'
|
|
34
|
+
| 'custom_message.details'
|
|
35
|
+
| 'artifact.metadata';
|
|
36
|
+
|
|
37
|
+
export type NestedAgentTelemetryConfidence = 'high' | 'medium' | 'low';
|
|
38
|
+
export type NestedAgentUsageMeasurement = 'cumulative' | 'delta';
|
|
39
|
+
|
|
40
|
+
export type NestedAgentIdentityKind =
|
|
41
|
+
| 'responseId'
|
|
42
|
+
| 'runId'
|
|
43
|
+
| 'sessionId'
|
|
44
|
+
| 'toolCallId'
|
|
45
|
+
| 'agentId'
|
|
46
|
+
| 'agent';
|
|
47
|
+
|
|
48
|
+
export interface NestedAgentIdentityCandidate {
|
|
49
|
+
kind: NestedAgentIdentityKind;
|
|
50
|
+
value: string;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* A bounded, metadata-only child-usage observation. It is intentionally not a
|
|
55
|
+
* ledger billing event: future billing code must independently require
|
|
56
|
+
* `childAgentCorroborated` and reconcile cumulative observations.
|
|
57
|
+
*/
|
|
58
|
+
export interface NestedAgentTelemetryObservation {
|
|
59
|
+
version: 1;
|
|
60
|
+
source: NestedAgentTelemetrySource;
|
|
61
|
+
sourcePath: string;
|
|
62
|
+
identityCandidates: NestedAgentIdentityCandidate[];
|
|
63
|
+
measurement: NestedAgentUsageMeasurement;
|
|
64
|
+
cumulative: boolean;
|
|
65
|
+
confidence: NestedAgentTelemetryConfidence;
|
|
66
|
+
childAgentCorroborated: boolean;
|
|
67
|
+
billingEligible: false;
|
|
68
|
+
outputTokens: number;
|
|
69
|
+
inputTokens?: number;
|
|
70
|
+
totalTokens?: number;
|
|
71
|
+
cacheReadTokens?: number;
|
|
72
|
+
cacheWriteTokens?: number;
|
|
73
|
+
costUsd?: number;
|
|
74
|
+
provider?: string;
|
|
75
|
+
model?: string;
|
|
76
|
+
responseId?: string;
|
|
77
|
+
sessionId?: string;
|
|
78
|
+
runId?: string;
|
|
79
|
+
toolCallId?: string;
|
|
80
|
+
logFile?: string;
|
|
81
|
+
sessionFile?: string;
|
|
82
|
+
artifactPath?: string;
|
|
83
|
+
timestamp?: number;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export interface NestedAgentTelemetryInput {
|
|
87
|
+
source: NestedAgentTelemetrySource;
|
|
88
|
+
value: unknown;
|
|
89
|
+
toolName?: unknown;
|
|
90
|
+
customType?: unknown;
|
|
91
|
+
toolCallId?: unknown;
|
|
92
|
+
sessionId?: unknown;
|
|
93
|
+
artifactPath?: unknown;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export interface NestedAgentTelemetryHarvesterOptions {
|
|
97
|
+
limits?: Partial<NestedAgentTelemetryLimits>;
|
|
98
|
+
onObservation?: (observation: NestedAgentTelemetryObservation) => void;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
interface UsageFields {
|
|
102
|
+
outputTokens: number;
|
|
103
|
+
inputTokens?: number;
|
|
104
|
+
totalTokens?: number;
|
|
105
|
+
cacheReadTokens?: number;
|
|
106
|
+
cacheWriteTokens?: number;
|
|
107
|
+
costUsd?: number;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
interface Metadata {
|
|
111
|
+
provider?: string;
|
|
112
|
+
model?: string;
|
|
113
|
+
responseId?: string;
|
|
114
|
+
sessionId?: string;
|
|
115
|
+
runId?: string;
|
|
116
|
+
toolCallId?: string;
|
|
117
|
+
agentId?: string;
|
|
118
|
+
agent?: string;
|
|
119
|
+
logFile?: string;
|
|
120
|
+
sessionFile?: string;
|
|
121
|
+
artifactPath?: string;
|
|
122
|
+
timestamp?: number;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
interface ScanItem {
|
|
126
|
+
value: object;
|
|
127
|
+
depth: number;
|
|
128
|
+
relativePath: string;
|
|
129
|
+
metadata: Metadata;
|
|
130
|
+
evidence: number;
|
|
131
|
+
parentKey?: string;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
interface DataEntry {
|
|
135
|
+
key: string;
|
|
136
|
+
normalizedKey: string;
|
|
137
|
+
value: unknown;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
interface DedupeSnapshot extends UsageFields {
|
|
141
|
+
fingerprint: string;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
const EVIDENCE_SOURCE_AGENT = 1 << 0;
|
|
145
|
+
const EVIDENCE_RESULTS = 1 << 1;
|
|
146
|
+
const EVIDENCE_CHILDREN = 1 << 2;
|
|
147
|
+
const EVIDENCE_AGENT_IDENTITY = 1 << 3;
|
|
148
|
+
const EVIDENCE_ASSISTANT = 1 << 4;
|
|
149
|
+
const EVIDENCE_CHILD_TOTAL = 1 << 5;
|
|
150
|
+
const EVIDENCE_FABRIC_AGENT = 1 << 6;
|
|
151
|
+
const EVIDENCE_METADATA_SOURCE = 1 << 7;
|
|
152
|
+
const EVIDENCE_MESSAGES = 1 << 8;
|
|
153
|
+
const EVIDENCE_FABRIC_SOURCE = 1 << 9;
|
|
154
|
+
|
|
155
|
+
const HARD_LIMITS: NestedAgentTelemetryLimits = {
|
|
156
|
+
maxDepth: 32,
|
|
157
|
+
maxNodes: 32_768,
|
|
158
|
+
maxArrayItems: 1_024,
|
|
159
|
+
maxObjectKeys: 1_024,
|
|
160
|
+
maxObservations: 2_048,
|
|
161
|
+
maxStringBytes: 4_096,
|
|
162
|
+
maxPathBytes: 16_384,
|
|
163
|
+
maxIdentityCandidates: 64,
|
|
164
|
+
maxDedupeEntries: 32_768,
|
|
165
|
+
};
|
|
166
|
+
|
|
167
|
+
const TELEMETRY_SOURCES = new Set<NestedAgentTelemetrySource>([
|
|
168
|
+
'tool_execution_update.partialResult',
|
|
169
|
+
'tool_execution_end.result',
|
|
170
|
+
'tool_result.details',
|
|
171
|
+
'tool_result_message.details',
|
|
172
|
+
'custom_message.details',
|
|
173
|
+
'artifact.metadata',
|
|
174
|
+
]);
|
|
175
|
+
|
|
176
|
+
const AGENT_TOOL_NAMES = new Set([
|
|
177
|
+
'agent',
|
|
178
|
+
'agents',
|
|
179
|
+
'delegate',
|
|
180
|
+
'delegation',
|
|
181
|
+
'pi-agent',
|
|
182
|
+
'pi-agents',
|
|
183
|
+
'pi-subagent',
|
|
184
|
+
'pi-subagents',
|
|
185
|
+
'pi_agent',
|
|
186
|
+
'pi_agents',
|
|
187
|
+
'pi_subagent',
|
|
188
|
+
'pi_subagents',
|
|
189
|
+
'subagent',
|
|
190
|
+
'subagents',
|
|
191
|
+
]);
|
|
192
|
+
|
|
193
|
+
const RAW_VALUE_KEYS = new Set([
|
|
194
|
+
'content',
|
|
195
|
+
'errormessage',
|
|
196
|
+
'finaloutput',
|
|
197
|
+
'outputtext',
|
|
198
|
+
'prompt',
|
|
199
|
+
'raw',
|
|
200
|
+
'reasoning',
|
|
201
|
+
'response',
|
|
202
|
+
'stderr',
|
|
203
|
+
'stdout',
|
|
204
|
+
'summary',
|
|
205
|
+
'task',
|
|
206
|
+
'text',
|
|
207
|
+
'thinking',
|
|
208
|
+
'value',
|
|
209
|
+
]);
|
|
210
|
+
|
|
211
|
+
const MEDIA_KEYS = new Set(['audio', 'base64', 'image', 'images', 'media', 'video']);
|
|
212
|
+
|
|
213
|
+
const USAGE_CONTAINER_KEYS = new Set([
|
|
214
|
+
'childusage',
|
|
215
|
+
'cumulativeusage',
|
|
216
|
+
'deltausage',
|
|
217
|
+
'totalchildusage',
|
|
218
|
+
'totalusage',
|
|
219
|
+
'tokens',
|
|
220
|
+
'usage',
|
|
221
|
+
'usagecumulative',
|
|
222
|
+
'usagedelta',
|
|
223
|
+
]);
|
|
224
|
+
|
|
225
|
+
const RESULT_CONTAINER_KEYS = new Set(['nodes', 'results', 'steps']);
|
|
226
|
+
const MESSAGE_CONTAINER_KEYS = new Set(['messages']);
|
|
227
|
+
const CHILD_CONTAINER_KEYS = new Set([
|
|
228
|
+
'agents',
|
|
229
|
+
'children',
|
|
230
|
+
'nestedagents',
|
|
231
|
+
'subagents',
|
|
232
|
+
'workers',
|
|
233
|
+
]);
|
|
234
|
+
|
|
235
|
+
const RELEVANT_KEYS = new Set([
|
|
236
|
+
...USAGE_CONTAINER_KEYS,
|
|
237
|
+
...RESULT_CONTAINER_KEYS,
|
|
238
|
+
...CHILD_CONTAINER_KEYS,
|
|
239
|
+
'action',
|
|
240
|
+
'agent',
|
|
241
|
+
'agentid',
|
|
242
|
+
'agentname',
|
|
243
|
+
'artifactpath',
|
|
244
|
+
'artifactpaths',
|
|
245
|
+
'artifacts',
|
|
246
|
+
'asyncid',
|
|
247
|
+
'audits',
|
|
248
|
+
'cacheread',
|
|
249
|
+
'cachereadtokens',
|
|
250
|
+
'cachewrite',
|
|
251
|
+
'cachewritetokens',
|
|
252
|
+
'completiontokens',
|
|
253
|
+
'cost',
|
|
254
|
+
'costusd',
|
|
255
|
+
'details',
|
|
256
|
+
'id',
|
|
257
|
+
'input',
|
|
258
|
+
'inputtokens',
|
|
259
|
+
'kind',
|
|
260
|
+
'logfile',
|
|
261
|
+
'messages',
|
|
262
|
+
'metadata',
|
|
263
|
+
'model',
|
|
264
|
+
'modelid',
|
|
265
|
+
'output',
|
|
266
|
+
'outputtokens',
|
|
267
|
+
'provider',
|
|
268
|
+
'ref',
|
|
269
|
+
'responseid',
|
|
270
|
+
'result',
|
|
271
|
+
'role',
|
|
272
|
+
'runid',
|
|
273
|
+
'runtimeplan',
|
|
274
|
+
'sessionfile',
|
|
275
|
+
'sessionid',
|
|
276
|
+
'sessionpath',
|
|
277
|
+
'target',
|
|
278
|
+
'timestamp',
|
|
279
|
+
'tool',
|
|
280
|
+
'toolcallid',
|
|
281
|
+
'total',
|
|
282
|
+
'totaltokens',
|
|
283
|
+
]);
|
|
284
|
+
|
|
285
|
+
const TOKEN_LIMIT = 1_000_000_000_000;
|
|
286
|
+
const COST_LIMIT = 1_000_000_000;
|
|
287
|
+
|
|
288
|
+
function normalizedKey(key: string): string {
|
|
289
|
+
return key.toLowerCase().replace(/[^a-z0-9]/g, '');
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
function isSensitiveKey(key: string): boolean {
|
|
293
|
+
const keyName = normalizedKey(key);
|
|
294
|
+
if (keyName === 'token') return true;
|
|
295
|
+
if (keyName.endsWith('token') && !keyName.endsWith('tokens')) return true;
|
|
296
|
+
return [
|
|
297
|
+
'apikey',
|
|
298
|
+
'authorization',
|
|
299
|
+
'clientsecret',
|
|
300
|
+
'cookie',
|
|
301
|
+
'credential',
|
|
302
|
+
'credentials',
|
|
303
|
+
'password',
|
|
304
|
+
'passwd',
|
|
305
|
+
'privatekey',
|
|
306
|
+
'secret',
|
|
307
|
+
].some((sensitive) => keyName === sensitive || keyName.endsWith(sensitive));
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
function byteLength(value: string): number {
|
|
311
|
+
return Buffer.byteLength(value, 'utf8');
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
function truncateUtf8(value: string, maxBytes: number): string {
|
|
315
|
+
if (byteLength(value) <= maxBytes) return value;
|
|
316
|
+
const ellipsis = '…';
|
|
317
|
+
const suffix = byteLength(ellipsis) <= maxBytes ? ellipsis : '';
|
|
318
|
+
const available = maxBytes - byteLength(suffix);
|
|
319
|
+
let result = '';
|
|
320
|
+
let used = 0;
|
|
321
|
+
for (const character of value) {
|
|
322
|
+
const characterBytes = byteLength(character);
|
|
323
|
+
if (used + characterBytes > available) break;
|
|
324
|
+
result += character;
|
|
325
|
+
used += characterBytes;
|
|
326
|
+
}
|
|
327
|
+
return `${result}${suffix}`;
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
function boundedInteger(value: unknown, maximum: number): number | undefined {
|
|
331
|
+
return typeof value === 'number' && Number.isSafeInteger(value) && value >= 0 && value <= maximum
|
|
332
|
+
? value
|
|
333
|
+
: undefined;
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
function boundedCost(value: unknown): number | undefined {
|
|
337
|
+
return typeof value === 'number' && Number.isFinite(value) && value >= 0 && value <= COST_LIMIT
|
|
338
|
+
? value
|
|
339
|
+
: undefined;
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
function configuredLimit(
|
|
343
|
+
requested: number | undefined,
|
|
344
|
+
fallback: number,
|
|
345
|
+
hardMaximum: number
|
|
346
|
+
): number {
|
|
347
|
+
if (!Number.isSafeInteger(requested) || requested === undefined || requested < 1) {
|
|
348
|
+
return fallback;
|
|
349
|
+
}
|
|
350
|
+
return Math.min(requested, hardMaximum);
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
function resolveLimits(
|
|
354
|
+
requested: Partial<NestedAgentTelemetryLimits> | undefined
|
|
355
|
+
): NestedAgentTelemetryLimits {
|
|
356
|
+
return {
|
|
357
|
+
maxDepth: configuredLimit(
|
|
358
|
+
requested?.maxDepth,
|
|
359
|
+
NESTED_AGENT_TELEMETRY_LIMITS.maxDepth,
|
|
360
|
+
HARD_LIMITS.maxDepth
|
|
361
|
+
),
|
|
362
|
+
maxNodes: configuredLimit(
|
|
363
|
+
requested?.maxNodes,
|
|
364
|
+
NESTED_AGENT_TELEMETRY_LIMITS.maxNodes,
|
|
365
|
+
HARD_LIMITS.maxNodes
|
|
366
|
+
),
|
|
367
|
+
maxArrayItems: configuredLimit(
|
|
368
|
+
requested?.maxArrayItems,
|
|
369
|
+
NESTED_AGENT_TELEMETRY_LIMITS.maxArrayItems,
|
|
370
|
+
HARD_LIMITS.maxArrayItems
|
|
371
|
+
),
|
|
372
|
+
maxObjectKeys: configuredLimit(
|
|
373
|
+
requested?.maxObjectKeys,
|
|
374
|
+
NESTED_AGENT_TELEMETRY_LIMITS.maxObjectKeys,
|
|
375
|
+
HARD_LIMITS.maxObjectKeys
|
|
376
|
+
),
|
|
377
|
+
maxObservations: configuredLimit(
|
|
378
|
+
requested?.maxObservations,
|
|
379
|
+
NESTED_AGENT_TELEMETRY_LIMITS.maxObservations,
|
|
380
|
+
HARD_LIMITS.maxObservations
|
|
381
|
+
),
|
|
382
|
+
maxStringBytes: configuredLimit(
|
|
383
|
+
requested?.maxStringBytes,
|
|
384
|
+
NESTED_AGENT_TELEMETRY_LIMITS.maxStringBytes,
|
|
385
|
+
HARD_LIMITS.maxStringBytes
|
|
386
|
+
),
|
|
387
|
+
maxPathBytes: configuredLimit(
|
|
388
|
+
requested?.maxPathBytes,
|
|
389
|
+
NESTED_AGENT_TELEMETRY_LIMITS.maxPathBytes,
|
|
390
|
+
HARD_LIMITS.maxPathBytes
|
|
391
|
+
),
|
|
392
|
+
maxIdentityCandidates: configuredLimit(
|
|
393
|
+
requested?.maxIdentityCandidates,
|
|
394
|
+
NESTED_AGENT_TELEMETRY_LIMITS.maxIdentityCandidates,
|
|
395
|
+
HARD_LIMITS.maxIdentityCandidates
|
|
396
|
+
),
|
|
397
|
+
maxDedupeEntries: configuredLimit(
|
|
398
|
+
requested?.maxDedupeEntries,
|
|
399
|
+
NESTED_AGENT_TELEMETRY_LIMITS.maxDedupeEntries,
|
|
400
|
+
HARD_LIMITS.maxDedupeEntries
|
|
401
|
+
),
|
|
402
|
+
};
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
function safeArray(value: object): boolean | undefined {
|
|
406
|
+
try {
|
|
407
|
+
return Array.isArray(value);
|
|
408
|
+
} catch {
|
|
409
|
+
return undefined;
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
function ownDataProperty(value: unknown, key: string): unknown {
|
|
414
|
+
if (typeof value !== 'object' || value === null) return undefined;
|
|
415
|
+
try {
|
|
416
|
+
const descriptor = Object.getOwnPropertyDescriptor(value, key);
|
|
417
|
+
return descriptor && 'value' in descriptor ? descriptor.value : undefined;
|
|
418
|
+
} catch {
|
|
419
|
+
return undefined;
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
function boundedPropertyKey(key: string, limits: NestedAgentTelemetryLimits): boolean {
|
|
424
|
+
if (byteLength(key) > limits.maxStringBytes) return false;
|
|
425
|
+
for (let index = 0; index < key.length; index++) {
|
|
426
|
+
const code = key.charCodeAt(index);
|
|
427
|
+
if (code < 32 || code === 127) return false;
|
|
428
|
+
}
|
|
429
|
+
return true;
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
function objectEntries(value: object, limits: NestedAgentTelemetryLimits): DataEntry[] {
|
|
433
|
+
const isArray = safeArray(value);
|
|
434
|
+
if (isArray === undefined) return [];
|
|
435
|
+
if (isArray) {
|
|
436
|
+
const length = boundedInteger(ownDataProperty(value, 'length'), Number.MAX_SAFE_INTEGER) ?? 0;
|
|
437
|
+
const entries: DataEntry[] = [];
|
|
438
|
+
const count = Math.min(length, limits.maxArrayItems);
|
|
439
|
+
for (let index = 0; index < count; index++) {
|
|
440
|
+
const key = String(index);
|
|
441
|
+
try {
|
|
442
|
+
const descriptor = Object.getOwnPropertyDescriptor(value, key);
|
|
443
|
+
if (descriptor && 'value' in descriptor) {
|
|
444
|
+
entries.push({ key, normalizedKey: key, value: descriptor.value });
|
|
445
|
+
}
|
|
446
|
+
} catch {
|
|
447
|
+
// A hostile proxy can fail individual descriptor reads.
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
return entries;
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
let ownKeys: (string | symbol)[];
|
|
454
|
+
try {
|
|
455
|
+
ownKeys = Reflect.ownKeys(value);
|
|
456
|
+
} catch {
|
|
457
|
+
return [];
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
const entries: DataEntry[] = [];
|
|
461
|
+
for (const key of ownKeys.slice(0, limits.maxObjectKeys)) {
|
|
462
|
+
if (typeof key !== 'string' || !boundedPropertyKey(key, limits)) continue;
|
|
463
|
+
try {
|
|
464
|
+
const descriptor = Object.getOwnPropertyDescriptor(value, key);
|
|
465
|
+
if (!descriptor || !descriptor.enumerable || !('value' in descriptor)) continue;
|
|
466
|
+
entries.push({ key, normalizedKey: normalizedKey(key), value: descriptor.value });
|
|
467
|
+
} catch {
|
|
468
|
+
// Skip getters, revoked proxies, and inconsistent descriptors.
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
entries.sort((left, right) => {
|
|
472
|
+
const leftPriority = RELEVANT_KEYS.has(left.normalizedKey) ? 0 : 1;
|
|
473
|
+
const rightPriority = RELEVANT_KEYS.has(right.normalizedKey) ? 0 : 1;
|
|
474
|
+
return leftPriority - rightPriority || left.key.localeCompare(right.key);
|
|
475
|
+
});
|
|
476
|
+
return entries;
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
function entryValue(entries: DataEntry[], ...keys: string[]): unknown {
|
|
480
|
+
for (const key of keys) {
|
|
481
|
+
const found = entries.find((entry) => entry.normalizedKey === key);
|
|
482
|
+
if (found) return found.value;
|
|
483
|
+
}
|
|
484
|
+
return undefined;
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
function boundedString(value: unknown, limits: NestedAgentTelemetryLimits): string | undefined {
|
|
488
|
+
if (typeof value !== 'string' || value.length === 0) return undefined;
|
|
489
|
+
if (byteLength(value) > limits.maxStringBytes) return undefined;
|
|
490
|
+
for (let index = 0; index < value.length; index++) {
|
|
491
|
+
const code = value.charCodeAt(index);
|
|
492
|
+
if (code < 32 || code === 127) return undefined;
|
|
493
|
+
}
|
|
494
|
+
return value;
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
function structuralIdentifier(
|
|
498
|
+
value: unknown,
|
|
499
|
+
limits: NestedAgentTelemetryLimits
|
|
500
|
+
): string | undefined {
|
|
501
|
+
const candidate = boundedString(value, limits);
|
|
502
|
+
if (!candidate || candidate.length > 512) return undefined;
|
|
503
|
+
if (!/^[A-Za-z0-9][A-Za-z0-9._:/@+~-]*$/.test(candidate)) return undefined;
|
|
504
|
+
return candidate;
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
function safeLocalPath(value: unknown, limits: NestedAgentTelemetryLimits): string | undefined {
|
|
508
|
+
if (typeof value !== 'string' || value.length === 0) return undefined;
|
|
509
|
+
if (byteLength(value) > limits.maxPathBytes || value.includes('\0')) return undefined;
|
|
510
|
+
for (let index = 0; index < value.length; index++) {
|
|
511
|
+
const code = value.charCodeAt(index);
|
|
512
|
+
if (code < 32 || code === 127) return undefined;
|
|
513
|
+
}
|
|
514
|
+
const isWindowsPath = /^[A-Za-z]:[\\/]/.test(value);
|
|
515
|
+
if (!isWindowsPath && /^[A-Za-z][A-Za-z0-9+.-]*:/.test(value)) return undefined;
|
|
516
|
+
if (value.startsWith('//') || value.startsWith('\\\\')) return undefined;
|
|
517
|
+
return value;
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
function timestampValue(value: unknown, limits: NestedAgentTelemetryLimits): number | undefined {
|
|
521
|
+
if (typeof value === 'number') {
|
|
522
|
+
return Number.isFinite(value) && value >= 0 ? value : undefined;
|
|
523
|
+
}
|
|
524
|
+
const text = boundedString(value, limits);
|
|
525
|
+
if (!text) return undefined;
|
|
526
|
+
const parsed = Date.parse(text);
|
|
527
|
+
return Number.isFinite(parsed) && parsed >= 0 ? parsed : undefined;
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
function recognizedAgentToolName(value: unknown, limits: NestedAgentTelemetryLimits): boolean {
|
|
531
|
+
const name = boundedString(value, limits)?.toLowerCase();
|
|
532
|
+
return name !== undefined && AGENT_TOOL_NAMES.has(name);
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
function recognizedAgentCustomType(value: unknown, limits: NestedAgentTelemetryLimits): boolean {
|
|
536
|
+
const customType = boundedString(value, limits)?.toLowerCase();
|
|
537
|
+
if (!customType) return false;
|
|
538
|
+
return (
|
|
539
|
+
/^(?:(?:pi|child|nested)[-_:])?(?:agent|agents|subagent|subagents)(?:[-_:][a-z0-9][a-z0-9._+~-]*)*$/.test(
|
|
540
|
+
customType
|
|
541
|
+
) || /^pi[-_:]tidy(?:[-_:][a-z0-9][a-z0-9._+~-]*)*$/.test(customType)
|
|
542
|
+
);
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
function mergeMetadata(parent: Metadata, local: Metadata): Metadata {
|
|
546
|
+
return {
|
|
547
|
+
...parent,
|
|
548
|
+
...Object.fromEntries(
|
|
549
|
+
Object.entries(local).filter(
|
|
550
|
+
(entry): entry is [string, string | number] => entry[1] !== undefined
|
|
551
|
+
)
|
|
552
|
+
),
|
|
553
|
+
};
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
function modelMetadata(
|
|
557
|
+
value: unknown,
|
|
558
|
+
limits: NestedAgentTelemetryLimits
|
|
559
|
+
): Pick<Metadata, 'provider' | 'model'> {
|
|
560
|
+
const direct = structuralIdentifier(value, limits);
|
|
561
|
+
if (direct) return { model: direct };
|
|
562
|
+
if (typeof value !== 'object' || value === null) return {};
|
|
563
|
+
const entries = objectEntries(value, limits);
|
|
564
|
+
const provider = structuralIdentifier(entryValue(entries, 'provider'), limits);
|
|
565
|
+
const model = structuralIdentifier(entryValue(entries, 'model', 'modelid', 'id'), limits);
|
|
566
|
+
return {
|
|
567
|
+
...(provider ? { provider } : {}),
|
|
568
|
+
...(model ? { model } : {}),
|
|
569
|
+
};
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
function nestedRuntimeMetadata(
|
|
573
|
+
value: unknown,
|
|
574
|
+
limits: NestedAgentTelemetryLimits
|
|
575
|
+
): Pick<Metadata, 'provider' | 'model'> {
|
|
576
|
+
if (typeof value !== 'object' || value === null) return {};
|
|
577
|
+
const entries = objectEntries(value, limits);
|
|
578
|
+
const observed = entryValue(entries, 'observed');
|
|
579
|
+
if (typeof observed === 'object' && observed !== null) {
|
|
580
|
+
const observedEntries = objectEntries(observed, limits);
|
|
581
|
+
const provider = structuralIdentifier(entryValue(observedEntries, 'provider'), limits);
|
|
582
|
+
const model = structuralIdentifier(entryValue(observedEntries, 'modelid', 'model'), limits);
|
|
583
|
+
if (provider || model) {
|
|
584
|
+
return {
|
|
585
|
+
...(provider ? { provider } : {}),
|
|
586
|
+
...(model ? { model } : {}),
|
|
587
|
+
};
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
const provider = structuralIdentifier(entryValue(entries, 'provider'), limits);
|
|
591
|
+
const model = structuralIdentifier(entryValue(entries, 'modelid', 'model'), limits);
|
|
592
|
+
return {
|
|
593
|
+
...(provider ? { provider } : {}),
|
|
594
|
+
...(model ? { model } : {}),
|
|
595
|
+
};
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
function nestedPathMetadata(
|
|
599
|
+
value: unknown,
|
|
600
|
+
limits: NestedAgentTelemetryLimits
|
|
601
|
+
): Pick<Metadata, 'logFile' | 'sessionFile' | 'artifactPath'> {
|
|
602
|
+
if (typeof value !== 'object' || value === null) return {};
|
|
603
|
+
const entries = objectEntries(value, limits);
|
|
604
|
+
const logFile = safeLocalPath(entryValue(entries, 'logfile'), limits);
|
|
605
|
+
const sessionFile = safeLocalPath(entryValue(entries, 'sessionfile', 'sessionpath'), limits);
|
|
606
|
+
const artifactPath = safeLocalPath(
|
|
607
|
+
entryValue(
|
|
608
|
+
entries,
|
|
609
|
+
'artifactpath',
|
|
610
|
+
'outputpath',
|
|
611
|
+
'savedoutputpath',
|
|
612
|
+
'structuredoutputpath',
|
|
613
|
+
'transcriptpath',
|
|
614
|
+
'metadatapath',
|
|
615
|
+
'jsonlpath',
|
|
616
|
+
'path',
|
|
617
|
+
'dir'
|
|
618
|
+
),
|
|
619
|
+
limits
|
|
620
|
+
);
|
|
621
|
+
return {
|
|
622
|
+
...(logFile ? { logFile } : {}),
|
|
623
|
+
...(sessionFile ? { sessionFile } : {}),
|
|
624
|
+
...(artifactPath ? { artifactPath } : {}),
|
|
625
|
+
};
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
function directMetadata(
|
|
629
|
+
entries: DataEntry[],
|
|
630
|
+
parent: Metadata,
|
|
631
|
+
evidence: number,
|
|
632
|
+
relativePath: string,
|
|
633
|
+
limits: NestedAgentTelemetryLimits
|
|
634
|
+
): Metadata {
|
|
635
|
+
const provider = structuralIdentifier(entryValue(entries, 'provider'), limits);
|
|
636
|
+
const model = modelMetadata(entryValue(entries, 'model'), limits);
|
|
637
|
+
const responseId = structuralIdentifier(entryValue(entries, 'responseid'), limits);
|
|
638
|
+
const sessionId = structuralIdentifier(entryValue(entries, 'sessionid'), limits);
|
|
639
|
+
const explicitRunId = structuralIdentifier(entryValue(entries, 'runid', 'asyncid'), limits);
|
|
640
|
+
const toolCallId = structuralIdentifier(entryValue(entries, 'toolcallid'), limits);
|
|
641
|
+
const agentId = structuralIdentifier(entryValue(entries, 'agentid', 'childid', 'target'), limits);
|
|
642
|
+
const agent = structuralIdentifier(entryValue(entries, 'agent', 'agentname'), limits);
|
|
643
|
+
const genericId = structuralIdentifier(entryValue(entries, 'id'), limits);
|
|
644
|
+
const logFile = safeLocalPath(entryValue(entries, 'logfile'), limits);
|
|
645
|
+
const sessionFile = safeLocalPath(entryValue(entries, 'sessionfile', 'sessionpath'), limits);
|
|
646
|
+
const explicitArtifactPath = safeLocalPath(
|
|
647
|
+
entryValue(
|
|
648
|
+
entries,
|
|
649
|
+
'artifactpath',
|
|
650
|
+
'outputpath',
|
|
651
|
+
'savedoutputpath',
|
|
652
|
+
'structuredoutputpath',
|
|
653
|
+
'transcriptpath'
|
|
654
|
+
),
|
|
655
|
+
limits
|
|
656
|
+
);
|
|
657
|
+
const genericPath = /(?:^|\.)(?:artifact|artifacts|metadata)(?:\.|\[|$)/i.test(relativePath)
|
|
658
|
+
? safeLocalPath(entryValue(entries, 'path', 'dir'), limits)
|
|
659
|
+
: undefined;
|
|
660
|
+
const timestamp = timestampValue(
|
|
661
|
+
entryValue(entries, 'timestamp', 'createdat', 'endedat', 'finishedat', 'updatedat'),
|
|
662
|
+
limits
|
|
663
|
+
);
|
|
664
|
+
const runtimePlan = nestedRuntimeMetadata(entryValue(entries, 'runtimeplan'), limits);
|
|
665
|
+
const nestedPaths = nestedPathMetadata(
|
|
666
|
+
entryValue(entries, 'artifactpaths', 'artifacts', 'metadata'),
|
|
667
|
+
limits
|
|
668
|
+
);
|
|
669
|
+
const inferredRunId =
|
|
670
|
+
!explicitRunId &&
|
|
671
|
+
!parent.runId &&
|
|
672
|
+
genericId &&
|
|
673
|
+
(evidence & (EVIDENCE_FABRIC_AGENT | EVIDENCE_CHILDREN | EVIDENCE_SOURCE_AGENT)) !== 0
|
|
674
|
+
? genericId
|
|
675
|
+
: undefined;
|
|
676
|
+
|
|
677
|
+
return mergeMetadata(parent, {
|
|
678
|
+
...(provider ? { provider } : {}),
|
|
679
|
+
...model,
|
|
680
|
+
...runtimePlan,
|
|
681
|
+
...(responseId ? { responseId } : {}),
|
|
682
|
+
...(sessionId ? { sessionId } : {}),
|
|
683
|
+
...(explicitRunId || inferredRunId ? { runId: explicitRunId ?? inferredRunId } : {}),
|
|
684
|
+
...(toolCallId ? { toolCallId } : {}),
|
|
685
|
+
...(agentId ? { agentId } : {}),
|
|
686
|
+
...(agent ? { agent } : {}),
|
|
687
|
+
...nestedPaths,
|
|
688
|
+
...(logFile ? { logFile } : {}),
|
|
689
|
+
...(sessionFile ? { sessionFile } : {}),
|
|
690
|
+
...(explicitArtifactPath || genericPath
|
|
691
|
+
? { artifactPath: explicitArtifactPath ?? genericPath }
|
|
692
|
+
: {}),
|
|
693
|
+
...(timestamp !== undefined ? { timestamp } : {}),
|
|
694
|
+
});
|
|
695
|
+
}
|
|
696
|
+
|
|
697
|
+
function evidenceFromObject(
|
|
698
|
+
entries: DataEntry[],
|
|
699
|
+
inherited: number,
|
|
700
|
+
limits: NestedAgentTelemetryLimits
|
|
701
|
+
): number {
|
|
702
|
+
let evidence = inherited;
|
|
703
|
+
const role = boundedString(entryValue(entries, 'role'), limits);
|
|
704
|
+
const provider = structuralIdentifier(entryValue(entries, 'provider'), limits);
|
|
705
|
+
const model = structuralIdentifier(entryValue(entries, 'model', 'modelid'), limits);
|
|
706
|
+
const timestamp = timestampValue(entryValue(entries, 'timestamp'), limits);
|
|
707
|
+
if (role === 'assistant' && provider && model && timestamp !== undefined) {
|
|
708
|
+
evidence |= EVIDENCE_ASSISTANT;
|
|
709
|
+
}
|
|
710
|
+
if (
|
|
711
|
+
structuralIdentifier(entryValue(entries, 'agent', 'agentid', 'agentname', 'childid'), limits) ||
|
|
712
|
+
entryValue(entries, 'kind') === 'agent'
|
|
713
|
+
) {
|
|
714
|
+
evidence |= EVIDENCE_AGENT_IDENTITY;
|
|
715
|
+
}
|
|
716
|
+
const ref = structuralIdentifier(entryValue(entries, 'ref'), limits)?.toLowerCase();
|
|
717
|
+
const action = structuralIdentifier(entryValue(entries, 'action', 'tool'), limits)?.toLowerCase();
|
|
718
|
+
if (
|
|
719
|
+
(inherited & EVIDENCE_FABRIC_SOURCE) !== 0 &&
|
|
720
|
+
ref === 'agents.run' &&
|
|
721
|
+
provider?.toLowerCase() === 'agents' &&
|
|
722
|
+
action === 'run'
|
|
723
|
+
) {
|
|
724
|
+
evidence |= EVIDENCE_FABRIC_AGENT;
|
|
725
|
+
}
|
|
726
|
+
return evidence;
|
|
727
|
+
}
|
|
728
|
+
|
|
729
|
+
function nestedNumber(
|
|
730
|
+
value: unknown,
|
|
731
|
+
keys: string[],
|
|
732
|
+
maximum: number,
|
|
733
|
+
limits: NestedAgentTelemetryLimits
|
|
734
|
+
): number | undefined {
|
|
735
|
+
if (typeof value !== 'object' || value === null) return undefined;
|
|
736
|
+
const entries = objectEntries(value, limits);
|
|
737
|
+
return boundedInteger(entryValue(entries, ...keys), maximum);
|
|
738
|
+
}
|
|
739
|
+
|
|
740
|
+
function costValue(value: unknown, limits: NestedAgentTelemetryLimits): number | undefined {
|
|
741
|
+
const direct = boundedCost(value);
|
|
742
|
+
if (direct !== undefined) return direct;
|
|
743
|
+
if (typeof value !== 'object' || value === null) return undefined;
|
|
744
|
+
const entries = objectEntries(value, limits);
|
|
745
|
+
return boundedCost(entryValue(entries, 'total', 'costusd', 'usd'));
|
|
746
|
+
}
|
|
747
|
+
|
|
748
|
+
function parseUsage(
|
|
749
|
+
entries: DataEntry[],
|
|
750
|
+
parentKey: string | undefined,
|
|
751
|
+
evidence: number,
|
|
752
|
+
limits: NestedAgentTelemetryLimits
|
|
753
|
+
): UsageFields | undefined {
|
|
754
|
+
const output = boundedInteger(
|
|
755
|
+
entryValue(entries, 'output', 'outputtokens', 'completiontokens'),
|
|
756
|
+
TOKEN_LIMIT
|
|
757
|
+
);
|
|
758
|
+
const tokenObject = entryValue(entries, 'tokens');
|
|
759
|
+
const nestedOutput = nestedNumber(
|
|
760
|
+
tokenObject,
|
|
761
|
+
['output', 'outputtokens', 'completiontokens'],
|
|
762
|
+
TOKEN_LIMIT,
|
|
763
|
+
limits
|
|
764
|
+
);
|
|
765
|
+
const outputTokens = output ?? nestedOutput;
|
|
766
|
+
if (outputTokens === undefined) return undefined;
|
|
767
|
+
|
|
768
|
+
const normalizedParent = parentKey ? normalizedKey(parentKey) : undefined;
|
|
769
|
+
const explicitContainer = normalizedParent ? USAGE_CONTAINER_KEYS.has(normalizedParent) : false;
|
|
770
|
+
const flattenedNames = entries.some((entry) =>
|
|
771
|
+
['inputtokens', 'outputtokens', 'completiontokens', 'costusd'].includes(entry.normalizedKey)
|
|
772
|
+
);
|
|
773
|
+
const flatChildUsage =
|
|
774
|
+
(evidence &
|
|
775
|
+
(EVIDENCE_RESULTS | EVIDENCE_CHILDREN | EVIDENCE_SOURCE_AGENT | EVIDENCE_FABRIC_AGENT)) !==
|
|
776
|
+
0 &&
|
|
777
|
+
entries.some((entry) => entry.normalizedKey === 'input') &&
|
|
778
|
+
entries.some((entry) => entry.normalizedKey === 'output') &&
|
|
779
|
+
entries.some((entry) =>
|
|
780
|
+
[
|
|
781
|
+
'cacheread',
|
|
782
|
+
'cachewrite',
|
|
783
|
+
'cost',
|
|
784
|
+
'providertaffic',
|
|
785
|
+
'providertraffic',
|
|
786
|
+
'tokens',
|
|
787
|
+
'turns',
|
|
788
|
+
].includes(entry.normalizedKey)
|
|
789
|
+
);
|
|
790
|
+
if (!explicitContainer && !flattenedNames && !flatChildUsage) return undefined;
|
|
791
|
+
|
|
792
|
+
const inputTokens =
|
|
793
|
+
boundedInteger(entryValue(entries, 'input', 'inputtokens', 'prompttokens'), TOKEN_LIMIT) ??
|
|
794
|
+
nestedNumber(tokenObject, ['input', 'inputtokens', 'prompttokens'], TOKEN_LIMIT, limits);
|
|
795
|
+
const totalTokens =
|
|
796
|
+
boundedInteger(entryValue(entries, 'total', 'totaltokens'), TOKEN_LIMIT) ??
|
|
797
|
+
nestedNumber(tokenObject, ['total', 'totaltokens'], TOKEN_LIMIT, limits);
|
|
798
|
+
const cacheReadTokens = boundedInteger(
|
|
799
|
+
entryValue(entries, 'cacheread', 'cachereadtokens'),
|
|
800
|
+
TOKEN_LIMIT
|
|
801
|
+
);
|
|
802
|
+
const cacheWriteTokens = boundedInteger(
|
|
803
|
+
entryValue(entries, 'cachewrite', 'cachewritetokens'),
|
|
804
|
+
TOKEN_LIMIT
|
|
805
|
+
);
|
|
806
|
+
const costUsd = costValue(entryValue(entries, 'cost', 'costusd'), limits);
|
|
807
|
+
return {
|
|
808
|
+
outputTokens,
|
|
809
|
+
...(inputTokens !== undefined ? { inputTokens } : {}),
|
|
810
|
+
...(totalTokens !== undefined ? { totalTokens } : {}),
|
|
811
|
+
...(cacheReadTokens !== undefined ? { cacheReadTokens } : {}),
|
|
812
|
+
...(cacheWriteTokens !== undefined ? { cacheWriteTokens } : {}),
|
|
813
|
+
...(costUsd !== undefined ? { costUsd } : {}),
|
|
814
|
+
};
|
|
815
|
+
}
|
|
816
|
+
|
|
817
|
+
function childEvidenceForKey(evidence: number, key: string): number {
|
|
818
|
+
const normalized = normalizedKey(key);
|
|
819
|
+
if (RESULT_CONTAINER_KEYS.has(normalized)) return evidence | EVIDENCE_RESULTS;
|
|
820
|
+
if (CHILD_CONTAINER_KEYS.has(normalized)) return evidence | EVIDENCE_CHILDREN;
|
|
821
|
+
if (MESSAGE_CONTAINER_KEYS.has(normalized)) return evidence | EVIDENCE_MESSAGES;
|
|
822
|
+
if (normalized === 'totalchildusage' || normalized === 'childusage') {
|
|
823
|
+
return evidence | EVIDENCE_CHILD_TOTAL;
|
|
824
|
+
}
|
|
825
|
+
return evidence;
|
|
826
|
+
}
|
|
827
|
+
|
|
828
|
+
function confidenceFor(evidence: number): {
|
|
829
|
+
confidence: NestedAgentTelemetryConfidence;
|
|
830
|
+
corroborated: boolean;
|
|
831
|
+
} | null {
|
|
832
|
+
const fromAgentSource = (evidence & EVIDENCE_SOURCE_AGENT) !== 0;
|
|
833
|
+
const fromMetadata = (evidence & EVIDENCE_METADATA_SOURCE) !== 0;
|
|
834
|
+
const hasAgentIdentity = (evidence & EVIDENCE_AGENT_IDENTITY) !== 0;
|
|
835
|
+
const inChildStructure = (evidence & (EVIDENCE_RESULTS | EVIDENCE_CHILDREN)) !== 0;
|
|
836
|
+
const isAssistant = (evidence & EVIDENCE_ASSISTANT) !== 0;
|
|
837
|
+
|
|
838
|
+
if ((evidence & EVIDENCE_MESSAGES) !== 0 && !isAssistant) return null;
|
|
839
|
+
if ((evidence & EVIDENCE_FABRIC_AGENT) !== 0) {
|
|
840
|
+
return { confidence: 'high', corroborated: true };
|
|
841
|
+
}
|
|
842
|
+
if (fromAgentSource && (evidence & EVIDENCE_CHILD_TOTAL) !== 0) {
|
|
843
|
+
return { confidence: 'high', corroborated: true };
|
|
844
|
+
}
|
|
845
|
+
if (fromAgentSource && (hasAgentIdentity || (isAssistant && inChildStructure))) {
|
|
846
|
+
return { confidence: 'high', corroborated: true };
|
|
847
|
+
}
|
|
848
|
+
if (fromMetadata && hasAgentIdentity && inChildStructure) {
|
|
849
|
+
return { confidence: 'medium', corroborated: true };
|
|
850
|
+
}
|
|
851
|
+
if (fromAgentSource || (fromMetadata && isAssistant && inChildStructure)) {
|
|
852
|
+
return { confidence: 'low', corroborated: false };
|
|
853
|
+
}
|
|
854
|
+
return null;
|
|
855
|
+
}
|
|
856
|
+
|
|
857
|
+
function measurementFor(
|
|
858
|
+
parentKey: string | undefined,
|
|
859
|
+
evidence: number
|
|
860
|
+
): NestedAgentUsageMeasurement {
|
|
861
|
+
const normalized = parentKey ? normalizedKey(parentKey) : '';
|
|
862
|
+
if (normalized.includes('delta') || (evidence & EVIDENCE_ASSISTANT) !== 0) return 'delta';
|
|
863
|
+
return 'cumulative';
|
|
864
|
+
}
|
|
865
|
+
|
|
866
|
+
function sourcePath(
|
|
867
|
+
source: NestedAgentTelemetrySource,
|
|
868
|
+
relativePath: string,
|
|
869
|
+
limits: NestedAgentTelemetryLimits
|
|
870
|
+
): string {
|
|
871
|
+
const joined = relativePath ? `${source}.${relativePath}` : source;
|
|
872
|
+
return truncateUtf8(joined, limits.maxPathBytes);
|
|
873
|
+
}
|
|
874
|
+
|
|
875
|
+
function hashedPathKey(key: string): string {
|
|
876
|
+
let hash = 2_166_136_261;
|
|
877
|
+
for (let index = 0; index < key.length; index++) {
|
|
878
|
+
hash ^= key.charCodeAt(index);
|
|
879
|
+
hash = Math.imul(hash, 16_777_619);
|
|
880
|
+
}
|
|
881
|
+
return `#${(hash >>> 0).toString(16).padStart(8, '0')}`;
|
|
882
|
+
}
|
|
883
|
+
|
|
884
|
+
function childPath(
|
|
885
|
+
parent: string,
|
|
886
|
+
key: string,
|
|
887
|
+
array: boolean,
|
|
888
|
+
limits: NestedAgentTelemetryLimits
|
|
889
|
+
): string | undefined {
|
|
890
|
+
const segment = array
|
|
891
|
+
? `[${key}]`
|
|
892
|
+
: /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(key)
|
|
893
|
+
? parent
|
|
894
|
+
? `.${key}`
|
|
895
|
+
: key
|
|
896
|
+
: `['${hashedPathKey(key)}']`;
|
|
897
|
+
const path = `${parent}${segment}`;
|
|
898
|
+
return byteLength(path) <= limits.maxPathBytes ? path : undefined;
|
|
899
|
+
}
|
|
900
|
+
|
|
901
|
+
function canonicalDedupePath(relativePath: string): string {
|
|
902
|
+
return relativePath
|
|
903
|
+
.replace(/^details\./, '')
|
|
904
|
+
.replace(/^message\.details\./, '')
|
|
905
|
+
.replace(/\.details\./g, '.');
|
|
906
|
+
}
|
|
907
|
+
|
|
908
|
+
function identityCandidates(
|
|
909
|
+
metadata: Metadata,
|
|
910
|
+
limits: NestedAgentTelemetryLimits
|
|
911
|
+
): NestedAgentIdentityCandidate[] {
|
|
912
|
+
const candidates: NestedAgentIdentityCandidate[] = [];
|
|
913
|
+
const seen = new Set<string>();
|
|
914
|
+
const add = (kind: NestedAgentIdentityKind, value: string | undefined) => {
|
|
915
|
+
if (!value || candidates.length >= limits.maxIdentityCandidates) return;
|
|
916
|
+
const key = `${kind}:${value}`;
|
|
917
|
+
if (seen.has(key)) return;
|
|
918
|
+
seen.add(key);
|
|
919
|
+
candidates.push({ kind, value });
|
|
920
|
+
};
|
|
921
|
+
add('responseId', metadata.responseId);
|
|
922
|
+
add('runId', metadata.runId);
|
|
923
|
+
add('sessionId', metadata.sessionId);
|
|
924
|
+
add('toolCallId', metadata.toolCallId);
|
|
925
|
+
add('agentId', metadata.agentId);
|
|
926
|
+
add('agent', metadata.agent);
|
|
927
|
+
return candidates;
|
|
928
|
+
}
|
|
929
|
+
|
|
930
|
+
function usageFingerprint(observation: NestedAgentTelemetryObservation): string {
|
|
931
|
+
return [
|
|
932
|
+
observation.outputTokens,
|
|
933
|
+
observation.inputTokens ?? '',
|
|
934
|
+
observation.totalTokens ?? '',
|
|
935
|
+
observation.cacheReadTokens ?? '',
|
|
936
|
+
observation.cacheWriteTokens ?? '',
|
|
937
|
+
observation.costUsd ?? '',
|
|
938
|
+
].join(':');
|
|
939
|
+
}
|
|
940
|
+
|
|
941
|
+
function dedupeSnapshot(observation: NestedAgentTelemetryObservation): DedupeSnapshot {
|
|
942
|
+
return {
|
|
943
|
+
outputTokens: observation.outputTokens,
|
|
944
|
+
...(observation.inputTokens !== undefined ? { inputTokens: observation.inputTokens } : {}),
|
|
945
|
+
...(observation.totalTokens !== undefined ? { totalTokens: observation.totalTokens } : {}),
|
|
946
|
+
...(observation.cacheReadTokens !== undefined
|
|
947
|
+
? { cacheReadTokens: observation.cacheReadTokens }
|
|
948
|
+
: {}),
|
|
949
|
+
...(observation.cacheWriteTokens !== undefined
|
|
950
|
+
? { cacheWriteTokens: observation.cacheWriteTokens }
|
|
951
|
+
: {}),
|
|
952
|
+
...(observation.costUsd !== undefined ? { costUsd: observation.costUsd } : {}),
|
|
953
|
+
fingerprint: usageFingerprint(observation),
|
|
954
|
+
};
|
|
955
|
+
}
|
|
956
|
+
|
|
957
|
+
function cumulativeProgress(previous: DedupeSnapshot, current: DedupeSnapshot): boolean {
|
|
958
|
+
const fields = [
|
|
959
|
+
'outputTokens',
|
|
960
|
+
'inputTokens',
|
|
961
|
+
'totalTokens',
|
|
962
|
+
'cacheReadTokens',
|
|
963
|
+
'cacheWriteTokens',
|
|
964
|
+
'costUsd',
|
|
965
|
+
] as const;
|
|
966
|
+
let advanced = false;
|
|
967
|
+
for (const field of fields) {
|
|
968
|
+
const previousValue = previous[field];
|
|
969
|
+
const currentValue = current[field];
|
|
970
|
+
if (currentValue === undefined) {
|
|
971
|
+
if (previousValue !== undefined) return false;
|
|
972
|
+
continue;
|
|
973
|
+
}
|
|
974
|
+
if (previousValue === undefined) {
|
|
975
|
+
advanced = true;
|
|
976
|
+
continue;
|
|
977
|
+
}
|
|
978
|
+
if (currentValue < previousValue) return false;
|
|
979
|
+
if (currentValue > previousValue) advanced = true;
|
|
980
|
+
}
|
|
981
|
+
return advanced;
|
|
982
|
+
}
|
|
983
|
+
|
|
984
|
+
function safeTelemetryInput(input: unknown): NestedAgentTelemetryInput | undefined {
|
|
985
|
+
const source = ownDataProperty(input, 'source');
|
|
986
|
+
if (typeof source !== 'string' || !TELEMETRY_SOURCES.has(source as NestedAgentTelemetrySource)) {
|
|
987
|
+
return undefined;
|
|
988
|
+
}
|
|
989
|
+
return {
|
|
990
|
+
source: source as NestedAgentTelemetrySource,
|
|
991
|
+
value: ownDataProperty(input, 'value'),
|
|
992
|
+
toolName: ownDataProperty(input, 'toolName'),
|
|
993
|
+
customType: ownDataProperty(input, 'customType'),
|
|
994
|
+
toolCallId: ownDataProperty(input, 'toolCallId'),
|
|
995
|
+
sessionId: ownDataProperty(input, 'sessionId'),
|
|
996
|
+
artifactPath: ownDataProperty(input, 'artifactPath'),
|
|
997
|
+
};
|
|
998
|
+
}
|
|
999
|
+
|
|
1000
|
+
function safeMessageEnvelope(message: unknown): {
|
|
1001
|
+
role?: string;
|
|
1002
|
+
customType?: unknown;
|
|
1003
|
+
toolCallId?: unknown;
|
|
1004
|
+
toolName?: unknown;
|
|
1005
|
+
details?: unknown;
|
|
1006
|
+
} {
|
|
1007
|
+
const role = ownDataProperty(message, 'role');
|
|
1008
|
+
return {
|
|
1009
|
+
...(typeof role === 'string' ? { role } : {}),
|
|
1010
|
+
customType: ownDataProperty(message, 'customType'),
|
|
1011
|
+
toolCallId: ownDataProperty(message, 'toolCallId'),
|
|
1012
|
+
toolName: ownDataProperty(message, 'toolName'),
|
|
1013
|
+
details: ownDataProperty(message, 'details'),
|
|
1014
|
+
};
|
|
1015
|
+
}
|
|
1016
|
+
|
|
1017
|
+
/**
|
|
1018
|
+
* Structural scanner for untrusted extension/tool metadata. It never reads an
|
|
1019
|
+
* artifact path, invokes an accessor, or retains raw model/tool text.
|
|
1020
|
+
*/
|
|
1021
|
+
export class NestedAgentTelemetryHarvester {
|
|
1022
|
+
readonly limits: NestedAgentTelemetryLimits;
|
|
1023
|
+
private readonly onObservation:
|
|
1024
|
+
| ((observation: NestedAgentTelemetryObservation) => void)
|
|
1025
|
+
| undefined;
|
|
1026
|
+
private readonly dedupe = new Map<string, DedupeSnapshot>();
|
|
1027
|
+
|
|
1028
|
+
constructor(options: NestedAgentTelemetryHarvesterOptions = {}) {
|
|
1029
|
+
this.limits = Object.freeze(resolveLimits(options.limits));
|
|
1030
|
+
this.onObservation = options.onObservation;
|
|
1031
|
+
}
|
|
1032
|
+
|
|
1033
|
+
reset(): void {
|
|
1034
|
+
this.dedupe.clear();
|
|
1035
|
+
}
|
|
1036
|
+
|
|
1037
|
+
harvest(input: NestedAgentTelemetryInput): NestedAgentTelemetryObservation[] {
|
|
1038
|
+
try {
|
|
1039
|
+
const safeInput = safeTelemetryInput(input);
|
|
1040
|
+
return safeInput ? this.harvestUnchecked(safeInput) : [];
|
|
1041
|
+
} catch {
|
|
1042
|
+
return [];
|
|
1043
|
+
}
|
|
1044
|
+
}
|
|
1045
|
+
|
|
1046
|
+
private harvestUnchecked(input: NestedAgentTelemetryInput): NestedAgentTelemetryObservation[] {
|
|
1047
|
+
if (typeof input.value !== 'object' || input.value === null) return [];
|
|
1048
|
+
const rootIsArray = safeArray(input.value);
|
|
1049
|
+
if (rootIsArray === undefined) return [];
|
|
1050
|
+
|
|
1051
|
+
const toolName = boundedString(input.toolName, this.limits)?.toLowerCase();
|
|
1052
|
+
let evidence = input.source === 'artifact.metadata' ? EVIDENCE_METADATA_SOURCE : 0;
|
|
1053
|
+
if (
|
|
1054
|
+
recognizedAgentToolName(input.toolName, this.limits) ||
|
|
1055
|
+
recognizedAgentCustomType(input.customType, this.limits)
|
|
1056
|
+
) {
|
|
1057
|
+
evidence |= EVIDENCE_SOURCE_AGENT;
|
|
1058
|
+
}
|
|
1059
|
+
if (toolName === 'fabric_exec') evidence |= EVIDENCE_FABRIC_SOURCE;
|
|
1060
|
+
|
|
1061
|
+
const toolCallId = structuralIdentifier(input.toolCallId, this.limits);
|
|
1062
|
+
const sessionId = structuralIdentifier(input.sessionId, this.limits);
|
|
1063
|
+
const artifactPath = safeLocalPath(input.artifactPath, this.limits);
|
|
1064
|
+
const initialMetadata: Metadata = {
|
|
1065
|
+
...(toolCallId ? { toolCallId } : {}),
|
|
1066
|
+
...(sessionId ? { sessionId } : {}),
|
|
1067
|
+
...(artifactPath ? { artifactPath } : {}),
|
|
1068
|
+
};
|
|
1069
|
+
const queue: ScanItem[] = [
|
|
1070
|
+
{
|
|
1071
|
+
value: input.value,
|
|
1072
|
+
depth: 0,
|
|
1073
|
+
relativePath: '',
|
|
1074
|
+
metadata: initialMetadata,
|
|
1075
|
+
evidence,
|
|
1076
|
+
},
|
|
1077
|
+
];
|
|
1078
|
+
const visited = new WeakSet<object>();
|
|
1079
|
+
const observations: NestedAgentTelemetryObservation[] = [];
|
|
1080
|
+
let queueIndex = 0;
|
|
1081
|
+
let nodes = 0;
|
|
1082
|
+
|
|
1083
|
+
while (
|
|
1084
|
+
queueIndex < queue.length &&
|
|
1085
|
+
nodes < this.limits.maxNodes &&
|
|
1086
|
+
observations.length < this.limits.maxObservations
|
|
1087
|
+
) {
|
|
1088
|
+
const item = queue[queueIndex++];
|
|
1089
|
+
if (!item) break;
|
|
1090
|
+
nodes++;
|
|
1091
|
+
try {
|
|
1092
|
+
if (visited.has(item.value)) continue;
|
|
1093
|
+
visited.add(item.value);
|
|
1094
|
+
} catch {
|
|
1095
|
+
continue;
|
|
1096
|
+
}
|
|
1097
|
+
const entries = objectEntries(item.value, this.limits);
|
|
1098
|
+
if (entries.length === 0) continue;
|
|
1099
|
+
const objectEvidence = evidenceFromObject(entries, item.evidence, this.limits);
|
|
1100
|
+
const metadata = directMetadata(
|
|
1101
|
+
entries,
|
|
1102
|
+
item.metadata,
|
|
1103
|
+
objectEvidence,
|
|
1104
|
+
item.relativePath,
|
|
1105
|
+
this.limits
|
|
1106
|
+
);
|
|
1107
|
+
const usage = parseUsage(entries, item.parentKey, objectEvidence, this.limits);
|
|
1108
|
+
if (usage) {
|
|
1109
|
+
const confidence = confidenceFor(objectEvidence);
|
|
1110
|
+
if (confidence) {
|
|
1111
|
+
const measurement = measurementFor(item.parentKey, objectEvidence);
|
|
1112
|
+
const observation: NestedAgentTelemetryObservation = {
|
|
1113
|
+
version: 1,
|
|
1114
|
+
source: input.source,
|
|
1115
|
+
sourcePath: sourcePath(input.source, item.relativePath, this.limits),
|
|
1116
|
+
identityCandidates: identityCandidates(metadata, this.limits),
|
|
1117
|
+
measurement,
|
|
1118
|
+
cumulative: measurement === 'cumulative',
|
|
1119
|
+
confidence: confidence.confidence,
|
|
1120
|
+
childAgentCorroborated: confidence.corroborated,
|
|
1121
|
+
billingEligible: false,
|
|
1122
|
+
...usage,
|
|
1123
|
+
...(metadata.provider ? { provider: metadata.provider } : {}),
|
|
1124
|
+
...(metadata.model ? { model: metadata.model } : {}),
|
|
1125
|
+
...(metadata.responseId ? { responseId: metadata.responseId } : {}),
|
|
1126
|
+
...(metadata.sessionId ? { sessionId: metadata.sessionId } : {}),
|
|
1127
|
+
...(metadata.runId ? { runId: metadata.runId } : {}),
|
|
1128
|
+
...(metadata.toolCallId ? { toolCallId: metadata.toolCallId } : {}),
|
|
1129
|
+
...(metadata.logFile ? { logFile: metadata.logFile } : {}),
|
|
1130
|
+
...(metadata.sessionFile ? { sessionFile: metadata.sessionFile } : {}),
|
|
1131
|
+
...(metadata.artifactPath ? { artifactPath: metadata.artifactPath } : {}),
|
|
1132
|
+
...(metadata.timestamp !== undefined ? { timestamp: metadata.timestamp } : {}),
|
|
1133
|
+
};
|
|
1134
|
+
if (this.acceptObservation(observation, canonicalDedupePath(item.relativePath))) {
|
|
1135
|
+
observations.push(observation);
|
|
1136
|
+
try {
|
|
1137
|
+
this.onObservation?.(observation);
|
|
1138
|
+
} catch {
|
|
1139
|
+
// Telemetry consumers must not affect tool execution.
|
|
1140
|
+
}
|
|
1141
|
+
}
|
|
1142
|
+
}
|
|
1143
|
+
}
|
|
1144
|
+
|
|
1145
|
+
if (item.depth >= this.limits.maxDepth) continue;
|
|
1146
|
+
const parentIsArray = safeArray(item.value) === true;
|
|
1147
|
+
for (const entry of entries) {
|
|
1148
|
+
if (isSensitiveKey(entry.key) || MEDIA_KEYS.has(entry.normalizedKey)) continue;
|
|
1149
|
+
if (RAW_VALUE_KEYS.has(entry.normalizedKey)) continue;
|
|
1150
|
+
if (typeof entry.value !== 'object' || entry.value === null) continue;
|
|
1151
|
+
const entryIsArray = safeArray(entry.value);
|
|
1152
|
+
if (entryIsArray === undefined) continue;
|
|
1153
|
+
const nextPath = childPath(item.relativePath, entry.key, parentIsArray, this.limits);
|
|
1154
|
+
if (nextPath === undefined) continue;
|
|
1155
|
+
if (queue.length >= this.limits.maxNodes) break;
|
|
1156
|
+
const nextEvidence = childEvidenceForKey(objectEvidence, entry.key);
|
|
1157
|
+
queue.push({
|
|
1158
|
+
value: entry.value,
|
|
1159
|
+
depth: item.depth + 1,
|
|
1160
|
+
relativePath: nextPath,
|
|
1161
|
+
metadata,
|
|
1162
|
+
evidence: nextEvidence,
|
|
1163
|
+
parentKey: entry.key,
|
|
1164
|
+
});
|
|
1165
|
+
}
|
|
1166
|
+
}
|
|
1167
|
+
return observations;
|
|
1168
|
+
}
|
|
1169
|
+
|
|
1170
|
+
private acceptObservation(
|
|
1171
|
+
observation: NestedAgentTelemetryObservation,
|
|
1172
|
+
relativePath: string
|
|
1173
|
+
): boolean {
|
|
1174
|
+
let stableIdentity: string | undefined;
|
|
1175
|
+
if (observation.responseId) {
|
|
1176
|
+
stableIdentity = `response:${observation.responseId}`;
|
|
1177
|
+
} else {
|
|
1178
|
+
const identifiers = [
|
|
1179
|
+
observation.runId ? `run:${observation.runId}` : '',
|
|
1180
|
+
observation.sessionId ? `session:${observation.sessionId}` : '',
|
|
1181
|
+
observation.toolCallId ? `tool:${observation.toolCallId}` : '',
|
|
1182
|
+
].filter(Boolean);
|
|
1183
|
+
if (identifiers.length === 0) return true;
|
|
1184
|
+
stableIdentity = [...identifiers, `path:${relativePath}`].join('|');
|
|
1185
|
+
}
|
|
1186
|
+
|
|
1187
|
+
const current = dedupeSnapshot(observation);
|
|
1188
|
+
const previous = this.dedupe.get(stableIdentity);
|
|
1189
|
+
const monotonic = observation.cumulative || observation.responseId !== undefined;
|
|
1190
|
+
if (previous) {
|
|
1191
|
+
if (previous.fingerprint === current.fingerprint) return false;
|
|
1192
|
+
if (monotonic && !cumulativeProgress(previous, current)) return false;
|
|
1193
|
+
this.dedupe.delete(stableIdentity);
|
|
1194
|
+
}
|
|
1195
|
+
this.dedupe.set(stableIdentity, current);
|
|
1196
|
+
while (this.dedupe.size > this.limits.maxDedupeEntries) {
|
|
1197
|
+
const oldest = this.dedupe.keys().next().value as string | undefined;
|
|
1198
|
+
if (oldest === undefined) break;
|
|
1199
|
+
this.dedupe.delete(oldest);
|
|
1200
|
+
}
|
|
1201
|
+
return true;
|
|
1202
|
+
}
|
|
1203
|
+
}
|
|
1204
|
+
|
|
1205
|
+
export function harvestNestedAgentTelemetry(
|
|
1206
|
+
input: NestedAgentTelemetryInput,
|
|
1207
|
+
options: NestedAgentTelemetryHarvesterOptions = {}
|
|
1208
|
+
): NestedAgentTelemetryObservation[] {
|
|
1209
|
+
return new NestedAgentTelemetryHarvester(options).harvest(input);
|
|
1210
|
+
}
|
|
1211
|
+
|
|
1212
|
+
/**
|
|
1213
|
+
* Attach the isolated harvester to Pi lifecycle metadata surfaces. The only
|
|
1214
|
+
* side effect is a shared event-bus observation; ledger totals and billing are
|
|
1215
|
+
* deliberately untouched.
|
|
1216
|
+
*/
|
|
1217
|
+
export function installNestedAgentTelemetryHarvester(
|
|
1218
|
+
pi: Pick<ExtensionAPI, 'on' | 'events'>,
|
|
1219
|
+
options: Omit<NestedAgentTelemetryHarvesterOptions, 'onObservation'> = {}
|
|
1220
|
+
): NestedAgentTelemetryHarvester {
|
|
1221
|
+
const harvester = new NestedAgentTelemetryHarvester({
|
|
1222
|
+
...options,
|
|
1223
|
+
onObservation: (observation) => {
|
|
1224
|
+
try {
|
|
1225
|
+
pi.events.emit(NESTED_AGENT_TELEMETRY_EVENT, observation);
|
|
1226
|
+
} catch {
|
|
1227
|
+
// Shared-bus listeners are observational and never load-bearing.
|
|
1228
|
+
}
|
|
1229
|
+
},
|
|
1230
|
+
});
|
|
1231
|
+
|
|
1232
|
+
pi.on('session_start', () => {
|
|
1233
|
+
harvester.reset();
|
|
1234
|
+
});
|
|
1235
|
+
pi.on('tool_execution_update', (event) => {
|
|
1236
|
+
harvester.harvest({
|
|
1237
|
+
source: 'tool_execution_update.partialResult',
|
|
1238
|
+
value: ownDataProperty(event, 'partialResult'),
|
|
1239
|
+
toolName: ownDataProperty(event, 'toolName'),
|
|
1240
|
+
toolCallId: ownDataProperty(event, 'toolCallId'),
|
|
1241
|
+
});
|
|
1242
|
+
});
|
|
1243
|
+
pi.on('tool_execution_end', (event) => {
|
|
1244
|
+
harvester.harvest({
|
|
1245
|
+
source: 'tool_execution_end.result',
|
|
1246
|
+
value: ownDataProperty(event, 'result'),
|
|
1247
|
+
toolName: ownDataProperty(event, 'toolName'),
|
|
1248
|
+
toolCallId: ownDataProperty(event, 'toolCallId'),
|
|
1249
|
+
});
|
|
1250
|
+
});
|
|
1251
|
+
pi.on('tool_result', (event) => {
|
|
1252
|
+
harvester.harvest({
|
|
1253
|
+
source: 'tool_result.details',
|
|
1254
|
+
value: ownDataProperty(event, 'details'),
|
|
1255
|
+
toolName: ownDataProperty(event, 'toolName'),
|
|
1256
|
+
toolCallId: ownDataProperty(event, 'toolCallId'),
|
|
1257
|
+
});
|
|
1258
|
+
});
|
|
1259
|
+
pi.on('message_end', (event) => {
|
|
1260
|
+
const message = safeMessageEnvelope(ownDataProperty(event, 'message'));
|
|
1261
|
+
if (message.role !== 'custom' && message.role !== 'toolResult') return;
|
|
1262
|
+
harvester.harvest({
|
|
1263
|
+
source: message.role === 'custom' ? 'custom_message.details' : 'tool_result_message.details',
|
|
1264
|
+
value: message.details,
|
|
1265
|
+
customType: message.customType,
|
|
1266
|
+
toolName: message.toolName,
|
|
1267
|
+
toolCallId: message.toolCallId,
|
|
1268
|
+
});
|
|
1269
|
+
});
|
|
1270
|
+
return harvester;
|
|
1271
|
+
}
|