@ramarivera/coding-agent-langfuse 0.1.17 → 0.1.18

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.
Files changed (2) hide show
  1. package/dist/backfill.js +87 -12
  2. package/package.json +1 -1
package/dist/backfill.js CHANGED
@@ -5,7 +5,7 @@ import { existsSync, mkdirSync, readdirSync, readFileSync, statSync, writeFileSy
5
5
  import { homedir } from "node:os";
6
6
  import { dirname, join } from "node:path";
7
7
  const allAgents = ["claude", "codex", "grok", "opencode", "pi"];
8
- const importIdentityVersion = "v8-cached-input-token-split";
8
+ const importIdentityVersion = "v9-codex-conversation-events";
9
9
  const defaultEndpoint = "https://langfuse.ai.roxasroot.net/otel/v1/traces";
10
10
  const deadRemoteEndpoint = "http://langfuse.ai.roxasroot.net:14318/v1/traces";
11
11
  const defaultStatePath = join(homedir(), ".local/state/coding-agent-langfuse/backfill-v6.json");
@@ -229,7 +229,10 @@ function extractText(value, maxLength = 4000) {
229
229
  const text = value
230
230
  .map((item) => {
231
231
  const record = asRecord(item);
232
- return asString(record.text) ?? asString(record.content) ?? "";
232
+ return asString(record.text) ??
233
+ extractText(record.content, maxLength) ??
234
+ extractText(record.summary, maxLength) ??
235
+ "";
233
236
  })
234
237
  .filter(Boolean)
235
238
  .join("\n");
@@ -363,7 +366,8 @@ function costDetails(usage, model) {
363
366
  return undefined;
364
367
  }
365
368
  function isGenerationEvent(event) {
366
- return event.usage !== undefined && event.role !== "user";
369
+ return event.usage !== undefined && event.role !== "user" &&
370
+ event.role !== "developer" && event.role !== "system";
367
371
  }
368
372
  function codexEvents(homeDir) {
369
373
  const files = listFiles(join(homeDir, ".codex/sessions"), (path) => path.endsWith(".jsonl"));
@@ -378,6 +382,7 @@ function codexEvents(homeDir) {
378
382
  asString(getPath(payload, ["model"]));
379
383
  let currentModel = model;
380
384
  let currentCwd = cwd;
385
+ let currentTurnRecordId = "session";
381
386
  const seenTokenCounts = new Set();
382
387
  const events = [
383
388
  {
@@ -406,11 +411,12 @@ function codexEvents(homeDir) {
406
411
  if (type === "turn_context") {
407
412
  currentModel = asString(rowPayload.model) ?? currentModel;
408
413
  currentCwd = asString(rowPayload.cwd) ?? currentCwd;
414
+ currentTurnRecordId = `turn-${asString(rowPayload.turn_id) ?? index}`;
409
415
  events.push({
410
416
  agent: "codex",
411
417
  sourcePath: path,
412
418
  sessionId,
413
- recordId: `turn-${asString(rowPayload.turn_id) ?? index}`,
419
+ recordId: currentTurnRecordId,
414
420
  name: "codex turn",
415
421
  cwd: currentCwd,
416
422
  model: currentModel,
@@ -420,34 +426,99 @@ function codexEvents(homeDir) {
420
426
  });
421
427
  }
422
428
  if (type === "response_item" && itemType === "message") {
429
+ const role = asString(rowPayload.role);
430
+ const text = extractText(rowPayload.content);
431
+ const input = role === "user" || role === "developer" ||
432
+ role === "system"
433
+ ? text
434
+ : undefined;
435
+ const output = role === "assistant" ? text : undefined;
423
436
  events.push({
424
437
  agent: "codex",
425
438
  sourcePath: path,
426
439
  sessionId,
427
440
  recordId: `message-${asString(rowPayload.id) ?? index}`,
428
- name: `codex ${asString(rowPayload.role) ?? "message"}`,
429
- role: asString(rowPayload.role),
441
+ name: `codex ${role ?? "message"}`,
442
+ role,
430
443
  model: currentModel,
431
444
  cwd: currentCwd,
432
445
  startMs: timestamp,
433
- parentRecordId: "session",
434
- output: extractText(rowPayload.content),
446
+ parentRecordId: currentTurnRecordId,
447
+ input,
448
+ output,
435
449
  usage: normalizeUsage(rowPayload.usage),
450
+ metadata: pick(rowPayload, ["phase"]),
451
+ });
452
+ }
453
+ if (type === "response_item" && itemType === "reasoning") {
454
+ events.push({
455
+ agent: "codex",
456
+ sourcePath: path,
457
+ sessionId,
458
+ recordId: `reasoning-${index}`,
459
+ name: "codex reasoning",
460
+ model: currentModel,
461
+ cwd: currentCwd,
462
+ startMs: timestamp,
463
+ parentRecordId: currentTurnRecordId,
464
+ output: extractText(rowPayload.content) ??
465
+ extractText(rowPayload.summary),
466
+ metadata: {
467
+ has_encrypted_content: rowPayload.encrypted_content !== undefined,
468
+ },
436
469
  });
437
470
  }
438
471
  if (type === "response_item" && itemType === "function_call") {
472
+ const callId = asString(rowPayload.call_id) ?? `${index}`;
439
473
  events.push({
440
474
  agent: "codex",
441
475
  sourcePath: path,
442
476
  sessionId,
443
- recordId: `tool-${asString(rowPayload.call_id) ?? index}`,
477
+ recordId: `tool-${callId}`,
444
478
  name: `codex tool ${asString(rowPayload.name) ?? "call"}`,
445
479
  model: currentModel,
446
480
  cwd: currentCwd,
447
481
  startMs: timestamp,
448
- parentRecordId: "session",
482
+ parentRecordId: currentTurnRecordId,
449
483
  input: rowPayload.arguments,
450
- metadata: pick(rowPayload, ["name", "call_id"]),
484
+ metadata: pick(rowPayload, ["name", "namespace", "call_id"]),
485
+ });
486
+ }
487
+ if (type === "response_item" && itemType === "function_call_output") {
488
+ const callId = asString(rowPayload.call_id) ?? `${index}`;
489
+ events.push({
490
+ agent: "codex",
491
+ sourcePath: path,
492
+ sessionId,
493
+ recordId: `tool-result-${callId}`,
494
+ name: "codex tool result",
495
+ model: currentModel,
496
+ cwd: currentCwd,
497
+ startMs: timestamp,
498
+ parentRecordId: `tool-${callId}`,
499
+ output: rowPayload.output,
500
+ metadata: pick(rowPayload, ["call_id", "status", "execution"]),
501
+ });
502
+ }
503
+ if (type === "response_item" &&
504
+ (itemType === "tool_search_call" || itemType === "tool_search_output")) {
505
+ const callId = asString(rowPayload.call_id) ?? `${index}`;
506
+ const isOutput = itemType === "tool_search_output";
507
+ events.push({
508
+ agent: "codex",
509
+ sourcePath: path,
510
+ sessionId,
511
+ recordId: `${isOutput ? "tool-search-result" : "tool-search"}-${callId}`,
512
+ name: isOutput ? "codex tool_search result" : "codex tool_search",
513
+ model: currentModel,
514
+ cwd: currentCwd,
515
+ startMs: timestamp,
516
+ parentRecordId: isOutput
517
+ ? `tool-search-${callId}`
518
+ : currentTurnRecordId,
519
+ input: isOutput ? undefined : rowPayload.arguments,
520
+ output: isOutput ? rowPayload.tools : undefined,
521
+ metadata: pick(rowPayload, ["call_id", "status", "execution"]),
451
522
  });
452
523
  }
453
524
  if (type === "event_msg" && rowPayload.type === "token_count") {
@@ -862,6 +933,7 @@ function toOtlp(events) {
862
933
  attributes: rootAttributes,
863
934
  status: { code: 1 },
864
935
  };
936
+ const spanIdsByRecordId = new Map(sortedEvents.map((event) => [event.recordId, spanId(event)]));
865
937
  const childSpans = sortedEvents.map((event) => {
866
938
  const startMs = event.startMs;
867
939
  const durationMs = Math.max(1, (event.endMs ?? event.startMs + 1) - event.startMs);
@@ -923,7 +995,10 @@ function toOtlp(events) {
923
995
  return {
924
996
  traceId: traceId(event),
925
997
  spanId: spanId(event),
926
- parentSpanId: rootSpanId(event),
998
+ parentSpanId: event.parentRecordId &&
999
+ event.parentRecordId !== "session"
1000
+ ? spanIdsByRecordId.get(event.parentRecordId) ?? rootSpanId(event)
1001
+ : rootSpanId(event),
927
1002
  name: event.name,
928
1003
  kind: 1,
929
1004
  startTimeUnixNano: ns(startMs),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ramarivera/coding-agent-langfuse",
3
- "version": "0.1.17",
3
+ "version": "0.1.18",
4
4
  "description": "Universal coding-agent Langfuse backfiller and live OTLP helpers",
5
5
  "type": "module",
6
6
  "license": "MIT",