@monotykamary/pi-ledger 0.3.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.
@@ -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
+ }