@volcengine/tls-observer-opencode 0.0.3

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,1997 @@
1
+ import { homedir, hostname, networkInterfaces, userInfo as external_node_os_userInfo } from "node:os";
2
+ import { createHash as external_node_crypto_createHash } from "node:crypto";
3
+ import { tlsOpenapi } from "@volcengine/openapi";
4
+ import "node:url";
5
+ import * as __rspack_external_node_fs_5ea92f0c from "node:fs";
6
+ import * as __rspack_external_node_path_c5b9b54f from "node:path";
7
+ import * as __rspack_external_node_fs_promises_153e37e0 from "node:fs/promises";
8
+ var package_namespaceObject = {
9
+ rE: "0.0.3"
10
+ };
11
+ function firstEnv(...names) {
12
+ for (const name of names){
13
+ const value = process.env[name];
14
+ if ('string' == typeof value && '' !== value.trim()) return value.trim();
15
+ }
16
+ }
17
+ function envDisabled(name) {
18
+ return /^(0|false|no|n|off)$/i.test(process.env[name] ?? '');
19
+ }
20
+ function envFlag(name, defaultValue = false) {
21
+ const value = process.env[name];
22
+ if (void 0 === value || '' === value) return defaultValue;
23
+ return /^(1|true|yes|y|on)$/i.test(value);
24
+ }
25
+ function envPositiveInteger(name, fallback) {
26
+ const value = Number.parseInt(process.env[name] ?? '', 10);
27
+ return Number.isFinite(value) && value > 0 ? value : fallback;
28
+ }
29
+ function parseEnvValue(raw) {
30
+ const value = raw.trim();
31
+ if (value.startsWith('"') && value.endsWith('"') || value.startsWith("'") && value.endsWith("'")) return value.slice(1, -1).replace(/\\n/g, '\n').replace(/\\"/g, '"').replace(/\\'/g, "'");
32
+ return value;
33
+ }
34
+ function defaultEnvFile() {
35
+ return __rspack_external_node_path_c5b9b54f.join(homedir(), '.config', 'opencode', 'tls-observer-opencode.env');
36
+ }
37
+ function defaultDataRoot() {
38
+ return __rspack_external_node_path_c5b9b54f.join(homedir(), '.config', 'opencode', 'plugins', 'data', 'tls-observer-opencode');
39
+ }
40
+ function loadEnvFile(filePath) {
41
+ const target = filePath || firstEnv('OPENCODE_TLS_ENV_FILE') || defaultEnvFile();
42
+ let text;
43
+ try {
44
+ text = __rspack_external_node_fs_5ea92f0c.readFileSync(target, 'utf8');
45
+ } catch {
46
+ return false;
47
+ }
48
+ for (const rawLine of text.split(/\r?\n/)){
49
+ const line = rawLine.trim();
50
+ if (!line || line.startsWith('#')) continue;
51
+ const normalized = line.startsWith('export ') ? line.slice(7).trim() : line;
52
+ const match = normalized.match(/^([A-Za-z_][A-Za-z0-9_]*)=(.*)$/);
53
+ if (!match) continue;
54
+ const [, key, rawValue] = match;
55
+ process.env[key] ??= parseEnvValue(rawValue);
56
+ }
57
+ return true;
58
+ }
59
+ function mirroredTlsEnv() {
60
+ return {
61
+ ...process.env,
62
+ TRACE_TLS_ENDPOINT: firstEnv('OPENCODE_TLS_ENDPOINT', 'TRACE_TLS_ENDPOINT', 'TLS_ENDPOINT'),
63
+ TRACE_TLS_REGION: firstEnv('OPENCODE_TLS_REGION', 'TRACE_TLS_REGION', 'TLS_REGION'),
64
+ TRACE_TLS_TRACE_TOPIC_ID: firstEnv('OPENCODE_TLS_TRACE_TOPIC_ID', 'OPENCODE_TLS_TOPIC_ID', 'TRACE_TLS_TRACE_TOPIC_ID', 'TLS_TRACE_TOPIC_ID'),
65
+ TRACE_TLS_API_KEY: firstEnv('OPENCODE_TLS_API_KEY', 'TRACE_TLS_API_KEY', 'TLS_API_KEY'),
66
+ TRACE_TLS_AK: firstEnv('OPENCODE_TLS_AK', 'TRACE_TLS_AK', 'TLS_AK'),
67
+ TRACE_TLS_SK: firstEnv('OPENCODE_TLS_SK', 'TRACE_TLS_SK', 'TLS_SK'),
68
+ TRACE_TLS_EXPORT_TIMEOUT_MS: firstEnv('OPENCODE_TLS_EXPORT_TIMEOUT_MS', 'TRACE_TLS_EXPORT_TIMEOUT_MS'),
69
+ TLS_ENDPOINT: firstEnv('OPENCODE_TLS_ENDPOINT', 'TLS_ENDPOINT'),
70
+ TLS_REGION: firstEnv('OPENCODE_TLS_REGION', 'TLS_REGION'),
71
+ TLS_TRACE_TOPIC_ID: firstEnv('OPENCODE_TLS_TRACE_TOPIC_ID', 'OPENCODE_TLS_TOPIC_ID', 'TLS_TRACE_TOPIC_ID'),
72
+ TLS_API_KEY: firstEnv('OPENCODE_TLS_API_KEY', 'TLS_API_KEY'),
73
+ TLS_AK: firstEnv('OPENCODE_TLS_AK', 'TLS_AK'),
74
+ TLS_SK: firstEnv('OPENCODE_TLS_SK', 'TLS_SK'),
75
+ TLS_AUTH_MODE: firstEnv('OPENCODE_TLS_AUTH_MODE', 'TLS_AUTH_MODE')
76
+ };
77
+ }
78
+ function validateTlsConfig(env) {
79
+ const missing = [];
80
+ if (!env.TRACE_TLS_ENDPOINT) missing.push('OPENCODE_TLS_ENDPOINT, TRACE_TLS_ENDPOINT, or TLS_ENDPOINT');
81
+ if (!env.TRACE_TLS_REGION) missing.push('OPENCODE_TLS_REGION, TRACE_TLS_REGION, or TLS_REGION');
82
+ if (!env.TRACE_TLS_TRACE_TOPIC_ID) missing.push('OPENCODE_TLS_TRACE_TOPIC_ID, TRACE_TLS_TRACE_TOPIC_ID, or TLS_TRACE_TOPIC_ID');
83
+ const apiKey = env.TRACE_TLS_API_KEY;
84
+ const ak = env.TRACE_TLS_AK;
85
+ const sk = env.TRACE_TLS_SK;
86
+ if (!apiKey && (!ak || !sk)) missing.push('OPENCODE_TLS_API_KEY or OPENCODE_TLS_AK/OPENCODE_TLS_SK');
87
+ if (missing.length > 0) throw new Error(`Missing required OpenCode TLS config: ${missing.join(', ')}`);
88
+ if (apiKey && (ak || sk)) throw new Error('OpenCode TLS config must use API Key or AK/SK, not both');
89
+ }
90
+ function loadConfig() {
91
+ loadEnvFile();
92
+ const serviceName = firstEnv('OPENCODE_OTEL_SERVICE_NAME') || 'opencode-plugin';
93
+ const enabled = !envDisabled('OPENCODE_TLS_EXPORT');
94
+ const env = mirroredTlsEnv();
95
+ if (enabled) validateTlsConfig(env);
96
+ return {
97
+ enabled,
98
+ dataRoot: firstEnv('OPENCODE_TLS_DATA_ROOT') || defaultDataRoot(),
99
+ captureContent: !envDisabled('OPENCODE_TLS_CAPTURE_CONTENT'),
100
+ debugArtifacts: envFlag('OPENCODE_TLS_DEBUG_ARTIFACTS', false),
101
+ serviceName,
102
+ serviceInstanceId: `${serviceName}@${hostname()}:${process.pid}`,
103
+ pluginVersion: package_namespaceObject.rE,
104
+ exportTimeoutMs: envPositiveInteger('OPENCODE_TLS_EXPORT_TIMEOUT_MS', 20000),
105
+ env
106
+ };
107
+ }
108
+ function stableHexId(seed, length) {
109
+ const normalized = String(seed ?? '');
110
+ const digest = external_node_crypto_createHash('sha256').update(normalized).digest('hex').slice(0, length);
111
+ return digest === '0'.repeat(length) ? '1'.padStart(length, '0') : digest;
112
+ }
113
+ function model_traceIdFromSeed(seed) {
114
+ return stableHexId(seed, 32);
115
+ }
116
+ function spanIdFromSeed(...parts) {
117
+ return stableHexId(parts.map((part)=>String(part ?? '')).join(':'), 16);
118
+ }
119
+ function model_toEpochMs(value, fallback = Date.now()) {
120
+ if (value instanceof Date) {
121
+ const time = value.getTime();
122
+ return Number.isFinite(time) ? time : fallback;
123
+ }
124
+ if ('number' == typeof value && Number.isFinite(value)) return value;
125
+ if ('string' == typeof value && '' !== value.trim()) {
126
+ const parsed = Date.parse(value);
127
+ if (Number.isFinite(parsed)) return parsed;
128
+ }
129
+ return fallback;
130
+ }
131
+ function isRecord(value) {
132
+ return null != value && 'object' == typeof value && !Array.isArray(value);
133
+ }
134
+ function validateSpan(span, path) {
135
+ const errors = [];
136
+ if (!isRecord(span)) return [
137
+ `${path} must be an object`
138
+ ];
139
+ if ('string' != typeof span.name || '' === span.name.trim()) errors.push(`${path}.name is required`);
140
+ if (void 0 === span.startedAt && void 0 === span.start_time_unix_ms) errors.push(`${path}.startedAt is required`);
141
+ if (void 0 === span.endedAt && void 0 === span.end_time_unix_ms) errors.push(`${path}.endedAt is required`);
142
+ if (void 0 !== span.attributes && !isRecord(span.attributes)) errors.push(`${path}.attributes must be an object`);
143
+ return errors;
144
+ }
145
+ function validateCanonicalTrace(trace) {
146
+ const errors = [];
147
+ const warnings = [];
148
+ if (!isRecord(trace)) return {
149
+ valid: false,
150
+ errors: [
151
+ 'trace must be an object'
152
+ ],
153
+ warnings
154
+ };
155
+ if (isRecord(trace.source)) {
156
+ const source = trace.source;
157
+ if ('string' != typeof source.tool || '' === source.tool.trim()) errors.push('source.tool is required');
158
+ if ('string' != typeof source.sessionId || '' === source.sessionId.trim()) errors.push('source.sessionId is required');
159
+ } else errors.push('source is required');
160
+ errors.push(...validateSpan(trace.root, 'root'));
161
+ if (Array.isArray(trace.spans)) for (const [index, span] of trace.spans.entries())errors.push(...validateSpan(span, `spans[${index}]`));
162
+ else errors.push('spans must be an array');
163
+ if (void 0 !== trace.messages && !Array.isArray(trace.messages)) errors.push('messages must be an array');
164
+ if (void 0 !== trace.diagnostics) if (isRecord(trace.diagnostics)) {
165
+ if (void 0 !== trace.diagnostics.warnings && !Array.isArray(trace.diagnostics.warnings)) warnings.push('diagnostics.warnings should be an array');
166
+ } else warnings.push('diagnostics should be an object');
167
+ return {
168
+ valid: 0 === errors.length,
169
+ errors,
170
+ warnings
171
+ };
172
+ }
173
+ function assertCanonicalTrace(trace) {
174
+ const result = validateCanonicalTrace(trace);
175
+ if (!result.valid) {
176
+ const error = new Error(`Invalid canonical trace: ${result.errors.join('; ')}`);
177
+ error.name = 'CanonicalTraceValidationError';
178
+ error.validation = result;
179
+ throw error;
180
+ }
181
+ return trace;
182
+ }
183
+ const DEFAULT_MAX_ATTR_LENGTH = 65536;
184
+ const MAX_ATTR_LENGTH_ENV_KEYS = [
185
+ 'TRACE_OTLP_ATTRIBUTE_VALUE_MAX_CHARS',
186
+ 'CODEX_OTLP_ATTRIBUTE_VALUE_MAX_CHARS'
187
+ ];
188
+ function resolveMaxAttrLength(env = process.env) {
189
+ for (const key of MAX_ATTR_LENGTH_ENV_KEYS){
190
+ const raw = env[key];
191
+ if (null == raw || '' === raw.trim()) continue;
192
+ const parsed = Number(raw);
193
+ if (Number.isFinite(parsed) && parsed >= 0) return Math.trunc(parsed);
194
+ }
195
+ return DEFAULT_MAX_ATTR_LENGTH;
196
+ }
197
+ function truncateTextForAttr(value) {
198
+ return String(value ?? '').slice(0, resolveMaxAttrLength());
199
+ }
200
+ function safeStringify(value) {
201
+ const seen = new WeakSet();
202
+ return JSON.stringify(value, (_key, item)=>{
203
+ if (item && 'object' == typeof item) {
204
+ if (seen.has(item)) return '[Circular]';
205
+ seen.add(item);
206
+ }
207
+ return item;
208
+ });
209
+ }
210
+ function maskToken(value) {
211
+ if (value.length <= 8) return '***';
212
+ return `${value.slice(0, 3)}***${value.slice(-2)}`;
213
+ }
214
+ function redactSensitiveText(text) {
215
+ 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)}`);
216
+ }
217
+ function attributes_serializeForAttr(value) {
218
+ const serialized = 'string' == typeof value ? value : safeStringify(value);
219
+ return redactSensitiveText(truncateTextForAttr(serialized ?? String(value)));
220
+ }
221
+ function spanKindName(span) {
222
+ const kind = String(span.span_kind ?? span.kind ?? '').toUpperCase();
223
+ if ('SERVER' === kind) return 'server';
224
+ if ('CLIENT' === kind) return 'client';
225
+ return 'internal';
226
+ }
227
+ function unixMsToUs(value) {
228
+ return String(1000 * Math.trunc(model_toEpochMs(value)));
229
+ }
230
+ function span_utils_hasError(span) {
231
+ const statusObjectCode = null != span.status && 'object' == typeof span.status ? span.status.code : void 0;
232
+ const statusCode = statusObjectCode ?? span.status_code ?? span.statusCode ?? span.status;
233
+ if (2 === statusCode || 'ERROR' === String(statusCode).toUpperCase()) return true;
234
+ return Object.entries(span.attributes ?? {}).some(([key, value])=>{
235
+ if (true === value && ('error' === key || key.endsWith('.error'))) return true;
236
+ if ('tool.error' === key && 'string' == typeof value && '' !== value.trim()) return true;
237
+ return false;
238
+ });
239
+ }
240
+ function spanDurationMs(span) {
241
+ if ('number' == typeof span.duration_ms && Number.isFinite(span.duration_ms)) return Math.max(0, Math.trunc(span.duration_ms));
242
+ const start = model_toEpochMs(span.start_time_unix_ms ?? span.startedAt);
243
+ const end = model_toEpochMs(span.end_time_unix_ms ?? span.endedAt, start);
244
+ return Math.max(0, Math.trunc(end - start));
245
+ }
246
+ const SCHEMA_FIELD_ORDER = [
247
+ 'span.name',
248
+ 'span.kind',
249
+ 'tls.app.type',
250
+ 'session.id',
251
+ 'span.status.code',
252
+ 'start_time_unix_us',
253
+ 'end_time_unix_us',
254
+ 'span.duration_us',
255
+ 'service.name',
256
+ 'service.instance.id',
257
+ 'host.name',
258
+ 'client.address',
259
+ 'observed_time_unix_ms',
260
+ 'telemetry.sdk.name',
261
+ 'telemetry.sdk.language',
262
+ 'telemetry.sdk.version',
263
+ 'otel.scope.name',
264
+ 'otel.scope.version',
265
+ 'event.name',
266
+ 'user.role',
267
+ 'gen_ai.operation.name',
268
+ 'gen_ai.provider.name',
269
+ 'gen_ai.request.model',
270
+ 'gen_ai.response.model',
271
+ 'gen_ai.request.duration_ms',
272
+ 'gen_ai.input.messages',
273
+ 'gen_ai.output.messages',
274
+ 'gen_ai.usage.input_tokens',
275
+ 'gen_ai.usage.output_tokens',
276
+ 'gen_ai.usage.reasoning.output_tokens',
277
+ 'gen_ai.usage.cache_read.input_tokens',
278
+ 'gen_ai.usage.cache_creation.input_tokens',
279
+ 'gen_ai.usage.total_tokens',
280
+ 'gen_ai.tool.name',
281
+ 'gen_ai.tool.call.id',
282
+ 'gen_ai.tool.call.arguments',
283
+ 'gen_ai.tool.call.result',
284
+ 'tool.error',
285
+ 'error.type',
286
+ 'error.message'
287
+ ];
288
+ function normalizeOptions(options) {
289
+ if ('number' == typeof options) return {
290
+ observedTimeUnixMs: options,
291
+ version: '-'
292
+ };
293
+ return {
294
+ observedTimeUnixMs: options.observedTimeUnixMs ?? Date.now(),
295
+ version: options.version?.trim() || '-'
296
+ };
297
+ }
298
+ function hasMessages(value) {
299
+ return null != value && 'object' == typeof value && !Array.isArray(value);
300
+ }
301
+ function stableFieldValue(value) {
302
+ if (null == value) return '';
303
+ if ('string' == typeof value) return attributes_serializeForAttr(value);
304
+ if ('number' == typeof value || 'boolean' == typeof value) return value;
305
+ return attributes_serializeForAttr(value);
306
+ }
307
+ function inferUserRole(span) {
308
+ const inputMessage = hasMessages(span.input) ? span.input.messages?.find((message)=>'string' == typeof message?.role) : void 0;
309
+ const role = inputMessage?.role;
310
+ if ('string' == typeof role && [
311
+ 'assistant',
312
+ 'system',
313
+ 'tool',
314
+ 'user'
315
+ ].includes(role)) return role;
316
+ return '';
317
+ }
318
+ function inferErrorMessage(span) {
319
+ const output = span.output;
320
+ if (null == output) return '';
321
+ if ('string' == typeof output) return output;
322
+ if ('object' == typeof output && 'error' in output && 'string' == typeof output.error) return output.error;
323
+ if ('object' == typeof output && 'error' in output && null != output.error) return attributes_serializeForAttr(output.error);
324
+ return '';
325
+ }
326
+ function schemaSourceAttribute(attributes, dotted, legacy) {
327
+ return attributes[dotted] ?? attributes[legacy] ?? '';
328
+ }
329
+ function normalizedSpanName(span) {
330
+ if ('model' === span.type) return 'llm.request';
331
+ if ('tool' === span.type) return 'tool.call';
332
+ if ('message' === span.type) return 'agent.turn';
333
+ return span.name ?? '';
334
+ }
335
+ function legacyToolErrorMessage(value) {
336
+ return 'string' == typeof value ? value : '';
337
+ }
338
+ function buildSchemaAlignedRecord(trace, span, options = {}) {
339
+ const { observedTimeUnixMs, version } = normalizeOptions(options);
340
+ const attributes = span.attributes ?? {};
341
+ const resource = trace.resource ?? {};
342
+ const isError = span_utils_hasError(span);
343
+ const isTool = 'tool' === span.type;
344
+ const isModel = 'model' === span.type;
345
+ const isMessage = 'message' === span.type;
346
+ const inputMessages = hasMessages(span.input) ? span.input.messages ?? attributes['gen_ai.input.messages'] : attributes['gen_ai.input.messages'];
347
+ const outputMessages = hasMessages(span.output) ? span.output.messages ?? attributes['gen_ai.output.messages'] : attributes['gen_ai.output.messages'];
348
+ const explicitErrorMessage = attributes['error.message'];
349
+ const spanName = normalizedSpanName(span);
350
+ const toolErrorMessage = isTool ? 'string' == typeof explicitErrorMessage && explicitErrorMessage || legacyToolErrorMessage(attributes['tool.error']) || inferErrorMessage(span) : '';
351
+ const record = {
352
+ 'span.name': spanName,
353
+ 'span.kind': spanKindName(span),
354
+ 'tls.app.type': attributes['tls.app.type'] ?? 'codex',
355
+ 'session.id': schemaSourceAttribute(attributes, 'session.id', 'codex.session_id'),
356
+ 'span.status.code': isError ? 'ERROR' : 'UNSET',
357
+ start_time_unix_us: unixMsToUs(span.start_time_unix_ms ?? span.startedAt),
358
+ end_time_unix_us: unixMsToUs(span.end_time_unix_ms ?? span.endedAt),
359
+ 'span.duration_us': String(1000 * spanDurationMs(span)),
360
+ 'service.name': resource['service.name'] ?? "codex-transcript-stop-hook",
361
+ 'service.instance.id': resource['service.instance.id'] ?? '',
362
+ 'host.name': resource['host.name'] ?? '',
363
+ 'client.address': '',
364
+ observed_time_unix_ms: observedTimeUnixMs,
365
+ 'telemetry.sdk.name': resource['telemetry.sdk.name'] ?? "codex-transcript-stop-hook",
366
+ 'telemetry.sdk.language': resource['telemetry.sdk.language'] ?? 'nodejs',
367
+ 'telemetry.sdk.version': resource['telemetry.sdk.version'] ?? version,
368
+ 'otel.scope.name': trace.scope?.name ?? "codex-transcript-stop-hook",
369
+ 'otel.scope.version': trace.scope?.version ?? version,
370
+ 'event.name': spanName,
371
+ 'user.role': inferUserRole(span),
372
+ 'gen_ai.operation.name': attributes['gen_ai.operation.name'] ?? (isModel ? 'chat' : isMessage ? 'invoke_agent' : ''),
373
+ 'gen_ai.provider.name': attributes['gen_ai.provider.name'] ?? '',
374
+ 'gen_ai.request.model': attributes['gen_ai.request.model'] ?? '',
375
+ 'gen_ai.response.model': attributes['gen_ai.response.model'] ?? attributes['gen_ai.request.model'] ?? '',
376
+ 'gen_ai.request.duration_ms': isModel ? spanDurationMs(span) : attributes['request.duration_ms'] ?? '',
377
+ 'gen_ai.input.messages': inputMessages ?? '',
378
+ 'gen_ai.output.messages': outputMessages ?? '',
379
+ 'gen_ai.usage.input_tokens': attributes['gen_ai.usage.input_tokens'] ?? '',
380
+ 'gen_ai.usage.output_tokens': attributes['gen_ai.usage.output_tokens'] ?? '',
381
+ 'gen_ai.usage.reasoning.output_tokens': attributes['gen_ai.usage.reasoning.output_tokens'] ?? '',
382
+ 'gen_ai.usage.cache_read.input_tokens': attributes['gen_ai.usage.cache_read.input_tokens'] ?? '',
383
+ 'gen_ai.usage.cache_creation.input_tokens': attributes['gen_ai.usage.cache_creation.input_tokens'] ?? '',
384
+ 'gen_ai.usage.total_tokens': attributes['gen_ai.usage.total_tokens'] ?? '',
385
+ 'gen_ai.tool.name': isTool ? attributes['gen_ai.tool.name'] ?? attributes['tool.name'] ?? '' : '',
386
+ 'gen_ai.tool.call.id': isTool ? attributes['gen_ai.tool.call.id'] ?? attributes['codex.tool_call_id'] ?? '' : '',
387
+ 'gen_ai.tool.call.arguments': isTool ? span.input ?? '' : '',
388
+ 'gen_ai.tool.call.result': isTool ? span.output ?? '' : '',
389
+ 'tool.error': isTool ? isError : '',
390
+ 'error.type': isError ? attributes['codex.tool_status'] ?? 'error' : '',
391
+ 'error.message': isError ? toolErrorMessage : ''
392
+ };
393
+ const aligned = {};
394
+ for (const key of SCHEMA_FIELD_ORDER)aligned[key] = stableFieldValue(record[key]);
395
+ return aligned;
396
+ }
397
+ function buildSchemaAlignedRecords(trace, options = {}) {
398
+ if (!trace || !Array.isArray(trace.spans)) return [];
399
+ return trace.spans.map((span)=>buildSchemaAlignedRecord(trace, span, options));
400
+ }
401
+ new Set([
402
+ 'trace.id',
403
+ 'trace.state',
404
+ 'span.id',
405
+ 'parent.span.id',
406
+ 'span.kind',
407
+ 'span.name',
408
+ 'span.status.code',
409
+ 'span.status.message',
410
+ 'span.events',
411
+ 'span.links',
412
+ 'event.type',
413
+ 'event.name',
414
+ 'service.name',
415
+ 'service.instance.id',
416
+ 'host.name',
417
+ 'client.address',
418
+ 'observed_time_unix_ms',
419
+ 'telemetry.sdk.name',
420
+ 'telemetry.sdk.language',
421
+ 'telemetry.sdk.version',
422
+ 'otel.scope.name',
423
+ 'otel.scope.version',
424
+ 'start_time_unix_us',
425
+ 'end_time_unix_us',
426
+ 'span.duration_us'
427
+ ]);
428
+ new Set([
429
+ 'session.id',
430
+ 'app.thread.id',
431
+ 'gen_ai.conversation.id',
432
+ 'gen_ai.agent.id',
433
+ 'app.run.id',
434
+ 'app.turn.id',
435
+ 'user.id',
436
+ 'codex.input',
437
+ 'codex.output',
438
+ 'span_type',
439
+ 'codex_span_type',
440
+ 'codex_session_id',
441
+ 'codex_thread_id',
442
+ 'codex_conversation_id',
443
+ 'codex_parent_conversation_id',
444
+ 'codex_agent_id',
445
+ 'codex_turn_id',
446
+ 'codex_turn_index',
447
+ 'codex_originator',
448
+ 'codex_cli_version',
449
+ 'codex_cwd',
450
+ "codex_transcript_path",
451
+ 'codex_permission_mode',
452
+ 'codex_agent_scope',
453
+ 'codex_runtime_tags',
454
+ 'codex_decision_tool_name',
455
+ 'codex_decision_tool_call_id',
456
+ 'codex_tool_call_id',
457
+ 'codex_tool_status',
458
+ 'codex_tool_exit_code',
459
+ 'codex.system_tag_runtime',
460
+ 'gen_ai.usage.input',
461
+ 'gen_ai.usage.output',
462
+ 'gen_ai.usage.totalTokens',
463
+ 'gen_ai.usage.cacheRead',
464
+ 'gen_ai.usage.reasoningOutput'
465
+ ]);
466
+ function sourceAttributes(source) {
467
+ return {
468
+ 'source.tool': source.tool,
469
+ 'session.id': source.sessionId,
470
+ 'app.turn.id': source.turnId,
471
+ 'app.thread.id': source.threadId,
472
+ 'gen_ai.conversation.id': source.conversationId,
473
+ 'process.command.cwd': source.cwd,
474
+ 'gen_ai.request.model': source.model,
475
+ ...source.attributes ?? {}
476
+ };
477
+ }
478
+ function resourceAttributes(trace, options) {
479
+ const source = trace.source ?? {};
480
+ return {
481
+ 'service.name': options.serviceName ?? `${source.tool ?? 'agent'}-trace-hook`,
482
+ 'service.instance.id': source.sessionId ?? '',
483
+ 'host.name': hostname(),
484
+ 'telemetry.sdk.name': 'agent-trace-reporter',
485
+ 'telemetry.sdk.language': 'nodejs',
486
+ 'telemetry.sdk.version': options.sdkVersion ?? '0.1.0',
487
+ ...options.resourceAttributes ?? {}
488
+ };
489
+ }
490
+ function canonicalSpanToLegacy(span, traceId, parentId, commonAttributes, index) {
491
+ const spanId = span.id ?? span.span_id ?? spanIdFromSeed(traceId, span.name, index);
492
+ const startedAt = span.start_time_unix_ms ?? span.startedAt;
493
+ const endedAt = span.end_time_unix_ms ?? span.endedAt;
494
+ return {
495
+ trace_id: traceId,
496
+ span_id: spanId,
497
+ parent_span_id: span.parentId ?? span.parent_span_id ?? parentId,
498
+ name: span.name,
499
+ type: span.type ?? 'internal',
500
+ span_kind: String(span.kind ?? 'internal').toUpperCase(),
501
+ start_time_unix_ms: model_toEpochMs(startedAt),
502
+ end_time_unix_ms: model_toEpochMs(endedAt, model_toEpochMs(startedAt)),
503
+ duration_ms: Math.max(0, model_toEpochMs(endedAt, model_toEpochMs(startedAt)) - model_toEpochMs(startedAt)),
504
+ status_code: 'error' === span.status ? 'ERROR' : 'UNSET',
505
+ input: span.input,
506
+ output: span.output,
507
+ attributes: {
508
+ ...commonAttributes,
509
+ ...span.attributes ?? {}
510
+ }
511
+ };
512
+ }
513
+ function canonicalTraceToOtelTrace(trace, options = {}) {
514
+ const seed = [
515
+ trace.source?.tool,
516
+ trace.source?.sessionId,
517
+ trace.source?.turnId,
518
+ trace.root?.name,
519
+ trace.root?.startedAt
520
+ ].join(':');
521
+ const traceId = trace.traceId ?? model_traceIdFromSeed(seed);
522
+ const commonAttributes = sourceAttributes(trace.source ?? {});
523
+ const root = canonicalSpanToLegacy(trace.root ?? {
524
+ name: 'root'
525
+ }, traceId, '', commonAttributes, 'root');
526
+ root.span_id = trace.root?.id ?? trace.spanId ?? spanIdFromSeed(traceId, 'root');
527
+ root.parent_span_id = '';
528
+ return {
529
+ traceId,
530
+ resource: resourceAttributes(trace, options),
531
+ scope: {
532
+ name: options.scopeName ?? 'agent-trace-reporter',
533
+ version: options.scopeVersion ?? '0.1.0'
534
+ },
535
+ spans: [
536
+ root,
537
+ ...(trace.spans ?? []).map((span, index)=>canonicalSpanToLegacy(span, traceId, root.span_id ?? '', commonAttributes, index))
538
+ ]
539
+ };
540
+ }
541
+ const TLS_ANONYMOUS_IDENTITY_HEADER = 'x-tls-anonymous-identity';
542
+ const DEFAULT_FILE_NAME = 'agent-trace';
543
+ const NO_RETRY_STATUS_CODES = [
544
+ 400,
545
+ 401,
546
+ 403,
547
+ 404
548
+ ];
549
+ const producerEntries = new Map();
550
+ function firstExternalIpv4() {
551
+ for (const items of Object.values(networkInterfaces()))for (const item of items ?? [])if ('IPv4' === item.family && !item.internal && item.address) return item.address;
552
+ return '127.0.0.1';
553
+ }
554
+ function normalizeProducerEndpoint(endpoint) {
555
+ const raw = endpoint.trim();
556
+ if (!raw) return raw;
557
+ try {
558
+ const url = new URL(/^https?:\/\//.test(raw) ? raw : `https://${raw}`);
559
+ return url.host || raw;
560
+ } catch {
561
+ return raw.replace(/^https?:\/\//, '').replace(/\/.*$/, '');
562
+ }
563
+ }
564
+ function producerKey(config) {
565
+ return JSON.stringify({
566
+ endpoint: normalizeProducerEndpoint(config.endpoint),
567
+ region: config.region,
568
+ apiKey: config.apiKey || '',
569
+ ak: config.ak || '',
570
+ sk: config.sk || ''
571
+ });
572
+ }
573
+ function createProducer(config) {
574
+ const producerConfig = tlsOpenapi.Producer.getDefaultProducerConfig();
575
+ producerConfig.Endpoint = normalizeProducerEndpoint(config.endpoint);
576
+ producerConfig.Region = config.region;
577
+ producerConfig.NoRetryStatusCodeList = NO_RETRY_STATUS_CODES;
578
+ if (config.apiKey) {
579
+ producerConfig.AccessKeyID = '';
580
+ producerConfig.AccessKeySecret = '';
581
+ producerConfig.Headers = {
582
+ [TLS_ANONYMOUS_IDENTITY_HEADER]: config.apiKey
583
+ };
584
+ } else {
585
+ producerConfig.AccessKeyID = config.ak || '';
586
+ producerConfig.AccessKeySecret = config.sk || '';
587
+ }
588
+ const producer = new tlsOpenapi.Producer(producerConfig);
589
+ producer.Start();
590
+ return {
591
+ producer,
592
+ source: firstExternalIpv4(),
593
+ timeoutMs: config.timeoutMs
594
+ };
595
+ }
596
+ function timeoutError(label, timeoutMs) {
597
+ return new Error(`${label} timed out after ${timeoutMs}ms`);
598
+ }
599
+ function withTimeout(promise, timeoutMs, label) {
600
+ const effectiveTimeoutMs = Number.isFinite(timeoutMs) && timeoutMs > 0 ? Math.trunc(timeoutMs) : 20000;
601
+ return new Promise((resolve, reject)=>{
602
+ const timer = setTimeout(()=>reject(timeoutError(label, effectiveTimeoutMs)), effectiveTimeoutMs);
603
+ promise.then((value)=>{
604
+ clearTimeout(timer);
605
+ resolve(value);
606
+ }, (error)=>{
607
+ clearTimeout(timer);
608
+ reject(error);
609
+ });
610
+ });
611
+ }
612
+ function getProducer(config) {
613
+ const key = producerKey(config);
614
+ const existing = producerEntries.get(key);
615
+ if (existing) return existing;
616
+ const created = createProducer(config);
617
+ producerEntries.set(key, created);
618
+ return created;
619
+ }
620
+ function recordContents(record) {
621
+ return Object.entries(record).map(([Key, Value])=>({
622
+ Key,
623
+ Value
624
+ }));
625
+ }
626
+ async function sendTlsLogRecords(config, records, fileName = DEFAULT_FILE_NAME) {
627
+ if (0 === records.length) return;
628
+ const testMode = process.env.TRACE_TLS_TEST_PRODUCER_MODE;
629
+ const captureFile = process.env.TRACE_TLS_TEST_PRODUCER_CAPTURE_FILE;
630
+ if (testMode || captureFile) {
631
+ if (captureFile) {
632
+ await __rspack_external_node_fs_promises_153e37e0.mkdir(__rspack_external_node_path_c5b9b54f.dirname(captureFile), {
633
+ recursive: true
634
+ });
635
+ await __rspack_external_node_fs_promises_153e37e0.appendFile(captureFile, `${JSON.stringify({
636
+ topic_id: config.traceTopicId,
637
+ endpoint: normalizeProducerEndpoint(config.endpoint),
638
+ region: config.region,
639
+ file_name: fileName,
640
+ records
641
+ })}\n`, 'utf8');
642
+ }
643
+ if ('fail' === testMode) throw new Error('TRACE_TLS_TEST_PRODUCER_MODE=fail');
644
+ return;
645
+ }
646
+ const entry = getProducer(config);
647
+ const logGroup = {
648
+ Logs: records.map((record)=>({
649
+ Time: Date.now(),
650
+ Contents: recordContents(record)
651
+ })),
652
+ Source: entry.source,
653
+ LogTags: [],
654
+ FileName: fileName
655
+ };
656
+ await withTimeout(new Promise((resolve, reject)=>{
657
+ let settled = false;
658
+ const settle = (fn, value)=>{
659
+ if (settled) return;
660
+ settled = true;
661
+ fn(value);
662
+ };
663
+ const resolveUnknown = resolve;
664
+ const sendPromise = entry.producer.SendLogs('', config.traceTopicId, entry.source, fileName, logGroup, {
665
+ Success: ()=>settle(resolveUnknown),
666
+ Fail: (result)=>{
667
+ const attempts = result?.Attempts ?? [];
668
+ const lastAttempt = attempts[attempts.length - 1] ?? {};
669
+ settle(reject, new Error(`TLS SendLogs failed topicId=${config.traceTopicId} requestId=${String(lastAttempt.RequestId ?? '')} errorCode=${String(lastAttempt.ErrorCode ?? '')} errorMessage=${String(lastAttempt.ErrorMessage ?? '')}`));
670
+ }
671
+ });
672
+ Promise.resolve(sendPromise).catch((error)=>settle(reject, error));
673
+ }), config.timeoutMs, 'TLS SendLogs');
674
+ }
675
+ function config_firstEnv(env, ...names) {
676
+ for (const name of names){
677
+ const value = env[name];
678
+ if ('string' == typeof value && '' !== value.trim()) return value.trim();
679
+ }
680
+ }
681
+ function tlsEndpointFromLegacyOtelEndpoint(value) {
682
+ if (!value) return;
683
+ try {
684
+ const url = new URL(/^https?:\/\//i.test(value) ? value : `https://${value}`);
685
+ return url.hostname || void 0;
686
+ } catch {
687
+ return value.trim().replace(/^https?:\/\//i, '').replace(/\/.*$/, '').replace(/:\d+$/, '') || void 0;
688
+ }
689
+ }
690
+ function config_resolveTlsConfig(env) {
691
+ const disabled = /^(0|false|no)$/i.test(config_firstEnv(env, 'TRACE_TLS_EXPORT', 'CODEX_TLS_EXPORT') ?? '');
692
+ if (disabled) return {
693
+ enabled: false,
694
+ reason: 'disabled'
695
+ };
696
+ const endpoint = config_firstEnv(env, 'TRACE_TLS_ENDPOINT', 'CODEX_TLS_ENDPOINT') || tlsEndpointFromLegacyOtelEndpoint(config_firstEnv(env, 'TRACE_TLS_OTEL_ENDPOINT', 'CODEX_TLS_OTEL_ENDPOINT', 'OTEL_EXPORTER_OTLP_TRACES_ENDPOINT'));
697
+ const region = config_firstEnv(env, 'TRACE_TLS_REGION', 'CODEX_TLS_REGION', 'TLS_REGION');
698
+ const traceTopicId = config_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');
699
+ const apiKey = config_firstEnv(env, 'TRACE_TLS_API_KEY', 'CODEX_TLS_API_KEY', 'TLS_API_KEY');
700
+ const ak = config_firstEnv(env, 'TRACE_TLS_AK', 'CODEX_TLS_AK', 'TLS_AK');
701
+ const sk = config_firstEnv(env, 'TRACE_TLS_SK', 'CODEX_TLS_SK', 'TLS_SK');
702
+ const timeoutMs = Number(config_firstEnv(env, 'TRACE_TLS_EXPORT_TIMEOUT_MS', 'CODEX_TLS_EXPORT_TIMEOUT_MS') ?? 20000);
703
+ const missing = [];
704
+ if (!endpoint) missing.push('TRACE_TLS_ENDPOINT or CODEX_TLS_ENDPOINT');
705
+ if (!region) missing.push('TRACE_TLS_REGION or CODEX_TLS_REGION');
706
+ if (!traceTopicId) missing.push('TRACE_TLS_TRACE_TOPIC_ID or CODEX_TLS_TRACE_TOPIC_ID');
707
+ if (!apiKey && (!ak || !sk)) missing.push('TRACE_TLS_API_KEY or TRACE_TLS_AK/TRACE_TLS_SK');
708
+ if (missing.length > 0 || !endpoint || !region || !traceTopicId) return {
709
+ enabled: false,
710
+ reason: 'missing_config',
711
+ missing
712
+ };
713
+ if (apiKey && (ak || sk)) return {
714
+ enabled: false,
715
+ reason: 'invalid_auth',
716
+ missing: [
717
+ 'use apiKey or ak/sk, not both'
718
+ ]
719
+ };
720
+ return {
721
+ enabled: true,
722
+ endpoint,
723
+ region,
724
+ traceTopicId,
725
+ apiKey,
726
+ ak,
727
+ sk,
728
+ timeoutMs: Number.isFinite(timeoutMs) && timeoutMs > 0 ? timeoutMs : 20000
729
+ };
730
+ }
731
+ const TLS_AUTH_RESOURCE_KEYS = {
732
+ traceTopicId: 'tls.otel.tracetopic',
733
+ apiKey: 'tls.otel.apikey',
734
+ ak: 'tls.otel.ak',
735
+ sk: 'tls.otel.sk',
736
+ region: 'tls.otel.region'
737
+ };
738
+ const TLS_AUTH_RESOURCE_CREDENTIAL_KEYS = new Set([
739
+ TLS_AUTH_RESOURCE_KEYS.apiKey,
740
+ TLS_AUTH_RESOURCE_KEYS.ak,
741
+ TLS_AUTH_RESOURCE_KEYS.sk
742
+ ]);
743
+ function otlpAnyValueHasContent(value) {
744
+ if (!value || 'object' != typeof value) return false;
745
+ return Object.values(value).some((item)=>null != item && '' !== String(item).trim());
746
+ }
747
+ function otlpPayloadResourceAttributes(payload) {
748
+ return (payload.resourceSpans ?? []).flatMap((resourceSpan)=>{
749
+ const attributes = resourceSpan?.resource?.attributes;
750
+ return Array.isArray(attributes) ? attributes : [];
751
+ });
752
+ }
753
+ function otlpPayloadContainsTlsCredentials(body) {
754
+ let payload;
755
+ try {
756
+ payload = JSON.parse(body);
757
+ } catch {
758
+ return [
759
+ ...TLS_AUTH_RESOURCE_CREDENTIAL_KEYS
760
+ ].some((key)=>body.includes(key));
761
+ }
762
+ if ([
763
+ ...TLS_AUTH_RESOURCE_CREDENTIAL_KEYS
764
+ ].some((key)=>body.includes(key))) return true;
765
+ return otlpPayloadResourceAttributes(payload).some((attribute)=>TLS_AUTH_RESOURCE_CREDENTIAL_KEYS.has(attribute?.key) && otlpAnyValueHasContent(attribute?.value));
766
+ }
767
+ const ATTRIBUTE_PREFIXES = [
768
+ 'gen_ai.',
769
+ 'error.'
770
+ ];
771
+ const ATTRIBUTE_KEYS = new Set([
772
+ 'tool.error'
773
+ ]);
774
+ const NUMERIC_ATTRIBUTE_KEYS = new Set([
775
+ 'gen_ai.request.duration_ms',
776
+ 'gen_ai.usage.input_tokens',
777
+ 'gen_ai.usage.output_tokens',
778
+ 'gen_ai.usage.reasoning.output_tokens',
779
+ 'gen_ai.usage.cache_read.input_tokens',
780
+ 'gen_ai.usage.cache_creation.input_tokens',
781
+ 'gen_ai.usage.total_tokens'
782
+ ]);
783
+ const TLS_AUTH_RESOURCE_KEY_SET = new Set(Object.values(TLS_AUTH_RESOURCE_KEYS));
784
+ function stringifyLogValue(value) {
785
+ if (null == value) return '';
786
+ if ('string' == typeof value) return value;
787
+ if ('number' == typeof value || 'boolean' == typeof value) return String(value);
788
+ return attributes_serializeForAttr(value);
789
+ }
790
+ function stringifyRecord(record) {
791
+ return Object.fromEntries(Object.entries(record).map(([key, value])=>[
792
+ key,
793
+ stringifyLogValue(value)
794
+ ]));
795
+ }
796
+ function traceIdOf(trace) {
797
+ return trace?.trace_id ?? trace?.traceId ?? '';
798
+ }
799
+ function spanIdOf(span) {
800
+ return span?.span_id ?? span?.id ?? '';
801
+ }
802
+ function parentSpanIdOf(span) {
803
+ return span?.parent_span_id ?? span?.parentId ?? '';
804
+ }
805
+ function records_traceSpansWithRoot(trace) {
806
+ const spans = trace?.spans ?? [];
807
+ const root = trace?.root;
808
+ if (!root) return spans;
809
+ const rootSpanId = spanIdOf(root);
810
+ if (rootSpanId && spans.some((span)=>spanIdOf(span) === rootSpanId)) return spans;
811
+ return [
812
+ root,
813
+ ...spans
814
+ ];
815
+ }
816
+ function safeJsonStringify(value) {
817
+ try {
818
+ return JSON.stringify(value ?? {});
819
+ } catch {
820
+ return '{}';
821
+ }
822
+ }
823
+ function normalizeSpanName(span, fallback = '') {
824
+ if (span?.type === 'model') return 'llm.request';
825
+ if (span?.type === 'tool') return 'tool.call';
826
+ if (span?.type === 'message') return 'agent.turn';
827
+ if ('codex_turn' === fallback) return 'agent.turn';
828
+ return fallback || span?.name || '';
829
+ }
830
+ function envelopeKind(span, name) {
831
+ if ('agent.turn' === name) return 'server';
832
+ if ('llm.request' === name || 'tool.call' === name) return 'client';
833
+ return span ? spanKindName(span) : 'internal';
834
+ }
835
+ function isEmptyAttributeValue(value) {
836
+ return null == value || '' === value;
837
+ }
838
+ function normalizeAttributeValue(key, value) {
839
+ if (isEmptyAttributeValue(value)) return;
840
+ if (NUMERIC_ATTRIBUTE_KEYS.has(key)) {
841
+ const numeric = Number(value);
842
+ if (Number.isFinite(numeric)) return numeric;
843
+ }
844
+ if ('string' == typeof value || 'number' == typeof value || 'boolean' == typeof value) return value;
845
+ return attributes_serializeForAttr(value);
846
+ }
847
+ function envelopeAttributes(record, span) {
848
+ const attributes = {};
849
+ const spanAttributes = span?.attributes ?? {};
850
+ const conversationId = record['gen_ai.conversation.id'] ?? spanAttributes['gen_ai.conversation.id'] ?? record['session.id'];
851
+ 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))) {
852
+ const normalized = normalizeAttributeValue(key, value);
853
+ if (void 0 !== normalized) attributes[key] = normalized;
854
+ }
855
+ attributes['tls.app.type'] ??= 'codex';
856
+ if (!isEmptyAttributeValue(record['session.id'])) attributes['session.id'] ??= record['session.id'];
857
+ if (!isEmptyAttributeValue(conversationId)) attributes['gen_ai.conversation.id'] ??= conversationId;
858
+ if (span?.type === 'message') attributes['gen_ai.operation.name'] = 'invoke_agent';
859
+ if (span?.type === 'model') attributes['gen_ai.operation.name'] = 'chat';
860
+ return attributes;
861
+ }
862
+ function envelopeResource(trace, record) {
863
+ const resource = trace.resource ?? {};
864
+ return Object.fromEntries(Object.entries({
865
+ ...resource,
866
+ 'service.instance.id': record['service.instance.id'] ?? resource['service.instance.id'],
867
+ 'telemetry.sdk.language': record['telemetry.sdk.language'] ?? resource['telemetry.sdk.language'] ?? 'nodejs',
868
+ 'telemetry.sdk.name': record['telemetry.sdk.name'] ?? resource['telemetry.sdk.name'] ?? "codex-transcript-stop-hook",
869
+ 'telemetry.sdk.version': record['telemetry.sdk.version'] ?? resource['telemetry.sdk.version'] ?? '0.1.0'
870
+ }).filter(([key, value])=>!TLS_AUTH_RESOURCE_KEY_SET.has(key) && !isEmptyAttributeValue(value)));
871
+ }
872
+ function otlpEnvelopeRecord(trace, span, record) {
873
+ const name = normalizeSpanName(span, String(record['span.name'] ?? ''));
874
+ const start = String(record['start_time_unix_us'] ?? unixMsToUs(span?.start_time_unix_ms ?? span?.startedAt));
875
+ const end = String(record['end_time_unix_us'] ?? unixMsToUs(span?.end_time_unix_ms ?? span?.endedAt));
876
+ const duration = String(record['span.duration_us'] ?? 1000 * spanDurationMs(span ?? {}));
877
+ const error = span ? span_utils_hasError(span) : 'ERROR' === record['span.status.code'];
878
+ const resource = envelopeResource(trace, record);
879
+ const serviceName = stringifyLogValue(record['service.name'] ?? resource['service.name'] ?? "codex-transcript-stop-hook");
880
+ const host = stringifyLogValue(record['host.name'] ?? resource['host.name'] ?? '');
881
+ const scopeName = stringifyLogValue(record['otel.scope.name'] ?? trace.scope?.name ?? "codex-transcript-stop-hook");
882
+ const scopeVersion = stringifyLogValue(record['otel.scope.version'] ?? trace.scope?.version ?? '0.1.0');
883
+ return stringifyRecord({
884
+ Attributes: safeJsonStringify(envelopeAttributes(record, span)),
885
+ Duration: duration,
886
+ End: end,
887
+ Events: '[]',
888
+ Host: host,
889
+ Kind: envelopeKind(span, name),
890
+ Links: '[]',
891
+ Name: name,
892
+ OTLPName: scopeName,
893
+ OTLPVersion: scopeVersion,
894
+ ParentSpanID: parentSpanIdOf(span) || record['parent.span.id'] || '',
895
+ Resource: safeJsonStringify(resource),
896
+ ServiceName: serviceName,
897
+ SpanID: spanIdOf(span) || String(record['span.id'] ?? ''),
898
+ Start: start,
899
+ StatusCode: error ? 'ERROR' : 'OK',
900
+ StatusDescription: error ? record['error.message'] ?? '' : '',
901
+ TraceID: span?.trace_id ?? traceIdOf(trace) ?? record['trace.id'] ?? '',
902
+ TraceState: ''
903
+ });
904
+ }
905
+ function recordsFromTrace(trace) {
906
+ const spans = records_traceSpansWithRoot(trace);
907
+ const schemaRecords = buildSchemaAlignedRecords({
908
+ ...trace,
909
+ spans
910
+ });
911
+ return schemaRecords.map((record, index)=>otlpEnvelopeRecord(trace, spans[index], record));
912
+ }
913
+ function exporter_traceIdOf(trace) {
914
+ return trace?.trace_id ?? trace?.traceId ?? '';
915
+ }
916
+ function notifyRequestBody(options, request) {
917
+ if ('function' != typeof options.onRequestBody) return;
918
+ try {
919
+ options.onRequestBody(request);
920
+ } catch {}
921
+ }
922
+ function endpointDiagnostics(config) {
923
+ return {
924
+ endpoint_host: normalizeProducerEndpoint(config.endpoint),
925
+ endpoint_path: 'SendLogs',
926
+ region: config.region
927
+ };
928
+ }
929
+ async function exportRecordsToTls(records, config, metadata = {}, options = {}, fileName = 'agent-trace') {
930
+ if (0 === records.length) return {
931
+ status: 'skipped',
932
+ reason: 'empty_records'
933
+ };
934
+ const body = JSON.stringify({
935
+ records
936
+ });
937
+ const startedAt = Date.now();
938
+ const traceId = 'string' == typeof metadata.trace_id ? metadata.trace_id : records[0]?.TraceID ?? records[0]?.['trace.id'] ?? '';
939
+ const spanCount = Number.isFinite(Number(metadata.span_count)) ? Number(metadata.span_count) : records.length;
940
+ notifyRequestBody(options, {
941
+ body,
942
+ attempt: 1,
943
+ request_kind: 'tls_log_records',
944
+ payload_bytes: Buffer.byteLength(body, 'utf8'),
945
+ record_count: records.length,
946
+ span_count: spanCount,
947
+ trace_id: traceId,
948
+ ...endpointDiagnostics(config)
949
+ });
950
+ try {
951
+ await sendTlsLogRecords(config, records, fileName);
952
+ return {
953
+ status: 'sent',
954
+ trace_id: traceId,
955
+ span_count: spanCount,
956
+ record_count: records.length,
957
+ payload_bytes: Buffer.byteLength(body, 'utf8'),
958
+ attempts: 1,
959
+ total_duration_ms: Date.now() - startedAt,
960
+ ...endpointDiagnostics(config)
961
+ };
962
+ } catch (error) {
963
+ return {
964
+ status: 'failed',
965
+ reason: 'producer_error',
966
+ message: error instanceof Error ? error.message : String(error),
967
+ trace_id: traceId,
968
+ span_count: spanCount,
969
+ record_count: records.length,
970
+ payload_bytes: Buffer.byteLength(body, 'utf8'),
971
+ attempts: 1,
972
+ total_duration_ms: Date.now() - startedAt,
973
+ error_name: error instanceof Error ? error.name : void 0,
974
+ ...endpointDiagnostics(config)
975
+ };
976
+ }
977
+ }
978
+ async function exportTraceToTls(trace, env = process.env, options = {}) {
979
+ const spans = records_traceSpansWithRoot(trace);
980
+ if (!trace || 0 === spans.length) return {
981
+ status: 'skipped',
982
+ reason: 'empty_trace'
983
+ };
984
+ const config = config_resolveTlsConfig(env);
985
+ if (!config.enabled) return {
986
+ status: 'skipped',
987
+ reason: config.reason,
988
+ missing: config.missing
989
+ };
990
+ return exportRecordsToTls(recordsFromTrace(trace), config, {
991
+ span_count: spans.length,
992
+ trace_id: exporter_traceIdOf(trace)
993
+ }, options, `${trace.source?.tool ?? 'agent'}-trace`);
994
+ }
995
+ function file_store_hasErrorCode(error, code) {
996
+ return 'object' == typeof error && null !== error && 'code' in error && error.code === code;
997
+ }
998
+ async function ensureParent(filePath) {
999
+ await __rspack_external_node_fs_promises_153e37e0.mkdir(__rspack_external_node_path_c5b9b54f.dirname(filePath), {
1000
+ recursive: true
1001
+ });
1002
+ }
1003
+ async function writeJsonFile(filePath, value, options = {}) {
1004
+ await ensureParent(filePath);
1005
+ const tempPath = `${filePath}.${process.pid}.tmp`;
1006
+ const writeOptions = options.mode ? {
1007
+ encoding: 'utf8',
1008
+ mode: options.mode
1009
+ } : 'utf8';
1010
+ await __rspack_external_node_fs_promises_153e37e0.writeFile(tempPath, `${JSON.stringify(value, null, 2)}\n`, writeOptions);
1011
+ await __rspack_external_node_fs_promises_153e37e0.rename(tempPath, filePath);
1012
+ if (options.mode) await __rspack_external_node_fs_promises_153e37e0.chmod(filePath, options.mode);
1013
+ }
1014
+ async function appendJsonl(filePath, value) {
1015
+ await ensureParent(filePath);
1016
+ await __rspack_external_node_fs_promises_153e37e0.appendFile(filePath, `${JSON.stringify(value)}\n`, 'utf8');
1017
+ }
1018
+ async function readJsonl(filePath) {
1019
+ let text;
1020
+ try {
1021
+ text = await __rspack_external_node_fs_promises_153e37e0.readFile(filePath, 'utf8');
1022
+ } catch (error) {
1023
+ if (file_store_hasErrorCode(error, 'ENOENT')) return [];
1024
+ throw error;
1025
+ }
1026
+ return text.split(/\r?\n/).filter(Boolean).map((line)=>JSON.parse(line));
1027
+ }
1028
+ function safeName(value) {
1029
+ return String(value || 'trace').replace(/[^a-zA-Z0-9._-]+/g, '-').replace(/^-+|-+$/g, '').slice(0, 160) || 'trace';
1030
+ }
1031
+ async function writeFailedTraceCache(paths, key, trace, body, result, config = {}) {
1032
+ if (result?.status !== 'failed') return {
1033
+ persisted: false,
1034
+ reason: 'not_failed'
1035
+ };
1036
+ if (!body) return {
1037
+ persisted: false,
1038
+ reason: 'missing_otlp_payload'
1039
+ };
1040
+ if (otlpPayloadContainsTlsCredentials(body)) return {
1041
+ persisted: false,
1042
+ reason: 'payload_contains_tls_credentials'
1043
+ };
1044
+ const now = Date.now();
1045
+ const retryIntervalMs = config.retryIntervalMs ?? 300000;
1046
+ const caseDir = __rspack_external_node_path_c5b9b54f.join(paths.failedTracesDir, `${safeName(key.slice(0, 16))}.${safeName(trace.source?.tool)}`);
1047
+ const payloadPath = __rspack_external_node_path_c5b9b54f.join(caseDir, 'payload.otlp.json');
1048
+ const manifestPath = __rspack_external_node_path_c5b9b54f.join(caseDir, 'manifest.json');
1049
+ await __rspack_external_node_fs_promises_153e37e0.mkdir(caseDir, {
1050
+ recursive: true
1051
+ });
1052
+ await __rspack_external_node_fs_promises_153e37e0.writeFile(payloadPath, body, {
1053
+ encoding: 'utf8',
1054
+ mode: 384
1055
+ });
1056
+ await __rspack_external_node_fs_promises_153e37e0.chmod(payloadPath, 384);
1057
+ const manifest = {
1058
+ version: 1,
1059
+ state: 'pending',
1060
+ created_at: new Date(now).toISOString(),
1061
+ updated_at: new Date(now).toISOString(),
1062
+ next_retry_at: new Date(now + retryIntervalMs).toISOString(),
1063
+ retry_interval_ms: retryIntervalMs,
1064
+ retry_count: 0,
1065
+ max_retry_count: config.maxRetryCount ?? 1,
1066
+ dedup_key: key,
1067
+ payload_path: payloadPath,
1068
+ source_tool: trace.source?.tool,
1069
+ session_id: trace.source?.sessionId,
1070
+ turn_id: trace.source?.turnId,
1071
+ span_count: (trace.spans?.length ?? 0) + 1,
1072
+ payload_bytes: Buffer.byteLength(body, 'utf8'),
1073
+ trace_export: result
1074
+ };
1075
+ await writeJsonFile(manifestPath, manifest, {
1076
+ mode: 384
1077
+ });
1078
+ return {
1079
+ persisted: true,
1080
+ manifest_path: manifestPath,
1081
+ next_retry_at: manifest.next_retry_at
1082
+ };
1083
+ }
1084
+ function dedup_hasErrorCode(error, code) {
1085
+ return 'object' == typeof error && null !== error && 'code' in error && error.code === code;
1086
+ }
1087
+ function dedupKeyForTrace(trace) {
1088
+ const seed = [
1089
+ trace.source?.tool,
1090
+ trace.source?.sessionId,
1091
+ trace.source?.turnId,
1092
+ trace.source?.threadId,
1093
+ trace.root?.name,
1094
+ trace.root?.startedAt
1095
+ ].join(':');
1096
+ return external_node_crypto_createHash('sha256').update(seed).digest('hex');
1097
+ }
1098
+ async function hasUploaded(uploadedFile, key) {
1099
+ return (await readJsonl(uploadedFile)).some((entry)=>entry.key === key);
1100
+ }
1101
+ async function markUploaded(uploadedFile, key, trace, result) {
1102
+ await appendJsonl(uploadedFile, {
1103
+ key,
1104
+ source_tool: trace.source?.tool,
1105
+ session_id: trace.source?.sessionId,
1106
+ turn_id: trace.source?.turnId,
1107
+ uploaded_at: new Date().toISOString(),
1108
+ span_count: (trace.spans?.length ?? 0) + 1,
1109
+ trace_export: {
1110
+ status: result.status,
1111
+ reason: result.reason
1112
+ }
1113
+ });
1114
+ }
1115
+ async function acquireLock(locksDir, key, timeoutMs = 120000) {
1116
+ await __rspack_external_node_fs_promises_153e37e0.mkdir(locksDir, {
1117
+ recursive: true
1118
+ });
1119
+ const lockPath = __rspack_external_node_path_c5b9b54f.join(locksDir, `${key}.lock`);
1120
+ try {
1121
+ const handle = await __rspack_external_node_fs_promises_153e37e0.open(lockPath, 'wx');
1122
+ await handle.writeFile(JSON.stringify({
1123
+ pid: process.pid,
1124
+ created_at: new Date().toISOString()
1125
+ }));
1126
+ await handle.close();
1127
+ return {
1128
+ acquired: true,
1129
+ lockPath
1130
+ };
1131
+ } catch (error) {
1132
+ if (!dedup_hasErrorCode(error, 'EEXIST')) throw error;
1133
+ }
1134
+ const stat = await __rspack_external_node_fs_promises_153e37e0.stat(lockPath).catch(()=>null);
1135
+ if (stat && Date.now() - stat.mtimeMs > timeoutMs) {
1136
+ await __rspack_external_node_fs_promises_153e37e0.rm(lockPath, {
1137
+ force: true
1138
+ });
1139
+ return acquireLock(locksDir, key, timeoutMs);
1140
+ }
1141
+ return {
1142
+ acquired: false,
1143
+ lockPath,
1144
+ reason: 'dedup_lock_timeout'
1145
+ };
1146
+ }
1147
+ async function releaseLock(lock) {
1148
+ if (lock?.acquired && lock.lockPath) await __rspack_external_node_fs_promises_153e37e0.rm(lock.lockPath, {
1149
+ force: true
1150
+ });
1151
+ }
1152
+ function paths_defaultDataRoot(source = 'agent') {
1153
+ return __rspack_external_node_path_c5b9b54f.join(homedir(), '.codex', 'plugins', 'data', `storage-tls-${source}`);
1154
+ }
1155
+ function runtimePaths(config = {}) {
1156
+ const source = config.sourceName ?? config.source?.tool ?? 'agent';
1157
+ 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));
1158
+ return {
1159
+ dataRoot,
1160
+ sendLogPath: __rspack_external_node_path_c5b9b54f.join(dataRoot, 'logs', 'send', 'latest.send-log.json'),
1161
+ diagnosticsDir: __rspack_external_node_path_c5b9b54f.join(dataRoot, 'logs', 'diagnostics'),
1162
+ uploadedFile: __rspack_external_node_path_c5b9b54f.join(dataRoot, 'state', 'dedup', 'uploaded.jsonl'),
1163
+ locksDir: __rspack_external_node_path_c5b9b54f.join(dataRoot, 'state', 'dedup', 'locks'),
1164
+ failedTracesDir: __rspack_external_node_path_c5b9b54f.join(dataRoot, 'state', 'failed-traces')
1165
+ };
1166
+ }
1167
+ function nowIso() {
1168
+ return new Date().toISOString();
1169
+ }
1170
+ function metadataForTrace(trace, otelTrace) {
1171
+ return {
1172
+ span_count: otelTrace.spans.length,
1173
+ trace_id: otelTrace.traceId,
1174
+ source_tool: trace.source?.tool,
1175
+ session_id: trace.source?.sessionId,
1176
+ turn_id: trace.source?.turnId
1177
+ };
1178
+ }
1179
+ async function writeSendLog(paths, event) {
1180
+ await writeJsonFile(paths.sendLogPath, {
1181
+ generated_at: nowIso(),
1182
+ ...event
1183
+ });
1184
+ }
1185
+ async function runTracePipeline(input, adapter, config = {}) {
1186
+ if (!adapter || 'function' != typeof adapter.load || 'function' != typeof adapter.toCanonical) throw new Error('A SourceAdapter with load() and toCanonical() is required');
1187
+ if ('function' == typeof adapter.detect && !adapter.detect(input)) return {
1188
+ status: 'skipped',
1189
+ reason: 'adapter_not_detected',
1190
+ adapter: adapter.name
1191
+ };
1192
+ const context = {
1193
+ env: config.env ?? process.env,
1194
+ dataRoot: config.dataRoot,
1195
+ now: config.now ?? Date.now,
1196
+ source: config.source ?? {}
1197
+ };
1198
+ const session = await adapter.load(input, context);
1199
+ const trace = assertCanonicalTrace(await adapter.toCanonical(session, context));
1200
+ const paths = runtimePaths({
1201
+ ...config,
1202
+ source: trace.source,
1203
+ sourceName: trace.source?.tool
1204
+ });
1205
+ const key = config.dedupKey ?? dedupKeyForTrace(trace);
1206
+ if (false !== config.dedup && await hasUploaded(paths.uploadedFile, key)) {
1207
+ await writeSendLog(paths, {
1208
+ status: 'deduped',
1209
+ dedup_key: key,
1210
+ source: trace.source
1211
+ });
1212
+ return {
1213
+ status: 'deduped',
1214
+ dedup_key: key
1215
+ };
1216
+ }
1217
+ const lock = false === config.dedup ? {
1218
+ acquired: true
1219
+ } : await acquireLock(paths.locksDir, key, config.dedupLockTimeoutMs);
1220
+ if (!lock.acquired) {
1221
+ await writeSendLog(paths, {
1222
+ status: 'skipped',
1223
+ reason: lock.reason,
1224
+ dedup_key: key,
1225
+ source: trace.source
1226
+ });
1227
+ return {
1228
+ status: 'skipped',
1229
+ reason: lock.reason,
1230
+ dedup_key: key
1231
+ };
1232
+ }
1233
+ try {
1234
+ const otelTrace = canonicalTraceToOtelTrace(trace, config.otel);
1235
+ let capturedRequestBody = '';
1236
+ const metadata = metadataForTrace(trace, otelTrace);
1237
+ const traceExport = await exportTraceToTls(otelTrace, context.env, {
1238
+ ...config.tlsOptions ?? {},
1239
+ onRequestBody: (request)=>{
1240
+ if ('string' == typeof request.body) capturedRequestBody = request.body;
1241
+ config.tlsOptions?.onRequestBody?.(request);
1242
+ }
1243
+ });
1244
+ let failedTraceCache = {
1245
+ persisted: false,
1246
+ reason: 'not_failed'
1247
+ };
1248
+ if ('sent' === traceExport.status && false !== config.dedup) await markUploaded(paths.uploadedFile, key, trace, traceExport);
1249
+ else if ('failed' === traceExport.status) failedTraceCache = await writeFailedTraceCache(paths, key, trace, capturedRequestBody, traceExport, config.failedTraceCache);
1250
+ await writeSendLog(paths, {
1251
+ status: traceExport.status,
1252
+ dedup_key: key,
1253
+ source: trace.source,
1254
+ trace_export: traceExport,
1255
+ failed_trace_cache: failedTraceCache,
1256
+ otlp: metadata
1257
+ });
1258
+ return {
1259
+ status: traceExport.status,
1260
+ dedup_key: key,
1261
+ trace_export: traceExport,
1262
+ failed_trace_cache: failedTraceCache
1263
+ };
1264
+ } finally{
1265
+ await releaseLock(lock);
1266
+ }
1267
+ }
1268
+ new Map();
1269
+ const TLS_APP_TYPE = 'opencode';
1270
+ const ROOT_SPAN_NAME = 'agent.turn';
1271
+ const LLM_SPAN_NAME = 'llm.request';
1272
+ const TOOL_SPAN_NAME = 'tool.call';
1273
+ function opencode_isRecord(value) {
1274
+ return null != value && 'object' == typeof value && !Array.isArray(value);
1275
+ }
1276
+ function textValue(value) {
1277
+ return 'string' == typeof value && value.length > 0 ? value : void 0;
1278
+ }
1279
+ function firstString(...values) {
1280
+ for (const value of values){
1281
+ const text = textValue(value);
1282
+ if (text) return text;
1283
+ }
1284
+ }
1285
+ function numberValue(value) {
1286
+ return 'number' == typeof value && Number.isFinite(value) ? value : void 0;
1287
+ }
1288
+ function compactRecord(input) {
1289
+ return Object.fromEntries(Object.entries(input).filter(([, value])=>null != value && '' !== value));
1290
+ }
1291
+ function captureEnabled(input, context) {
1292
+ if ('boolean' == typeof input.captureContent) return input.captureContent;
1293
+ const raw = context.env?.OPENCODE_TLS_CAPTURE_CONTENT;
1294
+ if (null == raw || '' === raw) return true;
1295
+ return !/^(0|false|no|n|off)$/i.test(raw);
1296
+ }
1297
+ function sortAndMergeParts(parts) {
1298
+ const merged = new Map();
1299
+ for (const part of parts)if (part?.id) merged.set(part.id, {
1300
+ ...merged.get(part.id) ?? {},
1301
+ ...part
1302
+ });
1303
+ return [
1304
+ ...merged.values()
1305
+ ];
1306
+ }
1307
+ function partTime(part) {
1308
+ return {
1309
+ start: numberValue(part.time?.start),
1310
+ end: numberValue(part.time?.end),
1311
+ observed: numberValue(part.receivedAt)
1312
+ };
1313
+ }
1314
+ function latestPartTime(parts, fallback) {
1315
+ let value = fallback;
1316
+ for (const part of parts){
1317
+ const timing = partTime(part);
1318
+ value = Math.max(value, timing.end ?? 0, timing.start ?? 0, timing.observed ?? 0);
1319
+ }
1320
+ return value;
1321
+ }
1322
+ function messageStart(message, fallback) {
1323
+ const created = opencode_isRecord(message.info?.time) ? numberValue(message.info.time.created) : void 0;
1324
+ return created ?? fallback;
1325
+ }
1326
+ function messageEnd(message, fallback) {
1327
+ return latestPartTime(sortAndMergeParts(message.parts), fallback);
1328
+ }
1329
+ function safeContent(value, captureContent) {
1330
+ if (!captureContent) return;
1331
+ return value;
1332
+ }
1333
+ function contentBlock(part, captureContent) {
1334
+ if ('step-start' === part.type || 'step-finish' === part.type) return [];
1335
+ if ('text' === part.type) return [
1336
+ compactRecord({
1337
+ type: 'text',
1338
+ content: safeContent(part.text, captureContent),
1339
+ metadata: part.metadata
1340
+ })
1341
+ ];
1342
+ if ('reasoning' === part.type) return [
1343
+ compactRecord({
1344
+ type: 'reasoning',
1345
+ content: safeContent(part.text, captureContent),
1346
+ metadata: part.metadata
1347
+ })
1348
+ ];
1349
+ if ('file' === part.type) return [
1350
+ compactRecord({
1351
+ type: 'file',
1352
+ id: part.filename ?? part.id,
1353
+ url: safeContent(part.url, captureContent),
1354
+ mime_type: part.mime
1355
+ })
1356
+ ];
1357
+ if ('tool' === part.type) {
1358
+ const state = opencode_isRecord(part.state) ? part.state : {};
1359
+ return [
1360
+ compactRecord({
1361
+ type: 'tool_call',
1362
+ id: part.callID,
1363
+ name: part.tool,
1364
+ arguments: safeContent(state.input, captureContent)
1365
+ })
1366
+ ];
1367
+ }
1368
+ if ('compaction' === part.type) return [
1369
+ compactRecord({
1370
+ type: 'compaction',
1371
+ auto: part.auto
1372
+ })
1373
+ ];
1374
+ return [
1375
+ compactRecord({
1376
+ type: part.type || 'unknown',
1377
+ value: safeContent(part, captureContent)
1378
+ })
1379
+ ];
1380
+ }
1381
+ function roleOf(message) {
1382
+ return firstString(message.info?.role) ?? 'unknown';
1383
+ }
1384
+ function toGenAiMessages(messages, captureContent) {
1385
+ return messages.flatMap((message)=>{
1386
+ const parts = sortAndMergeParts(message.parts);
1387
+ const role = roleOf(message);
1388
+ const primary = compactRecord({
1389
+ role,
1390
+ parts: parts.flatMap((part)=>contentBlock(part, captureContent))
1391
+ });
1392
+ const toolResults = parts.flatMap((part)=>{
1393
+ if ('tool' !== part.type) return [];
1394
+ const state = opencode_isRecord(part.state) ? part.state : {};
1395
+ const status = firstString(state.status);
1396
+ if ('completed' !== status && 'success' !== status && 'error' !== status) return [];
1397
+ return [
1398
+ compactRecord({
1399
+ role: 'tool',
1400
+ name: part.tool,
1401
+ tool_call_id: part.callID,
1402
+ content: safeContent('error' === status ? state.error : state.output, captureContent)
1403
+ })
1404
+ ];
1405
+ });
1406
+ return [
1407
+ primary,
1408
+ ...toolResults
1409
+ ];
1410
+ });
1411
+ }
1412
+ function toTraceMessages(messages, captureContent) {
1413
+ return messages.map((message)=>({
1414
+ role: roleOf(message),
1415
+ content: toGenAiMessages([
1416
+ message
1417
+ ], captureContent),
1418
+ timestamp: new Date(messageStart(message, Date.now())).toISOString(),
1419
+ attributes: compactRecord({
1420
+ 'opencode.message_id': message.info?.id,
1421
+ 'opencode.parent_message_id': message.info?.parentID,
1422
+ 'opencode.agent': message.info?.agent,
1423
+ 'opencode.mode': message.info?.mode
1424
+ })
1425
+ }));
1426
+ }
1427
+ function stepFinish(message) {
1428
+ return sortAndMergeParts(message.parts).find((part)=>'step-finish' === part.type);
1429
+ }
1430
+ function usageFromStep(part) {
1431
+ const tokens = opencode_isRecord(part?.tokens) ? part.tokens : {};
1432
+ const cache = opencode_isRecord(tokens.cache) ? tokens.cache : {};
1433
+ const input = numberValue(tokens.input) ?? 0;
1434
+ const output = numberValue(tokens.output) ?? 0;
1435
+ const reasoning = numberValue(tokens.reasoning) ?? 0;
1436
+ const cacheRead = numberValue(cache.read) ?? 0;
1437
+ const cacheWrite = numberValue(cache.write) ?? 0;
1438
+ return compactRecord({
1439
+ 'gen_ai.usage.input_tokens': input || void 0,
1440
+ 'gen_ai.usage.output_tokens': output || void 0,
1441
+ 'gen_ai.usage.reasoning.output_tokens': reasoning || void 0,
1442
+ 'gen_ai.usage.cache_read.input_tokens': cacheRead || void 0,
1443
+ 'gen_ai.usage.cache_creation.input_tokens': cacheWrite || void 0,
1444
+ 'gen_ai.usage.total_tokens': input || output || reasoning ? input + output + reasoning : void 0
1445
+ });
1446
+ }
1447
+ function sumUsage(records) {
1448
+ const keys = [
1449
+ 'gen_ai.usage.input_tokens',
1450
+ 'gen_ai.usage.output_tokens',
1451
+ 'gen_ai.usage.reasoning.output_tokens',
1452
+ 'gen_ai.usage.cache_read.input_tokens',
1453
+ 'gen_ai.usage.cache_creation.input_tokens',
1454
+ 'gen_ai.usage.total_tokens'
1455
+ ];
1456
+ return compactRecord(Object.fromEntries(keys.map((key)=>[
1457
+ key,
1458
+ records.reduce((sum, record)=>{
1459
+ const value = record[key];
1460
+ return 'number' == typeof value && Number.isFinite(value) ? sum + value : sum;
1461
+ }, 0) || void 0
1462
+ ])));
1463
+ }
1464
+ function modelInfo(message) {
1465
+ const systemModel = opencode_isRecord(message.systemPrompt?.model) ? message.systemPrompt.model : {};
1466
+ const api = opencode_isRecord(systemModel.api) ? systemModel.api : {};
1467
+ return {
1468
+ model: firstString(systemModel.id, message.info?.modelID),
1469
+ provider: firstString(systemModel.providerID, message.info?.providerID),
1470
+ displayName: firstString(systemModel.name),
1471
+ apiModel: firstString(api.id)
1472
+ };
1473
+ }
1474
+ function systemMessages(message) {
1475
+ const system = message.systemPrompt?.system;
1476
+ if (!Array.isArray(system) || 0 === system.length) return [];
1477
+ return [
1478
+ {
1479
+ role: 'system',
1480
+ content: system.join('\n')
1481
+ }
1482
+ ];
1483
+ }
1484
+ function messageId(message) {
1485
+ return firstString(message.info?.id);
1486
+ }
1487
+ function currentTurnMessagesBefore(messages, message) {
1488
+ const id = messageId(message);
1489
+ const index = messages.findIndex((candidate)=>{
1490
+ const candidateId = messageId(candidate);
1491
+ return id ? candidateId === id : candidate === message;
1492
+ });
1493
+ return index >= 0 ? messages.slice(0, index) : [];
1494
+ }
1495
+ function toolParts(message) {
1496
+ return sortAndMergeParts(message.parts).filter((part)=>'tool' === part.type);
1497
+ }
1498
+ function toolSpan(traceId, rootSpanId, message, part, fallbackStart, fallbackEnd, common, captureContent) {
1499
+ const state = opencode_isRecord(part.state) ? part.state : {};
1500
+ const status = firstString(state.status);
1501
+ const errored = 'error' === status;
1502
+ const start = numberValue(opencode_isRecord(state.time) ? state.time.start : void 0) ?? fallbackStart;
1503
+ const end = numberValue(opencode_isRecord(state.time) ? state.time.end : void 0) ?? fallbackEnd;
1504
+ const result = errored ? state.error : state.output;
1505
+ const callId = firstString(part.callID, part.id) ?? part.id;
1506
+ return {
1507
+ id: spanIdFromSeed(traceId, 'opencode-tool', callId),
1508
+ parentId: rootSpanId,
1509
+ name: TOOL_SPAN_NAME,
1510
+ kind: 'client',
1511
+ type: 'tool',
1512
+ startedAt: start,
1513
+ endedAt: Math.max(start, end),
1514
+ status: errored ? 'error' : 'unset',
1515
+ input: safeContent(state.input, captureContent),
1516
+ output: safeContent(result, captureContent),
1517
+ attributes: compactRecord({
1518
+ ...common,
1519
+ 'gen_ai.tool.name': part.tool,
1520
+ 'gen_ai.tool.call.id': callId,
1521
+ 'gen_ai.tool.call.arguments': safeContent(state.input, captureContent),
1522
+ 'gen_ai.tool.call.result': safeContent(result, captureContent),
1523
+ 'tool.error': errored || void 0,
1524
+ 'error.message': errored ? state.error : void 0
1525
+ })
1526
+ };
1527
+ }
1528
+ function assistantMessages(input) {
1529
+ return input.messages.filter((message)=>'assistant' === roleOf(message));
1530
+ }
1531
+ function userMessages(input) {
1532
+ return input.messages.filter((message)=>'user' === roleOf(message));
1533
+ }
1534
+ function finalAssistantMessage(input) {
1535
+ return [
1536
+ ...assistantMessages(input)
1537
+ ].reverse()[0];
1538
+ }
1539
+ function opencode_commonAttributes(input, pluginVersion) {
1540
+ const agentMessage = finalAssistantMessage(input) ?? input.messages[0];
1541
+ return compactRecord({
1542
+ 'tls.app.type': TLS_APP_TYPE,
1543
+ 'tls.plugin.version': pluginVersion,
1544
+ 'session.id': input.session.id,
1545
+ 'gen_ai.conversation.id': input.session.id,
1546
+ 'agent.name': firstString(agentMessage?.info?.agent, agentMessage?.info?.mode),
1547
+ 'agent.version': input.session.version ?? pluginVersion,
1548
+ 'user.name': external_node_os_userInfo().username
1549
+ });
1550
+ }
1551
+ function detectOpenCodeInput(input) {
1552
+ return opencode_isRecord(input) && 'opencode' === input.source && 'string' == typeof input.turnId;
1553
+ }
1554
+ async function loadOpenCodeTurn(input) {
1555
+ if (!detectOpenCodeInput(input)) throw new Error('OpenCode adapter requires a turn snapshot');
1556
+ return input;
1557
+ }
1558
+ async function toCanonicalOpenCodeTrace(input, context = {}) {
1559
+ const captureContent = captureEnabled(input, context);
1560
+ const pluginVersion = input.pluginVersion ?? context.version ?? '0.0.0';
1561
+ const now = context.now?.() ?? Date.now();
1562
+ const traceId = model_traceIdFromSeed(`opencode:${input.session.id}:${input.turnId}`);
1563
+ const rootSpanId = spanIdFromSeed(traceId, 'opencode-turn', input.turnId);
1564
+ const users = userMessages(input);
1565
+ const assistants = assistantMessages(input);
1566
+ const start = users[0] ? messageStart(users[0], now) : now;
1567
+ const endCandidates = assistants.map((message)=>messageEnd(message, start));
1568
+ const end = Math.max(start, input.completedAt ?? 0, ...endCandidates);
1569
+ const common = opencode_commonAttributes(input, pluginVersion);
1570
+ const llmUsageRecords = assistants.map((message)=>usageFromStep(stepFinish(message)));
1571
+ const lastModel = modelInfo(finalAssistantMessage(input) ?? assistants[0] ?? input.messages[0]);
1572
+ const inputMessages = toGenAiMessages(users, captureContent);
1573
+ const outputMessages = toGenAiMessages(assistants, captureContent);
1574
+ const root = {
1575
+ id: rootSpanId,
1576
+ name: ROOT_SPAN_NAME,
1577
+ kind: 'server',
1578
+ type: 'agent',
1579
+ startedAt: start,
1580
+ endedAt: end,
1581
+ status: 'unset',
1582
+ input: {
1583
+ messages: inputMessages
1584
+ },
1585
+ output: {
1586
+ messages: outputMessages
1587
+ },
1588
+ attributes: compactRecord({
1589
+ ...common,
1590
+ ...sumUsage(llmUsageRecords),
1591
+ 'gen_ai.operation.name': 'invoke_agent',
1592
+ 'gen_ai.request.model': lastModel.model,
1593
+ 'gen_ai.provider.name': lastModel.provider,
1594
+ 'gen_ai.input.messages': inputMessages,
1595
+ 'gen_ai.output.messages': outputMessages
1596
+ })
1597
+ };
1598
+ const spans = [];
1599
+ assistants.forEach((message, index)=>{
1600
+ const llmStart = messageStart(message, start);
1601
+ const llmEnd = Math.max(llmStart, messageEnd(message, llmStart));
1602
+ const model = modelInfo(message);
1603
+ const llmInputMessages = [
1604
+ ...systemMessages(message),
1605
+ ...toGenAiMessages(currentTurnMessagesBefore(input.messages, message), captureContent)
1606
+ ];
1607
+ const llmOutputMessages = toGenAiMessages([
1608
+ message
1609
+ ], captureContent);
1610
+ const llmSpanId = spanIdFromSeed(traceId, 'opencode-llm', message.info?.id ?? index);
1611
+ spans.push({
1612
+ id: llmSpanId,
1613
+ parentId: rootSpanId,
1614
+ name: LLM_SPAN_NAME,
1615
+ kind: 'client',
1616
+ type: 'model',
1617
+ startedAt: llmStart,
1618
+ endedAt: llmEnd,
1619
+ status: 'unset',
1620
+ input: {
1621
+ messages: llmInputMessages
1622
+ },
1623
+ output: {
1624
+ messages: llmOutputMessages
1625
+ },
1626
+ attributes: compactRecord({
1627
+ ...common,
1628
+ ...usageFromStep(stepFinish(message)),
1629
+ 'gen_ai.operation.name': 'chat',
1630
+ 'gen_ai.request.model': model.model,
1631
+ 'gen_ai.provider.name': model.provider,
1632
+ 'gen_ai.input.messages': llmInputMessages,
1633
+ 'gen_ai.output.messages': llmOutputMessages
1634
+ })
1635
+ });
1636
+ for (const part of toolParts(message))spans.push(toolSpan(traceId, llmSpanId, message, part, llmStart, llmEnd, common, captureContent));
1637
+ });
1638
+ return {
1639
+ source: {
1640
+ tool: 'opencode',
1641
+ sessionId: input.session.id,
1642
+ turnId: input.turnId,
1643
+ threadId: input.session.id,
1644
+ conversationId: input.session.id,
1645
+ cwd: firstString(common['opencode.cwd']),
1646
+ model: lastModel.model,
1647
+ attributes: compactRecord({
1648
+ 'opencode.parent_session_id': input.session.parentID,
1649
+ 'opencode.session_title': input.session.title
1650
+ })
1651
+ },
1652
+ root,
1653
+ spans,
1654
+ messages: toTraceMessages(input.messages, captureContent),
1655
+ metrics: {
1656
+ inputTokens: numberValue(root.attributes?.['gen_ai.usage.input_tokens']),
1657
+ outputTokens: numberValue(root.attributes?.['gen_ai.usage.output_tokens']),
1658
+ totalTokens: numberValue(root.attributes?.['gen_ai.usage.total_tokens']),
1659
+ toolCallCount: spans.filter((span)=>'tool' === span.type).length,
1660
+ latencyMs: Math.max(0, end - start)
1661
+ },
1662
+ diagnostics: {
1663
+ warnings: [],
1664
+ parser: {
1665
+ assistant_message_count: assistants.length,
1666
+ user_message_count: users.length
1667
+ }
1668
+ }
1669
+ };
1670
+ }
1671
+ const opencodeAdapter = {
1672
+ name: 'opencode',
1673
+ detect: detectOpenCodeInput,
1674
+ load: loadOpenCodeTurn,
1675
+ toCanonical: toCanonicalOpenCodeTrace
1676
+ };
1677
+ function tracer_isRecord(value) {
1678
+ return null != value && 'object' == typeof value && !Array.isArray(value);
1679
+ }
1680
+ function stringValue(value) {
1681
+ return 'string' == typeof value && value.length > 0 ? value : void 0;
1682
+ }
1683
+ function numericValue(value) {
1684
+ return 'number' == typeof value && Number.isFinite(value) ? value : void 0;
1685
+ }
1686
+ function eventTimestamp(properties) {
1687
+ const time = properties.time;
1688
+ if ('number' == typeof time && Number.isFinite(time)) return time;
1689
+ if (tracer_isRecord(time)) return numericValue(time.receivedAt);
1690
+ }
1691
+ function sessionIDOf(event) {
1692
+ const properties = event.properties ?? {};
1693
+ const direct = stringValue(properties.sessionID);
1694
+ if (direct) return direct;
1695
+ const info = tracer_isRecord(properties.info) ? properties.info : void 0;
1696
+ const fromInfo = stringValue(info?.id) ?? stringValue(info?.sessionID);
1697
+ if (fromInfo) return fromInfo;
1698
+ const part = tracer_isRecord(properties.part) ? properties.part : void 0;
1699
+ return stringValue(part?.sessionID);
1700
+ }
1701
+ function messageIDOf(message) {
1702
+ return stringValue(message.info?.id);
1703
+ }
1704
+ function messageRole(message) {
1705
+ return stringValue(message.info?.role);
1706
+ }
1707
+ function parentMessageID(message) {
1708
+ const info = message.info;
1709
+ return stringValue(info?.parentID);
1710
+ }
1711
+ function mergedParts(parts) {
1712
+ const byId = new Map();
1713
+ for (const part of parts){
1714
+ if (!part?.id) continue;
1715
+ const existing = byId.get(part.id) ?? {};
1716
+ byId.set(part.id, {
1717
+ ...existing,
1718
+ ...part
1719
+ });
1720
+ }
1721
+ return [
1722
+ ...byId.values()
1723
+ ];
1724
+ }
1725
+ function finishPart(message) {
1726
+ return mergedParts(message.parts).find((part)=>'step-finish' === part.type);
1727
+ }
1728
+ function messageIsComplete(message) {
1729
+ const role = messageRole(message);
1730
+ if ('user' === role) return message.parts.length > 0;
1731
+ if ('assistant' === role) return Boolean(finishPart(message));
1732
+ return false;
1733
+ }
1734
+ function turnIsReady(turn) {
1735
+ return turn.messages.some((message)=>{
1736
+ const part = finishPart(message);
1737
+ return part && 'stop' === part.reason;
1738
+ });
1739
+ }
1740
+ function toObservedHistory(items) {
1741
+ return items.map((item)=>({
1742
+ info: item.info,
1743
+ parts: item.parts.map((part)=>({
1744
+ ...part
1745
+ })),
1746
+ completed: true
1747
+ }));
1748
+ }
1749
+ function toAdapterMessage(message) {
1750
+ return {
1751
+ info: message.info,
1752
+ parts: mergedParts(message.parts),
1753
+ systemPrompt: message.promptContext ? {
1754
+ model: message.promptContext.model,
1755
+ system: message.promptContext.system ?? []
1756
+ } : void 0
1757
+ };
1758
+ }
1759
+ function observedSessionInfo(sessionID, session) {
1760
+ return {
1761
+ id: session.info?.id ?? sessionID,
1762
+ parentID: session.parentID ?? session.info?.parentID,
1763
+ directory: session.info?.directory,
1764
+ title: session.info?.title,
1765
+ version: session.info?.version
1766
+ };
1767
+ }
1768
+ function trackPendingExport(session, task) {
1769
+ session.pendingExports.push(task);
1770
+ task.finally(()=>{
1771
+ session.pendingExports = session.pendingExports.filter((pending)=>pending !== task);
1772
+ });
1773
+ }
1774
+ class OpenCodeTlsTracer {
1775
+ config;
1776
+ sessions = new Map();
1777
+ constructor(config){
1778
+ this.config = config;
1779
+ }
1780
+ getSession(sessionID) {
1781
+ const current = this.sessions.get(sessionID);
1782
+ if (current) return current;
1783
+ const created = {
1784
+ sessionID,
1785
+ messages: new Map(),
1786
+ turns: new Map(),
1787
+ pendingExports: []
1788
+ };
1789
+ this.sessions.set(sessionID, created);
1790
+ return created;
1791
+ }
1792
+ getMessage(sessionID, messageID) {
1793
+ const session = this.getSession(sessionID);
1794
+ const current = session.messages.get(messageID);
1795
+ if (current) return current;
1796
+ const created = {
1797
+ parts: [],
1798
+ completed: false
1799
+ };
1800
+ session.messages.set(messageID, created);
1801
+ return created;
1802
+ }
1803
+ async loadHistory(sessionID, loader, options = {}) {
1804
+ const session = this.getSession(sessionID);
1805
+ if (session.history && !options.refresh) return;
1806
+ try {
1807
+ session.history = toObservedHistory(await loader(sessionID));
1808
+ } catch {
1809
+ session.history = [];
1810
+ }
1811
+ }
1812
+ updateSessionInfo(sessionID, info) {
1813
+ const session = this.getSession(sessionID);
1814
+ session.parentID = stringValue(info.parentID) ?? session.parentID;
1815
+ session.info = {
1816
+ id: stringValue(info.id) ?? sessionID,
1817
+ parentID: stringValue(info.parentID),
1818
+ directory: stringValue(info.directory),
1819
+ title: stringValue(info.title),
1820
+ version: stringValue(info.version)
1821
+ };
1822
+ }
1823
+ attachPromptContextIfReady(sessionID, message) {
1824
+ const session = this.getSession(sessionID);
1825
+ if (!session.pendingSystemPrompt || 'assistant' !== messageRole(message)) return;
1826
+ message.promptContext = session.pendingSystemPrompt;
1827
+ session.pendingSystemPrompt = void 0;
1828
+ }
1829
+ registerCompletedMessage(sessionID, message) {
1830
+ if (!messageIsComplete(message) || message.completed) return;
1831
+ message.completed = true;
1832
+ const role = messageRole(message);
1833
+ const turnId = 'user' === role ? messageIDOf(message) : parentMessageID(message);
1834
+ if (!turnId) return;
1835
+ const session = this.getSession(sessionID);
1836
+ const turn = session.turns.get(turnId) ?? {
1837
+ turnId,
1838
+ messages: [],
1839
+ exportState: 'open'
1840
+ };
1841
+ if (!turn.messages.includes(message)) turn.messages.push(message);
1842
+ if (turnIsReady(turn) && 'open' === turn.exportState) turn.exportState = 'ready';
1843
+ session.turns.set(turnId, turn);
1844
+ return turn;
1845
+ }
1846
+ exportTurn(sessionID, turn) {
1847
+ if ('ready' !== turn.exportState) return;
1848
+ turn.exportState = 'exporting';
1849
+ const session = this.getSession(sessionID);
1850
+ const snapshot = {
1851
+ source: 'opencode',
1852
+ session: observedSessionInfo(sessionID, session),
1853
+ turnId: turn.turnId,
1854
+ messages: turn.messages.map(toAdapterMessage),
1855
+ history: session.history?.map(toAdapterMessage) ?? [],
1856
+ captureContent: this.config.captureContent,
1857
+ pluginVersion: this.config.pluginVersion
1858
+ };
1859
+ const task = runTracePipeline(snapshot, opencodeAdapter, {
1860
+ env: this.config.env,
1861
+ dataRoot: this.config.dataRoot,
1862
+ sourceName: 'opencode',
1863
+ source: {
1864
+ tool: 'opencode',
1865
+ sessionId: snapshot.session.id,
1866
+ turnId: snapshot.turnId,
1867
+ threadId: snapshot.session.id
1868
+ },
1869
+ otel: {
1870
+ serviceName: this.config.serviceName,
1871
+ sdkVersion: this.config.pluginVersion,
1872
+ scopeName: 'tls-observer-opencode',
1873
+ scopeVersion: this.config.pluginVersion,
1874
+ resourceAttributes: {
1875
+ 'service.instance.id': this.config.serviceInstanceId,
1876
+ 'tls.app.type': 'opencode'
1877
+ }
1878
+ },
1879
+ failedTraceCache: {
1880
+ maxRetryCount: 1
1881
+ }
1882
+ }).then(()=>{
1883
+ turn.exportState = 'exported';
1884
+ }).catch(()=>{
1885
+ turn.exportState = 'ready';
1886
+ });
1887
+ trackPendingExport(session, task);
1888
+ }
1889
+ async handleSystemPrompt(input, output, loader) {
1890
+ if (!input.sessionID) return;
1891
+ await this.loadHistory(input.sessionID, loader);
1892
+ const session = this.getSession(input.sessionID);
1893
+ session.pendingSystemPrompt = {
1894
+ model: input.model,
1895
+ system: output.system
1896
+ };
1897
+ }
1898
+ async handleEvent(event, loader) {
1899
+ if ('server.instance.disposed' === event.type) return void await this.flush();
1900
+ const sessionID = sessionIDOf(event);
1901
+ if (!sessionID) return;
1902
+ await this.loadHistory(sessionID, loader);
1903
+ const session = this.getSession(sessionID);
1904
+ const properties = event.properties ?? {};
1905
+ let changedMessageID;
1906
+ if (('session.created' === event.type || 'session.updated' === event.type) && tracer_isRecord(properties.info)) this.updateSessionInfo(sessionID, properties.info);
1907
+ if ('message.updated' === event.type && tracer_isRecord(properties.info)) {
1908
+ const id = stringValue(properties.info.id);
1909
+ if (id) {
1910
+ const message = this.getMessage(sessionID, id);
1911
+ message.info = properties.info;
1912
+ this.attachPromptContextIfReady(sessionID, message);
1913
+ changedMessageID = id;
1914
+ }
1915
+ }
1916
+ if ('message.part.updated' === event.type && tracer_isRecord(properties.part)) {
1917
+ const part = properties.part;
1918
+ const messageID = stringValue(part.messageID);
1919
+ if (messageID) {
1920
+ const message = this.getMessage(sessionID, messageID);
1921
+ message.parts.push({
1922
+ ...part,
1923
+ receivedAt: eventTimestamp(properties)
1924
+ });
1925
+ changedMessageID = messageID;
1926
+ }
1927
+ }
1928
+ if ('message.part.removed' === event.type) {
1929
+ const messageID = stringValue(properties.messageID);
1930
+ const partID = stringValue(properties.partID);
1931
+ if (messageID && partID) {
1932
+ const message = this.getMessage(sessionID, messageID);
1933
+ message.parts = message.parts.filter((part)=>part.id !== partID);
1934
+ changedMessageID = messageID;
1935
+ }
1936
+ }
1937
+ if ('message.removed' === event.type) {
1938
+ const messageID = stringValue(properties.messageID);
1939
+ if (messageID) {
1940
+ session.messages.delete(messageID);
1941
+ for (const turn of session.turns.values())turn.messages = turn.messages.filter((message)=>messageIDOf(message) !== messageID);
1942
+ }
1943
+ }
1944
+ if (changedMessageID) {
1945
+ const message = session.messages.get(changedMessageID);
1946
+ if (message) {
1947
+ const turn = this.registerCompletedMessage(sessionID, message);
1948
+ if (turn?.exportState === 'ready') this.exportTurn(sessionID, turn);
1949
+ }
1950
+ }
1951
+ if ('session.idle' === event.type) await this.flush(sessionID);
1952
+ if ('session.deleted' === event.type) {
1953
+ await this.flush(sessionID);
1954
+ this.sessions.delete(sessionID);
1955
+ }
1956
+ }
1957
+ async flush(sessionID) {
1958
+ const sessions = sessionID ? [
1959
+ this.getSession(sessionID)
1960
+ ] : [
1961
+ ...this.sessions.values()
1962
+ ];
1963
+ await Promise.allSettled(sessions.flatMap((session)=>session.pendingExports));
1964
+ }
1965
+ get observedSessionCount() {
1966
+ return this.sessions.size;
1967
+ }
1968
+ }
1969
+ async function loadSessionMessages(ctx, sessionID) {
1970
+ const result = await ctx.client.session.messages({
1971
+ path: {
1972
+ id: sessionID
1973
+ }
1974
+ });
1975
+ if (result.error) throw result.error;
1976
+ return result.data;
1977
+ }
1978
+ const TlsObserverOpenCodePlugin = async (ctx)=>{
1979
+ const config = loadConfig();
1980
+ if (!config.enabled) return {};
1981
+ const tracer = new OpenCodeTlsTracer(config);
1982
+ const history = (sessionID)=>loadSessionMessages(ctx, sessionID);
1983
+ return {
1984
+ 'experimental.chat.system.transform': async (input, output)=>{
1985
+ await tracer.handleSystemPrompt(input, output, history);
1986
+ },
1987
+ event: async ({ event })=>{
1988
+ await tracer.handleEvent(event, history);
1989
+ },
1990
+ dispose: async ()=>{
1991
+ await tracer.flush();
1992
+ }
1993
+ };
1994
+ };
1995
+ const src = TlsObserverOpenCodePlugin;
1996
+ export default src;
1997
+ export { OpenCodeTlsTracer, TlsObserverOpenCodePlugin, loadConfig };