@volcengine/tls-observer-trae 0.0.2

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,2604 @@
1
+ import { homedir, hostname, networkInterfaces, userInfo } from "node:os";
2
+ import { tlsOpenapi } from "@volcengine/openapi";
3
+ import { createHash as external_node_crypto_createHash, randomUUID } from "node:crypto";
4
+ import "node:url";
5
+ import { execFile } from "node:child_process";
6
+ import { promisify } from "node:util";
7
+ import * as __rspack_external_node_fs_promises_153e37e0 from "node:fs/promises";
8
+ import * as __rspack_external_node_path_c5b9b54f from "node:path";
9
+ import * as __rspack_external_node_fs_5ea92f0c from "node:fs";
10
+ const TLS_ANONYMOUS_IDENTITY_HEADER = 'x-tls-anonymous-identity';
11
+ const DEFAULT_FILE_NAME = 'agent-trace';
12
+ const NO_RETRY_STATUS_CODES = [
13
+ 400,
14
+ 401,
15
+ 403,
16
+ 404
17
+ ];
18
+ const producerEntries = new Map();
19
+ function firstExternalIpv4() {
20
+ for (const items of Object.values(networkInterfaces()))for (const item of items ?? [])if ('IPv4' === item.family && !item.internal && item.address) return item.address;
21
+ return '127.0.0.1';
22
+ }
23
+ function normalizeProducerEndpoint(endpoint) {
24
+ const raw = endpoint.trim();
25
+ if (!raw) return raw;
26
+ try {
27
+ const url = new URL(/^https?:\/\//.test(raw) ? raw : `https://${raw}`);
28
+ return url.host || raw;
29
+ } catch {
30
+ return raw.replace(/^https?:\/\//, '').replace(/\/.*$/, '');
31
+ }
32
+ }
33
+ function producerKey(config) {
34
+ return JSON.stringify({
35
+ endpoint: normalizeProducerEndpoint(config.endpoint),
36
+ region: config.region,
37
+ apiKey: config.apiKey || '',
38
+ ak: config.ak || '',
39
+ sk: config.sk || ''
40
+ });
41
+ }
42
+ function createProducer(config) {
43
+ const producerConfig = tlsOpenapi.Producer.getDefaultProducerConfig();
44
+ producerConfig.Endpoint = normalizeProducerEndpoint(config.endpoint);
45
+ producerConfig.Region = config.region;
46
+ producerConfig.NoRetryStatusCodeList = NO_RETRY_STATUS_CODES;
47
+ if (config.apiKey) {
48
+ producerConfig.AccessKeyID = '';
49
+ producerConfig.AccessKeySecret = '';
50
+ producerConfig.Headers = {
51
+ [TLS_ANONYMOUS_IDENTITY_HEADER]: config.apiKey
52
+ };
53
+ } else {
54
+ producerConfig.AccessKeyID = config.ak || '';
55
+ producerConfig.AccessKeySecret = config.sk || '';
56
+ }
57
+ const producer = new tlsOpenapi.Producer(producerConfig);
58
+ producer.Start();
59
+ return {
60
+ producer,
61
+ source: firstExternalIpv4(),
62
+ timeoutMs: config.timeoutMs
63
+ };
64
+ }
65
+ function timeoutError(label, timeoutMs) {
66
+ return new Error(`${label} timed out after ${timeoutMs}ms`);
67
+ }
68
+ function withTimeout(promise, timeoutMs, label) {
69
+ const effectiveTimeoutMs = Number.isFinite(timeoutMs) && timeoutMs > 0 ? Math.trunc(timeoutMs) : 20000;
70
+ return new Promise((resolve, reject)=>{
71
+ const timer = setTimeout(()=>reject(timeoutError(label, effectiveTimeoutMs)), effectiveTimeoutMs);
72
+ promise.then((value)=>{
73
+ clearTimeout(timer);
74
+ resolve(value);
75
+ }, (error)=>{
76
+ clearTimeout(timer);
77
+ reject(error);
78
+ });
79
+ });
80
+ }
81
+ function getProducer(config) {
82
+ const key = producerKey(config);
83
+ const existing = producerEntries.get(key);
84
+ if (existing) return existing;
85
+ const created = createProducer(config);
86
+ producerEntries.set(key, created);
87
+ return created;
88
+ }
89
+ function recordContents(record) {
90
+ return Object.entries(record).map(([Key, Value])=>({
91
+ Key,
92
+ Value
93
+ }));
94
+ }
95
+ async function sendTlsLogRecords(config, records, fileName = DEFAULT_FILE_NAME) {
96
+ if (0 === records.length) return;
97
+ const testMode = process.env.TRACE_TLS_TEST_PRODUCER_MODE;
98
+ const captureFile = process.env.TRACE_TLS_TEST_PRODUCER_CAPTURE_FILE;
99
+ if (testMode || captureFile) {
100
+ if (captureFile) {
101
+ await __rspack_external_node_fs_promises_153e37e0.mkdir(__rspack_external_node_path_c5b9b54f.dirname(captureFile), {
102
+ recursive: true
103
+ });
104
+ await __rspack_external_node_fs_promises_153e37e0.appendFile(captureFile, `${JSON.stringify({
105
+ topic_id: config.traceTopicId,
106
+ endpoint: normalizeProducerEndpoint(config.endpoint),
107
+ region: config.region,
108
+ file_name: fileName,
109
+ records
110
+ })}\n`, 'utf8');
111
+ }
112
+ if ('fail' === testMode) throw new Error('TRACE_TLS_TEST_PRODUCER_MODE=fail');
113
+ return;
114
+ }
115
+ const entry = getProducer(config);
116
+ const logGroup = {
117
+ Logs: records.map((record)=>({
118
+ Time: Date.now(),
119
+ Contents: recordContents(record)
120
+ })),
121
+ Source: entry.source,
122
+ LogTags: [],
123
+ FileName: fileName
124
+ };
125
+ await withTimeout(new Promise((resolve, reject)=>{
126
+ let settled = false;
127
+ const settle = (fn, value)=>{
128
+ if (settled) return;
129
+ settled = true;
130
+ fn(value);
131
+ };
132
+ const resolveUnknown = resolve;
133
+ const sendPromise = entry.producer.SendLogs('', config.traceTopicId, entry.source, fileName, logGroup, {
134
+ Success: ()=>settle(resolveUnknown),
135
+ Fail: (result)=>{
136
+ const attempts = result?.Attempts ?? [];
137
+ const lastAttempt = attempts[attempts.length - 1] ?? {};
138
+ settle(reject, new Error(`TLS SendLogs failed topicId=${config.traceTopicId} requestId=${String(lastAttempt.RequestId ?? '')} errorCode=${String(lastAttempt.ErrorCode ?? '')} errorMessage=${String(lastAttempt.ErrorMessage ?? '')}`));
139
+ }
140
+ });
141
+ Promise.resolve(sendPromise).catch((error)=>settle(reject, error));
142
+ }), config.timeoutMs, 'TLS SendLogs');
143
+ }
144
+ async function closeTraceLogProducers() {
145
+ const entries = [
146
+ ...producerEntries.values()
147
+ ];
148
+ producerEntries.clear();
149
+ await Promise.allSettled(entries.map((entry)=>{
150
+ const closeTimeoutMs = Math.min(Math.max(entry.timeoutMs || 20000, 1000), 5000);
151
+ return withTimeout(Promise.resolve(entry.producer.Close()), closeTimeoutMs, 'TLS Producer Close');
152
+ }));
153
+ }
154
+ function firstEnv(env, ...names) {
155
+ for (const name of names){
156
+ const value = env[name];
157
+ if ('string' == typeof value && '' !== value.trim()) return value.trim();
158
+ }
159
+ }
160
+ function tlsEndpointFromLegacyOtelEndpoint(value) {
161
+ if (!value) return;
162
+ try {
163
+ const url = new URL(/^https?:\/\//i.test(value) ? value : `https://${value}`);
164
+ return url.hostname || void 0;
165
+ } catch {
166
+ return value.trim().replace(/^https?:\/\//i, '').replace(/\/.*$/, '').replace(/:\d+$/, '') || void 0;
167
+ }
168
+ }
169
+ function config_resolveTlsConfig(env) {
170
+ const disabled = /^(0|false|no)$/i.test(firstEnv(env, 'TRACE_TLS_EXPORT', 'CODEX_TLS_EXPORT') ?? '');
171
+ if (disabled) return {
172
+ enabled: false,
173
+ reason: 'disabled'
174
+ };
175
+ const endpoint = firstEnv(env, 'TRACE_TLS_ENDPOINT', 'CODEX_TLS_ENDPOINT') || tlsEndpointFromLegacyOtelEndpoint(firstEnv(env, 'TRACE_TLS_OTEL_ENDPOINT', 'CODEX_TLS_OTEL_ENDPOINT', 'OTEL_EXPORTER_OTLP_TRACES_ENDPOINT'));
176
+ const region = firstEnv(env, 'TRACE_TLS_REGION', 'CODEX_TLS_REGION', 'TLS_REGION');
177
+ const traceTopicId = firstEnv(env, 'TRACE_TLS_TRACE_TOPIC_ID', 'TRACE_TLS_TOPIC_ID', 'CODEX_TLS_TRACE_TOPIC_ID', 'CODEX_TLS_TOPIC_ID', 'CODEX_TLS_TRACE_TOPICID', 'TLS_TRACE_TOPIC_ID');
178
+ const apiKey = firstEnv(env, 'TRACE_TLS_API_KEY', 'CODEX_TLS_API_KEY', 'TLS_API_KEY');
179
+ const ak = firstEnv(env, 'TRACE_TLS_AK', 'CODEX_TLS_AK', 'TLS_AK');
180
+ const sk = firstEnv(env, 'TRACE_TLS_SK', 'CODEX_TLS_SK', 'TLS_SK');
181
+ const timeoutMs = Number(firstEnv(env, 'TRACE_TLS_EXPORT_TIMEOUT_MS', 'CODEX_TLS_EXPORT_TIMEOUT_MS') ?? 20000);
182
+ const missing = [];
183
+ if (!endpoint) missing.push('TRACE_TLS_ENDPOINT or CODEX_TLS_ENDPOINT');
184
+ if (!region) missing.push('TRACE_TLS_REGION or CODEX_TLS_REGION');
185
+ if (!traceTopicId) missing.push('TRACE_TLS_TRACE_TOPIC_ID or CODEX_TLS_TRACE_TOPIC_ID');
186
+ if (!apiKey && (!ak || !sk)) missing.push('TRACE_TLS_API_KEY or TRACE_TLS_AK/TRACE_TLS_SK');
187
+ if (missing.length > 0 || !endpoint || !region || !traceTopicId) return {
188
+ enabled: false,
189
+ reason: 'missing_config',
190
+ missing
191
+ };
192
+ if (apiKey && (ak || sk)) return {
193
+ enabled: false,
194
+ reason: 'invalid_auth',
195
+ missing: [
196
+ 'use apiKey or ak/sk, not both'
197
+ ]
198
+ };
199
+ return {
200
+ enabled: true,
201
+ endpoint,
202
+ region,
203
+ traceTopicId,
204
+ apiKey,
205
+ ak,
206
+ sk,
207
+ timeoutMs: Number.isFinite(timeoutMs) && timeoutMs > 0 ? timeoutMs : 20000
208
+ };
209
+ }
210
+ const DEFAULT_MAX_ATTR_LENGTH = 65536;
211
+ const MAX_ATTR_LENGTH_ENV_KEYS = [
212
+ 'TRACE_OTLP_ATTRIBUTE_VALUE_MAX_CHARS',
213
+ 'CODEX_OTLP_ATTRIBUTE_VALUE_MAX_CHARS'
214
+ ];
215
+ function resolveMaxAttrLength(env = process.env) {
216
+ for (const key of MAX_ATTR_LENGTH_ENV_KEYS){
217
+ const raw = env[key];
218
+ if (null == raw || '' === raw.trim()) continue;
219
+ const parsed = Number(raw);
220
+ if (Number.isFinite(parsed) && parsed >= 0) return Math.trunc(parsed);
221
+ }
222
+ return DEFAULT_MAX_ATTR_LENGTH;
223
+ }
224
+ function truncateTextForAttr(value) {
225
+ return String(value ?? '').slice(0, resolveMaxAttrLength());
226
+ }
227
+ function safeStringify(value) {
228
+ const seen = new WeakSet();
229
+ return JSON.stringify(value, (_key, item)=>{
230
+ if (item && 'object' == typeof item) {
231
+ if (seen.has(item)) return '[Circular]';
232
+ seen.add(item);
233
+ }
234
+ return item;
235
+ });
236
+ }
237
+ function maskToken(value) {
238
+ if (value.length <= 8) return '***';
239
+ return `${value.slice(0, 3)}***${value.slice(-2)}`;
240
+ }
241
+ function redactSensitiveText(text) {
242
+ return String(text ?? '').replace(/\bAK[0-9A-Za-z]{16,}\b/g, maskToken).replace(/\b(Bearer\s+)([A-Za-z0-9._-]{8,})/gi, (_match, prefix, token)=>`${prefix}${maskToken(token)}`).replace(/\b(token|secret|password|passwd|api[_-]?key|access[_-]?key|sk)\s*[:=]\s*([^,\s"']{8,})/gi, (_match, key, secret)=>`${key}=***${String(secret).slice(-2)}`);
243
+ }
244
+ function attributes_serializeForAttr(value) {
245
+ const serialized = 'string' == typeof value ? value : safeStringify(value);
246
+ return redactSensitiveText(truncateTextForAttr(serialized ?? String(value)));
247
+ }
248
+ function stableHexId(seed, length) {
249
+ const normalized = String(seed ?? '');
250
+ const digest = external_node_crypto_createHash('sha256').update(normalized).digest('hex').slice(0, length);
251
+ return digest === '0'.repeat(length) ? '1'.padStart(length, '0') : digest;
252
+ }
253
+ function model_traceIdFromSeed(seed) {
254
+ return stableHexId(seed, 32);
255
+ }
256
+ function spanIdFromSeed(...parts) {
257
+ return stableHexId(parts.map((part)=>String(part ?? '')).join(':'), 16);
258
+ }
259
+ function model_toEpochMs(value, fallback = Date.now()) {
260
+ if (value instanceof Date) {
261
+ const time = value.getTime();
262
+ return Number.isFinite(time) ? time : fallback;
263
+ }
264
+ if ('number' == typeof value && Number.isFinite(value)) return value;
265
+ if ('string' == typeof value && '' !== value.trim()) {
266
+ const parsed = Date.parse(value);
267
+ if (Number.isFinite(parsed)) return parsed;
268
+ }
269
+ return fallback;
270
+ }
271
+ function isRecord(value) {
272
+ return null != value && 'object' == typeof value && !Array.isArray(value);
273
+ }
274
+ function validateSpan(span, path) {
275
+ const errors = [];
276
+ if (!isRecord(span)) return [
277
+ `${path} must be an object`
278
+ ];
279
+ if ('string' != typeof span.name || '' === span.name.trim()) errors.push(`${path}.name is required`);
280
+ if (void 0 === span.startedAt && void 0 === span.start_time_unix_ms) errors.push(`${path}.startedAt is required`);
281
+ if (void 0 === span.endedAt && void 0 === span.end_time_unix_ms) errors.push(`${path}.endedAt is required`);
282
+ if (void 0 !== span.attributes && !isRecord(span.attributes)) errors.push(`${path}.attributes must be an object`);
283
+ return errors;
284
+ }
285
+ function validateCanonicalTrace(trace) {
286
+ const errors = [];
287
+ const warnings = [];
288
+ if (!isRecord(trace)) return {
289
+ valid: false,
290
+ errors: [
291
+ 'trace must be an object'
292
+ ],
293
+ warnings
294
+ };
295
+ if (isRecord(trace.source)) {
296
+ const source = trace.source;
297
+ if ('string' != typeof source.tool || '' === source.tool.trim()) errors.push('source.tool is required');
298
+ if ('string' != typeof source.sessionId || '' === source.sessionId.trim()) errors.push('source.sessionId is required');
299
+ } else errors.push('source is required');
300
+ errors.push(...validateSpan(trace.root, 'root'));
301
+ if (Array.isArray(trace.spans)) for (const [index, span] of trace.spans.entries())errors.push(...validateSpan(span, `spans[${index}]`));
302
+ else errors.push('spans must be an array');
303
+ if (void 0 !== trace.messages && !Array.isArray(trace.messages)) errors.push('messages must be an array');
304
+ if (void 0 !== trace.diagnostics) if (isRecord(trace.diagnostics)) {
305
+ if (void 0 !== trace.diagnostics.warnings && !Array.isArray(trace.diagnostics.warnings)) warnings.push('diagnostics.warnings should be an array');
306
+ } else warnings.push('diagnostics should be an object');
307
+ return {
308
+ valid: 0 === errors.length,
309
+ errors,
310
+ warnings
311
+ };
312
+ }
313
+ function assertCanonicalTrace(trace) {
314
+ const result = validateCanonicalTrace(trace);
315
+ if (!result.valid) {
316
+ const error = new Error(`Invalid canonical trace: ${result.errors.join('; ')}`);
317
+ error.name = 'CanonicalTraceValidationError';
318
+ error.validation = result;
319
+ throw error;
320
+ }
321
+ return trace;
322
+ }
323
+ function spanKindName(span) {
324
+ const kind = String(span.span_kind ?? span.kind ?? '').toUpperCase();
325
+ if ('SERVER' === kind) return 'server';
326
+ if ('CLIENT' === kind) return 'client';
327
+ return 'internal';
328
+ }
329
+ function unixMsToUs(value) {
330
+ return String(1000 * Math.trunc(model_toEpochMs(value)));
331
+ }
332
+ function span_utils_hasError(span) {
333
+ const statusObjectCode = null != span.status && 'object' == typeof span.status ? span.status.code : void 0;
334
+ const statusCode = statusObjectCode ?? span.status_code ?? span.statusCode ?? span.status;
335
+ if (2 === statusCode || 'ERROR' === String(statusCode).toUpperCase()) return true;
336
+ return Object.entries(span.attributes ?? {}).some(([key, value])=>{
337
+ if (true === value && ('error' === key || key.endsWith('.error'))) return true;
338
+ if ('tool.error' === key && 'string' == typeof value && '' !== value.trim()) return true;
339
+ return false;
340
+ });
341
+ }
342
+ function spanDurationMs(span) {
343
+ if ('number' == typeof span.duration_ms && Number.isFinite(span.duration_ms)) return Math.max(0, Math.trunc(span.duration_ms));
344
+ const start = model_toEpochMs(span.start_time_unix_ms ?? span.startedAt);
345
+ const end = model_toEpochMs(span.end_time_unix_ms ?? span.endedAt, start);
346
+ return Math.max(0, Math.trunc(end - start));
347
+ }
348
+ const SCHEMA_FIELD_ORDER = [
349
+ 'span.name',
350
+ 'span.kind',
351
+ 'tls.app.type',
352
+ 'session.id',
353
+ 'span.status.code',
354
+ 'start_time_unix_us',
355
+ 'end_time_unix_us',
356
+ 'span.duration_us',
357
+ 'service.name',
358
+ 'service.instance.id',
359
+ 'host.name',
360
+ 'client.address',
361
+ 'observed_time_unix_ms',
362
+ 'telemetry.sdk.name',
363
+ 'telemetry.sdk.language',
364
+ 'telemetry.sdk.version',
365
+ 'otel.scope.name',
366
+ 'otel.scope.version',
367
+ 'event.name',
368
+ 'user.role',
369
+ 'gen_ai.operation.name',
370
+ 'gen_ai.provider.name',
371
+ 'gen_ai.request.model',
372
+ 'gen_ai.response.model',
373
+ 'gen_ai.request.duration_ms',
374
+ 'gen_ai.input.messages',
375
+ 'gen_ai.output.messages',
376
+ 'gen_ai.usage.input_tokens',
377
+ 'gen_ai.usage.output_tokens',
378
+ 'gen_ai.usage.reasoning.output_tokens',
379
+ 'gen_ai.usage.cache_read.input_tokens',
380
+ 'gen_ai.usage.cache_creation.input_tokens',
381
+ 'gen_ai.usage.total_tokens',
382
+ 'gen_ai.tool.name',
383
+ 'gen_ai.tool.call.id',
384
+ 'gen_ai.tool.call.arguments',
385
+ 'gen_ai.tool.call.result',
386
+ 'tool.error',
387
+ 'error.type',
388
+ 'error.message'
389
+ ];
390
+ function normalizeOptions(options) {
391
+ if ('number' == typeof options) return {
392
+ observedTimeUnixMs: options,
393
+ version: '-'
394
+ };
395
+ return {
396
+ observedTimeUnixMs: options.observedTimeUnixMs ?? Date.now(),
397
+ version: options.version?.trim() || '-'
398
+ };
399
+ }
400
+ function hasMessages(value) {
401
+ return null != value && 'object' == typeof value && !Array.isArray(value);
402
+ }
403
+ function stableFieldValue(value) {
404
+ if (null == value) return '';
405
+ if ('string' == typeof value) return attributes_serializeForAttr(value);
406
+ if ('number' == typeof value || 'boolean' == typeof value) return value;
407
+ return attributes_serializeForAttr(value);
408
+ }
409
+ function inferUserRole(span) {
410
+ const inputMessage = hasMessages(span.input) ? span.input.messages?.find((message)=>'string' == typeof message?.role) : void 0;
411
+ const role = inputMessage?.role;
412
+ if ('string' == typeof role && [
413
+ 'assistant',
414
+ 'system',
415
+ 'tool',
416
+ 'user'
417
+ ].includes(role)) return role;
418
+ return '';
419
+ }
420
+ function inferErrorMessage(span) {
421
+ const output = span.output;
422
+ if (null == output) return '';
423
+ if ('string' == typeof output) return output;
424
+ if ('object' == typeof output && 'error' in output && 'string' == typeof output.error) return output.error;
425
+ if ('object' == typeof output && 'error' in output && null != output.error) return attributes_serializeForAttr(output.error);
426
+ return '';
427
+ }
428
+ function schemaSourceAttribute(attributes, dotted, legacy) {
429
+ return attributes[dotted] ?? attributes[legacy] ?? '';
430
+ }
431
+ function normalizedSpanName(span) {
432
+ if ('model' === span.type) return 'llm.request';
433
+ if ('tool' === span.type) return 'tool.call';
434
+ if ('message' === span.type) return 'agent.turn';
435
+ return span.name ?? '';
436
+ }
437
+ function legacyToolErrorMessage(value) {
438
+ return 'string' == typeof value ? value : '';
439
+ }
440
+ function buildSchemaAlignedRecord(trace, span, options = {}) {
441
+ const { observedTimeUnixMs, version } = normalizeOptions(options);
442
+ const attributes = span.attributes ?? {};
443
+ const resource = trace.resource ?? {};
444
+ const isError = span_utils_hasError(span);
445
+ const isTool = 'tool' === span.type;
446
+ const isModel = 'model' === span.type;
447
+ const isMessage = 'message' === span.type;
448
+ const inputMessages = hasMessages(span.input) ? span.input.messages ?? attributes['gen_ai.input.messages'] : attributes['gen_ai.input.messages'];
449
+ const outputMessages = hasMessages(span.output) ? span.output.messages ?? attributes['gen_ai.output.messages'] : attributes['gen_ai.output.messages'];
450
+ const explicitErrorMessage = attributes['error.message'];
451
+ const spanName = normalizedSpanName(span);
452
+ const toolErrorMessage = isTool ? 'string' == typeof explicitErrorMessage && explicitErrorMessage || legacyToolErrorMessage(attributes['tool.error']) || inferErrorMessage(span) : '';
453
+ const record = {
454
+ 'span.name': spanName,
455
+ 'span.kind': spanKindName(span),
456
+ 'tls.app.type': attributes['tls.app.type'] ?? 'codex',
457
+ 'session.id': schemaSourceAttribute(attributes, 'session.id', 'codex.session_id'),
458
+ 'span.status.code': isError ? 'ERROR' : 'UNSET',
459
+ start_time_unix_us: unixMsToUs(span.start_time_unix_ms ?? span.startedAt),
460
+ end_time_unix_us: unixMsToUs(span.end_time_unix_ms ?? span.endedAt),
461
+ 'span.duration_us': String(1000 * spanDurationMs(span)),
462
+ 'service.name': resource['service.name'] ?? "codex-transcript-stop-hook",
463
+ 'service.instance.id': resource['service.instance.id'] ?? '',
464
+ 'host.name': resource['host.name'] ?? '',
465
+ 'client.address': '',
466
+ observed_time_unix_ms: observedTimeUnixMs,
467
+ 'telemetry.sdk.name': resource['telemetry.sdk.name'] ?? "codex-transcript-stop-hook",
468
+ 'telemetry.sdk.language': resource['telemetry.sdk.language'] ?? 'nodejs',
469
+ 'telemetry.sdk.version': resource['telemetry.sdk.version'] ?? version,
470
+ 'otel.scope.name': trace.scope?.name ?? "codex-transcript-stop-hook",
471
+ 'otel.scope.version': trace.scope?.version ?? version,
472
+ 'event.name': spanName,
473
+ 'user.role': inferUserRole(span),
474
+ 'gen_ai.operation.name': attributes['gen_ai.operation.name'] ?? (isModel ? 'chat' : isMessage ? 'invoke_agent' : ''),
475
+ 'gen_ai.provider.name': attributes['gen_ai.provider.name'] ?? '',
476
+ 'gen_ai.request.model': attributes['gen_ai.request.model'] ?? '',
477
+ 'gen_ai.response.model': attributes['gen_ai.response.model'] ?? attributes['gen_ai.request.model'] ?? '',
478
+ 'gen_ai.request.duration_ms': isModel ? spanDurationMs(span) : attributes['request.duration_ms'] ?? '',
479
+ 'gen_ai.input.messages': inputMessages ?? '',
480
+ 'gen_ai.output.messages': outputMessages ?? '',
481
+ 'gen_ai.usage.input_tokens': attributes['gen_ai.usage.input_tokens'] ?? '',
482
+ 'gen_ai.usage.output_tokens': attributes['gen_ai.usage.output_tokens'] ?? '',
483
+ 'gen_ai.usage.reasoning.output_tokens': attributes['gen_ai.usage.reasoning.output_tokens'] ?? '',
484
+ 'gen_ai.usage.cache_read.input_tokens': attributes['gen_ai.usage.cache_read.input_tokens'] ?? '',
485
+ 'gen_ai.usage.cache_creation.input_tokens': attributes['gen_ai.usage.cache_creation.input_tokens'] ?? '',
486
+ 'gen_ai.usage.total_tokens': attributes['gen_ai.usage.total_tokens'] ?? '',
487
+ 'gen_ai.tool.name': isTool ? attributes['gen_ai.tool.name'] ?? attributes['tool.name'] ?? '' : '',
488
+ 'gen_ai.tool.call.id': isTool ? attributes['gen_ai.tool.call.id'] ?? attributes['codex.tool_call_id'] ?? '' : '',
489
+ 'gen_ai.tool.call.arguments': isTool ? span.input ?? '' : '',
490
+ 'gen_ai.tool.call.result': isTool ? span.output ?? '' : '',
491
+ 'tool.error': isTool ? isError : '',
492
+ 'error.type': isError ? attributes['codex.tool_status'] ?? 'error' : '',
493
+ 'error.message': isError ? toolErrorMessage : ''
494
+ };
495
+ const aligned = {};
496
+ for (const key of SCHEMA_FIELD_ORDER)aligned[key] = stableFieldValue(record[key]);
497
+ return aligned;
498
+ }
499
+ function buildSchemaAlignedRecords(trace, options = {}) {
500
+ if (!trace || !Array.isArray(trace.spans)) return [];
501
+ return trace.spans.map((span)=>buildSchemaAlignedRecord(trace, span, options));
502
+ }
503
+ const TLS_AUTH_RESOURCE_KEYS = {
504
+ traceTopicId: 'tls.otel.tracetopic',
505
+ apiKey: 'tls.otel.apikey',
506
+ ak: 'tls.otel.ak',
507
+ sk: 'tls.otel.sk',
508
+ region: 'tls.otel.region'
509
+ };
510
+ const TLS_AUTH_RESOURCE_CREDENTIAL_KEYS = new Set([
511
+ TLS_AUTH_RESOURCE_KEYS.apiKey,
512
+ TLS_AUTH_RESOURCE_KEYS.ak,
513
+ TLS_AUTH_RESOURCE_KEYS.sk
514
+ ]);
515
+ function otlpAnyValueHasContent(value) {
516
+ if (!value || 'object' != typeof value) return false;
517
+ return Object.values(value).some((item)=>null != item && '' !== String(item).trim());
518
+ }
519
+ function otlpPayloadResourceAttributes(payload) {
520
+ return (payload.resourceSpans ?? []).flatMap((resourceSpan)=>{
521
+ const attributes = resourceSpan?.resource?.attributes;
522
+ return Array.isArray(attributes) ? attributes : [];
523
+ });
524
+ }
525
+ function otlpPayloadContainsTlsCredentials(body) {
526
+ let payload;
527
+ try {
528
+ payload = JSON.parse(body);
529
+ } catch {
530
+ return [
531
+ ...TLS_AUTH_RESOURCE_CREDENTIAL_KEYS
532
+ ].some((key)=>body.includes(key));
533
+ }
534
+ if ([
535
+ ...TLS_AUTH_RESOURCE_CREDENTIAL_KEYS
536
+ ].some((key)=>body.includes(key))) return true;
537
+ return otlpPayloadResourceAttributes(payload).some((attribute)=>TLS_AUTH_RESOURCE_CREDENTIAL_KEYS.has(attribute?.key) && otlpAnyValueHasContent(attribute?.value));
538
+ }
539
+ const ATTRIBUTE_PREFIXES = [
540
+ 'gen_ai.',
541
+ 'error.'
542
+ ];
543
+ const ATTRIBUTE_KEYS = new Set([
544
+ 'tool.error'
545
+ ]);
546
+ const NUMERIC_ATTRIBUTE_KEYS = new Set([
547
+ 'gen_ai.request.duration_ms',
548
+ 'gen_ai.usage.input_tokens',
549
+ 'gen_ai.usage.output_tokens',
550
+ 'gen_ai.usage.reasoning.output_tokens',
551
+ 'gen_ai.usage.cache_read.input_tokens',
552
+ 'gen_ai.usage.cache_creation.input_tokens',
553
+ 'gen_ai.usage.total_tokens'
554
+ ]);
555
+ const TLS_AUTH_RESOURCE_KEY_SET = new Set(Object.values(TLS_AUTH_RESOURCE_KEYS));
556
+ function stringifyLogValue(value) {
557
+ if (null == value) return '';
558
+ if ('string' == typeof value) return value;
559
+ if ('number' == typeof value || 'boolean' == typeof value) return String(value);
560
+ return attributes_serializeForAttr(value);
561
+ }
562
+ function stringifyRecord(record) {
563
+ return Object.fromEntries(Object.entries(record).map(([key, value])=>[
564
+ key,
565
+ stringifyLogValue(value)
566
+ ]));
567
+ }
568
+ function traceIdOf(trace) {
569
+ return trace?.trace_id ?? trace?.traceId ?? '';
570
+ }
571
+ function spanIdOf(span) {
572
+ return span?.span_id ?? span?.id ?? '';
573
+ }
574
+ function parentSpanIdOf(span) {
575
+ return span?.parent_span_id ?? span?.parentId ?? '';
576
+ }
577
+ function records_traceSpansWithRoot(trace) {
578
+ const spans = trace?.spans ?? [];
579
+ const root = trace?.root;
580
+ if (!root) return spans;
581
+ const rootSpanId = spanIdOf(root);
582
+ if (rootSpanId && spans.some((span)=>spanIdOf(span) === rootSpanId)) return spans;
583
+ return [
584
+ root,
585
+ ...spans
586
+ ];
587
+ }
588
+ function safeJsonStringify(value) {
589
+ try {
590
+ return JSON.stringify(value ?? {});
591
+ } catch {
592
+ return '{}';
593
+ }
594
+ }
595
+ function normalizeSpanName(span, fallback = '') {
596
+ if (span?.type === 'model') return 'llm.request';
597
+ if (span?.type === 'tool') return 'tool.call';
598
+ if (span?.type === 'message') return 'agent.turn';
599
+ if ('codex_turn' === fallback) return 'agent.turn';
600
+ return fallback || span?.name || '';
601
+ }
602
+ function envelopeKind(span, name) {
603
+ if ('agent.turn' === name) return 'server';
604
+ if ('llm.request' === name || 'tool.call' === name) return 'client';
605
+ return span ? spanKindName(span) : 'internal';
606
+ }
607
+ function isEmptyAttributeValue(value) {
608
+ return null == value || '' === value;
609
+ }
610
+ function normalizeAttributeValue(key, value) {
611
+ if (isEmptyAttributeValue(value)) return;
612
+ if (NUMERIC_ATTRIBUTE_KEYS.has(key)) {
613
+ const numeric = Number(value);
614
+ if (Number.isFinite(numeric)) return numeric;
615
+ }
616
+ if ('string' == typeof value || 'number' == typeof value || 'boolean' == typeof value) return value;
617
+ return attributes_serializeForAttr(value);
618
+ }
619
+ function envelopeAttributes(record, span) {
620
+ const attributes = {};
621
+ const spanAttributes = span?.attributes ?? {};
622
+ const conversationId = record['gen_ai.conversation.id'] ?? spanAttributes['gen_ai.conversation.id'] ?? record['session.id'];
623
+ for (const [key, value] of Object.entries(record))if ('tls.app.type' === key || 'session.id' === key || 'gen_ai.conversation.id' === key || ATTRIBUTE_KEYS.has(key) || ATTRIBUTE_PREFIXES.some((prefix)=>key.startsWith(prefix))) {
624
+ const normalized = normalizeAttributeValue(key, value);
625
+ if (void 0 !== normalized) attributes[key] = normalized;
626
+ }
627
+ attributes['tls.app.type'] ??= 'codex';
628
+ if (!isEmptyAttributeValue(record['session.id'])) attributes['session.id'] ??= record['session.id'];
629
+ if (!isEmptyAttributeValue(conversationId)) attributes['gen_ai.conversation.id'] ??= conversationId;
630
+ if (span?.type === 'message') attributes['gen_ai.operation.name'] = 'invoke_agent';
631
+ if (span?.type === 'model') attributes['gen_ai.operation.name'] = 'chat';
632
+ return attributes;
633
+ }
634
+ function envelopeResource(trace, record) {
635
+ const resource = trace.resource ?? {};
636
+ return Object.fromEntries(Object.entries({
637
+ ...resource,
638
+ 'service.instance.id': record['service.instance.id'] ?? resource['service.instance.id'],
639
+ 'telemetry.sdk.language': record['telemetry.sdk.language'] ?? resource['telemetry.sdk.language'] ?? 'nodejs',
640
+ 'telemetry.sdk.name': record['telemetry.sdk.name'] ?? resource['telemetry.sdk.name'] ?? "codex-transcript-stop-hook",
641
+ 'telemetry.sdk.version': record['telemetry.sdk.version'] ?? resource['telemetry.sdk.version'] ?? '0.1.0'
642
+ }).filter(([key, value])=>!TLS_AUTH_RESOURCE_KEY_SET.has(key) && !isEmptyAttributeValue(value)));
643
+ }
644
+ function otlpEnvelopeRecord(trace, span, record) {
645
+ const name = normalizeSpanName(span, String(record['span.name'] ?? ''));
646
+ const start = String(record['start_time_unix_us'] ?? unixMsToUs(span?.start_time_unix_ms ?? span?.startedAt));
647
+ const end = String(record['end_time_unix_us'] ?? unixMsToUs(span?.end_time_unix_ms ?? span?.endedAt));
648
+ const duration = String(record['span.duration_us'] ?? 1000 * spanDurationMs(span ?? {}));
649
+ const error = span ? span_utils_hasError(span) : 'ERROR' === record['span.status.code'];
650
+ const resource = envelopeResource(trace, record);
651
+ const serviceName = stringifyLogValue(record['service.name'] ?? resource['service.name'] ?? "codex-transcript-stop-hook");
652
+ const host = stringifyLogValue(record['host.name'] ?? resource['host.name'] ?? '');
653
+ const scopeName = stringifyLogValue(record['otel.scope.name'] ?? trace.scope?.name ?? "codex-transcript-stop-hook");
654
+ const scopeVersion = stringifyLogValue(record['otel.scope.version'] ?? trace.scope?.version ?? '0.1.0');
655
+ return stringifyRecord({
656
+ Attributes: safeJsonStringify(envelopeAttributes(record, span)),
657
+ Duration: duration,
658
+ End: end,
659
+ Events: '[]',
660
+ Host: host,
661
+ Kind: envelopeKind(span, name),
662
+ Links: '[]',
663
+ Name: name,
664
+ OTLPName: scopeName,
665
+ OTLPVersion: scopeVersion,
666
+ ParentSpanID: parentSpanIdOf(span) || record['parent.span.id'] || '',
667
+ Resource: safeJsonStringify(resource),
668
+ ServiceName: serviceName,
669
+ SpanID: spanIdOf(span) || String(record['span.id'] ?? ''),
670
+ Start: start,
671
+ StatusCode: error ? 'ERROR' : 'OK',
672
+ StatusDescription: error ? record['error.message'] ?? '' : '',
673
+ TraceID: span?.trace_id ?? traceIdOf(trace) ?? record['trace.id'] ?? '',
674
+ TraceState: ''
675
+ });
676
+ }
677
+ function recordsFromTrace(trace) {
678
+ const spans = records_traceSpansWithRoot(trace);
679
+ const schemaRecords = buildSchemaAlignedRecords({
680
+ ...trace,
681
+ spans
682
+ });
683
+ return schemaRecords.map((record, index)=>otlpEnvelopeRecord(trace, spans[index], record));
684
+ }
685
+ function exporter_traceIdOf(trace) {
686
+ return trace?.trace_id ?? trace?.traceId ?? '';
687
+ }
688
+ function notifyRequestBody(options, request) {
689
+ if ('function' != typeof options.onRequestBody) return;
690
+ try {
691
+ options.onRequestBody(request);
692
+ } catch {}
693
+ }
694
+ function endpointDiagnostics(config) {
695
+ return {
696
+ endpoint_host: normalizeProducerEndpoint(config.endpoint),
697
+ endpoint_path: 'SendLogs',
698
+ region: config.region
699
+ };
700
+ }
701
+ async function exportRecordsToTls(records, config, metadata = {}, options = {}, fileName = 'agent-trace') {
702
+ if (0 === records.length) return {
703
+ status: 'skipped',
704
+ reason: 'empty_records'
705
+ };
706
+ const body = JSON.stringify({
707
+ records
708
+ });
709
+ const startedAt = Date.now();
710
+ const traceId = 'string' == typeof metadata.trace_id ? metadata.trace_id : records[0]?.TraceID ?? records[0]?.['trace.id'] ?? '';
711
+ const spanCount = Number.isFinite(Number(metadata.span_count)) ? Number(metadata.span_count) : records.length;
712
+ notifyRequestBody(options, {
713
+ body,
714
+ attempt: 1,
715
+ request_kind: 'tls_log_records',
716
+ payload_bytes: Buffer.byteLength(body, 'utf8'),
717
+ record_count: records.length,
718
+ span_count: spanCount,
719
+ trace_id: traceId,
720
+ ...endpointDiagnostics(config)
721
+ });
722
+ try {
723
+ await sendTlsLogRecords(config, records, fileName);
724
+ return {
725
+ status: 'sent',
726
+ trace_id: traceId,
727
+ span_count: spanCount,
728
+ record_count: records.length,
729
+ payload_bytes: Buffer.byteLength(body, 'utf8'),
730
+ attempts: 1,
731
+ total_duration_ms: Date.now() - startedAt,
732
+ ...endpointDiagnostics(config)
733
+ };
734
+ } catch (error) {
735
+ return {
736
+ status: 'failed',
737
+ reason: 'producer_error',
738
+ message: error instanceof Error ? error.message : String(error),
739
+ trace_id: traceId,
740
+ span_count: spanCount,
741
+ record_count: records.length,
742
+ payload_bytes: Buffer.byteLength(body, 'utf8'),
743
+ attempts: 1,
744
+ total_duration_ms: Date.now() - startedAt,
745
+ error_name: error instanceof Error ? error.name : void 0,
746
+ ...endpointDiagnostics(config)
747
+ };
748
+ }
749
+ }
750
+ async function exportTraceToTls(trace, env = process.env, options = {}) {
751
+ const spans = records_traceSpansWithRoot(trace);
752
+ if (!trace || 0 === spans.length) return {
753
+ status: 'skipped',
754
+ reason: 'empty_trace'
755
+ };
756
+ const config = config_resolveTlsConfig(env);
757
+ if (!config.enabled) return {
758
+ status: 'skipped',
759
+ reason: config.reason,
760
+ missing: config.missing
761
+ };
762
+ return exportRecordsToTls(recordsFromTrace(trace), config, {
763
+ span_count: spans.length,
764
+ trace_id: exporter_traceIdOf(trace)
765
+ }, options, `${trace.source?.tool ?? 'agent'}-trace`);
766
+ }
767
+ function config_firstEnv(...names) {
768
+ for (const name of names){
769
+ const value = process.env[name];
770
+ if ('string' == typeof value && '' !== value.trim()) return value.trim();
771
+ }
772
+ }
773
+ function envFlag(name, defaultValue = false) {
774
+ const value = process.env[name];
775
+ if (void 0 === value || '' === value) return defaultValue;
776
+ return /^(1|true|yes|y|on)$/i.test(value);
777
+ }
778
+ function envDisabled(name) {
779
+ return /^(0|false|no|n|off)$/i.test(process.env[name] ?? '');
780
+ }
781
+ function envPositiveInteger(name, fallback) {
782
+ const value = Number.parseInt(process.env[name] ?? '', 10);
783
+ return Number.isFinite(value) && value > 0 ? value : fallback;
784
+ }
785
+ function defaultDataRoot() {
786
+ return __rspack_external_node_path_c5b9b54f.join(homedir(), '.trae-cn', 'plugins', 'data', 'tls-observer-trae');
787
+ }
788
+ function defaultEnvFile() {
789
+ return __rspack_external_node_path_c5b9b54f.join(homedir(), '.trae-cn', 'tls-observer-trae.env');
790
+ }
791
+ function config_parseEnvValue(raw) {
792
+ const value = raw.trim();
793
+ if (value.startsWith('"') && value.endsWith('"') || value.startsWith("'") && value.endsWith("'")) return value.slice(1, -1).replace(/\\n/g, '\n').replace(/\\"/g, '"').replace(/\\'/g, "'");
794
+ return value;
795
+ }
796
+ function loadEnvFile(filePath) {
797
+ const target = filePath || config_firstEnv('TRAE_TLS_ENV_FILE') || defaultEnvFile();
798
+ let text;
799
+ try {
800
+ text = __rspack_external_node_fs_5ea92f0c.readFileSync(target, 'utf8');
801
+ } catch {
802
+ return false;
803
+ }
804
+ for (const rawLine of text.split(/\r?\n/)){
805
+ const line = rawLine.trim();
806
+ if (!line || line.startsWith('#')) continue;
807
+ const normalized = line.startsWith('export ') ? line.slice(7).trim() : line;
808
+ const match = normalized.match(/^([A-Za-z_][A-Za-z0-9_]*)=(.*)$/);
809
+ if (!match) continue;
810
+ const [, key, rawValue] = match;
811
+ process.env[key] ??= config_parseEnvValue(rawValue);
812
+ }
813
+ return true;
814
+ }
815
+ function loadConfig() {
816
+ loadEnvFile();
817
+ const dataRoot = config_firstEnv('TRAE_TLS_DATA_ROOT', 'TRAE_PLUGIN_DATA') || defaultDataRoot();
818
+ const serviceName = config_firstEnv('TRAE_OTEL_SERVICE_NAME') || 'trae-contextfs-hook';
819
+ const exportRetries = Math.min(envPositiveInteger('TRAE_TLS_EXPORT_TIMEOUT_RETRIES', 1), 3);
820
+ return {
821
+ enabled: !envDisabled('TRAE_TLS_EXPORT'),
822
+ dataRoot,
823
+ turnStateFilePath: config_firstEnv('TRAE_TLS_TURN_STATE_FILE') || __rspack_external_node_path_c5b9b54f.join(dataRoot, 'state', 'trae-turn-state.json'),
824
+ sendLogsDir: __rspack_external_node_path_c5b9b54f.join(dataRoot, 'logs', 'send'),
825
+ diagnosticLogMaxBytes: envPositiveInteger('TRAE_TLS_DIAGNOSTIC_LOG_MAX_BYTES', 5242880),
826
+ diagnosticLogMaxFiles: envPositiveInteger('TRAE_TLS_DIAGNOSTIC_LOG_MAX_FILES', 3),
827
+ captureContent: !envDisabled('TRAE_TLS_CAPTURE_CONTENT'),
828
+ debugArtifacts: envFlag('TRAE_TLS_DEBUG_ARTIFACTS', false),
829
+ serviceName,
830
+ serviceInstanceId: `${serviceName}@${hostname()}:${process.pid}`,
831
+ ctxCli: config_firstEnv('TRAE_CONTEXTFS_CLI') || 'ctx-cli',
832
+ ctxCliTimeoutMs: envPositiveInteger('TRAE_CONTEXTFS_TIMEOUT_MS', 10000),
833
+ tlsEndpoint: config_firstEnv('TRAE_TLS_ENDPOINT', 'TRACE_TLS_ENDPOINT', 'TLS_ENDPOINT'),
834
+ otelEndpoint: config_firstEnv('TRAE_TLS_OTEL_ENDPOINT', 'OTEL_EXPORTER_OTLP_TRACES_ENDPOINT'),
835
+ region: config_firstEnv('TRAE_TLS_REGION', 'TLS_REGION'),
836
+ traceTopicId: config_firstEnv('TRAE_TLS_TRACE_TOPIC_ID', 'TRAE_TLS_TOPIC_ID', 'TLS_TRACE_TOPIC_ID'),
837
+ apiKey: config_firstEnv('TRAE_TLS_API_KEY', 'TLS_API_KEY'),
838
+ ak: config_firstEnv('TRAE_TLS_AK', 'TLS_AK'),
839
+ sk: config_firstEnv('TRAE_TLS_SK', 'TLS_SK'),
840
+ authMode: 'resource' === config_firstEnv('TRAE_TLS_AUTH_MODE', 'TLS_AUTH_MODE') ? 'resource' : 'header',
841
+ exportTimeoutMs: envPositiveInteger('TRAE_TLS_EXPORT_TIMEOUT_MS', 20000),
842
+ exportTimeoutRetries: exportRetries
843
+ };
844
+ }
845
+ function localIdentityMetadata() {
846
+ return {
847
+ 'user.name': userInfo().username
848
+ };
849
+ }
850
+ async function readStdinJson() {
851
+ const text = await new Promise((resolve, reject)=>{
852
+ let input = '';
853
+ process.stdin.setEncoding('utf8');
854
+ process.stdin.on('data', (chunk)=>{
855
+ input += chunk;
856
+ });
857
+ process.stdin.on('end', ()=>resolve(input));
858
+ process.stdin.on('error', reject);
859
+ });
860
+ if (!text.trim()) return {};
861
+ return JSON.parse(text);
862
+ }
863
+ new Set([
864
+ 'trace.id',
865
+ 'trace.state',
866
+ 'span.id',
867
+ 'parent.span.id',
868
+ 'span.kind',
869
+ 'span.name',
870
+ 'span.status.code',
871
+ 'span.status.message',
872
+ 'span.events',
873
+ 'span.links',
874
+ 'event.type',
875
+ 'event.name',
876
+ 'service.name',
877
+ 'service.instance.id',
878
+ 'host.name',
879
+ 'client.address',
880
+ 'observed_time_unix_ms',
881
+ 'telemetry.sdk.name',
882
+ 'telemetry.sdk.language',
883
+ 'telemetry.sdk.version',
884
+ 'otel.scope.name',
885
+ 'otel.scope.version',
886
+ 'start_time_unix_us',
887
+ 'end_time_unix_us',
888
+ 'span.duration_us'
889
+ ]);
890
+ new Set([
891
+ 'session.id',
892
+ 'app.thread.id',
893
+ 'gen_ai.conversation.id',
894
+ 'gen_ai.agent.id',
895
+ 'app.run.id',
896
+ 'app.turn.id',
897
+ 'user.id',
898
+ 'codex.input',
899
+ 'codex.output',
900
+ 'span_type',
901
+ 'codex_span_type',
902
+ 'codex_session_id',
903
+ 'codex_thread_id',
904
+ 'codex_conversation_id',
905
+ 'codex_parent_conversation_id',
906
+ 'codex_agent_id',
907
+ 'codex_turn_id',
908
+ 'codex_turn_index',
909
+ 'codex_originator',
910
+ 'codex_cli_version',
911
+ 'codex_cwd',
912
+ "codex_transcript_path",
913
+ 'codex_permission_mode',
914
+ 'codex_agent_scope',
915
+ 'codex_runtime_tags',
916
+ 'codex_decision_tool_name',
917
+ 'codex_decision_tool_call_id',
918
+ 'codex_tool_call_id',
919
+ 'codex_tool_status',
920
+ 'codex_tool_exit_code',
921
+ 'codex.system_tag_runtime',
922
+ 'gen_ai.usage.input',
923
+ 'gen_ai.usage.output',
924
+ 'gen_ai.usage.totalTokens',
925
+ 'gen_ai.usage.cacheRead',
926
+ 'gen_ai.usage.reasoningOutput'
927
+ ]);
928
+ function sourceAttributes(source) {
929
+ return {
930
+ 'source.tool': source.tool,
931
+ 'session.id': source.sessionId,
932
+ 'app.turn.id': source.turnId,
933
+ 'app.thread.id': source.threadId,
934
+ 'gen_ai.conversation.id': source.conversationId,
935
+ 'process.command.cwd': source.cwd,
936
+ 'gen_ai.request.model': source.model,
937
+ ...source.attributes ?? {}
938
+ };
939
+ }
940
+ function resourceAttributes(trace, options) {
941
+ const source = trace.source ?? {};
942
+ return {
943
+ 'service.name': options.serviceName ?? `${source.tool ?? 'agent'}-trace-hook`,
944
+ 'service.instance.id': source.sessionId ?? '',
945
+ 'host.name': hostname(),
946
+ 'telemetry.sdk.name': 'agent-trace-reporter',
947
+ 'telemetry.sdk.language': 'nodejs',
948
+ 'telemetry.sdk.version': options.sdkVersion ?? '0.1.0',
949
+ ...options.resourceAttributes ?? {}
950
+ };
951
+ }
952
+ function canonicalSpanToLegacy(span, traceId, parentId, commonAttributes, index) {
953
+ const spanId = span.id ?? span.span_id ?? spanIdFromSeed(traceId, span.name, index);
954
+ const startedAt = span.start_time_unix_ms ?? span.startedAt;
955
+ const endedAt = span.end_time_unix_ms ?? span.endedAt;
956
+ return {
957
+ trace_id: traceId,
958
+ span_id: spanId,
959
+ parent_span_id: span.parentId ?? span.parent_span_id ?? parentId,
960
+ name: span.name,
961
+ type: span.type ?? 'internal',
962
+ span_kind: String(span.kind ?? 'internal').toUpperCase(),
963
+ start_time_unix_ms: model_toEpochMs(startedAt),
964
+ end_time_unix_ms: model_toEpochMs(endedAt, model_toEpochMs(startedAt)),
965
+ duration_ms: Math.max(0, model_toEpochMs(endedAt, model_toEpochMs(startedAt)) - model_toEpochMs(startedAt)),
966
+ status_code: 'error' === span.status ? 'ERROR' : 'UNSET',
967
+ input: span.input,
968
+ output: span.output,
969
+ attributes: {
970
+ ...commonAttributes,
971
+ ...span.attributes ?? {}
972
+ }
973
+ };
974
+ }
975
+ function canonicalTraceToOtelTrace(trace, options = {}) {
976
+ const seed = [
977
+ trace.source?.tool,
978
+ trace.source?.sessionId,
979
+ trace.source?.turnId,
980
+ trace.root?.name,
981
+ trace.root?.startedAt
982
+ ].join(':');
983
+ const traceId = trace.traceId ?? model_traceIdFromSeed(seed);
984
+ const commonAttributes = sourceAttributes(trace.source ?? {});
985
+ const root = canonicalSpanToLegacy(trace.root ?? {
986
+ name: 'root'
987
+ }, traceId, '', commonAttributes, 'root');
988
+ root.span_id = trace.root?.id ?? trace.spanId ?? spanIdFromSeed(traceId, 'root');
989
+ root.parent_span_id = '';
990
+ return {
991
+ traceId,
992
+ resource: resourceAttributes(trace, options),
993
+ scope: {
994
+ name: options.scopeName ?? 'agent-trace-reporter',
995
+ version: options.scopeVersion ?? '0.1.0'
996
+ },
997
+ spans: [
998
+ root,
999
+ ...(trace.spans ?? []).map((span, index)=>canonicalSpanToLegacy(span, traceId, root.span_id ?? '', commonAttributes, index))
1000
+ ]
1001
+ };
1002
+ }
1003
+ function file_store_hasErrorCode(error, code) {
1004
+ return 'object' == typeof error && null !== error && 'code' in error && error.code === code;
1005
+ }
1006
+ async function ensureParent(filePath) {
1007
+ await __rspack_external_node_fs_promises_153e37e0.mkdir(__rspack_external_node_path_c5b9b54f.dirname(filePath), {
1008
+ recursive: true
1009
+ });
1010
+ }
1011
+ async function writeJsonFile(filePath, value, options = {}) {
1012
+ await ensureParent(filePath);
1013
+ const tempPath = `${filePath}.${process.pid}.tmp`;
1014
+ const writeOptions = options.mode ? {
1015
+ encoding: 'utf8',
1016
+ mode: options.mode
1017
+ } : 'utf8';
1018
+ await __rspack_external_node_fs_promises_153e37e0.writeFile(tempPath, `${JSON.stringify(value, null, 2)}\n`, writeOptions);
1019
+ await __rspack_external_node_fs_promises_153e37e0.rename(tempPath, filePath);
1020
+ if (options.mode) await __rspack_external_node_fs_promises_153e37e0.chmod(filePath, options.mode);
1021
+ }
1022
+ async function appendJsonl(filePath, value) {
1023
+ await ensureParent(filePath);
1024
+ await __rspack_external_node_fs_promises_153e37e0.appendFile(filePath, `${JSON.stringify(value)}\n`, 'utf8');
1025
+ }
1026
+ async function readJsonl(filePath) {
1027
+ let text;
1028
+ try {
1029
+ text = await __rspack_external_node_fs_promises_153e37e0.readFile(filePath, 'utf8');
1030
+ } catch (error) {
1031
+ if (file_store_hasErrorCode(error, 'ENOENT')) return [];
1032
+ throw error;
1033
+ }
1034
+ return text.split(/\r?\n/).filter(Boolean).map((line)=>JSON.parse(line));
1035
+ }
1036
+ function safeName(value) {
1037
+ return String(value || 'trace').replace(/[^a-zA-Z0-9._-]+/g, '-').replace(/^-+|-+$/g, '').slice(0, 160) || 'trace';
1038
+ }
1039
+ async function writeFailedTraceCache(paths, key, trace, body, result, config = {}) {
1040
+ if (result?.status !== 'failed') return {
1041
+ persisted: false,
1042
+ reason: 'not_failed'
1043
+ };
1044
+ if (!body) return {
1045
+ persisted: false,
1046
+ reason: 'missing_otlp_payload'
1047
+ };
1048
+ if (otlpPayloadContainsTlsCredentials(body)) return {
1049
+ persisted: false,
1050
+ reason: 'payload_contains_tls_credentials'
1051
+ };
1052
+ const now = Date.now();
1053
+ const retryIntervalMs = config.retryIntervalMs ?? 300000;
1054
+ const caseDir = __rspack_external_node_path_c5b9b54f.join(paths.failedTracesDir, `${safeName(key.slice(0, 16))}.${safeName(trace.source?.tool)}`);
1055
+ const payloadPath = __rspack_external_node_path_c5b9b54f.join(caseDir, 'payload.otlp.json');
1056
+ const manifestPath = __rspack_external_node_path_c5b9b54f.join(caseDir, 'manifest.json');
1057
+ await __rspack_external_node_fs_promises_153e37e0.mkdir(caseDir, {
1058
+ recursive: true
1059
+ });
1060
+ await __rspack_external_node_fs_promises_153e37e0.writeFile(payloadPath, body, {
1061
+ encoding: 'utf8',
1062
+ mode: 384
1063
+ });
1064
+ await __rspack_external_node_fs_promises_153e37e0.chmod(payloadPath, 384);
1065
+ const manifest = {
1066
+ version: 1,
1067
+ state: 'pending',
1068
+ created_at: new Date(now).toISOString(),
1069
+ updated_at: new Date(now).toISOString(),
1070
+ next_retry_at: new Date(now + retryIntervalMs).toISOString(),
1071
+ retry_interval_ms: retryIntervalMs,
1072
+ retry_count: 0,
1073
+ max_retry_count: config.maxRetryCount ?? 1,
1074
+ dedup_key: key,
1075
+ payload_path: payloadPath,
1076
+ source_tool: trace.source?.tool,
1077
+ session_id: trace.source?.sessionId,
1078
+ turn_id: trace.source?.turnId,
1079
+ span_count: (trace.spans?.length ?? 0) + 1,
1080
+ payload_bytes: Buffer.byteLength(body, 'utf8'),
1081
+ trace_export: result
1082
+ };
1083
+ await writeJsonFile(manifestPath, manifest, {
1084
+ mode: 384
1085
+ });
1086
+ return {
1087
+ persisted: true,
1088
+ manifest_path: manifestPath,
1089
+ next_retry_at: manifest.next_retry_at
1090
+ };
1091
+ }
1092
+ function dedup_hasErrorCode(error, code) {
1093
+ return 'object' == typeof error && null !== error && 'code' in error && error.code === code;
1094
+ }
1095
+ function dedupKeyForTrace(trace) {
1096
+ const seed = [
1097
+ trace.source?.tool,
1098
+ trace.source?.sessionId,
1099
+ trace.source?.turnId,
1100
+ trace.source?.threadId,
1101
+ trace.root?.name,
1102
+ trace.root?.startedAt
1103
+ ].join(':');
1104
+ return external_node_crypto_createHash('sha256').update(seed).digest('hex');
1105
+ }
1106
+ async function hasUploaded(uploadedFile, key) {
1107
+ return (await readJsonl(uploadedFile)).some((entry)=>entry.key === key);
1108
+ }
1109
+ async function markUploaded(uploadedFile, key, trace, result) {
1110
+ await appendJsonl(uploadedFile, {
1111
+ key,
1112
+ source_tool: trace.source?.tool,
1113
+ session_id: trace.source?.sessionId,
1114
+ turn_id: trace.source?.turnId,
1115
+ uploaded_at: new Date().toISOString(),
1116
+ span_count: (trace.spans?.length ?? 0) + 1,
1117
+ trace_export: {
1118
+ status: result.status,
1119
+ reason: result.reason
1120
+ }
1121
+ });
1122
+ }
1123
+ async function acquireLock(locksDir, key, timeoutMs = 120000) {
1124
+ await __rspack_external_node_fs_promises_153e37e0.mkdir(locksDir, {
1125
+ recursive: true
1126
+ });
1127
+ const lockPath = __rspack_external_node_path_c5b9b54f.join(locksDir, `${key}.lock`);
1128
+ try {
1129
+ const handle = await __rspack_external_node_fs_promises_153e37e0.open(lockPath, 'wx');
1130
+ await handle.writeFile(JSON.stringify({
1131
+ pid: process.pid,
1132
+ created_at: new Date().toISOString()
1133
+ }));
1134
+ await handle.close();
1135
+ return {
1136
+ acquired: true,
1137
+ lockPath
1138
+ };
1139
+ } catch (error) {
1140
+ if (!dedup_hasErrorCode(error, 'EEXIST')) throw error;
1141
+ }
1142
+ const stat = await __rspack_external_node_fs_promises_153e37e0.stat(lockPath).catch(()=>null);
1143
+ if (stat && Date.now() - stat.mtimeMs > timeoutMs) {
1144
+ await __rspack_external_node_fs_promises_153e37e0.rm(lockPath, {
1145
+ force: true
1146
+ });
1147
+ return acquireLock(locksDir, key, timeoutMs);
1148
+ }
1149
+ return {
1150
+ acquired: false,
1151
+ lockPath,
1152
+ reason: 'dedup_lock_timeout'
1153
+ };
1154
+ }
1155
+ async function releaseLock(lock) {
1156
+ if (lock?.acquired && lock.lockPath) await __rspack_external_node_fs_promises_153e37e0.rm(lock.lockPath, {
1157
+ force: true
1158
+ });
1159
+ }
1160
+ function paths_defaultDataRoot(source = 'agent') {
1161
+ return __rspack_external_node_path_c5b9b54f.join(homedir(), '.codex', 'plugins', 'data', `storage-tls-${source}`);
1162
+ }
1163
+ function runtimePaths(config = {}) {
1164
+ const source = config.sourceName ?? config.source?.tool ?? 'agent';
1165
+ const dataRoot = __rspack_external_node_path_c5b9b54f.resolve(config.dataRoot ?? process.env.TRACE_TLS_DATA_ROOT ?? process.env.CODEX_TLS_DATA_ROOT ?? paths_defaultDataRoot(source));
1166
+ return {
1167
+ dataRoot,
1168
+ sendLogPath: __rspack_external_node_path_c5b9b54f.join(dataRoot, 'logs', 'send', 'latest.send-log.json'),
1169
+ diagnosticsDir: __rspack_external_node_path_c5b9b54f.join(dataRoot, 'logs', 'diagnostics'),
1170
+ uploadedFile: __rspack_external_node_path_c5b9b54f.join(dataRoot, 'state', 'dedup', 'uploaded.jsonl'),
1171
+ locksDir: __rspack_external_node_path_c5b9b54f.join(dataRoot, 'state', 'dedup', 'locks'),
1172
+ failedTracesDir: __rspack_external_node_path_c5b9b54f.join(dataRoot, 'state', 'failed-traces')
1173
+ };
1174
+ }
1175
+ function nowIso() {
1176
+ return new Date().toISOString();
1177
+ }
1178
+ function metadataForTrace(trace, otelTrace) {
1179
+ return {
1180
+ span_count: otelTrace.spans.length,
1181
+ trace_id: otelTrace.traceId,
1182
+ source_tool: trace.source?.tool,
1183
+ session_id: trace.source?.sessionId,
1184
+ turn_id: trace.source?.turnId
1185
+ };
1186
+ }
1187
+ async function writeSendLog(paths, event) {
1188
+ await writeJsonFile(paths.sendLogPath, {
1189
+ generated_at: nowIso(),
1190
+ ...event
1191
+ });
1192
+ }
1193
+ async function runTracePipeline(input, adapter, config = {}) {
1194
+ if (!adapter || 'function' != typeof adapter.load || 'function' != typeof adapter.toCanonical) throw new Error('A SourceAdapter with load() and toCanonical() is required');
1195
+ if ('function' == typeof adapter.detect && !adapter.detect(input)) return {
1196
+ status: 'skipped',
1197
+ reason: 'adapter_not_detected',
1198
+ adapter: adapter.name
1199
+ };
1200
+ const context = {
1201
+ env: config.env ?? process.env,
1202
+ dataRoot: config.dataRoot,
1203
+ now: config.now ?? Date.now,
1204
+ source: config.source ?? {}
1205
+ };
1206
+ const session = await adapter.load(input, context);
1207
+ const trace = assertCanonicalTrace(await adapter.toCanonical(session, context));
1208
+ const paths = runtimePaths({
1209
+ ...config,
1210
+ source: trace.source,
1211
+ sourceName: trace.source?.tool
1212
+ });
1213
+ const key = config.dedupKey ?? dedupKeyForTrace(trace);
1214
+ if (false !== config.dedup && await hasUploaded(paths.uploadedFile, key)) {
1215
+ await writeSendLog(paths, {
1216
+ status: 'deduped',
1217
+ dedup_key: key,
1218
+ source: trace.source
1219
+ });
1220
+ return {
1221
+ status: 'deduped',
1222
+ dedup_key: key
1223
+ };
1224
+ }
1225
+ const lock = false === config.dedup ? {
1226
+ acquired: true
1227
+ } : await acquireLock(paths.locksDir, key, config.dedupLockTimeoutMs);
1228
+ if (!lock.acquired) {
1229
+ await writeSendLog(paths, {
1230
+ status: 'skipped',
1231
+ reason: lock.reason,
1232
+ dedup_key: key,
1233
+ source: trace.source
1234
+ });
1235
+ return {
1236
+ status: 'skipped',
1237
+ reason: lock.reason,
1238
+ dedup_key: key
1239
+ };
1240
+ }
1241
+ try {
1242
+ const otelTrace = canonicalTraceToOtelTrace(trace, config.otel);
1243
+ let capturedRequestBody = '';
1244
+ const metadata = metadataForTrace(trace, otelTrace);
1245
+ const traceExport = await exportTraceToTls(otelTrace, context.env, {
1246
+ ...config.tlsOptions ?? {},
1247
+ onRequestBody: (request)=>{
1248
+ if ('string' == typeof request.body) capturedRequestBody = request.body;
1249
+ config.tlsOptions?.onRequestBody?.(request);
1250
+ }
1251
+ });
1252
+ let failedTraceCache = {
1253
+ persisted: false,
1254
+ reason: 'not_failed'
1255
+ };
1256
+ if ('sent' === traceExport.status && false !== config.dedup) await markUploaded(paths.uploadedFile, key, trace, traceExport);
1257
+ else if ('failed' === traceExport.status) failedTraceCache = await writeFailedTraceCache(paths, key, trace, capturedRequestBody, traceExport, config.failedTraceCache);
1258
+ await writeSendLog(paths, {
1259
+ status: traceExport.status,
1260
+ dedup_key: key,
1261
+ source: trace.source,
1262
+ trace_export: traceExport,
1263
+ failed_trace_cache: failedTraceCache,
1264
+ otlp: metadata
1265
+ });
1266
+ return {
1267
+ status: traceExport.status,
1268
+ dedup_key: key,
1269
+ trace_export: traceExport,
1270
+ failed_trace_cache: failedTraceCache
1271
+ };
1272
+ } finally{
1273
+ await releaseLock(lock);
1274
+ }
1275
+ }
1276
+ new Map();
1277
+ const TLS_APP_TYPE = 'trae';
1278
+ const ROOT_SPAN_NAME = 'agent.turn';
1279
+ const LLM_SPAN_NAME = 'llm.request';
1280
+ const TOOL_SPAN_NAME = 'tool.call';
1281
+ function trae_isRecord(value) {
1282
+ return null != value && 'object' == typeof value && !Array.isArray(value);
1283
+ }
1284
+ function detectTraeInput(input) {
1285
+ return input?.tool === 'trae' || input?.source_tool === 'trae' || Array.isArray(input?.messages) || Array.isArray(input?.lines) || Array.isArray(input?.jsonl);
1286
+ }
1287
+ async function loadTraeSession(input) {
1288
+ if (!detectTraeInput(input)) throw new Error('Trae adapter requires tool=trae and messages/lines/jsonl');
1289
+ const messages = normalizeMessages(input.messages ?? input.lines ?? input.jsonl ?? []);
1290
+ return {
1291
+ ...input,
1292
+ messages
1293
+ };
1294
+ }
1295
+ function parseJsonLine(line) {
1296
+ if (trae_isRecord(line)) return line;
1297
+ if ('string' != typeof line || !line.trim()) return;
1298
+ try {
1299
+ const parsed = JSON.parse(line);
1300
+ return trae_isRecord(parsed) ? parsed : void 0;
1301
+ } catch {
1302
+ return;
1303
+ }
1304
+ }
1305
+ function normalizeMessages(raw) {
1306
+ return raw.map(parseJsonLine).filter((line)=>Boolean(line));
1307
+ }
1308
+ function compactText(value) {
1309
+ if ('string' == typeof value) return value;
1310
+ if (Array.isArray(value)) return value.map((item)=>{
1311
+ if ('string' == typeof item) return item;
1312
+ if (trae_isRecord(item)) return item.text ?? item.content ?? item.value ?? '';
1313
+ return '';
1314
+ }).filter(Boolean).join('\n');
1315
+ if (trae_isRecord(value)) return value.text ?? value.content ?? JSON.stringify(value);
1316
+ return null == value ? '' : String(value);
1317
+ }
1318
+ function extractUserInput(text) {
1319
+ const matches = [
1320
+ ...text.matchAll(/<user_input>\s*([\s\S]*?)\s*<\/user_input>/g)
1321
+ ];
1322
+ const last = matches.at(-1)?.[1]?.trim();
1323
+ if (last) return last;
1324
+ return text.trim();
1325
+ }
1326
+ function messageContent(message) {
1327
+ const text = compactText(message.content);
1328
+ if ('user' === message.role) return extractUserInput(text);
1329
+ return text;
1330
+ }
1331
+ function eventTime(message, fallbackMs) {
1332
+ const raw = message.timestamp ?? message.created_at ?? message.createdAt ?? message.time;
1333
+ if ('number' == typeof raw && Number.isFinite(raw)) return new Date(raw).toISOString();
1334
+ if ('string' == typeof raw && raw.trim()) {
1335
+ const parsed = Date.parse(raw);
1336
+ if (Number.isFinite(parsed)) return new Date(parsed).toISOString();
1337
+ }
1338
+ return new Date(fallbackMs).toISOString();
1339
+ }
1340
+ function parseArguments(value) {
1341
+ if ('string' != typeof value) return value;
1342
+ try {
1343
+ return JSON.parse(value);
1344
+ } catch {
1345
+ return value;
1346
+ }
1347
+ }
1348
+ function toolCallFromToolMessage(message, index) {
1349
+ const output = compactText(message.content ?? message.output ?? message.result ?? message.response ?? '');
1350
+ return {
1351
+ id: message.tool_call_id ?? `tool-${index + 1}`,
1352
+ name: message.llm_tool_name ?? message.tool_name ?? message.name ?? 'tool',
1353
+ input: parseArguments(message.tool_input ?? message.input),
1354
+ output,
1355
+ timestamp: message.timestamp,
1356
+ start_time_ms: message.start_time_ms ?? message.startTimeMs,
1357
+ end_time_ms: message.end_time_ms ?? message.endTimeMs,
1358
+ is_error: Boolean(message.is_error || message.error),
1359
+ error: message.error,
1360
+ raw: message
1361
+ };
1362
+ }
1363
+ function usageFromMessage(message) {
1364
+ const usage = trae_isRecord(message.usage) ? message.usage : {};
1365
+ const result = {
1366
+ input_tokens: usage.input_tokens ?? usage.inputTokens ?? usage.prompt_tokens ?? usage.promptTokens,
1367
+ output_tokens: usage.output_tokens ?? usage.outputTokens ?? usage.completion_tokens ?? usage.completionTokens,
1368
+ cache_read_input_tokens: usage.cache_read_input_tokens ?? usage.cacheReadInputTokens ?? usage.cached_input_tokens ?? usage.cachedInputTokens,
1369
+ reasoning_output_tokens: usage.reasoning_output_tokens ?? usage.reasoningOutputTokens ?? usage.reasoning_tokens ?? usage.reasoningTokens,
1370
+ total_tokens: usage.total_tokens ?? usage.totalTokens,
1371
+ estimated: usage.estimated,
1372
+ token_estimate_method: usage.token_estimate_method ?? usage.tokenEstimateMethod
1373
+ };
1374
+ if ('number' != typeof result.total_tokens) {
1375
+ const input = result.input_tokens;
1376
+ const output = result.output_tokens;
1377
+ if ('number' == typeof input || 'number' == typeof output) result.total_tokens = (input ?? 0) + (output ?? 0);
1378
+ }
1379
+ return result;
1380
+ }
1381
+ function sumUsage(messages, sessionUsage = {}) {
1382
+ const hasSessionUsage = 'number' == typeof (sessionUsage.input_tokens ?? sessionUsage.inputTokens) || 'number' == typeof (sessionUsage.output_tokens ?? sessionUsage.outputTokens) || 'number' == typeof (sessionUsage.total_tokens ?? sessionUsage.totalTokens);
1383
+ const total = {
1384
+ input_tokens: sessionUsage.input_tokens ?? sessionUsage.inputTokens,
1385
+ output_tokens: sessionUsage.output_tokens ?? sessionUsage.outputTokens,
1386
+ cache_read_input_tokens: sessionUsage.cache_read_input_tokens ?? sessionUsage.cacheReadInputTokens,
1387
+ reasoning_output_tokens: sessionUsage.reasoning_output_tokens ?? sessionUsage.reasoningOutputTokens,
1388
+ total_tokens: sessionUsage.total_tokens ?? sessionUsage.totalTokens,
1389
+ estimated: sessionUsage.estimated,
1390
+ token_estimate_method: sessionUsage.token_estimate_method ?? sessionUsage.tokenEstimateMethod
1391
+ };
1392
+ if (hasSessionUsage) return total;
1393
+ for (const message of messages){
1394
+ const usage = usageFromMessage(message);
1395
+ for (const key of [
1396
+ 'input_tokens',
1397
+ 'output_tokens',
1398
+ 'cache_read_input_tokens',
1399
+ 'reasoning_output_tokens',
1400
+ 'total_tokens'
1401
+ ])if ('number' == typeof usage[key]) total[key] = (total[key] ?? 0) + usage[key];
1402
+ }
1403
+ if ('number' != typeof total.total_tokens) {
1404
+ const input = total.input_tokens;
1405
+ const output = total.output_tokens;
1406
+ if ('number' == typeof input || 'number' == typeof output) total.total_tokens = (input ?? 0) + (output ?? 0);
1407
+ }
1408
+ return total;
1409
+ }
1410
+ function hasUsageTokens(usage) {
1411
+ return 'number' == typeof usage.input_tokens || 'number' == typeof usage.output_tokens || 'number' == typeof usage.total_tokens;
1412
+ }
1413
+ function sourceSpecificAttributes(session, common) {
1414
+ return Object.fromEntries(Object.entries({
1415
+ 'trae.hook_event_name': session.hook_event_name ?? session.hookEventName,
1416
+ 'trae.operation_id': session.operation_id ?? session.operationId,
1417
+ 'trae.trace_id': session.trace_id ?? session.traceId,
1418
+ 'gen_ai.agent.name': common['agent.name'],
1419
+ ...session.attributes ?? {}
1420
+ }).filter(([key, value])=>{
1421
+ if (key.startsWith('gen_ai.usage.') || key.startsWith('trae.usage.')) return false;
1422
+ return null != value && '' !== value;
1423
+ }));
1424
+ }
1425
+ function shouldCaptureContent(session) {
1426
+ return false !== session.capture_content && false !== session.captureContent;
1427
+ }
1428
+ function maybeCaptured(session, value) {
1429
+ return shouldCaptureContent(session) ? value : '[content capture disabled]';
1430
+ }
1431
+ function otelMessage(session, message) {
1432
+ return {
1433
+ role: message.role,
1434
+ parts: [
1435
+ {
1436
+ type: 'text',
1437
+ content: maybeCaptured(session, messageContent(message))
1438
+ }
1439
+ ]
1440
+ };
1441
+ }
1442
+ function messagesPayload(messages) {
1443
+ return messages && messages.length > 0 ? {
1444
+ messages
1445
+ } : void 0;
1446
+ }
1447
+ function providerName(session, message) {
1448
+ return firstString(message?.provider, message?.provider_name, message?.providerName, session.provider, session.provider_name, session.providerName, session.model_provider, session.modelProvider) ?? 'trae';
1449
+ }
1450
+ function firstString(...values) {
1451
+ for (const value of values)if ('string' == typeof value && value.trim()) return value.trim();
1452
+ }
1453
+ function baseAttributes(session) {
1454
+ return {
1455
+ 'session.id': session.session_id ?? session.sessionId,
1456
+ 'tls.app.type': TLS_APP_TYPE,
1457
+ 'tls.plugin.version': session.plugin_version ?? session.pluginVersion,
1458
+ 'agent.name': TLS_APP_TYPE,
1459
+ 'agent.version': session.agent_version,
1460
+ 'user.name': session.user_name ?? session.userName ?? session.email ?? session.attributes?.['user.name']
1461
+ };
1462
+ }
1463
+ function previousUserMessage(session, assistantIndex) {
1464
+ for(let index = assistantIndex - 1; index >= 0; index -= 1)if (session.messages[index]?.role === 'user') return [
1465
+ otelMessage(session, session.messages[index])
1466
+ ];
1467
+ }
1468
+ function firstUserMessages(session) {
1469
+ const user = session.messages.find((message)=>'user' === message.role);
1470
+ return user ? [
1471
+ otelMessage(session, user)
1472
+ ] : void 0;
1473
+ }
1474
+ function rootOutputMessages(session) {
1475
+ const messages = session.messages.filter((message)=>'assistant' === message.role && true !== message.synthetic_tool_request).map((message)=>otelMessage(session, message));
1476
+ return messages.length > 0 ? messages : void 0;
1477
+ }
1478
+ function assistantSpans(session, rootId, startedAt, endedAt) {
1479
+ const common = baseAttributes(session);
1480
+ sumUsage(session.messages, session.usage);
1481
+ const assistantMessages = session.messages.filter((message)=>'assistant' === message.role && true !== message.synthetic_tool_request);
1482
+ return session.messages.map((message, index)=>({
1483
+ message,
1484
+ index
1485
+ })).filter(({ message })=>'assistant' === message.role && true !== message.synthetic_tool_request).map(({ message, index })=>{
1486
+ const id = spanIdFromSeed(session.session_id, session.turn_id, index, 'llm');
1487
+ const messageStartedAt = eventTime(message, Date.parse(startedAt) + index);
1488
+ const inputMessages = previousUserMessage(session, index);
1489
+ const outputMessages = [
1490
+ otelMessage(session, message)
1491
+ ];
1492
+ const messageUsage = usageFromMessage(message);
1493
+ hasUsageTokens(messageUsage) || assistantMessages.length;
1494
+ return {
1495
+ id,
1496
+ parentId: rootId,
1497
+ name: LLM_SPAN_NAME,
1498
+ kind: 'client',
1499
+ type: 'model',
1500
+ startedAt: message.started_at ?? messageStartedAt,
1501
+ endedAt: message.ended_at ?? messageStartedAt,
1502
+ status: message.error ? 'error' : 'unset',
1503
+ input: messagesPayload(inputMessages),
1504
+ output: messagesPayload(outputMessages),
1505
+ attributes: {
1506
+ ...common,
1507
+ 'source.tool': 'trae',
1508
+ 'gen_ai.system': 'trae',
1509
+ 'gen_ai.request.model': message.model ?? session.model,
1510
+ 'gen_ai.provider.name': providerName(session, message),
1511
+ 'gen_ai.operation.name': 'chat',
1512
+ 'gen_ai.input.messages': inputMessages,
1513
+ 'gen_ai.output.messages': outputMessages,
1514
+ 'trae.message.index': index,
1515
+ 'trae.message.role': message.role,
1516
+ 'trae.message.id': message.id ?? message.message_id ?? message.messageId
1517
+ }
1518
+ };
1519
+ });
1520
+ }
1521
+ function toolSpans(session, rootId, startedAt) {
1522
+ const spans = [];
1523
+ const common = baseAttributes(session);
1524
+ for (const [index, message] of session.messages.entries()){
1525
+ if ('tool' !== message.role) continue;
1526
+ const call = toolCallFromToolMessage(message, index);
1527
+ const resultOutput = call.output;
1528
+ const toolStartedAt = 'number' == typeof call.start_time_ms && Number.isFinite(call.start_time_ms) ? new Date(call.start_time_ms).toISOString() : call.timestamp ? eventTime(call, Date.parse(startedAt) + index) : eventTime(message, Date.parse(startedAt) + index);
1529
+ const toolEndedAt = 'number' == typeof call.end_time_ms && Number.isFinite(call.end_time_ms) ? new Date(call.end_time_ms).toISOString() : eventTime(message, Date.parse(toolStartedAt) + 1);
1530
+ spans.push({
1531
+ id: spanIdFromSeed(session.session_id, session.turn_id, call.id, index, 'tool'),
1532
+ parentId: rootId,
1533
+ name: TOOL_SPAN_NAME,
1534
+ kind: 'client',
1535
+ type: 'tool',
1536
+ startedAt: toolStartedAt,
1537
+ endedAt: toolEndedAt,
1538
+ status: call.is_error || call.error ? 'error' : 'unset',
1539
+ input: maybeCaptured(session, call.input),
1540
+ output: maybeCaptured(session, resultOutput),
1541
+ attributes: {
1542
+ ...common,
1543
+ 'tool.error': Boolean(call.is_error || call.error),
1544
+ 'gen_ai.tool.name': call.name,
1545
+ 'gen_ai.tool.call.id': call.id,
1546
+ 'gen_ai.tool.call.arguments': maybeCaptured(session, call.input),
1547
+ 'gen_ai.tool.call.result': maybeCaptured(session, resultOutput),
1548
+ 'trae.message.index': index
1549
+ }
1550
+ });
1551
+ }
1552
+ return spans;
1553
+ }
1554
+ function canonicalMessages(session) {
1555
+ return session.messages.filter((message)=>[
1556
+ 'user',
1557
+ 'assistant',
1558
+ 'tool'
1559
+ ].includes(String(message.role))).map((message, index)=>({
1560
+ role: message.role,
1561
+ content: maybeCaptured(session, messageContent(message)),
1562
+ timestamp: eventTime(message, (session.started_at_ms ?? Date.now()) + index),
1563
+ attributes: {
1564
+ 'trae.message.index': index,
1565
+ 'trae.message.id': message.id ?? message.message_id ?? message.messageId,
1566
+ 'trae.tool_call_id': message.tool_call_id
1567
+ }
1568
+ }));
1569
+ }
1570
+ function rootTimes(session) {
1571
+ const now = Date.now();
1572
+ const first = session.messages[0] ?? {};
1573
+ const last = session.messages.at(-1) ?? first;
1574
+ return {
1575
+ startedAt: eventTime(first, session.started_at_ms ?? now),
1576
+ endedAt: eventTime(last, session.ended_at_ms ?? session.started_at_ms ?? now)
1577
+ };
1578
+ }
1579
+ async function toCanonicalTraeTrace(session) {
1580
+ const { startedAt, endedAt } = rootTimes(session);
1581
+ const sessionId = session.session_id ?? session.sessionId ?? 'trae-session';
1582
+ const turnId = session.turn_id ?? session.turnId ?? session.message_id ?? session.messageId;
1583
+ const rootId = session.root_span_id ?? spanIdFromSeed(sessionId, turnId ?? 'turn', 'root');
1584
+ const usage = sumUsage(session.messages, session.usage);
1585
+ const toolSpanList = toolSpans({
1586
+ ...session,
1587
+ session_id: sessionId,
1588
+ turn_id: turnId
1589
+ }, rootId, startedAt);
1590
+ const normalizedSession = {
1591
+ ...session,
1592
+ session_id: sessionId,
1593
+ turn_id: turnId
1594
+ };
1595
+ const rootInputMessages = firstUserMessages(normalizedSession);
1596
+ const rootOutputMessageList = rootOutputMessages(normalizedSession);
1597
+ const common = baseAttributes(normalizedSession);
1598
+ const warnings = [];
1599
+ if (!session.messages.some((message)=>'assistant' === message.role)) warnings.push('Trae hook turn does not contain an assistant message yet');
1600
+ if ('number' != typeof usage.input_tokens && 'number' != typeof usage.output_tokens) warnings.push('Trae token usage was not found in hook payload or messages');
1601
+ return {
1602
+ source: {
1603
+ tool: 'trae',
1604
+ sessionId,
1605
+ turnId,
1606
+ threadId: session.thread_id ?? session.threadId,
1607
+ conversationId: session.conversation_id ?? session.conversationId,
1608
+ cwd: session.cwd,
1609
+ model: session.model,
1610
+ attributes: sourceSpecificAttributes(session, common)
1611
+ },
1612
+ root: {
1613
+ id: rootId,
1614
+ name: ROOT_SPAN_NAME,
1615
+ kind: 'server',
1616
+ type: 'agent',
1617
+ startedAt,
1618
+ endedAt,
1619
+ status: session.error ? 'error' : 'unset',
1620
+ input: messagesPayload(rootInputMessages),
1621
+ output: messagesPayload(rootOutputMessageList),
1622
+ attributes: {
1623
+ ...common,
1624
+ 'gen_ai.request.model': session.model,
1625
+ 'gen_ai.provider.name': providerName(session),
1626
+ 'gen_ai.operation.name': 'agent',
1627
+ 'gen_ai.input.messages': rootInputMessages,
1628
+ 'gen_ai.output.messages': rootOutputMessageList
1629
+ }
1630
+ },
1631
+ spans: [
1632
+ ...toolSpanList,
1633
+ ...assistantSpans(normalizedSession, rootId, startedAt, endedAt)
1634
+ ],
1635
+ messages: canonicalMessages(session),
1636
+ metrics: {
1637
+ inputTokens: usage.input_tokens,
1638
+ outputTokens: usage.output_tokens,
1639
+ totalTokens: usage.total_tokens,
1640
+ toolCallCount: toolSpanList.length
1641
+ },
1642
+ diagnostics: {
1643
+ warnings,
1644
+ incomplete: !session.messages.some((message)=>'assistant' === message.role),
1645
+ parser: {
1646
+ contextfs_files: session.contextfs_files ?? session.contextfsFiles
1647
+ }
1648
+ }
1649
+ };
1650
+ }
1651
+ const traeAdapter = {
1652
+ name: 'trae',
1653
+ detect: detectTraeInput,
1654
+ load: loadTraeSession,
1655
+ toCanonical: toCanonicalTraeTrace
1656
+ };
1657
+ async function writeTextFileAtomic(filePath, text, options = {}) {
1658
+ await __rspack_external_node_fs_promises_153e37e0.mkdir(__rspack_external_node_path_c5b9b54f.dirname(filePath), {
1659
+ recursive: true
1660
+ });
1661
+ 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`);
1662
+ let renamed = false;
1663
+ try {
1664
+ await __rspack_external_node_fs_promises_153e37e0.writeFile(tempPath, text, {
1665
+ encoding: 'utf8',
1666
+ ...void 0 === options.mode ? {} : {
1667
+ mode: options.mode
1668
+ }
1669
+ });
1670
+ await __rspack_external_node_fs_promises_153e37e0.rename(tempPath, filePath);
1671
+ renamed = true;
1672
+ if (void 0 !== options.mode) await __rspack_external_node_fs_promises_153e37e0.chmod(filePath, options.mode);
1673
+ } finally{
1674
+ if (!renamed) await __rspack_external_node_fs_promises_153e37e0.rm(tempPath, {
1675
+ force: true
1676
+ }).catch(()=>void 0);
1677
+ }
1678
+ }
1679
+ const DEFAULT_MAX_FILES = 3;
1680
+ const LOCK_STALE_MS = 30000;
1681
+ const LOCK_TIMEOUT_MS = 5000;
1682
+ const LOCK_RETRY_MS = 25;
1683
+ async function appendJsonlFile(filePath, values) {
1684
+ const entries = Array.isArray(values) ? values : [
1685
+ values
1686
+ ];
1687
+ if (0 === entries.length) return;
1688
+ await __rspack_external_node_fs_promises_153e37e0.mkdir(__rspack_external_node_path_c5b9b54f.dirname(filePath), {
1689
+ recursive: true
1690
+ });
1691
+ await __rspack_external_node_fs_promises_153e37e0.appendFile(filePath, `${entries.map((entry)=>JSON.stringify(entry)).join('\n')}\n`, 'utf8');
1692
+ }
1693
+ async function delay(ms) {
1694
+ await new Promise((resolve)=>setTimeout(resolve, ms));
1695
+ }
1696
+ async function withJsonlFileLock(filePath, action) {
1697
+ await __rspack_external_node_fs_promises_153e37e0.mkdir(__rspack_external_node_path_c5b9b54f.dirname(filePath), {
1698
+ recursive: true
1699
+ });
1700
+ const lockPath = `${filePath}.lock`;
1701
+ const startedAt = Date.now();
1702
+ while(true)try {
1703
+ const handle = await __rspack_external_node_fs_promises_153e37e0.open(lockPath, 'wx');
1704
+ try {
1705
+ await handle.writeFile(`${process.pid}:${new Date().toISOString()}\n`, 'utf8');
1706
+ return await action();
1707
+ } finally{
1708
+ await handle.close().catch(()=>void 0);
1709
+ await __rspack_external_node_fs_promises_153e37e0.rm(lockPath, {
1710
+ force: true
1711
+ }).catch(()=>void 0);
1712
+ }
1713
+ } catch (error) {
1714
+ if ('EEXIST' !== error.code) throw error;
1715
+ try {
1716
+ const stat = await __rspack_external_node_fs_promises_153e37e0.stat(lockPath);
1717
+ if (Date.now() - stat.mtimeMs > LOCK_STALE_MS) {
1718
+ await __rspack_external_node_fs_promises_153e37e0.rm(lockPath, {
1719
+ force: true
1720
+ }).catch(()=>void 0);
1721
+ continue;
1722
+ }
1723
+ } catch (statError) {
1724
+ if ('ENOENT' === statError.code) continue;
1725
+ throw statError;
1726
+ }
1727
+ if (Date.now() - startedAt > LOCK_TIMEOUT_MS) throw new Error(`Timed out waiting for JSONL file lock: ${lockPath}`);
1728
+ await delay(LOCK_RETRY_MS);
1729
+ }
1730
+ }
1731
+ function rotatedFileName(filePath, now = new Date()) {
1732
+ const dir = __rspack_external_node_path_c5b9b54f.dirname(filePath);
1733
+ const ext = __rspack_external_node_path_c5b9b54f.extname(filePath);
1734
+ const base = __rspack_external_node_path_c5b9b54f.basename(filePath, ext);
1735
+ const stamp = now.toISOString().replace(/[-:.TZ]/g, '').slice(0, 17);
1736
+ return __rspack_external_node_path_c5b9b54f.join(dir, `${base}.${stamp}.${process.pid}.${randomUUID()}${ext || '.jsonl'}`);
1737
+ }
1738
+ function rotatedFilePattern(filePath) {
1739
+ const ext = __rspack_external_node_path_c5b9b54f.extname(filePath);
1740
+ const base = __rspack_external_node_path_c5b9b54f.basename(filePath, ext).replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
1741
+ const escapedExt = (ext || '.jsonl').replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
1742
+ return new RegExp(`^${base}\\.\\d{17}\\.\\d+\\.[0-9a-f-]+${escapedExt}$`);
1743
+ }
1744
+ async function pruneRotatedJsonlFiles(filePath, maxFiles) {
1745
+ const maxRotatedFiles = Math.max(0, maxFiles - 1);
1746
+ const dir = __rspack_external_node_path_c5b9b54f.dirname(filePath);
1747
+ const pattern = rotatedFilePattern(filePath);
1748
+ let entries;
1749
+ try {
1750
+ entries = await __rspack_external_node_fs_promises_153e37e0.readdir(dir);
1751
+ } catch (error) {
1752
+ if ('ENOENT' === error.code) return;
1753
+ throw error;
1754
+ }
1755
+ const rotated = entries.filter((entry)=>pattern.test(entry)).sort((left, right)=>right.localeCompare(left)).map((entry)=>__rspack_external_node_path_c5b9b54f.join(dir, entry));
1756
+ await Promise.all(rotated.slice(maxRotatedFiles).map((target)=>__rspack_external_node_fs_promises_153e37e0.rm(target, {
1757
+ force: true
1758
+ })));
1759
+ }
1760
+ async function rotateActiveJsonlFile(filePath, maxFiles) {
1761
+ await __rspack_external_node_fs_promises_153e37e0.rename(filePath, rotatedFileName(filePath)).catch(async (error)=>{
1762
+ if ('ENOENT' !== error.code) throw error;
1763
+ });
1764
+ await pruneRotatedJsonlFiles(filePath, maxFiles);
1765
+ }
1766
+ async function trimJsonlFileByBytes(filePath, maxBytes) {
1767
+ if (maxBytes <= 0) return void await __rspack_external_node_fs_promises_153e37e0.rm(filePath, {
1768
+ force: true
1769
+ }).catch(()=>void 0);
1770
+ let stat;
1771
+ try {
1772
+ stat = await __rspack_external_node_fs_promises_153e37e0.stat(filePath);
1773
+ } catch (error) {
1774
+ if ('ENOENT' === error.code) return;
1775
+ throw error;
1776
+ }
1777
+ if (stat.size <= maxBytes) return;
1778
+ const lines = (await __rspack_external_node_fs_promises_153e37e0.readFile(filePath, 'utf8')).split(/\r?\n/).filter(Boolean);
1779
+ const kept = [];
1780
+ let keptBytes = 0;
1781
+ for(let index = lines.length - 1; index >= 0; index -= 1){
1782
+ const line = lines[index];
1783
+ const lineBytes = Buffer.byteLength(`${line}\n`, 'utf8');
1784
+ if (kept.length > 0 && keptBytes + lineBytes > maxBytes) break;
1785
+ kept.push(line);
1786
+ keptBytes += lineBytes;
1787
+ if (keptBytes >= maxBytes) break;
1788
+ }
1789
+ await writeTextFileAtomic(filePath, kept.length ? `${kept.reverse().join('\n')}\n` : '');
1790
+ }
1791
+ async function appendBoundedJsonlFile(filePath, values, maxBytes, options = {}) {
1792
+ const entries = Array.isArray(values) ? values : [
1793
+ values
1794
+ ];
1795
+ if (0 === entries.length) return;
1796
+ if ('rotate' !== options.strategy) return void await withJsonlFileLock(filePath, async ()=>{
1797
+ await appendJsonlFile(filePath, entries);
1798
+ await trimJsonlFileByBytes(filePath, maxBytes);
1799
+ });
1800
+ await withJsonlFileLock(filePath, async ()=>{
1801
+ const maxFiles = options.maxFiles ?? DEFAULT_MAX_FILES;
1802
+ if (maxBytes <= 0 || maxFiles <= 0) {
1803
+ await __rspack_external_node_fs_promises_153e37e0.rm(filePath, {
1804
+ force: true
1805
+ }).catch(()=>void 0);
1806
+ await pruneRotatedJsonlFiles(filePath, 0);
1807
+ return;
1808
+ }
1809
+ let currentBytes = 0;
1810
+ try {
1811
+ currentBytes = (await __rspack_external_node_fs_promises_153e37e0.stat(filePath)).size;
1812
+ } catch (error) {
1813
+ if ('ENOENT' !== error.code) throw error;
1814
+ }
1815
+ for (const entry of entries){
1816
+ const line = `${JSON.stringify(entry)}\n`;
1817
+ const lineBytes = Buffer.byteLength(line, 'utf8');
1818
+ if (currentBytes > 0 && currentBytes + lineBytes > maxBytes) {
1819
+ await rotateActiveJsonlFile(filePath, maxFiles);
1820
+ currentBytes = 0;
1821
+ }
1822
+ await __rspack_external_node_fs_promises_153e37e0.appendFile(filePath, line, 'utf8');
1823
+ currentBytes += lineBytes;
1824
+ }
1825
+ await pruneRotatedJsonlFiles(filePath, maxFiles);
1826
+ });
1827
+ }
1828
+ const execFileAsync = promisify(execFile);
1829
+ const REQUIRED_CFS_ENV = [
1830
+ 'CFS_TOKEN',
1831
+ 'CFS_IPC_NAME'
1832
+ ];
1833
+ function contextfs_isRecord(value) {
1834
+ return null != value && 'object' == typeof value && !Array.isArray(value);
1835
+ }
1836
+ function contextfs_firstString(...values) {
1837
+ for (const value of values)if ('string' == typeof value && value.trim()) return value.trim();
1838
+ }
1839
+ function sessionIdFromInput(input) {
1840
+ return contextfs_firstString(input.session_id);
1841
+ }
1842
+ function turnIdFromInput(input) {}
1843
+ function contextFsEnvStatus(env = process.env) {
1844
+ const missing = REQUIRED_CFS_ENV.filter((key)=>!env[key]?.trim());
1845
+ return {
1846
+ ready: 0 === missing.length,
1847
+ missing,
1848
+ cfsTokenPresent: Boolean(env.CFS_TOKEN?.trim()),
1849
+ cfsIpcNamePresent: Boolean(env.CFS_IPC_NAME?.trim()),
1850
+ cfsTransport: env.CFS_TRANSPORT?.trim() || 'ipc'
1851
+ };
1852
+ }
1853
+ function parseContextFsListing(stdout) {
1854
+ return stdout.split(/\r?\n/).map((line)=>line.trim()).filter(Boolean).map((line)=>line.split(/\s+/).at(-1) || line).filter((line)=>line.endsWith('.jsonl'));
1855
+ }
1856
+ function parseJsonl(text) {
1857
+ const messages = [];
1858
+ const errors = [];
1859
+ for (const [index, rawLine] of text.split(/\r?\n/).entries()){
1860
+ const line = rawLine.trim();
1861
+ if (line) try {
1862
+ const parsed = JSON.parse(line);
1863
+ if (contextfs_isRecord(parsed)) messages.push(parsed);
1864
+ } catch (error) {
1865
+ errors.push(`line ${index + 1}: ${error instanceof Error ? error.message : String(error)}`);
1866
+ }
1867
+ }
1868
+ return {
1869
+ messages,
1870
+ errors
1871
+ };
1872
+ }
1873
+ function contextFsChildEnv(env = process.env) {
1874
+ return {
1875
+ ...env,
1876
+ CFS_TRANSPORT: env.CFS_TRANSPORT?.trim() || 'ipc'
1877
+ };
1878
+ }
1879
+ function errorMessage(error) {
1880
+ return error instanceof Error ? error.message : String(error);
1881
+ }
1882
+ async function ctxCli(config, args, env) {
1883
+ const { stdout } = await execFileAsync(config.ctxCli, args, {
1884
+ timeout: config.ctxCliTimeoutMs,
1885
+ env: contextFsChildEnv(env),
1886
+ maxBuffer: 20971520
1887
+ });
1888
+ return stdout;
1889
+ }
1890
+ function selectHistoryFiles(files, turnId) {
1891
+ if (!turnId) return files;
1892
+ const exact = files.filter((file)=>file.includes(turnId));
1893
+ return exact.length > 0 ? exact : files;
1894
+ }
1895
+ async function readContextFsTurn(options) {
1896
+ const envStatus = contextFsEnvStatus(options.env);
1897
+ if (!envStatus.ready) return {
1898
+ files: [],
1899
+ messages: [],
1900
+ errors: [
1901
+ `missing required ContextFS environment: ${envStatus.missing.join(', ')}`
1902
+ ],
1903
+ missingEnv: envStatus.missing,
1904
+ envStatus
1905
+ };
1906
+ const baseUri = `context://history/${options.sessionId}/`;
1907
+ let listing = '';
1908
+ try {
1909
+ listing = await ctxCli(options.config, [
1910
+ 'ls',
1911
+ baseUri
1912
+ ], options.env);
1913
+ } catch (error) {
1914
+ return {
1915
+ files: [],
1916
+ messages: [],
1917
+ errors: [
1918
+ `ctx-cli ls ${baseUri} failed: ${errorMessage(error)}`
1919
+ ],
1920
+ envStatus
1921
+ };
1922
+ }
1923
+ const files = selectHistoryFiles(parseContextFsListing(listing), options.turnId);
1924
+ const messages = [];
1925
+ const errors = [];
1926
+ for (const file of files){
1927
+ const uri = file.startsWith('context://') ? file : `${baseUri}${file}`;
1928
+ try {
1929
+ const text = await ctxCli(options.config, [
1930
+ 'cat',
1931
+ uri
1932
+ ], options.env);
1933
+ const parsed = parseJsonl(text);
1934
+ messages.push(...parsed.messages);
1935
+ errors.push(...parsed.errors.map((error)=>`${file}: ${error}`));
1936
+ } catch (error) {
1937
+ errors.push(`${file}: ${errorMessage(error)}`);
1938
+ }
1939
+ }
1940
+ return {
1941
+ files,
1942
+ messages,
1943
+ errors,
1944
+ envStatus
1945
+ };
1946
+ }
1947
+ const state_LOCK_TIMEOUT_MS = 5000;
1948
+ const state_LOCK_RETRY_MS = 20;
1949
+ const SESSION_MAX_AGE_MS = 86400000;
1950
+ function state_sleep(ms) {
1951
+ return new Promise((resolve)=>setTimeout(resolve, ms));
1952
+ }
1953
+ function emptyState() {
1954
+ return {
1955
+ version: 1,
1956
+ sessions: {}
1957
+ };
1958
+ }
1959
+ function state_lockPath(stateFilePath) {
1960
+ return `${stateFilePath}.lock`;
1961
+ }
1962
+ async function state_acquireLock(stateFilePath) {
1963
+ const lock = state_lockPath(stateFilePath);
1964
+ const deadline = Date.now() + state_LOCK_TIMEOUT_MS;
1965
+ __rspack_external_node_fs_5ea92f0c.mkdirSync(__rspack_external_node_path_c5b9b54f.dirname(stateFilePath), {
1966
+ recursive: true
1967
+ });
1968
+ while(Date.now() < deadline)try {
1969
+ const fd = __rspack_external_node_fs_5ea92f0c.openSync(lock, 'wx');
1970
+ __rspack_external_node_fs_5ea92f0c.closeSync(fd);
1971
+ return;
1972
+ } catch {
1973
+ await state_sleep(state_LOCK_RETRY_MS);
1974
+ }
1975
+ try {
1976
+ __rspack_external_node_fs_5ea92f0c.unlinkSync(lock);
1977
+ } catch {}
1978
+ const fd = __rspack_external_node_fs_5ea92f0c.openSync(lock, 'wx');
1979
+ __rspack_external_node_fs_5ea92f0c.closeSync(fd);
1980
+ }
1981
+ function state_releaseLock(stateFilePath) {
1982
+ try {
1983
+ __rspack_external_node_fs_5ea92f0c.unlinkSync(state_lockPath(stateFilePath));
1984
+ } catch {}
1985
+ }
1986
+ function loadState(stateFilePath) {
1987
+ try {
1988
+ const state = JSON.parse(__rspack_external_node_fs_5ea92f0c.readFileSync(stateFilePath, 'utf8'));
1989
+ if (1 !== state.version || !state.sessions || 'object' != typeof state.sessions) return emptyState();
1990
+ return state;
1991
+ } catch {
1992
+ return emptyState();
1993
+ }
1994
+ }
1995
+ function getSessionState(state, sessionId) {
1996
+ return state.sessions[sessionId] ?? {
1997
+ sessionId,
1998
+ active: false,
1999
+ messages: [],
2000
+ pendingToolUses: {},
2001
+ createdAt: '',
2002
+ updatedAt: ''
2003
+ };
2004
+ }
2005
+ function pruneOldSessions(state, now = Date.now()) {
2006
+ const cutoff = now - SESSION_MAX_AGE_MS;
2007
+ const sessions = {};
2008
+ for (const [sessionId, session] of Object.entries(state.sessions ?? {})){
2009
+ const updatedMs = session.updatedAt ? new Date(session.updatedAt).getTime() : 0;
2010
+ if (session.active || updatedMs >= cutoff) sessions[sessionId] = session;
2011
+ }
2012
+ return {
2013
+ version: 1,
2014
+ sessions
2015
+ };
2016
+ }
2017
+ async function atomicUpdateState(stateFilePath, fn) {
2018
+ await state_acquireLock(stateFilePath);
2019
+ try {
2020
+ const next = pruneOldSessions(fn(loadState(stateFilePath)));
2021
+ __rspack_external_node_fs_5ea92f0c.mkdirSync(__rspack_external_node_path_c5b9b54f.dirname(stateFilePath), {
2022
+ recursive: true
2023
+ });
2024
+ __rspack_external_node_fs_5ea92f0c.writeFileSync(stateFilePath, `${JSON.stringify(next, null, 2)}\n`, {
2025
+ encoding: 'utf8',
2026
+ mode: 384
2027
+ });
2028
+ __rspack_external_node_fs_5ea92f0c.chmodSync(stateFilePath, 384);
2029
+ } finally{
2030
+ state_releaseLock(stateFilePath);
2031
+ }
2032
+ }
2033
+ function clearedActiveSession(session, updates = {}) {
2034
+ return {
2035
+ ...session,
2036
+ ...updates,
2037
+ active: false,
2038
+ turnId: void 0,
2039
+ hookEventName: void 0,
2040
+ messages: [],
2041
+ usage: void 0,
2042
+ pendingToolUses: {},
2043
+ updatedAt: new Date().toISOString()
2044
+ };
2045
+ }
2046
+ async function saveTurnStartState(stateFilePath, turn) {
2047
+ await atomicUpdateState(stateFilePath, (state)=>({
2048
+ version: 1,
2049
+ sessions: {
2050
+ ...state.sessions,
2051
+ [turn.sessionId]: turn
2052
+ }
2053
+ }));
2054
+ }
2055
+ function readTurnStartState(stateFilePath, sessionId) {
2056
+ return loadState(stateFilePath).sessions[sessionId];
2057
+ }
2058
+ function hookEventName(input) {
2059
+ return 'string' == typeof input.hook_event_name ? input.hook_event_name : void 0;
2060
+ }
2061
+ function normalizedHookEventName(input) {
2062
+ return String(hookEventName(input) ?? '').trim();
2063
+ }
2064
+ function tracer_isRecord(value) {
2065
+ return null != value && 'object' == typeof value && !Array.isArray(value);
2066
+ }
2067
+ function cwdFromInput(input) {
2068
+ return 'string' == typeof input.cwd && input.cwd.trim() ? input.cwd.trim() : process.cwd();
2069
+ }
2070
+ function agentIdFromInput(input) {
2071
+ return 'string' == typeof input.agent_id && input.agent_id.trim() ? input.agent_id.trim() : void 0;
2072
+ }
2073
+ function agentNameFromInput(input) {
2074
+ if ('string' == typeof input.agent_type && input.agent_type.trim()) return input.agent_type.trim();
2075
+ return agentIdFromInput(input);
2076
+ }
2077
+ function userNameFromInput(input) {
2078
+ if ('string' == typeof input.email && input.email.trim()) return input.email.trim();
2079
+ if ('string' == typeof input.user_name && input.user_name.trim()) return input.user_name.trim();
2080
+ if ('string' == typeof input.userName && input.userName.trim()) return input.userName.trim();
2081
+ }
2082
+ function modelFromInput(input) {
2083
+ return 'string' == typeof input.model && input.model.trim() ? input.model.trim() : void 0;
2084
+ }
2085
+ function timestampFromInput(_input, fallback = Date.now()) {
2086
+ return new Date(fallback).toISOString();
2087
+ }
2088
+ function numberValue(value) {
2089
+ if ('number' == typeof value && Number.isFinite(value)) return value;
2090
+ if ('string' == typeof value && value.trim()) {
2091
+ const parsed = Number(value);
2092
+ if (Number.isFinite(parsed)) return parsed;
2093
+ }
2094
+ }
2095
+ function usageFromRecord(record) {
2096
+ const usage = tracer_isRecord(record.usage) ? record.usage : {};
2097
+ const result = {
2098
+ input_tokens: numberValue(usage.input_tokens),
2099
+ output_tokens: numberValue(usage.output_tokens),
2100
+ cache_read_input_tokens: numberValue(usage.cache_read_input_tokens),
2101
+ reasoning_output_tokens: numberValue(usage.reasoning_output_tokens),
2102
+ total_tokens: numberValue(usage.total_tokens)
2103
+ };
2104
+ if (void 0 === result.total_tokens && (void 0 !== result.input_tokens || void 0 !== result.output_tokens)) result.total_tokens = (result.input_tokens ?? 0) + (result.output_tokens ?? 0);
2105
+ return result;
2106
+ }
2107
+ function tracer_hasUsageTokens(usage) {
2108
+ return Boolean(usage && ('number' == typeof usage.input_tokens || 'number' == typeof usage.output_tokens || 'number' == typeof usage.total_tokens));
2109
+ }
2110
+ function mergeUsage(...usages) {
2111
+ const result = {};
2112
+ for (const usage of usages)if (usage) {
2113
+ for (const key of [
2114
+ 'input_tokens',
2115
+ 'output_tokens',
2116
+ 'cache_read_input_tokens',
2117
+ 'reasoning_output_tokens',
2118
+ 'total_tokens'
2119
+ ])if ('number' == typeof usage[key]) result[key] = usage[key];
2120
+ if (void 0 !== usage.estimated) result.estimated = usage.estimated;
2121
+ if (usage.token_estimate_method) result.token_estimate_method = usage.token_estimate_method;
2122
+ }
2123
+ return Object.keys(result).length > 0 ? result : void 0;
2124
+ }
2125
+ function tracer_compactText(value) {
2126
+ if ('string' == typeof value) return value;
2127
+ if (Array.isArray(value)) return value.map(tracer_compactText).filter(Boolean).join('\n');
2128
+ if (tracer_isRecord(value)) {
2129
+ const text = 'string' == typeof value.content ? value.content : void 0;
2130
+ if ('string' == typeof text) return text;
2131
+ return JSON.stringify(value);
2132
+ }
2133
+ return null == value ? '' : String(value);
2134
+ }
2135
+ function estimateTokens(value) {
2136
+ const text = tracer_compactText(value);
2137
+ if (!text) return 0;
2138
+ const cjkCount = text.match(/[\u3400-\u9fff]/g)?.length ?? 0;
2139
+ const nonCjk = text.replace(/[\u3400-\u9fff]/g, '');
2140
+ const latinWordCount = nonCjk.match(/[A-Za-z0-9_]+/g)?.length ?? 0;
2141
+ const otherChars = nonCjk.replace(/[A-Za-z0-9_\s]/g, '').length;
2142
+ return Math.max(1, Math.ceil(cjkCount + 1.3 * latinWordCount + otherChars / 2));
2143
+ }
2144
+ function estimateTurnUsage(messages) {
2145
+ const finalAssistantIndex = [
2146
+ ...messages
2147
+ ].reverse().findIndex((message)=>'assistant' === message.role);
2148
+ if (finalAssistantIndex < 0) return;
2149
+ const assistantIndex = messages.length - 1 - finalAssistantIndex;
2150
+ const inputMessages = messages.slice(0, assistantIndex);
2151
+ const outputMessage = messages[assistantIndex];
2152
+ const inputTokens = inputMessages.reduce((sum, message)=>{
2153
+ const toolCalls = Array.isArray(message.tool_calls) ? message.tool_calls.reduce((toolSum, call)=>toolSum + estimateTokens(call.input), 0) : 0;
2154
+ return sum + estimateTokens(message.content) + toolCalls;
2155
+ }, 0);
2156
+ const outputTokens = estimateTokens(outputMessage.content);
2157
+ if (0 === inputTokens && 0 === outputTokens) return;
2158
+ return {
2159
+ input_tokens: inputTokens,
2160
+ output_tokens: outputTokens,
2161
+ total_tokens: inputTokens + outputTokens,
2162
+ estimated: true,
2163
+ token_estimate_method: 'estimated_from_hook_messages_when_trae_hook_payload_does_not_include_model_token_usage'
2164
+ };
2165
+ }
2166
+ function usageWithFallback(messages, usage) {
2167
+ if (tracer_hasUsageTokens(usage)) return usage;
2168
+ return estimateTurnUsage(messages);
2169
+ }
2170
+ function messageWithUsage(message, usage) {
2171
+ if (!tracer_hasUsageTokens(usage) || tracer_hasUsageTokens(message.usage)) return message;
2172
+ return {
2173
+ ...message,
2174
+ usage
2175
+ };
2176
+ }
2177
+ function userMessageFromInput(input) {
2178
+ const content = 'string' == typeof input.prompt && input.prompt.trim() ? input.prompt : void 0;
2179
+ if (!content) return;
2180
+ return {
2181
+ role: 'user',
2182
+ content,
2183
+ timestamp: timestampFromInput(input)
2184
+ };
2185
+ }
2186
+ function assistantMessageFromStop(input) {
2187
+ return {
2188
+ role: 'assistant',
2189
+ content: 'string' == typeof input.last_assistant_message ? input.last_assistant_message : 'string' == typeof input.text_content ? input.text_content : '',
2190
+ timestamp: timestampFromInput(input),
2191
+ usage: usageFromRecord(input)
2192
+ };
2193
+ }
2194
+ function toolNameFromInput(input) {
2195
+ if ('string' == typeof input.llm_tool_name && input.llm_tool_name.trim()) return input.llm_tool_name.trim();
2196
+ if ('string' == typeof input.tool_name && input.tool_name.trim()) return input.tool_name.trim();
2197
+ return 'tool';
2198
+ }
2199
+ function toolCallIdFromInput(input, name) {
2200
+ if ('string' == typeof input.tool_call_id && input.tool_call_id.trim()) return input.tool_call_id.trim();
2201
+ return 'string' == typeof input.tool_use_id && input.tool_use_id.trim() ? input.tool_use_id.trim() : `${name}-${Date.now()}`;
2202
+ }
2203
+ function toolOutputFromInput(input) {
2204
+ return input.tool_response;
2205
+ }
2206
+ function toolInputFromInput(input) {
2207
+ return input.tool_input;
2208
+ }
2209
+ function pendingToolUseFromInput(input, fallbackMs = Date.now()) {
2210
+ const name = toolNameFromInput(input);
2211
+ const id = toolCallIdFromInput(input, name);
2212
+ return {
2213
+ tool_call_id: id,
2214
+ tool_name: 'string' == typeof input.tool_name ? input.tool_name : void 0,
2215
+ llm_tool_name: 'string' == typeof input.llm_tool_name ? input.llm_tool_name : void 0,
2216
+ tool_input: toolInputFromInput(input),
2217
+ start_time_ms: fallbackMs,
2218
+ timestamp: timestampFromInput(input, fallbackMs)
2219
+ };
2220
+ }
2221
+ function appendToolMessages(messages, input, pendingToolUse) {
2222
+ const name = toolNameFromInput(input);
2223
+ const id = toolCallIdFromInput(input, name);
2224
+ const endMs = Date.now();
2225
+ const endedAt = timestampFromInput(input, endMs);
2226
+ const startMs = pendingToolUse?.start_time_ms ?? endMs;
2227
+ const inputValue = void 0 !== input.tool_input ? toolInputFromInput(input) : pendingToolUse?.tool_input;
2228
+ const outputValue = toolOutputFromInput(input);
2229
+ const isError = Boolean(tracer_isRecord(input.tool_response) && (input.tool_response.error_code || input.tool_response.error_msg));
2230
+ const nextMessages = [
2231
+ ...messages
2232
+ ];
2233
+ const resultMessage = {
2234
+ role: 'tool',
2235
+ tool_call_id: id,
2236
+ tool_name: 'string' == typeof input.tool_name ? input.tool_name : pendingToolUse?.tool_name ?? name,
2237
+ llm_tool_name: 'string' == typeof input.llm_tool_name ? input.llm_tool_name : pendingToolUse?.llm_tool_name,
2238
+ tool_input: inputValue,
2239
+ start_time_ms: startMs,
2240
+ end_time_ms: endMs,
2241
+ content: outputValue ?? '',
2242
+ timestamp: endedAt,
2243
+ is_error: isError,
2244
+ error: input.error
2245
+ };
2246
+ const existingResultIndex = nextMessages.findIndex((message)=>'tool' === message.role && message.tool_call_id === id);
2247
+ if (existingResultIndex >= 0) nextMessages[existingResultIndex] = resultMessage;
2248
+ else nextMessages.push(resultMessage);
2249
+ return nextMessages;
2250
+ }
2251
+ function shouldWriteSendLog(config, record) {
2252
+ if (config.debugArtifacts) return true;
2253
+ const status = String(record.status ?? record.export_status ?? '').toLowerCase();
2254
+ const reason = String(record.reason ?? '').toLowerCase();
2255
+ return 'failed' === status || 'error' === status || reason.includes('error') || reason.includes('failed') || reason.includes('failure');
2256
+ }
2257
+ async function tracer_writeSendLog(config, record) {
2258
+ if (!shouldWriteSendLog(config, record)) return;
2259
+ const log = {
2260
+ generated_at: new Date().toISOString(),
2261
+ ...record
2262
+ };
2263
+ const jsonl = __rspack_external_node_path_c5b9b54f.join(config.dataRoot, 'logs', 'diagnostics', 'send-log.jsonl');
2264
+ await appendBoundedJsonlFile(jsonl, log, config.diagnosticLogMaxBytes, {
2265
+ strategy: 'rotate',
2266
+ maxFiles: config.diagnosticLogMaxFiles
2267
+ });
2268
+ }
2269
+ function hasAssistant(messages) {
2270
+ return messages.some((message)=>'assistant' === message.role);
2271
+ }
2272
+ function internalTurnId(sessionId, createdAt) {
2273
+ return `${sessionId}:${Date.parse(createdAt) || Date.now()}`;
2274
+ }
2275
+ function buildTurnStartState(input, sessionId) {
2276
+ const now = new Date().toISOString();
2277
+ const userMessage = userMessageFromInput(input);
2278
+ return {
2279
+ sessionId,
2280
+ active: true,
2281
+ turnId: internalTurnId(sessionId, now),
2282
+ cwd: cwdFromInput(input),
2283
+ agentId: agentIdFromInput(input),
2284
+ agentName: agentNameFromInput(input),
2285
+ userName: userNameFromInput(input),
2286
+ model: modelFromInput(input),
2287
+ hookEventName: hookEventName(input),
2288
+ messages: userMessage ? [
2289
+ userMessage
2290
+ ] : [],
2291
+ usage: mergeUsage(usageFromRecord(input)),
2292
+ pendingToolUses: {},
2293
+ createdAt: now,
2294
+ updatedAt: now
2295
+ };
2296
+ }
2297
+ function mergeTurnState(input, sessionId, state) {
2298
+ const now = new Date().toISOString();
2299
+ return {
2300
+ sessionId,
2301
+ active: state?.active ?? true,
2302
+ turnId: state?.turnId ?? internalTurnId(sessionId, now),
2303
+ cwd: state?.cwd ?? cwdFromInput(input),
2304
+ agentId: state?.agentId ?? agentIdFromInput(input),
2305
+ agentName: state?.agentName ?? agentNameFromInput(input),
2306
+ userName: userNameFromInput(input) ?? state?.userName,
2307
+ model: modelFromInput(input) ?? state?.model,
2308
+ hookEventName: state?.hookEventName ?? hookEventName(input),
2309
+ messages: state?.messages ? [
2310
+ ...state.messages
2311
+ ] : [],
2312
+ usage: state?.usage,
2313
+ pendingToolUses: {
2314
+ ...state?.pendingToolUses ?? {}
2315
+ },
2316
+ createdAt: state?.createdAt ?? now,
2317
+ updatedAt: now
2318
+ };
2319
+ }
2320
+ function pipelineEnv(config) {
2321
+ return {
2322
+ ...process.env,
2323
+ TRACE_TLS_EXPORT: config.enabled ? '1' : '0',
2324
+ TRACE_TLS_ENDPOINT: config.tlsEndpoint,
2325
+ TRACE_TLS_OTEL_ENDPOINT: config.otelEndpoint,
2326
+ TRACE_TLS_REGION: config.region,
2327
+ TRACE_TLS_TRACE_TOPIC_ID: config.traceTopicId,
2328
+ TRACE_TLS_API_KEY: config.apiKey,
2329
+ TRACE_TLS_AK: config.ak,
2330
+ TRACE_TLS_SK: config.sk,
2331
+ TRACE_TLS_AUTH_MODE: config.authMode,
2332
+ TRACE_TLS_EXPORT_TIMEOUT_MS: String(config.exportTimeoutMs),
2333
+ TRACE_TLS_EXPORT_TIMEOUT_RETRIES: String(config.exportTimeoutRetries),
2334
+ OTEL_EXPORTER_OTLP_TRACES_ENDPOINT: config.otelEndpoint,
2335
+ TLS_REGION: config.region,
2336
+ TLS_TRACE_TOPIC_ID: config.traceTopicId,
2337
+ TLS_API_KEY: config.apiKey,
2338
+ TLS_AK: config.ak,
2339
+ TLS_SK: config.sk,
2340
+ TLS_AUTH_MODE: config.authMode,
2341
+ TLS_EXPORT_TIMEOUT_MS: String(config.exportTimeoutMs),
2342
+ TLS_EXPORT_TIMEOUT_RETRIES: String(config.exportTimeoutRetries)
2343
+ };
2344
+ }
2345
+ async function traceTraeSession(options) {
2346
+ const { input, config } = options;
2347
+ if (!config.enabled) {
2348
+ const result = {
2349
+ status: 'skipped',
2350
+ reason: 'trae_tls_export_disabled'
2351
+ };
2352
+ await tracer_writeSendLog(config, result);
2353
+ return result;
2354
+ }
2355
+ const sessionId = sessionIdFromInput(input);
2356
+ if (!sessionId) {
2357
+ const result = {
2358
+ status: 'skipped',
2359
+ reason: 'missing_session_id'
2360
+ };
2361
+ await tracer_writeSendLog(config, {
2362
+ ...result,
2363
+ hook_input_keys: Object.keys(input).sort()
2364
+ });
2365
+ return result;
2366
+ }
2367
+ const event = normalizedHookEventName(input);
2368
+ if (/^UserPromptSubmit$/i.test(event)) {
2369
+ const state = buildTurnStartState(input, sessionId);
2370
+ await saveTurnStartState(config.turnStateFilePath, state);
2371
+ const result = {
2372
+ status: 'recorded',
2373
+ reason: 'turn_start_recorded',
2374
+ source: {
2375
+ tool: 'trae',
2376
+ sessionId,
2377
+ turnId: state.turnId
2378
+ },
2379
+ hook_event_name: event,
2380
+ turn_state_file: config.turnStateFilePath,
2381
+ message_count: state.messages?.length ?? 0
2382
+ };
2383
+ await tracer_writeSendLog(config, result);
2384
+ return result;
2385
+ }
2386
+ if (/^PreToolUse$/i.test(event)) {
2387
+ let updatedState;
2388
+ let turnStateFound = false;
2389
+ const startMs = Date.now();
2390
+ await atomicUpdateState(config.turnStateFilePath, (stateFile)=>{
2391
+ const storedState = stateFile.sessions[sessionId];
2392
+ turnStateFound = Boolean(storedState);
2393
+ const state = mergeTurnState(input, sessionId, storedState);
2394
+ const pending = pendingToolUseFromInput(input, startMs);
2395
+ state.pendingToolUses = {
2396
+ ...state.pendingToolUses ?? {},
2397
+ [pending.tool_call_id]: pending
2398
+ };
2399
+ state.updatedAt = new Date().toISOString();
2400
+ updatedState = state;
2401
+ return {
2402
+ version: 1,
2403
+ sessions: {
2404
+ ...stateFile.sessions,
2405
+ [sessionId]: state
2406
+ }
2407
+ };
2408
+ });
2409
+ const result = {
2410
+ status: 'recorded',
2411
+ reason: 'tool_use_started',
2412
+ source: {
2413
+ tool: 'trae',
2414
+ sessionId,
2415
+ turnId: updatedState?.turnId
2416
+ },
2417
+ hook_event_name: event,
2418
+ turn_state_found: turnStateFound,
2419
+ turn_state_file: config.turnStateFilePath,
2420
+ tool_name: toolNameFromInput(input),
2421
+ message_count: updatedState?.messages?.length ?? 0
2422
+ };
2423
+ await tracer_writeSendLog(config, result);
2424
+ return result;
2425
+ }
2426
+ if (/^PostToolUse$/i.test(event)) {
2427
+ let updatedState;
2428
+ let turnStateFound = false;
2429
+ let outputFound = false;
2430
+ await atomicUpdateState(config.turnStateFilePath, (stateFile)=>{
2431
+ const storedState = stateFile.sessions[sessionId];
2432
+ turnStateFound = Boolean(storedState);
2433
+ const state = mergeTurnState(input, sessionId, storedState);
2434
+ const id = toolCallIdFromInput(input, toolNameFromInput(input));
2435
+ outputFound = void 0 !== toolOutputFromInput(input);
2436
+ state.messages = appendToolMessages(state.messages ?? [], input, state.pendingToolUses?.[id]);
2437
+ state.usage = mergeUsage(state.usage, usageFromRecord(input));
2438
+ if (state.pendingToolUses?.[id]) {
2439
+ state.pendingToolUses = {
2440
+ ...state.pendingToolUses
2441
+ };
2442
+ delete state.pendingToolUses[id];
2443
+ }
2444
+ state.updatedAt = new Date().toISOString();
2445
+ updatedState = state;
2446
+ return {
2447
+ version: 1,
2448
+ sessions: {
2449
+ ...stateFile.sessions,
2450
+ [sessionId]: state
2451
+ }
2452
+ };
2453
+ });
2454
+ const result = {
2455
+ status: 'recorded',
2456
+ reason: 'tool_use_recorded',
2457
+ source: {
2458
+ tool: 'trae',
2459
+ sessionId,
2460
+ turnId: updatedState?.turnId
2461
+ },
2462
+ hook_event_name: event,
2463
+ turn_state_found: turnStateFound,
2464
+ turn_state_file: config.turnStateFilePath,
2465
+ tool_name: toolNameFromInput(input),
2466
+ tool_output_found: outputFound,
2467
+ hook_input_keys: Object.keys(input).sort(),
2468
+ message_count: updatedState?.messages?.length ?? 0
2469
+ };
2470
+ await tracer_writeSendLog(config, result);
2471
+ return result;
2472
+ }
2473
+ if (event && !/^Stop$/i.test(event)) {
2474
+ const result = {
2475
+ status: 'skipped',
2476
+ reason: 'unsupported_hook_event',
2477
+ source: {
2478
+ tool: 'trae',
2479
+ sessionId
2480
+ },
2481
+ hook_event_name: event
2482
+ };
2483
+ await tracer_writeSendLog(config, result);
2484
+ return result;
2485
+ }
2486
+ const storedState = readTurnStartState(config.turnStateFilePath, sessionId);
2487
+ const turnState = mergeTurnState(input, sessionId, storedState);
2488
+ const turnId = turnState.turnId ?? internalTurnId(sessionId, turnState.createdAt);
2489
+ const assistantMessage = assistantMessageFromStop(input);
2490
+ const initialMessages = [
2491
+ ...turnState.messages ?? [],
2492
+ assistantMessage
2493
+ ];
2494
+ const usage = usageWithFallback(initialMessages, mergeUsage(turnState.usage, usageFromRecord(input), usageFromRecord(assistantMessage)));
2495
+ const messages = [
2496
+ ...turnState.messages ?? [],
2497
+ messageWithUsage(assistantMessage, usage)
2498
+ ];
2499
+ if (!hasAssistant(messages)) {
2500
+ const result = {
2501
+ status: 'skipped',
2502
+ reason: 'assistant_message_not_found',
2503
+ source: {
2504
+ tool: 'trae',
2505
+ sessionId,
2506
+ turnId
2507
+ },
2508
+ hook_event_name: event || 'Stop',
2509
+ turn_state_found: Boolean(storedState)
2510
+ };
2511
+ await tracer_writeSendLog(config, result);
2512
+ return result;
2513
+ }
2514
+ const snapshot = {
2515
+ tool: 'trae',
2516
+ session_id: sessionId,
2517
+ turn_id: turnId,
2518
+ cwd: turnState.cwd,
2519
+ plugin_version: process.env.TRAE_TLS_PLUGIN_VERSION ?? '0.0.1',
2520
+ agent_id: turnState.agentId ?? agentIdFromInput(input),
2521
+ agent_name: turnState.agentName ?? agentNameFromInput(input),
2522
+ user_name: turnState.userName ?? userNameFromInput(input),
2523
+ model: turnState.model ?? modelFromInput(input),
2524
+ hook_event_name: event || 'Stop',
2525
+ capture_content: config.captureContent,
2526
+ usage,
2527
+ messages,
2528
+ attributes: {
2529
+ ...localIdentityMetadata()
2530
+ }
2531
+ };
2532
+ const result = await runTracePipeline(snapshot, traeAdapter, {
2533
+ dataRoot: config.dataRoot,
2534
+ sourceName: 'trae',
2535
+ env: pipelineEnv(config),
2536
+ otel: {
2537
+ serviceName: config.serviceName,
2538
+ resourceAttributes: {
2539
+ 'service.instance.id': config.serviceInstanceId,
2540
+ 'tls.app.type': 'trae'
2541
+ },
2542
+ scopeName: 'tls-observer-trae'
2543
+ },
2544
+ tlsOptions: {}
2545
+ });
2546
+ await tracer_writeSendLog(config, {
2547
+ status: 'sent',
2548
+ reason: 'stop_hook_trace_sent',
2549
+ source: {
2550
+ tool: 'trae',
2551
+ sessionId,
2552
+ turnId
2553
+ },
2554
+ hook_event_name: event || 'Stop',
2555
+ turn_state_found: Boolean(storedState),
2556
+ message_count: messages.length,
2557
+ usage,
2558
+ pipeline_result: result
2559
+ });
2560
+ await atomicUpdateState(config.turnStateFilePath, (state)=>{
2561
+ const latestSession = getSessionState(state, sessionId);
2562
+ return {
2563
+ version: 1,
2564
+ sessions: {
2565
+ ...state.sessions,
2566
+ [sessionId]: clearedActiveSession(latestSession, {
2567
+ cwd: turnState.cwd,
2568
+ agentId: turnState.agentId,
2569
+ agentName: turnState.agentName,
2570
+ userName: turnState.userName,
2571
+ model: turnState.model
2572
+ })
2573
+ }
2574
+ };
2575
+ });
2576
+ return result;
2577
+ }
2578
+ async function main() {
2579
+ try {
2580
+ const input = await readStdinJson();
2581
+ const config = loadConfig();
2582
+ await __rspack_external_node_fs_promises_153e37e0.mkdir(config.dataRoot, {
2583
+ recursive: true
2584
+ });
2585
+ await traceTraeSession({
2586
+ input,
2587
+ config
2588
+ });
2589
+ } finally{
2590
+ await closeTraceLogProducers();
2591
+ }
2592
+ }
2593
+ main().catch(async (error)=>{
2594
+ try {
2595
+ const config = loadConfig();
2596
+ await tracer_writeSendLog(config, {
2597
+ status: 'failed',
2598
+ reason: 'hook_error',
2599
+ message: error instanceof Error ? error.stack || error.message : String(error)
2600
+ });
2601
+ } catch {}
2602
+ process.exit(0);
2603
+ });
2604
+ export { contextFsEnvStatus, loadConfig, loadEnvFile, readContextFsTurn, sessionIdFromInput, traceTraeSession, turnIdFromInput };