@volcengine/tls-observer-claude-code 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js ADDED
@@ -0,0 +1,1982 @@
1
+ import { homedir, hostname, networkInterfaces, userInfo } from "node:os";
2
+ import { tlsOpenapi } from "@volcengine/openapi";
3
+ import { createHash, randomUUID } from "node:crypto";
4
+ import * as __rspack_external_node_fs_promises_153e37e0 from "node:fs/promises";
5
+ import * as __rspack_external_node_fs_5ea92f0c from "node:fs";
6
+ import * as __rspack_external_node_path_c5b9b54f from "node:path";
7
+ function firstEnv(...names) {
8
+ for (const name of names){
9
+ const value = process.env[name];
10
+ if ('string' == typeof value && '' !== value.trim()) return value.trim();
11
+ }
12
+ }
13
+ function envFlag(name, defaultValue = false) {
14
+ const value = process.env[name];
15
+ if (void 0 === value || '' === value) return defaultValue;
16
+ return /^(1|true|yes|y|on)$/i.test(value);
17
+ }
18
+ function envDisabled(name) {
19
+ return /^(0|false|no|n|off)$/i.test(process.env[name] ?? '');
20
+ }
21
+ function envPositiveInteger(name, fallback) {
22
+ const value = Number.parseInt(process.env[name] ?? '', 10);
23
+ return Number.isFinite(value) && value > 0 ? value : fallback;
24
+ }
25
+ function defaultDataRoot() {
26
+ return __rspack_external_node_path_c5b9b54f.join(homedir(), '.claude', 'plugins', 'data', 'tls-observer-claude-code');
27
+ }
28
+ function defaultEnvFile() {
29
+ return __rspack_external_node_path_c5b9b54f.join(homedir(), '.claude', 'tls-observer-claude-code.env');
30
+ }
31
+ function parseEnvValue(raw) {
32
+ const value = raw.trim();
33
+ if (value.startsWith('"') && value.endsWith('"') || value.startsWith("'") && value.endsWith("'")) return value.slice(1, -1).replace(/\\n/g, '\n').replace(/\\"/g, '"').replace(/\\'/g, "'");
34
+ return value;
35
+ }
36
+ function loadEnvFile(filePath) {
37
+ const target = filePath || firstEnv('CLAUDE_CODE_TLS_ENV_FILE') || defaultEnvFile();
38
+ let text;
39
+ try {
40
+ text = __rspack_external_node_fs_5ea92f0c.readFileSync(target, 'utf8');
41
+ } catch {
42
+ return false;
43
+ }
44
+ for (const rawLine of text.split(/\r?\n/)){
45
+ const line = rawLine.trim();
46
+ if (!line || line.startsWith('#')) continue;
47
+ const normalized = line.startsWith('export ') ? line.slice(7).trim() : line;
48
+ const match = normalized.match(/^([A-Za-z_][A-Za-z0-9_]*)=(.*)$/);
49
+ if (!match) continue;
50
+ const [, key, rawValue] = match;
51
+ process.env[key] ??= parseEnvValue(rawValue);
52
+ }
53
+ return true;
54
+ }
55
+ function loadConfig() {
56
+ loadEnvFile();
57
+ const dataRoot = firstEnv('CLAUDE_CODE_TLS_DATA_ROOT', 'CLAUDE_PLUGIN_DATA') || defaultDataRoot();
58
+ const serviceName = firstEnv('CLAUDE_CODE_OTEL_SERVICE_NAME') || "claude-code-transcript-hook";
59
+ const exportRetries = Math.min(envPositiveInteger('CLAUDE_CODE_TLS_EXPORT_TIMEOUT_RETRIES', 1), 3);
60
+ return {
61
+ enabled: !envDisabled('CLAUDE_CODE_TLS_EXPORT'),
62
+ dataRoot,
63
+ stateFilePath: firstEnv('CLAUDE_CODE_TLS_STATE_FILE') || __rspack_external_node_path_c5b9b54f.join(dataRoot, 'state', 'claude-code-state.json'),
64
+ sendLogsDir: __rspack_external_node_path_c5b9b54f.join(dataRoot, 'logs', 'send'),
65
+ artifactsDir: __rspack_external_node_path_c5b9b54f.join(dataRoot, 'artifacts'),
66
+ diagnosticLogMaxBytes: envPositiveInteger('CLAUDE_CODE_TLS_DIAGNOSTIC_LOG_MAX_BYTES', 5242880),
67
+ captureContent: !envDisabled('CLAUDE_CODE_TLS_CAPTURE_CONTENT'),
68
+ debugArtifacts: envFlag('CLAUDE_CODE_TLS_DEBUG_ARTIFACTS', false),
69
+ serviceName,
70
+ serviceInstanceId: `${serviceName}@${hostname()}:${process.pid}`,
71
+ tlsEndpoint: firstEnv('CLAUDE_CODE_TLS_ENDPOINT', 'TRACE_TLS_ENDPOINT', 'TLS_ENDPOINT'),
72
+ otelEndpoint: firstEnv('CLAUDE_CODE_TLS_OTEL_ENDPOINT', 'OTEL_EXPORTER_OTLP_TRACES_ENDPOINT'),
73
+ region: firstEnv('CLAUDE_CODE_TLS_REGION', 'TLS_REGION'),
74
+ traceTopicId: firstEnv('CLAUDE_CODE_TLS_TRACE_TOPIC_ID', 'CLAUDE_CODE_TLS_TOPIC_ID', 'TLS_TRACE_TOPIC_ID'),
75
+ apiKey: firstEnv('CLAUDE_CODE_TLS_API_KEY', 'TLS_API_KEY'),
76
+ ak: firstEnv('CLAUDE_CODE_TLS_AK', 'TLS_AK'),
77
+ sk: firstEnv('CLAUDE_CODE_TLS_SK', 'TLS_SK'),
78
+ authMode: 'resource' === firstEnv('CLAUDE_CODE_TLS_AUTH_MODE', 'TLS_AUTH_MODE') ? 'resource' : 'header',
79
+ exportTimeoutMs: envPositiveInteger('CLAUDE_CODE_TLS_EXPORT_TIMEOUT_MS', 20000),
80
+ exportTimeoutRetries: exportRetries
81
+ };
82
+ }
83
+ function localIdentityMetadata() {
84
+ return {
85
+ 'user.name': userInfo().username
86
+ };
87
+ }
88
+ function safeJsonStringify(value, fallback = '{}') {
89
+ const seen = new WeakSet();
90
+ try {
91
+ return JSON.stringify(value, (_key, item)=>{
92
+ if (item && 'object' == typeof item) {
93
+ if (seen.has(item)) return '[Circular]';
94
+ seen.add(item);
95
+ }
96
+ return item;
97
+ }) ?? fallback;
98
+ } catch {
99
+ return fallback;
100
+ }
101
+ }
102
+ function compactObject(value) {
103
+ return Object.fromEntries(Object.entries(value).filter(([, item])=>null != item && '' !== item));
104
+ }
105
+ const TLS_ANONYMOUS_IDENTITY_HEADER = 'x-tls-anonymous-identity';
106
+ const NO_RETRY_STATUS_CODES = [
107
+ 400,
108
+ 401,
109
+ 403,
110
+ 404
111
+ ];
112
+ const producerEntries = new Map();
113
+ function firstExternalIpv4() {
114
+ for (const items of Object.values(networkInterfaces()))for (const item of items ?? [])if ('IPv4' === item.family && !item.internal && item.address) return item.address;
115
+ return '127.0.0.1';
116
+ }
117
+ function normalizeProducerEndpoint(endpoint) {
118
+ const raw = endpoint.trim();
119
+ if (!raw) return raw;
120
+ try {
121
+ const url = new URL(/^https?:\/\//.test(raw) ? raw : `https://${raw}`);
122
+ return url.hostname || raw;
123
+ } catch {
124
+ return raw.replace(/^https?:\/\//, '').replace(/\/.*$/, '').replace(/:\d+$/, '');
125
+ }
126
+ }
127
+ function producerKey(config) {
128
+ return JSON.stringify({
129
+ endpoint: normalizeProducerEndpoint(config.endpoint),
130
+ region: config.region,
131
+ apiKey: config.apiKey || '',
132
+ ak: config.ak || '',
133
+ sk: config.sk || ''
134
+ });
135
+ }
136
+ function timeoutError(label, timeoutMs) {
137
+ return new Error(`${label} timed out after ${timeoutMs}ms`);
138
+ }
139
+ function withTimeout(promise, timeoutMs, label) {
140
+ const effectiveTimeoutMs = Number.isFinite(timeoutMs) && Number(timeoutMs) > 0 ? Math.trunc(Number(timeoutMs)) : 20000;
141
+ return new Promise((resolve, reject)=>{
142
+ const timer = setTimeout(()=>reject(timeoutError(label, effectiveTimeoutMs)), effectiveTimeoutMs);
143
+ promise.then((value)=>{
144
+ clearTimeout(timer);
145
+ resolve(value);
146
+ }, (error)=>{
147
+ clearTimeout(timer);
148
+ reject(error);
149
+ });
150
+ });
151
+ }
152
+ function getProducer(config) {
153
+ const key = producerKey(config);
154
+ const existing = producerEntries.get(key);
155
+ if (existing) return existing;
156
+ const producerConfig = tlsOpenapi.Producer.getDefaultProducerConfig();
157
+ producerConfig.Endpoint = normalizeProducerEndpoint(config.endpoint);
158
+ producerConfig.Region = config.region;
159
+ producerConfig.NoRetryStatusCodeList = NO_RETRY_STATUS_CODES;
160
+ if (config.apiKey) {
161
+ producerConfig.AccessKeyID = '';
162
+ producerConfig.AccessKeySecret = '';
163
+ producerConfig.Headers = {
164
+ [TLS_ANONYMOUS_IDENTITY_HEADER]: config.apiKey
165
+ };
166
+ } else {
167
+ producerConfig.AccessKeyID = config.ak || '';
168
+ producerConfig.AccessKeySecret = config.sk || '';
169
+ }
170
+ const producer = new tlsOpenapi.Producer(producerConfig);
171
+ producer.Start();
172
+ const entry = {
173
+ producer,
174
+ source: firstExternalIpv4(),
175
+ timeoutMs: config.timeoutMs ?? 20000
176
+ };
177
+ producerEntries.set(key, entry);
178
+ return entry;
179
+ }
180
+ async function sendTlsLogRecords(config, records, fileName) {
181
+ if (0 === records.length) return;
182
+ const entry = getProducer(config);
183
+ const logGroup = {
184
+ Logs: records.map((record)=>({
185
+ Time: Date.now(),
186
+ Contents: Object.entries(record).map(([Key, Value])=>({
187
+ Key,
188
+ Value
189
+ }))
190
+ })),
191
+ Source: entry.source,
192
+ LogTags: [],
193
+ FileName: fileName
194
+ };
195
+ await withTimeout(new Promise((resolve, reject)=>{
196
+ let settled = false;
197
+ const settle = (fn, value)=>{
198
+ if (settled) return;
199
+ settled = true;
200
+ fn(value);
201
+ };
202
+ const resolveUnknown = resolve;
203
+ const sendPromise = entry.producer.SendLogs('', config.traceTopicId, entry.source, fileName, logGroup, {
204
+ Success: ()=>settle(resolveUnknown),
205
+ Fail: (result)=>{
206
+ const attempts = result?.Attempts ?? [];
207
+ const lastAttempt = attempts[attempts.length - 1] ?? {};
208
+ settle(reject, new Error(`TLS SendLogs failed topicId=${config.traceTopicId} requestId=${String(lastAttempt.RequestId ?? '')} errorCode=${String(lastAttempt.ErrorCode ?? '')} error=${JSON.stringify(lastAttempt)}`));
209
+ }
210
+ });
211
+ Promise.resolve(sendPromise).catch((error)=>settle(reject, error));
212
+ }), config.timeoutMs, 'TLS SendLogs');
213
+ }
214
+ async function closeTraceLogProducers() {
215
+ const entries = [
216
+ ...producerEntries.values()
217
+ ];
218
+ producerEntries.clear();
219
+ await Promise.allSettled(entries.map((entry)=>{
220
+ const closeTimeoutMs = Math.min(Math.max(entry.timeoutMs || 20000, 1000), 5000);
221
+ return withTimeout(Promise.resolve(entry.producer.Close()), closeTimeoutMs, 'TLS Producer Close');
222
+ }));
223
+ }
224
+ var package_namespaceObject = {
225
+ rE: "0.0.1"
226
+ };
227
+ function toOtlpAnyValue(value) {
228
+ if ('string' == typeof value) return {
229
+ stringValue: value
230
+ };
231
+ if ('boolean' == typeof value) return {
232
+ boolValue: value
233
+ };
234
+ if ('number' == typeof value) return Number.isInteger(value) ? {
235
+ intValue: String(value)
236
+ } : {
237
+ doubleValue: value
238
+ };
239
+ if (Array.isArray(value)) return {
240
+ arrayValue: {
241
+ values: value.map((item)=>toOtlpAnyValue(item))
242
+ }
243
+ };
244
+ if (value && 'object' == typeof value) return {
245
+ kvlistValue: {
246
+ values: Object.entries(value).filter(([, item])=>void 0 !== item).map(([key, item])=>({
247
+ key,
248
+ value: toOtlpAnyValue(item)
249
+ }))
250
+ }
251
+ };
252
+ return {
253
+ stringValue: String(value ?? '')
254
+ };
255
+ }
256
+ function toOtlpAttributes(attributes) {
257
+ return Object.entries(attributes).filter(([, value])=>null != value).map(([key, value])=>({
258
+ key,
259
+ value: toOtlpAnyValue(value)
260
+ }));
261
+ }
262
+ function otlpValueToJs(value) {
263
+ if (void 0 !== value.stringValue) return value.stringValue;
264
+ if (void 0 !== value.intValue) return Number(value.intValue);
265
+ if (void 0 !== value.doubleValue) return value.doubleValue;
266
+ if (void 0 !== value.boolValue) return value.boolValue;
267
+ if (value.arrayValue && 'object' == typeof value.arrayValue) {
268
+ const arr = value.arrayValue;
269
+ return (arr.values || []).map(otlpValueToJs);
270
+ }
271
+ if (value.kvlistValue && 'object' == typeof value.kvlistValue) {
272
+ const kv = value.kvlistValue;
273
+ const result = {};
274
+ for (const item of kv.values || [])result[item.key] = otlpValueToJs(item.value);
275
+ return result;
276
+ }
277
+ return value;
278
+ }
279
+ function otlpAttributesToMap(attributes) {
280
+ return attributes.reduce((acc, attr)=>{
281
+ acc[attr.key] = otlpValueToJs(attr.value);
282
+ return acc;
283
+ }, {});
284
+ }
285
+ function toUnixNanoString(ms) {
286
+ return String(BigInt(Math.trunc(Number.isFinite(ms) ? ms : Date.now())) * BigInt(1000000));
287
+ }
288
+ function spanKindToName(kind) {
289
+ const kindMap = {
290
+ 0: 'unspecified',
291
+ 1: 'internal',
292
+ 2: 'server',
293
+ 3: 'client',
294
+ 4: 'producer',
295
+ 5: 'consumer'
296
+ };
297
+ if ('number' == typeof kind) return kindMap[kind] || 'internal';
298
+ const upper = String(kind).toUpperCase();
299
+ if ('SERVER' === upper) return 'server';
300
+ if ('CLIENT' === upper) return 'client';
301
+ return 'internal';
302
+ }
303
+ function statusCodeToName(code) {
304
+ const statusMap = {
305
+ 0: 'UNSET',
306
+ 1: 'OK',
307
+ 2: 'ERROR'
308
+ };
309
+ if (null == code) return 'UNSET';
310
+ const num = Number(code);
311
+ return statusMap[num] ?? 'UNSET';
312
+ }
313
+ function inferSpanTypeFromName(name, explicitType) {
314
+ if ('string' == typeof explicitType && explicitType) return explicitType;
315
+ if ('agent.turn' === name || 'codex_turn' === name) return 'message';
316
+ if ('llm.request' === name) return 'model';
317
+ if ('tool.call' === name) return 'tool';
318
+ }
319
+ function otlpSpanToTlsEnvelope(otlpSpan, resource, scope, options) {
320
+ const status = otlpSpan.status;
321
+ const statusCodeNum = Number(status?.code);
322
+ const isError = 2 === statusCodeNum;
323
+ const attributes = otlpAttributesToMap(otlpSpan.attributes || []);
324
+ const spanType = inferSpanTypeFromName(otlpSpan.name, otlpSpan.type);
325
+ const isTool = 'tool' === spanType;
326
+ if (isTool) attributes['tool.error'] = isError;
327
+ else delete attributes['tool.error'];
328
+ if (isError) {
329
+ attributes['error.type'] = attributes['error.type'] ?? 'error';
330
+ attributes['error.message'] = String(status?.message || '');
331
+ } else {
332
+ attributes['error.type'] = '';
333
+ attributes['error.message'] = '';
334
+ }
335
+ if (options?.filterAttributes) {
336
+ for (const key of Object.keys(attributes))if (!options.filterAttributes(key)) delete attributes[key];
337
+ }
338
+ const resourceAttributes = otlpAttributesToMap(resource.attributes || []);
339
+ const events = (otlpSpan.events || []).map((event)=>({
340
+ timeUnixNano: event.timeUnixNano,
341
+ name: event.name,
342
+ attributes: otlpAttributesToMap(event.attributes || [])
343
+ }));
344
+ const links = (otlpSpan.links || []).map((link)=>({
345
+ traceId: link.traceId,
346
+ spanId: link.spanId,
347
+ traceState: link.traceState,
348
+ attributes: otlpAttributesToMap(link.attributes || [])
349
+ }));
350
+ const startNano = BigInt(String(otlpSpan.startTimeUnixNano));
351
+ const endNano = BigInt(String(otlpSpan.endTimeUnixNano));
352
+ const us = BigInt(1000);
353
+ const durationUs = (endNano - startNano) / us;
354
+ return {
355
+ TraceID: String(otlpSpan.traceId),
356
+ SpanID: String(otlpSpan.spanId),
357
+ ParentSpanID: String(otlpSpan.parentSpanId || ''),
358
+ Name: String(otlpSpan.name),
359
+ Kind: spanKindToName(otlpSpan.kind),
360
+ Start: String(startNano / us),
361
+ End: String(endNano / us),
362
+ Duration: String(durationUs),
363
+ StatusCode: statusCodeToName(statusCodeNum),
364
+ StatusDescription: String(status?.message || ''),
365
+ Attributes: safeJsonStringify(attributes),
366
+ Resource: safeJsonStringify(resourceAttributes),
367
+ ServiceName: String(resourceAttributes['service.name'] || ''),
368
+ Host: String(resourceAttributes['host.name'] || ''),
369
+ OTLPName: String(scope.name),
370
+ OTLPVersion: String(scope.version),
371
+ Events: safeJsonStringify(events),
372
+ Links: safeJsonStringify(links),
373
+ TraceState: String(otlpSpan.traceState || '')
374
+ };
375
+ }
376
+ const TLS_AUTH_RESOURCE_KEYS = {
377
+ traceTopicId: 'tls.otel.tracetopic',
378
+ apiKey: 'tls.otel.apikey',
379
+ ak: 'tls.otel.ak',
380
+ sk: 'tls.otel.sk',
381
+ region: 'tls.otel.region'
382
+ };
383
+ const SCHEMA_FIELD_ORDER = [
384
+ 'span.name',
385
+ 'span.kind',
386
+ 'tls.app.type',
387
+ 'session.id',
388
+ 'span.status.code',
389
+ 'start_time_unix_us',
390
+ 'end_time_unix_us',
391
+ 'span.duration_us',
392
+ 'service.name',
393
+ 'service.instance.id',
394
+ 'host.name',
395
+ 'observed_time_unix_ms',
396
+ 'telemetry.sdk.name',
397
+ 'telemetry.sdk.language',
398
+ 'telemetry.sdk.version',
399
+ 'otel.scope.name',
400
+ 'otel.scope.version',
401
+ 'event.name',
402
+ 'user.role',
403
+ 'gen_ai.operation.name',
404
+ 'gen_ai.provider.name',
405
+ 'gen_ai.request.model',
406
+ 'gen_ai.response.model',
407
+ 'gen_ai.request.duration_ms',
408
+ 'gen_ai.input.messages',
409
+ 'gen_ai.output.messages',
410
+ 'gen_ai.usage.input_tokens',
411
+ 'gen_ai.usage.output_tokens',
412
+ 'gen_ai.usage.reasoning.output_tokens',
413
+ 'gen_ai.usage.cache_read.input_tokens',
414
+ 'gen_ai.usage.cache_creation.input_tokens',
415
+ 'gen_ai.usage.total_tokens',
416
+ 'gen_ai.tool.name',
417
+ 'gen_ai.tool.call.id',
418
+ 'gen_ai.tool.call.arguments',
419
+ 'gen_ai.tool.call.result',
420
+ 'error.type',
421
+ 'error.message'
422
+ ];
423
+ function serializeForAttr(value) {
424
+ const serialized = 'string' == typeof value ? value : safeJsonStringify(value);
425
+ return String(serialized ?? '').slice(0, 65536);
426
+ }
427
+ function spanKind(span) {
428
+ if ('SERVER' === span.span_kind) return 2;
429
+ if ('CLIENT' === span.span_kind) return 3;
430
+ return 1;
431
+ }
432
+ function spanKindName(span) {
433
+ if ('SERVER' === span.span_kind) return 'server';
434
+ if ('CLIENT' === span.span_kind) return 'client';
435
+ return 'internal';
436
+ }
437
+ function unixMsToUs(ms) {
438
+ return String(1000 * Math.trunc(Number.isFinite(ms) ? ms : Date.now()));
439
+ }
440
+ function hasError(span) {
441
+ if (span.status?.code === 'ERROR') return true;
442
+ return Boolean(span.attributes['error.type']);
443
+ }
444
+ function buildResourceAttributes(trace, config) {
445
+ const attributes = {
446
+ ...trace.resource
447
+ };
448
+ if ('resource' === config.authMode) {
449
+ attributes[TLS_AUTH_RESOURCE_KEYS.traceTopicId] = config.traceTopicId;
450
+ attributes[TLS_AUTH_RESOURCE_KEYS.region] = config.region;
451
+ if (config.apiKey) attributes[TLS_AUTH_RESOURCE_KEYS.apiKey] = config.apiKey;
452
+ else {
453
+ attributes[TLS_AUTH_RESOURCE_KEYS.ak] = config.ak;
454
+ attributes[TLS_AUTH_RESOURCE_KEYS.sk] = config.sk;
455
+ }
456
+ }
457
+ return attributes;
458
+ }
459
+ function cleanSpanAttributesForOtlp(span) {
460
+ const attributes = {
461
+ ...span.attributes
462
+ };
463
+ for (const key of [
464
+ 'span.name',
465
+ 'span.kind',
466
+ 'span.status.code',
467
+ 'service.name',
468
+ 'service.instance.id',
469
+ 'host.name',
470
+ 'telemetry.sdk.name',
471
+ 'telemetry.sdk.language',
472
+ 'telemetry.sdk.version'
473
+ ])delete attributes[key];
474
+ return attributes;
475
+ }
476
+ function buildOtlpPayload(trace, config) {
477
+ return {
478
+ resourceSpans: [
479
+ {
480
+ schemaUrl: trace.schema_url,
481
+ resource: {
482
+ attributes: toOtlpAttributes(buildResourceAttributes(trace, config))
483
+ },
484
+ scopeSpans: [
485
+ {
486
+ schemaUrl: trace.schema_url,
487
+ scope: {
488
+ name: 'tls-observer-claude-code',
489
+ version: package_namespaceObject.rE
490
+ },
491
+ spans: trace.spans.map((span)=>({
492
+ traceId: span.trace_id,
493
+ spanId: span.span_id,
494
+ ...span.parent_span_id ? {
495
+ parentSpanId: span.parent_span_id
496
+ } : {},
497
+ name: span.name,
498
+ kind: spanKind(span),
499
+ startTimeUnixNano: toUnixNanoString(span.start_time_unix_ms),
500
+ endTimeUnixNano: toUnixNanoString(span.end_time_unix_ms),
501
+ attributes: toOtlpAttributes(cleanSpanAttributesForOtlp(span)),
502
+ status: hasError(span) ? {
503
+ code: 2,
504
+ message: span.status?.message || 'error'
505
+ } : {
506
+ code: span.status?.code === 'OK' ? 1 : 0
507
+ }
508
+ }))
509
+ }
510
+ ]
511
+ }
512
+ ]
513
+ };
514
+ }
515
+ function requireExportConfig(config) {
516
+ if (!config.enabled) return {
517
+ ok: false,
518
+ reason: 'disabled'
519
+ };
520
+ const missing = [];
521
+ const endpoint = config.tlsEndpoint || config.otelEndpoint;
522
+ if (!endpoint) missing.push('CLAUDE_CODE_TLS_ENDPOINT or CLAUDE_CODE_TLS_OTEL_ENDPOINT');
523
+ if (!config.region) missing.push('CLAUDE_CODE_TLS_REGION');
524
+ if (!config.traceTopicId) missing.push('CLAUDE_CODE_TLS_TRACE_TOPIC_ID');
525
+ if (!config.apiKey && (!config.ak || !config.sk)) missing.push('CLAUDE_CODE_TLS_API_KEY or CLAUDE_CODE_TLS_AK/CLAUDE_CODE_TLS_SK');
526
+ if (config.apiKey && (config.ak || config.sk)) return {
527
+ ok: false,
528
+ reason: 'invalid_auth',
529
+ missing: [
530
+ 'use apiKey or ak/sk, not both'
531
+ ]
532
+ };
533
+ if (missing.length > 0) return {
534
+ ok: false,
535
+ reason: 'missing_config',
536
+ missing
537
+ };
538
+ return {
539
+ ok: true,
540
+ url: endpoint
541
+ };
542
+ }
543
+ function exporter_producerConfig(config) {
544
+ return {
545
+ endpoint: config.tlsEndpoint || config.otelEndpoint || '',
546
+ region: config.region || '',
547
+ traceTopicId: config.traceTopicId || '',
548
+ apiKey: config.apiKey,
549
+ ak: config.ak,
550
+ sk: config.sk,
551
+ timeoutMs: config.exportTimeoutMs
552
+ };
553
+ }
554
+ async function exportTraceToTls(trace, config) {
555
+ const required = requireExportConfig(config);
556
+ if (!required.ok) return {
557
+ status: 'skipped',
558
+ reason: required.reason,
559
+ message: required.missing?.join(', '),
560
+ trace_id: trace.trace_id,
561
+ span_count: trace.spans.length
562
+ };
563
+ const otlpPayload = buildOtlpPayload(trace, config);
564
+ const resourceSpan = otlpPayload.resourceSpans[0];
565
+ const resource = resourceSpan.resource;
566
+ const scopeSpan = resourceSpan.scopeSpans[0];
567
+ const scope = scopeSpan.scope;
568
+ const otlpSpans = scopeSpan.spans;
569
+ const records = otlpSpans.map((otlpSpan)=>otlpSpanToTlsEnvelope(otlpSpan, resource, scope));
570
+ const totalPayloadBytes = records.reduce((sum, record)=>sum + Buffer.byteLength(JSON.stringify(record), 'utf8'), 0);
571
+ const startedAt = Date.now();
572
+ const diagnostics = {
573
+ endpoint_host: normalizeProducerEndpoint(required.url),
574
+ endpoint_path: 'SendLogs'
575
+ };
576
+ try {
577
+ await sendTlsLogRecords(exporter_producerConfig(config), records, 'claude-code-trace');
578
+ } catch (error) {
579
+ return {
580
+ status: 'failed',
581
+ reason: 'producer_error',
582
+ message: error instanceof Error ? error.message : String(error),
583
+ trace_id: trace.trace_id,
584
+ span_count: trace.spans.length,
585
+ payload_bytes: totalPayloadBytes,
586
+ attempts: 1,
587
+ duration_ms: Date.now() - startedAt,
588
+ ...diagnostics
589
+ };
590
+ }
591
+ return {
592
+ status: 'success',
593
+ trace_id: trace.trace_id,
594
+ span_count: trace.spans.length,
595
+ payload_bytes: totalPayloadBytes,
596
+ attempts: 1,
597
+ duration_ms: Date.now() - startedAt,
598
+ ...diagnostics
599
+ };
600
+ }
601
+ function stableFieldValue(value) {
602
+ if (null == value) return '';
603
+ if ('string' == typeof value || 'number' == typeof value || 'boolean' == typeof value) return value;
604
+ return serializeForAttr(value);
605
+ }
606
+ function inferUserRole(span) {
607
+ const input = span.input;
608
+ const role = input?.messages?.find((message)=>'string' == typeof message?.role)?.role;
609
+ return role || '';
610
+ }
611
+ function inferErrorMessage(span) {
612
+ if ('string' == typeof span.status?.message) return span.status.message;
613
+ const output = span.output;
614
+ if ('string' == typeof output?.error) return output.error;
615
+ return '';
616
+ }
617
+ function buildSchemaAlignedRecord(trace, span, observedTimeUnixMs = Date.now()) {
618
+ const attributes = span.attributes ?? {};
619
+ const resource = trace.resource ?? {};
620
+ const isModel = 'model' === span.type;
621
+ const isError = hasError(span);
622
+ const record = {
623
+ 'span.name': span.name,
624
+ 'span.kind': spanKindName(span),
625
+ 'tls.app.type': attributes['tls.app.type'] ?? '',
626
+ 'session.id': attributes['session.id'] ?? attributes['gen_ai.conversation.id'] ?? '',
627
+ 'span.status.code': isError ? 'ERROR' : 'UNSET',
628
+ start_time_unix_us: unixMsToUs(span.start_time_unix_ms),
629
+ end_time_unix_us: unixMsToUs(span.end_time_unix_ms),
630
+ 'span.duration_us': String(1000 * Math.max(0, Math.trunc(span.duration_ms))),
631
+ 'service.name': resource['service.name'] ?? '',
632
+ 'service.instance.id': resource['service.instance.id'] ?? '',
633
+ 'host.name': resource['host.name'] ?? '',
634
+ observed_time_unix_ms: observedTimeUnixMs,
635
+ 'telemetry.sdk.name': resource['telemetry.sdk.name'] ?? 'tls-observer-claude-code',
636
+ 'telemetry.sdk.language': resource['telemetry.sdk.language'] ?? 'nodejs',
637
+ 'telemetry.sdk.version': resource['telemetry.sdk.version'] ?? '',
638
+ 'otel.scope.name': 'tls-observer-claude-code',
639
+ 'otel.scope.version': package_namespaceObject.rE,
640
+ 'event.name': span.name,
641
+ 'user.role': inferUserRole(span),
642
+ 'gen_ai.operation.name': attributes['gen_ai.operation.name'] ?? '',
643
+ 'gen_ai.provider.name': attributes['gen_ai.provider.name'] ?? '',
644
+ 'gen_ai.request.model': attributes['gen_ai.request.model'] ?? '',
645
+ 'gen_ai.response.model': attributes['gen_ai.response.model'] ?? '',
646
+ 'gen_ai.request.duration_ms': isModel ? span.duration_ms : '',
647
+ 'gen_ai.input.messages': attributes['gen_ai.input.messages'] ?? '',
648
+ 'gen_ai.output.messages': attributes['gen_ai.output.messages'] ?? '',
649
+ 'gen_ai.usage.input_tokens': attributes['gen_ai.usage.input_tokens'] ?? '',
650
+ 'gen_ai.usage.output_tokens': attributes['gen_ai.usage.output_tokens'] ?? '',
651
+ 'gen_ai.usage.reasoning.output_tokens': attributes['gen_ai.usage.reasoning.output_tokens'] ?? '',
652
+ 'gen_ai.usage.cache_read.input_tokens': attributes['gen_ai.usage.cache_read.input_tokens'] ?? '',
653
+ 'gen_ai.usage.cache_creation.input_tokens': attributes['gen_ai.usage.cache_creation.input_tokens'] ?? '',
654
+ 'gen_ai.usage.total_tokens': attributes['gen_ai.usage.total_tokens'] ?? '',
655
+ 'gen_ai.tool.name': attributes['gen_ai.tool.name'] ?? '',
656
+ 'gen_ai.tool.call.id': attributes['gen_ai.tool.call.id'] ?? '',
657
+ 'gen_ai.tool.call.arguments': attributes['gen_ai.tool.call.arguments'] ?? '',
658
+ 'gen_ai.tool.call.result': attributes['gen_ai.tool.call.result'] ?? '',
659
+ 'error.type': isError ? attributes['error.type'] ?? 'error' : '',
660
+ 'error.message': isError ? attributes['error.message'] || inferErrorMessage(span) || '' : ''
661
+ };
662
+ return Object.fromEntries(SCHEMA_FIELD_ORDER.map((key)=>[
663
+ key,
664
+ stableFieldValue(record[key])
665
+ ]));
666
+ }
667
+ function buildSchemaAlignedRecords(trace, observedTimeUnixMs = Date.now()) {
668
+ return trace.spans.map((span)=>buildSchemaAlignedRecord(trace, span, observedTimeUnixMs));
669
+ }
670
+ function exportSummary(trace, result) {
671
+ return compactObject({
672
+ generated_at: new Date().toISOString(),
673
+ trace_id: trace.trace_id,
674
+ span_count: trace.spans.length,
675
+ export_status: result.status,
676
+ export_reason: result.reason,
677
+ export_message: result.message,
678
+ endpoint_host: result.endpoint_host,
679
+ endpoint_path: result.endpoint_path,
680
+ payload_bytes: result.payload_bytes,
681
+ duration_ms: result.duration_ms
682
+ });
683
+ }
684
+ const LOCK_TIMEOUT_MS = 5000;
685
+ const LOCK_RETRY_MS = 20;
686
+ const SESSION_MAX_AGE_MS = 86400000;
687
+ function sleep(ms) {
688
+ return new Promise((resolve)=>setTimeout(resolve, ms));
689
+ }
690
+ function lockPath(stateFilePath) {
691
+ return `${stateFilePath}.lock`;
692
+ }
693
+ async function acquireLock(stateFilePath) {
694
+ const lock = lockPath(stateFilePath);
695
+ const deadline = Date.now() + LOCK_TIMEOUT_MS;
696
+ __rspack_external_node_fs_5ea92f0c.mkdirSync(__rspack_external_node_path_c5b9b54f.dirname(stateFilePath), {
697
+ recursive: true
698
+ });
699
+ while(Date.now() < deadline)try {
700
+ const fd = __rspack_external_node_fs_5ea92f0c.openSync(lock, 'wx');
701
+ __rspack_external_node_fs_5ea92f0c.closeSync(fd);
702
+ return;
703
+ } catch {
704
+ await sleep(LOCK_RETRY_MS);
705
+ }
706
+ try {
707
+ __rspack_external_node_fs_5ea92f0c.unlinkSync(lock);
708
+ } catch {}
709
+ const fd = __rspack_external_node_fs_5ea92f0c.openSync(lock, 'wx');
710
+ __rspack_external_node_fs_5ea92f0c.closeSync(fd);
711
+ }
712
+ function releaseLock(stateFilePath) {
713
+ try {
714
+ __rspack_external_node_fs_5ea92f0c.unlinkSync(lockPath(stateFilePath));
715
+ } catch {}
716
+ }
717
+ function loadState(stateFilePath) {
718
+ try {
719
+ return JSON.parse(__rspack_external_node_fs_5ea92f0c.readFileSync(stateFilePath, 'utf8'));
720
+ } catch {
721
+ return {};
722
+ }
723
+ }
724
+ function getSessionState(state, sessionId) {
725
+ return state[sessionId] ?? {
726
+ last_line: -1,
727
+ last_offset: 0,
728
+ turn_count: 0,
729
+ updated: '',
730
+ tool_start_times: {},
731
+ tool_observations: {},
732
+ pending_subagent_traces: [],
733
+ subagent_turn_count: 0
734
+ };
735
+ }
736
+ async function atomicUpdateState(stateFilePath, fn) {
737
+ await acquireLock(stateFilePath);
738
+ try {
739
+ const next = pruneOldSessions(fn(loadState(stateFilePath)));
740
+ __rspack_external_node_fs_5ea92f0c.mkdirSync(__rspack_external_node_path_c5b9b54f.dirname(stateFilePath), {
741
+ recursive: true
742
+ });
743
+ __rspack_external_node_fs_5ea92f0c.writeFileSync(stateFilePath, JSON.stringify(next, null, 2));
744
+ } finally{
745
+ releaseLock(stateFilePath);
746
+ }
747
+ }
748
+ function pruneOldSessions(state, now = Date.now()) {
749
+ const cutoff = now - SESSION_MAX_AGE_MS;
750
+ const pruned = {};
751
+ for (const [sessionId, session] of Object.entries(state)){
752
+ const updatedMs = session.updated ? new Date(session.updated).getTime() : 0;
753
+ if (updatedMs >= cutoff || session.current_turn_id) pruned[sessionId] = session;
754
+ }
755
+ return pruned;
756
+ }
757
+ function putToolObservation(session, observation) {
758
+ return {
759
+ ...session,
760
+ updated: new Date().toISOString(),
761
+ tool_observations: {
762
+ ...session.tool_observations,
763
+ [observation.tool_use_id]: {
764
+ ...session.tool_observations?.[observation.tool_use_id],
765
+ ...observation
766
+ }
767
+ }
768
+ };
769
+ }
770
+ const SCHEMA_URL = 'https://opentelemetry.io/schemas/1.41.0';
771
+ const MAX_ATTRIBUTE_TEXT = 65536;
772
+ const TLS_APP_TYPE = 'claude-code';
773
+ const ROOT_SPAN_NAME = 'agent.turn';
774
+ const LLM_SPAN_NAME = 'llm.request';
775
+ const TOOL_SPAN_NAME = 'tool.call';
776
+ function hashHex(value, length) {
777
+ const hex = createHash('sha256').update(value).digest('hex').slice(0, length);
778
+ if (!/^0+$/.test(hex)) return hex;
779
+ return '1'.padStart(length, '0');
780
+ }
781
+ function toTraceId(value) {
782
+ const compact = value.replace(/-/g, '').toLowerCase();
783
+ if (/^[0-9a-f]{32}$/.test(compact) && compact !== '0'.repeat(32)) return compact;
784
+ return hashHex(value, 32);
785
+ }
786
+ function toSpanId(...parts) {
787
+ return hashHex(parts.map((part)=>String(part ?? '')).join(':'), 16);
788
+ }
789
+ function parseMs(value, fallback) {
790
+ if ('number' == typeof value && Number.isFinite(value)) return Math.trunc(value);
791
+ const parsed = Date.parse(String(value ?? ''));
792
+ return Number.isFinite(parsed) ? parsed : fallback;
793
+ }
794
+ function orderedTiming(startTime, endTime) {
795
+ const fallback = Date.now();
796
+ const startMs = parseMs(startTime, fallback);
797
+ const rawEndMs = parseMs(endTime, startMs);
798
+ if (rawEndMs < startMs) return {
799
+ startMs,
800
+ endMs: startMs
801
+ };
802
+ return {
803
+ startMs,
804
+ endMs: rawEndMs
805
+ };
806
+ }
807
+ function stringifyStructuredAttributes(attributes) {
808
+ return Object.fromEntries(Object.entries(attributes).map(([key, value])=>[
809
+ key,
810
+ null !== value && 'object' == typeof value ? clipString(safeJsonStringify(value)) : value
811
+ ]));
812
+ }
813
+ function traceSpan(options) {
814
+ const { startMs, endMs } = orderedTiming(options.startTime, options.endTime);
815
+ return {
816
+ trace_id: options.traceId,
817
+ span_id: options.spanId,
818
+ parent_span_id: options.parentSpanId ?? null,
819
+ name: options.name,
820
+ type: options.type,
821
+ span_kind: options.spanKind ?? ('entry' === options.type ? 'SERVER' : 'model' === options.type ? 'CLIENT' : 'INTERNAL'),
822
+ start_time: new Date(startMs).toISOString(),
823
+ end_time: new Date(endMs).toISOString(),
824
+ start_time_unix_ms: startMs,
825
+ end_time_unix_ms: endMs,
826
+ duration_ms: endMs - startMs,
827
+ attributes: compactObject(stringifyStructuredAttributes({
828
+ ...options.attributes ?? {},
829
+ 'tls.app.type': TLS_APP_TYPE
830
+ })),
831
+ input: options.input,
832
+ output: options.output,
833
+ status: options.status
834
+ };
835
+ }
836
+ function clipString(value) {
837
+ return value.length > MAX_ATTRIBUTE_TEXT ? `${value.slice(0, MAX_ATTRIBUTE_TEXT)}...[truncated]` : value;
838
+ }
839
+ function normalizeValue(value, captureContent) {
840
+ if (!captureContent) return;
841
+ if ('string' == typeof value) return clipString(value);
842
+ if (null == value) return value;
843
+ const text = safeJsonStringify(value);
844
+ return text.length > MAX_ATTRIBUTE_TEXT ? `${text.slice(0, MAX_ATTRIBUTE_TEXT)}...[truncated]` : value;
845
+ }
846
+ function contentBlockToPart(block, captureContent) {
847
+ if ('text' === block.type) return {
848
+ type: 'text',
849
+ content: normalizeValue(block.text, captureContent) ?? ''
850
+ };
851
+ if ('thinking' === block.type) return {
852
+ type: 'reasoning',
853
+ content: normalizeValue(block.thinking, captureContent) ?? ''
854
+ };
855
+ return {
856
+ type: 'tool_call',
857
+ id: block.id,
858
+ name: block.name,
859
+ arguments: normalizeValue(block.input, captureContent)
860
+ };
861
+ }
862
+ function messageContentToParts(content, captureContent) {
863
+ if ('string' == typeof content) return [
864
+ {
865
+ type: 'text',
866
+ content: normalizeValue(content, captureContent) ?? ''
867
+ }
868
+ ];
869
+ return content.map((item)=>normalizeValue(item, captureContent));
870
+ }
871
+ function assistantOutputMessages(call, captureContent) {
872
+ return [
873
+ {
874
+ role: 'assistant',
875
+ parts: call.content.map((block)=>contentBlockToPart(block, captureContent))
876
+ }
877
+ ];
878
+ }
879
+ function messagesAttribute(messages, captureContent) {
880
+ if (!captureContent || !messages) return;
881
+ return clipString(safeJsonStringify(messages));
882
+ }
883
+ function toolResultMessage(toolCall, captureContent) {
884
+ return {
885
+ role: 'tool',
886
+ parts: [
887
+ {
888
+ type: 'tool_call_response',
889
+ id: toolCall.tool_use.id,
890
+ result: normalizeValue(toolCall.result?.content, captureContent)
891
+ }
892
+ ]
893
+ };
894
+ }
895
+ function usageAttributes(usage) {
896
+ const cacheRead = usage.cache_read_input_tokens ?? 0;
897
+ const cacheCreation = usage.cache_creation_input_tokens ?? 0;
898
+ const inputTokens = usage.input_tokens ?? 0;
899
+ const outputTokens = usage.output_tokens ?? 0;
900
+ return compactObject({
901
+ 'gen_ai.usage.input_tokens': inputTokens || void 0,
902
+ 'gen_ai.usage.output_tokens': outputTokens || void 0,
903
+ 'gen_ai.usage.total_tokens': inputTokens || outputTokens ? inputTokens + outputTokens : void 0,
904
+ 'gen_ai.usage.reasoning.output_tokens': usage.reasoning_output_tokens || void 0,
905
+ 'gen_ai.usage.cache_read.input_tokens': cacheRead || void 0,
906
+ 'gen_ai.usage.cache_creation.input_tokens': cacheCreation || void 0
907
+ });
908
+ }
909
+ function sumNumericAttribute(records, key) {
910
+ return records.reduce((sum, record)=>{
911
+ const value = record[key];
912
+ return 'number' == typeof value && Number.isFinite(value) ? sum + value : sum;
913
+ }, 0);
914
+ }
915
+ function rootUsageAttributes(calls) {
916
+ const usageRecords = calls.map((call)=>usageAttributes(call.usage));
917
+ const totalTokens = sumNumericAttribute(usageRecords, 'gen_ai.usage.total_tokens');
918
+ const inputTokens = sumNumericAttribute(usageRecords, 'gen_ai.usage.input_tokens');
919
+ const outputTokens = sumNumericAttribute(usageRecords, 'gen_ai.usage.output_tokens');
920
+ const reasoningOutputTokens = sumNumericAttribute(usageRecords, 'gen_ai.usage.reasoning.output_tokens');
921
+ const cacheReadInputTokens = sumNumericAttribute(usageRecords, 'gen_ai.usage.cache_read.input_tokens');
922
+ return compactObject({
923
+ 'gen_ai.usage.total_tokens': totalTokens || void 0,
924
+ 'gen_ai.usage.input_tokens': inputTokens || void 0,
925
+ 'gen_ai.usage.output_tokens': outputTokens || void 0,
926
+ 'gen_ai.usage.reasoning.output_tokens': reasoningOutputTokens || void 0,
927
+ 'gen_ai.usage.cache_read.input_tokens': cacheReadInputTokens || void 0
928
+ });
929
+ }
930
+ function toolObservationForCall(toolCall, state) {
931
+ return state.tool_observations?.[toolCall.tool_use.id];
932
+ }
933
+ function toolTiming(toolCall, observation, fallbackStart, fallbackEnd) {
934
+ return {
935
+ start: observation?.start_time_ms ?? fallbackStart,
936
+ end: observation?.end_time_ms ?? toolCall.result?.timestamp ?? fallbackEnd
937
+ };
938
+ }
939
+ function commonSessionAttributes(input) {
940
+ return compactObject({
941
+ 'session.id': input.session_id,
942
+ 'gen_ai.conversation.id': input.session_id
943
+ });
944
+ }
945
+ function buildResource(config) {
946
+ return compactObject({
947
+ 'service.name': config.serviceName,
948
+ 'service.instance.id': config.serviceInstanceId,
949
+ 'host.name': hostname(),
950
+ 'telemetry.sdk.language': 'nodejs',
951
+ 'telemetry.sdk.name': 'tls-observer-claude-code',
952
+ 'telemetry.sdk.version': '0.1.0',
953
+ ...localIdentityMetadata()
954
+ });
955
+ }
956
+ function buildTraceFromTurn(options) {
957
+ const { input, turn, turnIndex, sessionState, config, extraAttributes } = options;
958
+ const traceSeed = [
959
+ input.session_id ?? 'claude',
960
+ turn.promptId ?? sessionState.current_turn_id ?? turnIndex,
961
+ turn.userTimestamp,
962
+ options.traceSeedSuffix
963
+ ].filter((part)=>void 0 !== part && '' !== part).join(':');
964
+ const traceId = toTraceId(traceSeed);
965
+ const spans = [];
966
+ const userStartMs = parseMs(turn.userTimestamp, Date.now());
967
+ const turnStartMs = Math.min(userStartMs, parseMs(sessionState.current_turn_start_ms, userStartMs), ...turn.llmCalls.map((call)=>parseMs(call.startTime, userStartMs)));
968
+ const activityEnds = [
969
+ ...turn.llmCalls.map((call)=>parseMs(call.endTime, turnStartMs)),
970
+ ...turn.llmCalls.flatMap((call)=>call.toolCalls.map((tool)=>parseMs(tool.result?.timestamp, turnStartMs)))
971
+ ].filter(Number.isFinite);
972
+ const turnEndMs = Math.max(turnStartMs, ...activityEnds, Date.now());
973
+ const turnSpanId = toSpanId(traceId, 'turn', turnIndex);
974
+ const sessionAttrs = compactObject({
975
+ ...commonSessionAttributes(input),
976
+ ...extraAttributes ?? {}
977
+ });
978
+ const userMessages = [
979
+ {
980
+ role: 'user',
981
+ parts: messageContentToParts(turn.userContent, config.captureContent)
982
+ }
983
+ ];
984
+ const lastAssistant = turn.llmCalls.at(-1);
985
+ const requestModel = lastAssistant?.model;
986
+ const responseModel = lastAssistant?.model;
987
+ const outputMessages = lastAssistant ? assistantOutputMessages(lastAssistant, config.captureContent) : void 0;
988
+ spans.push(traceSpan({
989
+ traceId,
990
+ spanId: turnSpanId,
991
+ name: ROOT_SPAN_NAME,
992
+ type: 'entry',
993
+ startTime: turnStartMs,
994
+ endTime: turnEndMs,
995
+ attributes: {
996
+ ...sessionAttrs,
997
+ 'gen_ai.operation.name': 'invoke_agent',
998
+ 'gen_ai.provider.name': 'anthropic',
999
+ 'gen_ai.request.model': requestModel,
1000
+ 'gen_ai.response.model': responseModel,
1001
+ 'gen_ai.input.messages': messagesAttribute(userMessages, config.captureContent),
1002
+ 'gen_ai.output.messages': messagesAttribute(outputMessages, config.captureContent),
1003
+ ...rootUsageAttributes(turn.llmCalls)
1004
+ },
1005
+ input: config.captureContent ? {
1006
+ messages: userMessages
1007
+ } : void 0,
1008
+ output: config.captureContent && outputMessages ? {
1009
+ messages: outputMessages
1010
+ } : void 0,
1011
+ status: turn.isComplete ? {
1012
+ code: 'OK'
1013
+ } : {
1014
+ code: 'ERROR',
1015
+ message: 'interrupted'
1016
+ }
1017
+ }));
1018
+ const accumulatedMessages = [
1019
+ ...userMessages
1020
+ ];
1021
+ for (const [llmIndex, call] of turn.llmCalls.entries()){
1022
+ const output = assistantOutputMessages(call, config.captureContent);
1023
+ const inputMessages = [
1024
+ ...accumulatedMessages
1025
+ ];
1026
+ const llmSpanId = toSpanId(traceId, 'llm', llmIndex, call.startTime, call.endTime);
1027
+ spans.push(traceSpan({
1028
+ traceId,
1029
+ spanId: llmSpanId,
1030
+ parentSpanId: turnSpanId,
1031
+ name: LLM_SPAN_NAME,
1032
+ type: 'model',
1033
+ spanKind: 'CLIENT',
1034
+ startTime: call.startTime,
1035
+ endTime: call.endTime,
1036
+ attributes: {
1037
+ ...sessionAttrs,
1038
+ 'gen_ai.operation.name': 'chat',
1039
+ 'gen_ai.provider.name': 'anthropic',
1040
+ 'gen_ai.request.model': call.model,
1041
+ 'gen_ai.response.model': call.model,
1042
+ 'gen_ai.response.finish_reasons': call.stopReason ? [
1043
+ call.stopReason
1044
+ ] : void 0,
1045
+ 'gen_ai.input.messages': messagesAttribute(inputMessages, config.captureContent),
1046
+ 'gen_ai.output.messages': messagesAttribute(output, config.captureContent),
1047
+ ...usageAttributes(call.usage)
1048
+ },
1049
+ input: config.captureContent ? {
1050
+ messages: inputMessages
1051
+ } : void 0,
1052
+ output: config.captureContent ? {
1053
+ messages: output
1054
+ } : void 0
1055
+ }));
1056
+ accumulatedMessages.push(...output);
1057
+ for (const toolCall of call.toolCalls){
1058
+ const observation = toolObservationForCall(toolCall, sessionState);
1059
+ const timing = toolTiming(toolCall, observation, call.endTime, toolCall.result?.timestamp ?? call.endTime);
1060
+ const isError = observation?.status === 'error' || toolCall.result?.is_error;
1061
+ const toolOutput = observation?.tool_response ?? toolCall.result?.content;
1062
+ const toolInput = observation?.tool_input ?? toolCall.tool_use.input;
1063
+ const toolSpanId = toSpanId(traceId, 'tool', toolCall.tool_use.id);
1064
+ spans.push(traceSpan({
1065
+ traceId,
1066
+ spanId: toolSpanId,
1067
+ parentSpanId: turnSpanId,
1068
+ name: TOOL_SPAN_NAME,
1069
+ type: 'tool',
1070
+ spanKind: 'INTERNAL',
1071
+ startTime: timing.start,
1072
+ endTime: timing.end,
1073
+ attributes: {
1074
+ ...sessionAttrs,
1075
+ 'gen_ai.operation.name': 'execute_tool',
1076
+ 'gen_ai.tool.name': toolCall.tool_use.name,
1077
+ 'gen_ai.tool.call.id': toolCall.tool_use.id,
1078
+ 'gen_ai.tool.call.arguments': normalizeValue(toolInput, config.captureContent),
1079
+ 'gen_ai.tool.call.result': normalizeValue(toolOutput, config.captureContent),
1080
+ 'error.type': isError ? 'tool_error' : void 0,
1081
+ 'error.message': isError ? normalizeValue(toolOutput, config.captureContent) : void 0
1082
+ },
1083
+ input: normalizeValue(toolInput, config.captureContent),
1084
+ output: normalizeValue(toolOutput, config.captureContent),
1085
+ status: isError ? {
1086
+ code: 'ERROR',
1087
+ message: 'tool_error'
1088
+ } : {
1089
+ code: 'OK'
1090
+ }
1091
+ }));
1092
+ accumulatedMessages.push(toolResultMessage(toolCall, config.captureContent));
1093
+ }
1094
+ }
1095
+ return {
1096
+ schema_url: SCHEMA_URL,
1097
+ source: options.source ?? "claude_stop_hook_transcript",
1098
+ trace_id: traceId,
1099
+ resource: buildResource(config),
1100
+ spans: spans.sort((left, right)=>left.start_time_unix_ms - right.start_time_unix_ms || left.end_time_unix_ms - right.end_time_unix_ms)
1101
+ };
1102
+ }
1103
+ function buildCompactTrace(options) {
1104
+ const { input, sessionState, config } = options;
1105
+ const endMs = Date.now();
1106
+ const startMs = sessionState.compaction_start_time_ms ?? endMs;
1107
+ const traceId = toTraceId(`${input.session_id ?? 'claude'}:compact:${startMs}:${endMs}`);
1108
+ const spanId = toSpanId(traceId, 'compact');
1109
+ const trigger = input.trigger ?? sessionState.compaction_trigger;
1110
+ const summary = normalizeValue(input.compact_summary, config.captureContent);
1111
+ const customInstructions = normalizeValue(sessionState.compaction_custom_instructions, config.captureContent);
1112
+ return {
1113
+ schema_url: SCHEMA_URL,
1114
+ source: 'claude_post_compact_hook',
1115
+ trace_id: traceId,
1116
+ resource: buildResource(config),
1117
+ spans: [
1118
+ traceSpan({
1119
+ traceId,
1120
+ spanId,
1121
+ name: 'agent.compact',
1122
+ type: 'event',
1123
+ spanKind: 'INTERNAL',
1124
+ startTime: startMs,
1125
+ endTime: endMs,
1126
+ attributes: {
1127
+ 'session.id': input.session_id,
1128
+ 'gen_ai.conversation.id': input.session_id,
1129
+ 'gen_ai.operation.name': 'context_compaction',
1130
+ 'event.name': 'context.compaction',
1131
+ 'gen_ai.output.messages': messagesAttribute([
1132
+ {
1133
+ role: 'assistant',
1134
+ parts: [
1135
+ {
1136
+ type: 'text',
1137
+ content: summary ?? ''
1138
+ }
1139
+ ]
1140
+ }
1141
+ ], config.captureContent),
1142
+ 'gen_ai.request.duration_ms': Math.max(0, endMs - startMs)
1143
+ },
1144
+ input: config.captureContent ? {
1145
+ trigger,
1146
+ custom_instructions: customInstructions
1147
+ } : void 0,
1148
+ output: config.captureContent ? {
1149
+ compact_summary: summary
1150
+ } : void 0,
1151
+ status: {
1152
+ code: 'OK'
1153
+ }
1154
+ })
1155
+ ]
1156
+ };
1157
+ }
1158
+ function buildStopFailureTrace(options) {
1159
+ const { input, config } = options;
1160
+ const now = Date.now();
1161
+ const traceId = toTraceId(`${input.session_id ?? 'claude'}:stop-failure:${now}`);
1162
+ const spanId = toSpanId(traceId, 'stop-failure');
1163
+ const errorMessage = input.error || 'Claude Code turn failed';
1164
+ return {
1165
+ schema_url: SCHEMA_URL,
1166
+ source: 'claude_stop_failure_hook',
1167
+ trace_id: traceId,
1168
+ resource: buildResource(config),
1169
+ spans: [
1170
+ traceSpan({
1171
+ traceId,
1172
+ spanId,
1173
+ name: ROOT_SPAN_NAME,
1174
+ type: 'event',
1175
+ startTime: now,
1176
+ endTime: now,
1177
+ attributes: {
1178
+ 'session.id': input.session_id,
1179
+ 'gen_ai.operation.name': 'invoke_agent',
1180
+ 'gen_ai.provider.name': 'anthropic',
1181
+ 'gen_ai.conversation.id': input.session_id,
1182
+ 'error.type': input.error_type || 'stop_failure',
1183
+ 'error.message': errorMessage
1184
+ },
1185
+ output: {
1186
+ error: errorMessage
1187
+ },
1188
+ status: {
1189
+ code: 'ERROR',
1190
+ message: errorMessage
1191
+ }
1192
+ })
1193
+ ]
1194
+ };
1195
+ }
1196
+ async function writeTextFileAtomic(filePath, text, options = {}) {
1197
+ await __rspack_external_node_fs_promises_153e37e0.mkdir(__rspack_external_node_path_c5b9b54f.dirname(filePath), {
1198
+ recursive: true
1199
+ });
1200
+ const tempPath = __rspack_external_node_path_c5b9b54f.join(__rspack_external_node_path_c5b9b54f.dirname(filePath), `.${__rspack_external_node_path_c5b9b54f.basename(filePath)}.${process.pid}.${Date.now()}.${randomUUID()}.tmp`);
1201
+ let renamed = false;
1202
+ try {
1203
+ await __rspack_external_node_fs_promises_153e37e0.writeFile(tempPath, text, {
1204
+ encoding: 'utf8',
1205
+ ...void 0 === options.mode ? {} : {
1206
+ mode: options.mode
1207
+ }
1208
+ });
1209
+ await __rspack_external_node_fs_promises_153e37e0.rename(tempPath, filePath);
1210
+ renamed = true;
1211
+ if (void 0 !== options.mode) await __rspack_external_node_fs_promises_153e37e0.chmod(filePath, options.mode);
1212
+ } finally{
1213
+ if (!renamed) await __rspack_external_node_fs_promises_153e37e0.rm(tempPath, {
1214
+ force: true
1215
+ }).catch(()=>void 0);
1216
+ }
1217
+ }
1218
+ async function writeJsonFileAtomic(filePath, value, options = {}) {
1219
+ await writeTextFileAtomic(filePath, `${JSON.stringify(value, null, 2)}\n`, options);
1220
+ }
1221
+ async function appendJsonlFile(filePath, values) {
1222
+ const entries = Array.isArray(values) ? values : [
1223
+ values
1224
+ ];
1225
+ if (0 === entries.length) return;
1226
+ await __rspack_external_node_fs_promises_153e37e0.mkdir(__rspack_external_node_path_c5b9b54f.dirname(filePath), {
1227
+ recursive: true
1228
+ });
1229
+ await __rspack_external_node_fs_promises_153e37e0.appendFile(filePath, `${entries.map((entry)=>JSON.stringify(entry)).join('\n')}\n`, 'utf8');
1230
+ }
1231
+ async function trimJsonlFileByBytes(filePath, maxBytes) {
1232
+ if (maxBytes <= 0) return void await __rspack_external_node_fs_promises_153e37e0.rm(filePath, {
1233
+ force: true
1234
+ }).catch(()=>void 0);
1235
+ let stat;
1236
+ try {
1237
+ stat = await __rspack_external_node_fs_promises_153e37e0.stat(filePath);
1238
+ } catch (error) {
1239
+ if ('ENOENT' === error.code) return;
1240
+ throw error;
1241
+ }
1242
+ if (stat.size <= maxBytes) return;
1243
+ const lines = (await __rspack_external_node_fs_promises_153e37e0.readFile(filePath, 'utf8')).split(/\r?\n/).filter(Boolean);
1244
+ const kept = [];
1245
+ let keptBytes = 0;
1246
+ for(let index = lines.length - 1; index >= 0; index -= 1){
1247
+ const line = lines[index];
1248
+ const lineBytes = Buffer.byteLength(`${line}\n`, 'utf8');
1249
+ if (kept.length > 0 && keptBytes + lineBytes > maxBytes) break;
1250
+ kept.push(line);
1251
+ keptBytes += lineBytes;
1252
+ if (keptBytes >= maxBytes) break;
1253
+ }
1254
+ await writeTextFileAtomic(filePath, kept.length ? `${kept.reverse().join('\n')}\n` : '');
1255
+ }
1256
+ async function appendBoundedJsonlFile(filePath, values, maxBytes) {
1257
+ await appendJsonlFile(filePath, values);
1258
+ await trimJsonlFileByBytes(filePath, maxBytes);
1259
+ }
1260
+ function readTranscript(filePath, afterLine = -1, afterOffset = 0) {
1261
+ let size;
1262
+ try {
1263
+ size = __rspack_external_node_fs_5ea92f0c.statSync(filePath).size;
1264
+ } catch {
1265
+ return {
1266
+ messages: [],
1267
+ lastLine: afterLine,
1268
+ lastOffset: Math.max(0, afterOffset)
1269
+ };
1270
+ }
1271
+ const startOffset = Number.isFinite(afterOffset) && afterOffset > 0 && afterOffset <= size ? Math.trunc(afterOffset) : 0;
1272
+ const usingOffset = startOffset > 0;
1273
+ const fd = __rspack_external_node_fs_5ea92f0c.openSync(filePath, 'r');
1274
+ try {
1275
+ const chunkSize = 2097152;
1276
+ const buf = Buffer.alloc(chunkSize);
1277
+ const messages = [];
1278
+ let lastLine = afterLine;
1279
+ let lastOffset = startOffset;
1280
+ let lineIndex = usingOffset ? afterLine : -1;
1281
+ let partial = '';
1282
+ let readPos = startOffset;
1283
+ let cursorOffset = startOffset;
1284
+ let bytesRead = 0;
1285
+ while((bytesRead = __rspack_external_node_fs_5ea92f0c.readSync(fd, buf, 0, chunkSize, readPos)) > 0){
1286
+ readPos += bytesRead;
1287
+ const chunk = partial + buf.toString('utf8', 0, bytesRead);
1288
+ const lines = chunk.split('\n');
1289
+ partial = lines.pop() ?? '';
1290
+ for (const line of lines){
1291
+ const rawWithNewline = `${line}\n`;
1292
+ cursorOffset += Buffer.byteLength(rawWithNewline, 'utf8');
1293
+ const trimmed = line.trim();
1294
+ if (trimmed) {
1295
+ lineIndex += 1;
1296
+ lastLine = lineIndex;
1297
+ lastOffset = cursorOffset;
1298
+ if (usingOffset || !(lineIndex <= afterLine)) try {
1299
+ messages.push(JSON.parse(trimmed));
1300
+ } catch {}
1301
+ }
1302
+ }
1303
+ }
1304
+ if (partial.trim()) {
1305
+ cursorOffset += Buffer.byteLength(partial, 'utf8');
1306
+ lineIndex += 1;
1307
+ lastLine = lineIndex;
1308
+ lastOffset = cursorOffset;
1309
+ if (usingOffset || lineIndex > afterLine) try {
1310
+ messages.push(JSON.parse(partial.trim()));
1311
+ } catch {}
1312
+ }
1313
+ return {
1314
+ messages,
1315
+ lastLine,
1316
+ lastOffset
1317
+ };
1318
+ } finally{
1319
+ __rspack_external_node_fs_5ea92f0c.closeSync(fd);
1320
+ }
1321
+ }
1322
+ function getTranscriptEndPosition(filePath) {
1323
+ try {
1324
+ return {
1325
+ lastLine: -1,
1326
+ lastOffset: __rspack_external_node_fs_5ea92f0c.statSync(filePath).size
1327
+ };
1328
+ } catch {
1329
+ return {
1330
+ lastLine: -1,
1331
+ lastOffset: 0
1332
+ };
1333
+ }
1334
+ }
1335
+ function isHumanMessage(msg) {
1336
+ if ('user' !== msg.type) return false;
1337
+ if ('string' == typeof msg.message.content) return true;
1338
+ if (Array.isArray(msg.message.content)) return !msg.message.content.some((block)=>block?.type === 'tool_result');
1339
+ return false;
1340
+ }
1341
+ function isToolResult(msg) {
1342
+ return 'user' === msg.type && Array.isArray(msg.message.content) && msg.message.content.some((block)=>block?.type === 'tool_result');
1343
+ }
1344
+ function isAssistantMessage(msg) {
1345
+ return 'assistant' === msg.type;
1346
+ }
1347
+ function stripModelDateSuffix(model) {
1348
+ return model.replace(/-\d{8}$/, '');
1349
+ }
1350
+ function mergeAdjacentTextBlocks(blocks) {
1351
+ const result = [];
1352
+ let textBuffer = null;
1353
+ for (const block of blocks){
1354
+ if ('text' === block.type) {
1355
+ textBuffer = (textBuffer ?? '') + block.text;
1356
+ continue;
1357
+ }
1358
+ if (null !== textBuffer) {
1359
+ result.push({
1360
+ type: 'text',
1361
+ text: textBuffer
1362
+ });
1363
+ textBuffer = null;
1364
+ }
1365
+ result.push(block);
1366
+ }
1367
+ if (null !== textBuffer) result.push({
1368
+ type: 'text',
1369
+ text: textBuffer
1370
+ });
1371
+ return result;
1372
+ }
1373
+ function mergeAssistantChunks(chunks) {
1374
+ const first = chunks[0];
1375
+ const last = chunks[chunks.length - 1];
1376
+ const content = mergeAdjacentTextBlocks(chunks.flatMap((chunk)=>chunk.message.content ?? []));
1377
+ return {
1378
+ content,
1379
+ model: stripModelDateSuffix(first.message.model || 'claude'),
1380
+ usage: last.message.usage ?? {},
1381
+ startTime: first.timestamp || new Date().toISOString(),
1382
+ endTime: last.timestamp || first.timestamp || new Date().toISOString(),
1383
+ stopReason: last.message.stop_reason
1384
+ };
1385
+ }
1386
+ function toolResultContent(content) {
1387
+ if ('string' == typeof content) return content;
1388
+ return content.filter((item)=>item?.type === 'text' && 'string' == typeof item.text).map((item)=>item.text).join('');
1389
+ }
1390
+ function findToolResult(toolUseId, toolResults) {
1391
+ for (const msg of toolResults)for (const block of msg.message.content)if ('tool_result' === block.type && block.tool_use_id === toolUseId) return {
1392
+ content: toolResultContent(block.content),
1393
+ timestamp: msg.timestamp || new Date().toISOString(),
1394
+ agentId: msg.toolUseResult?.agentId,
1395
+ is_error: block.is_error
1396
+ };
1397
+ }
1398
+ function groupIntoTurns(messages) {
1399
+ const turns = [];
1400
+ let currentPromptId;
1401
+ let currentUser = null;
1402
+ let assistantChunks = new Map();
1403
+ let assistantOrder = [];
1404
+ let toolResults = [];
1405
+ let hasStopReasonEndTurn = false;
1406
+ function resetForUser(msg) {
1407
+ currentUser = msg;
1408
+ currentPromptId = msg.promptId;
1409
+ assistantChunks = new Map();
1410
+ assistantOrder = [];
1411
+ toolResults = [];
1412
+ hasStopReasonEndTurn = false;
1413
+ }
1414
+ function finalizeTurn(forceIncomplete = false) {
1415
+ if (!currentUser || 0 === assistantChunks.size) return;
1416
+ const assistantMessages = Array.from(assistantChunks.values()).flat();
1417
+ const hasStopReasonField = assistantMessages.some((msg)=>void 0 !== msg.message.stop_reason);
1418
+ const isComplete = hasStopReasonEndTurn || !forceIncomplete && !hasStopReasonField;
1419
+ const llmCalls = [];
1420
+ for (const messageId of assistantOrder){
1421
+ const chunks = assistantChunks.get(messageId);
1422
+ if (!chunks?.length) continue;
1423
+ const merged = mergeAssistantChunks(chunks);
1424
+ const toolUses = merged.content.filter((block)=>'tool_use' === block.type);
1425
+ llmCalls.push({
1426
+ content: merged.content,
1427
+ model: merged.model,
1428
+ usage: merged.usage,
1429
+ startTime: merged.startTime,
1430
+ endTime: merged.endTime,
1431
+ stopReason: merged.stopReason,
1432
+ toolCalls: toolUses.map((toolUse)=>{
1433
+ const result = findToolResult(toolUse.id, toolResults);
1434
+ return {
1435
+ tool_use: toolUse,
1436
+ result: result ? {
1437
+ content: result.content,
1438
+ timestamp: result.timestamp,
1439
+ is_error: result.is_error
1440
+ } : void 0,
1441
+ agentId: result?.agentId
1442
+ };
1443
+ })
1444
+ });
1445
+ }
1446
+ turns.push({
1447
+ userContent: currentUser.message.content,
1448
+ userTimestamp: currentUser.timestamp || new Date().toISOString(),
1449
+ promptId: currentUser.promptId,
1450
+ llmCalls,
1451
+ isComplete
1452
+ });
1453
+ }
1454
+ for (const msg of messages){
1455
+ if (isHumanMessage(msg)) {
1456
+ const isNewTurn = null === currentUser || void 0 !== msg.promptId && msg.promptId !== currentPromptId || void 0 === msg.promptId;
1457
+ if (isNewTurn) {
1458
+ finalizeTurn();
1459
+ resetForUser(msg);
1460
+ }
1461
+ continue;
1462
+ }
1463
+ if (currentUser) {
1464
+ if (isAssistantMessage(msg)) {
1465
+ const messageId = msg.message.id || `${msg.timestamp || 'assistant'}:${assistantOrder.length}`;
1466
+ if (!assistantChunks.has(messageId)) {
1467
+ assistantChunks.set(messageId, []);
1468
+ assistantOrder.push(messageId);
1469
+ }
1470
+ assistantChunks.get(messageId)?.push(msg);
1471
+ if ('end_turn' === msg.message.stop_reason) hasStopReasonEndTurn = true;
1472
+ continue;
1473
+ }
1474
+ if (isToolResult(msg)) toolResults.push(msg);
1475
+ }
1476
+ }
1477
+ finalizeTurn(true);
1478
+ return turns;
1479
+ }
1480
+ function expandHome(filePath) {
1481
+ if (!filePath) return;
1482
+ if ('~' === filePath) return homedir();
1483
+ if (filePath.startsWith('~/')) return __rspack_external_node_path_c5b9b54f.join(homedir(), filePath.slice(2));
1484
+ return filePath;
1485
+ }
1486
+ function sanitizeFileName(value) {
1487
+ return value.replace(/[^a-zA-Z0-9._-]+/g, '-').replace(/^-+|-+$/g, '').slice(0, 160) || 'trace';
1488
+ }
1489
+ async function writeSendLog(config, record) {
1490
+ await writeJsonFileAtomic(__rspack_external_node_path_c5b9b54f.join(config.sendLogsDir, 'latest.send-log.json'), record);
1491
+ await appendBoundedJsonlFile(__rspack_external_node_path_c5b9b54f.join(config.dataRoot, 'logs', 'diagnostics', 'send-log.jsonl'), record, config.diagnosticLogMaxBytes);
1492
+ }
1493
+ async function writeDebugArtifacts(config, trace) {
1494
+ if (!config.debugArtifacts) return {};
1495
+ const baseName = sanitizeFileName(`${trace.trace_id}.${Date.now()}`);
1496
+ const tracePath = __rspack_external_node_path_c5b9b54f.join(config.artifactsDir, 'otel-traces', `${baseName}.trace.json`);
1497
+ const payloadPath = __rspack_external_node_path_c5b9b54f.join(config.artifactsDir, 'otlp-payloads', `${baseName}.otlp.json`);
1498
+ const schemaPath = __rspack_external_node_path_c5b9b54f.join(config.artifactsDir, 'schema-aligned', `${baseName}.schema-aligned.json`);
1499
+ await writeJsonFileAtomic(tracePath, {
1500
+ generated_at: new Date().toISOString(),
1501
+ trace
1502
+ });
1503
+ await writeJsonFileAtomic(payloadPath, buildOtlpPayload(trace, config));
1504
+ await writeJsonFileAtomic(schemaPath, {
1505
+ generated_at: new Date().toISOString(),
1506
+ records: buildSchemaAlignedRecords(trace)
1507
+ });
1508
+ return {
1509
+ trace_path: tracePath,
1510
+ otlp_payload_path: payloadPath,
1511
+ schema_aligned_path: schemaPath
1512
+ };
1513
+ }
1514
+ async function exportTraceAndLog(options) {
1515
+ const { config, trace, hookEventName } = options;
1516
+ const artifacts = await writeDebugArtifacts(config, trace);
1517
+ const result = await exportTraceToTls(trace, config);
1518
+ await writeSendLog(config, {
1519
+ ...exportSummary(trace, result),
1520
+ hook_event_name: hookEventName,
1521
+ capture_content: config.captureContent,
1522
+ debug_artifacts: config.debugArtifacts,
1523
+ ...artifacts
1524
+ });
1525
+ }
1526
+ function synthesizeMissingFinalAssistant(turns, input, session) {
1527
+ if (!turns.length || !input.last_assistant_message) return;
1528
+ const lastTurn = turns[turns.length - 1];
1529
+ const lastLlm = lastTurn.llmCalls.at(-1);
1530
+ if (!lastLlm || 0 === lastLlm.toolCalls.length) return;
1531
+ const lastToolEnd = Math.max(...lastLlm.toolCalls.map((tool)=>session.tool_observations?.[tool.tool_use.id]?.end_time_ms).filter((value)=>Number.isFinite(value)), Date.parse(lastLlm.endTime));
1532
+ lastTurn.llmCalls.push({
1533
+ content: [
1534
+ {
1535
+ type: 'text',
1536
+ text: input.last_assistant_message
1537
+ }
1538
+ ],
1539
+ model: lastLlm.model,
1540
+ usage: {},
1541
+ startTime: new Date(lastToolEnd).toISOString(),
1542
+ endTime: new Date().toISOString(),
1543
+ toolCalls: [],
1544
+ synthetic: true
1545
+ });
1546
+ }
1547
+ async function exportTranscriptTurns(options) {
1548
+ const { messages, lastLine, lastOffset } = readTranscript(options.transcriptPath, options.afterLine, options.afterOffset ?? 0);
1549
+ const turns = groupIntoTurns(messages);
1550
+ if (options.synthesizeFinalAssistant) synthesizeMissingFinalAssistant(turns, options.input, options.session);
1551
+ let tracedTurns = 0;
1552
+ for (const turn of turns){
1553
+ const trace = buildTraceFromTurn({
1554
+ input: options.input,
1555
+ turn,
1556
+ turnIndex: (options.turnIndexBase ?? options.session.turn_count) + tracedTurns + 1,
1557
+ sessionState: options.session,
1558
+ config: options.config,
1559
+ source: options.source,
1560
+ traceSeedSuffix: options.traceSeedSuffix,
1561
+ extraAttributes: options.extraAttributes
1562
+ });
1563
+ await exportTraceAndLog({
1564
+ config: options.config,
1565
+ trace,
1566
+ hookEventName: options.hookEventName
1567
+ });
1568
+ tracedTurns += 1;
1569
+ }
1570
+ return {
1571
+ lastLine,
1572
+ lastOffset,
1573
+ tracedTurns
1574
+ };
1575
+ }
1576
+ async function exportPendingSubagents(options) {
1577
+ const pending = options.session.pending_subagent_traces ?? [];
1578
+ let tracedTurns = 0;
1579
+ for (const [index, subagent] of pending.entries()){
1580
+ const transcriptPath = expandHome(subagent.agent_transcript_path);
1581
+ if (!transcriptPath) continue;
1582
+ const input = {
1583
+ hook_event_name: 'Stop',
1584
+ session_id: options.input.session_id,
1585
+ transcript_path: transcriptPath
1586
+ };
1587
+ const sessionState = {
1588
+ ...options.session,
1589
+ current_turn_id: `${options.session.current_turn_id ?? options.input.session_id ?? 'claude'}:subagent:${subagent.agent_id ?? index}`
1590
+ };
1591
+ const result = await exportTranscriptTurns({
1592
+ input,
1593
+ config: options.config,
1594
+ transcriptPath,
1595
+ afterLine: -1,
1596
+ afterOffset: 0,
1597
+ session: sessionState,
1598
+ hookEventName: options.input.hook_event_name ?? 'SubagentStop',
1599
+ source: "claude_subagent_stop_transcript",
1600
+ traceSeedSuffix: `subagent:${subagent.agent_id ?? index}`,
1601
+ turnIndexBase: options.session.subagent_turn_count ?? 0,
1602
+ extraAttributes: {
1603
+ 'gen_ai.agent.id': subagent.agent_id,
1604
+ 'gen_ai.agent.name': subagent.agent_type
1605
+ }
1606
+ });
1607
+ tracedTurns += result.tracedTurns;
1608
+ }
1609
+ return tracedTurns;
1610
+ }
1611
+ async function handlePreCompact(input, config) {
1612
+ if (!input.session_id) return;
1613
+ const sessionId = input.session_id;
1614
+ const startMs = Date.now();
1615
+ await atomicUpdateState(config.stateFilePath, (state)=>{
1616
+ const session = getSessionState(state, sessionId);
1617
+ return {
1618
+ ...state,
1619
+ [sessionId]: {
1620
+ ...session,
1621
+ compaction_start_time_ms: startMs,
1622
+ compaction_trigger: input.trigger,
1623
+ compaction_custom_instructions: input.custom_instructions,
1624
+ updated: new Date().toISOString()
1625
+ }
1626
+ };
1627
+ });
1628
+ }
1629
+ async function handlePostCompact(input, config) {
1630
+ if (!input.session_id) return;
1631
+ const sessionId = input.session_id;
1632
+ const session = getSessionState(loadState(config.stateFilePath), sessionId);
1633
+ const trace = buildCompactTrace({
1634
+ input,
1635
+ sessionState: session,
1636
+ config
1637
+ });
1638
+ await exportTraceAndLog({
1639
+ config,
1640
+ trace,
1641
+ hookEventName: 'PostCompact'
1642
+ });
1643
+ await atomicUpdateState(config.stateFilePath, (state)=>{
1644
+ const latestSession = getSessionState(state, sessionId);
1645
+ return {
1646
+ ...state,
1647
+ [sessionId]: {
1648
+ ...latestSession,
1649
+ compaction_start_time_ms: void 0,
1650
+ compaction_trigger: void 0,
1651
+ compaction_custom_instructions: void 0,
1652
+ updated: new Date().toISOString()
1653
+ }
1654
+ };
1655
+ });
1656
+ }
1657
+ const TRANSCRIPT_FLUSH_DELAY_MS = 200;
1658
+ function clearedActiveSession(session, updates = {}) {
1659
+ return {
1660
+ ...session,
1661
+ ...updates,
1662
+ current_turn_id: void 0,
1663
+ current_turn_start_ms: void 0,
1664
+ current_prompt: void 0,
1665
+ tool_start_times: {},
1666
+ tool_observations: {},
1667
+ pending_subagent_traces: [],
1668
+ updated: new Date().toISOString()
1669
+ };
1670
+ }
1671
+ async function waitForTranscriptFlush() {
1672
+ await new Promise((resolve)=>setTimeout(resolve, TRANSCRIPT_FLUSH_DELAY_MS));
1673
+ }
1674
+ async function handleStop(input, config) {
1675
+ if (!input.session_id || input.stop_hook_active) return;
1676
+ const sessionId = input.session_id;
1677
+ const transcriptPath = expandHome(input.transcript_path);
1678
+ if (!transcriptPath) return;
1679
+ await waitForTranscriptFlush();
1680
+ const session = getSessionState(loadState(config.stateFilePath), sessionId);
1681
+ const mainResult = await exportTranscriptTurns({
1682
+ input,
1683
+ config,
1684
+ transcriptPath,
1685
+ afterLine: session.last_line,
1686
+ afterOffset: session.last_offset ?? 0,
1687
+ session,
1688
+ hookEventName: 'Stop',
1689
+ synthesizeFinalAssistant: true
1690
+ });
1691
+ const subagentTurns = await exportPendingSubagents({
1692
+ input,
1693
+ config,
1694
+ session
1695
+ });
1696
+ await atomicUpdateState(config.stateFilePath, (state)=>{
1697
+ const latestSession = getSessionState(state, sessionId);
1698
+ const updates = 0 === mainResult.tracedTurns && 0 === subagentTurns ? {} : {
1699
+ last_line: mainResult.lastLine,
1700
+ last_offset: mainResult.lastOffset,
1701
+ turn_count: latestSession.turn_count + mainResult.tracedTurns,
1702
+ subagent_turn_count: (latestSession.subagent_turn_count ?? 0) + subagentTurns
1703
+ };
1704
+ return {
1705
+ ...state,
1706
+ [sessionId]: clearedActiveSession(latestSession, updates)
1707
+ };
1708
+ });
1709
+ }
1710
+ async function handleStopFailure(input, config) {
1711
+ const trace = buildStopFailureTrace({
1712
+ input,
1713
+ config
1714
+ });
1715
+ await exportTraceAndLog({
1716
+ config,
1717
+ trace,
1718
+ hookEventName: 'StopFailure'
1719
+ });
1720
+ }
1721
+ async function handleSessionEnd(input, config) {
1722
+ if (!input.session_id) return;
1723
+ const sessionId = input.session_id;
1724
+ const transcriptPath = expandHome(input.transcript_path);
1725
+ const session = getSessionState(loadState(config.stateFilePath), sessionId);
1726
+ if (!session.current_turn_id && !session.pending_subagent_traces?.length) return;
1727
+ await waitForTranscriptFlush();
1728
+ let lastLine = session.last_line;
1729
+ let lastOffset = session.last_offset ?? 0;
1730
+ let tracedTurns = 0;
1731
+ if (transcriptPath && session.current_turn_id) {
1732
+ const stopInput = {
1733
+ hook_event_name: 'Stop',
1734
+ session_id: sessionId,
1735
+ transcript_path: transcriptPath
1736
+ };
1737
+ const result = await exportTranscriptTurns({
1738
+ input: stopInput,
1739
+ config,
1740
+ transcriptPath,
1741
+ afterLine: session.last_line,
1742
+ afterOffset: session.last_offset ?? 0,
1743
+ session,
1744
+ hookEventName: 'SessionEnd',
1745
+ source: "claude_session_end_transcript",
1746
+ traceSeedSuffix: `session-end:${input.reason ?? 'unknown'}`
1747
+ });
1748
+ lastLine = result.lastLine;
1749
+ lastOffset = result.lastOffset;
1750
+ tracedTurns = result.tracedTurns;
1751
+ }
1752
+ const subagentTurns = await exportPendingSubagents({
1753
+ input,
1754
+ config,
1755
+ session
1756
+ });
1757
+ await atomicUpdateState(config.stateFilePath, (state)=>{
1758
+ const latestSession = getSessionState(state, sessionId);
1759
+ return {
1760
+ ...state,
1761
+ [sessionId]: clearedActiveSession(latestSession, {
1762
+ last_line: lastLine,
1763
+ last_offset: lastOffset,
1764
+ turn_count: latestSession.turn_count + tracedTurns,
1765
+ subagent_turn_count: (latestSession.subagent_turn_count ?? 0) + subagentTurns
1766
+ })
1767
+ };
1768
+ });
1769
+ }
1770
+ async function handleSubagentStop(input, config) {
1771
+ if (!input.session_id) return;
1772
+ const sessionId = input.session_id;
1773
+ const agentTranscriptPath = expandHome(input.agent_transcript_path);
1774
+ if (!agentTranscriptPath) return;
1775
+ await atomicUpdateState(config.stateFilePath, (state)=>{
1776
+ const session = getSessionState(state, sessionId);
1777
+ const pending = session.pending_subagent_traces ?? [];
1778
+ const nextSubagent = {
1779
+ agent_id: input.agent_id,
1780
+ agent_type: input.agent_type,
1781
+ agent_transcript_path: agentTranscriptPath,
1782
+ session_id: sessionId
1783
+ };
1784
+ const alreadyQueued = pending.some((item)=>item.agent_transcript_path === nextSubagent.agent_transcript_path && item.agent_id === nextSubagent.agent_id);
1785
+ return {
1786
+ ...state,
1787
+ [sessionId]: {
1788
+ ...session,
1789
+ pending_subagent_traces: alreadyQueued ? pending : [
1790
+ ...pending,
1791
+ nextSubagent
1792
+ ],
1793
+ updated: new Date().toISOString()
1794
+ }
1795
+ };
1796
+ });
1797
+ }
1798
+ async function handlePreToolUse(input, config) {
1799
+ if (!input.session_id || !input.tool_use_id) return;
1800
+ const sessionId = input.session_id;
1801
+ const toolUseId = input.tool_use_id;
1802
+ const startMs = Date.now();
1803
+ await atomicUpdateState(config.stateFilePath, (state)=>{
1804
+ const session = getSessionState(state, sessionId);
1805
+ return {
1806
+ ...state,
1807
+ [sessionId]: {
1808
+ ...session,
1809
+ updated: new Date().toISOString(),
1810
+ tool_start_times: {
1811
+ ...session.tool_start_times,
1812
+ [toolUseId]: startMs
1813
+ },
1814
+ tool_observations: {
1815
+ ...session.tool_observations,
1816
+ [toolUseId]: {
1817
+ ...session.tool_observations?.[toolUseId],
1818
+ tool_use_id: toolUseId,
1819
+ tool_name: input.tool_name,
1820
+ tool_input: input.tool_input,
1821
+ start_time_ms: startMs
1822
+ }
1823
+ }
1824
+ }
1825
+ };
1826
+ });
1827
+ }
1828
+ async function handlePostToolUse(input, config) {
1829
+ if (!input.session_id || !input.tool_use_id) return;
1830
+ const sessionId = input.session_id;
1831
+ const toolUseId = input.tool_use_id;
1832
+ const endMs = Date.now();
1833
+ await atomicUpdateState(config.stateFilePath, (state)=>{
1834
+ const session = getSessionState(state, sessionId);
1835
+ return {
1836
+ ...state,
1837
+ [sessionId]: putToolObservation(session, {
1838
+ tool_use_id: toolUseId,
1839
+ tool_name: input.tool_name,
1840
+ tool_input: input.tool_input,
1841
+ tool_response: input.tool_response,
1842
+ start_time_ms: session.tool_start_times?.[toolUseId] ?? endMs,
1843
+ end_time_ms: endMs,
1844
+ status: 'PostToolUseFailure' === input.hook_event_name ? 'error' : 'ok'
1845
+ })
1846
+ };
1847
+ });
1848
+ }
1849
+ async function handleUserPromptSubmit(input, config) {
1850
+ if (!input.session_id) return;
1851
+ const sessionId = input.session_id;
1852
+ const startMs = Date.now();
1853
+ const transcriptPath = expandHome(input.transcript_path);
1854
+ let interruptedLastLine;
1855
+ let interruptedLastOffset;
1856
+ let interruptedTurns = 0;
1857
+ let interruptedSubagentTurns = 0;
1858
+ if (transcriptPath) {
1859
+ const session = getSessionState(loadState(config.stateFilePath), sessionId);
1860
+ if (session.current_turn_id) {
1861
+ const stopInput = {
1862
+ hook_event_name: 'Stop',
1863
+ session_id: sessionId,
1864
+ transcript_path: transcriptPath
1865
+ };
1866
+ const result = await exportTranscriptTurns({
1867
+ input: stopInput,
1868
+ config,
1869
+ transcriptPath,
1870
+ afterLine: session.last_line,
1871
+ afterOffset: session.last_offset ?? 0,
1872
+ session,
1873
+ hookEventName: 'UserPromptSubmit',
1874
+ source: "claude_interrupted_turn_transcript",
1875
+ traceSeedSuffix: 'interrupted-before-new-prompt'
1876
+ });
1877
+ interruptedLastLine = result.lastLine;
1878
+ interruptedLastOffset = result.lastOffset;
1879
+ interruptedTurns = result.tracedTurns;
1880
+ interruptedSubagentTurns = await exportPendingSubagents({
1881
+ input: stopInput,
1882
+ config,
1883
+ session
1884
+ });
1885
+ }
1886
+ }
1887
+ const endPosition = transcriptPath ? getTranscriptEndPosition(transcriptPath) : {
1888
+ lastLine: -1,
1889
+ lastOffset: 0
1890
+ };
1891
+ await atomicUpdateState(config.stateFilePath, (state)=>{
1892
+ const session = getSessionState(state, sessionId);
1893
+ return {
1894
+ ...state,
1895
+ [sessionId]: {
1896
+ ...session,
1897
+ last_line: interruptedLastLine ?? (session.last_line >= 0 ? session.last_line : endPosition.lastLine),
1898
+ last_offset: interruptedLastOffset ?? (session.last_offset && session.last_offset > 0 ? session.last_offset : endPosition.lastOffset),
1899
+ turn_count: session.turn_count + interruptedTurns,
1900
+ subagent_turn_count: (session.subagent_turn_count ?? 0) + interruptedSubagentTurns,
1901
+ current_turn_id: `${sessionId}:${startMs}`,
1902
+ current_turn_start_ms: startMs,
1903
+ current_prompt: input.prompt,
1904
+ tool_start_times: {},
1905
+ tool_observations: {},
1906
+ pending_subagent_traces: [],
1907
+ updated: new Date().toISOString()
1908
+ }
1909
+ };
1910
+ });
1911
+ }
1912
+ async function dispatchHook(input, config) {
1913
+ switch(input.hook_event_name){
1914
+ case 'UserPromptSubmit':
1915
+ await handleUserPromptSubmit(input, config);
1916
+ return;
1917
+ case 'PreToolUse':
1918
+ await handlePreToolUse(input, config);
1919
+ return;
1920
+ case 'PostToolUse':
1921
+ case 'PostToolUseFailure':
1922
+ await handlePostToolUse(input, config);
1923
+ return;
1924
+ case 'SubagentStop':
1925
+ await handleSubagentStop(input, config);
1926
+ return;
1927
+ case 'PreCompact':
1928
+ await handlePreCompact(input, config);
1929
+ return;
1930
+ case 'PostCompact':
1931
+ await handlePostCompact(input, config);
1932
+ return;
1933
+ case 'Stop':
1934
+ await handleStop(input, config);
1935
+ return;
1936
+ case 'StopFailure':
1937
+ await handleStopFailure(input, config);
1938
+ return;
1939
+ case 'SessionEnd':
1940
+ await handleSessionEnd(input, config);
1941
+ return;
1942
+ default:
1943
+ return;
1944
+ }
1945
+ }
1946
+ async function readStdinJson() {
1947
+ const text = await new Promise((resolve, reject)=>{
1948
+ let input = '';
1949
+ process.stdin.setEncoding('utf8');
1950
+ process.stdin.on('data', (chunk)=>{
1951
+ input += chunk;
1952
+ });
1953
+ process.stdin.on('end', ()=>resolve(input));
1954
+ process.stdin.on('error', reject);
1955
+ });
1956
+ if (!text.trim()) return {};
1957
+ return JSON.parse(text);
1958
+ }
1959
+ async function main() {
1960
+ try {
1961
+ const input = await readStdinJson();
1962
+ const config = loadConfig();
1963
+ await __rspack_external_node_fs_promises_153e37e0.mkdir(config.dataRoot, {
1964
+ recursive: true
1965
+ });
1966
+ await dispatchHook(input, config);
1967
+ } finally{
1968
+ await closeTraceLogProducers();
1969
+ }
1970
+ }
1971
+ main().catch(async (error)=>{
1972
+ try {
1973
+ const config = loadConfig();
1974
+ await writeSendLog(config, {
1975
+ generated_at: new Date().toISOString(),
1976
+ export_status: 'failed',
1977
+ reason: 'hook_error',
1978
+ message: error instanceof Error ? error.stack || error.message : String(error)
1979
+ });
1980
+ } catch {}
1981
+ process.exit(0);
1982
+ });