@ramarivera/coding-agent-langfuse 0.1.18 → 0.1.19

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 +113 -6
  2. package/package.json +1 -1
package/dist/backfill.js CHANGED
@@ -5,7 +5,11 @@ 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 = "v9-codex-conversation-events";
8
+ const importIdentityVersion = "v8-cached-input-token-split";
9
+ const importIdentityVersions = {
10
+ codex: "v9-codex-conversation-events",
11
+ opencode: "v10-opencode-message-parts",
12
+ };
9
13
  const defaultEndpoint = "https://langfuse.ai.roxasroot.net/otel/v1/traces";
10
14
  const deadRemoteEndpoint = "http://langfuse.ai.roxasroot.net:14318/v1/traces";
11
15
  const defaultStatePath = join(homedir(), ".local/state/coding-agent-langfuse/backfill-v6.json");
@@ -635,6 +639,7 @@ function opencodeEvents(homeDir, rowLimit) {
635
639
  return [];
636
640
  let sessions = [];
637
641
  let messages = [];
642
+ let parts = [];
638
643
  try {
639
644
  sessions = sqliteJsonByRowid(db, "session", "id, directory, time_created, time_updated, title, version, slug, project_id", undefined, rowLimit, 5_000);
640
645
  messages = sqliteJsonByRowid(db, "message", [
@@ -656,12 +661,31 @@ function opencodeEvents(homeDir, rowLimit) {
656
661
  "json_extract(data, '$.mode') as mode",
657
662
  "json_extract(data, '$.error') as error",
658
663
  ].join(", "), undefined, rowLimit, 5_000);
664
+ parts = sqliteJsonByRowid(db, "part", [
665
+ "id",
666
+ "message_id",
667
+ "session_id",
668
+ "time_created",
669
+ "time_updated",
670
+ "json_extract(data, '$.type') as type",
671
+ "json_extract(data, '$') as data",
672
+ ].join(", "), undefined, rowLimit, 5_000);
659
673
  }
660
674
  catch (error) {
661
675
  console.error(`Skipping OpenCode history from ${db}: ${error instanceof Error ? error.message : String(error)}`);
662
676
  return [];
663
677
  }
664
678
  const sessionsById = new Map(sessions.map((row) => [asString(row.id), row]));
679
+ const partsByMessageId = new Map();
680
+ for (const part of parts) {
681
+ const messageId = asString(part.message_id);
682
+ if (!messageId)
683
+ continue;
684
+ partsByMessageId.set(messageId, [
685
+ ...(partsByMessageId.get(messageId) ?? []),
686
+ part,
687
+ ]);
688
+ }
665
689
  const events = [];
666
690
  for (const session of sessions) {
667
691
  const sessionId = asString(session.id);
@@ -682,15 +706,21 @@ function opencodeEvents(homeDir, rowLimit) {
682
706
  for (const message of messages) {
683
707
  const sessionId = asString(message.session_id);
684
708
  const session = sessionsById.get(sessionId);
709
+ const messageId = asString(message.id) ?? stableId(JSON.stringify(message));
710
+ const role = asString(message.role);
711
+ const messageParts = [...(partsByMessageId.get(messageId) ?? [])].sort((a, b) => getTimestampMs(a.time_created) - getTimestampMs(b.time_created));
712
+ const textOutput = opencodeTextFromParts(messageParts);
713
+ const textInput = role === "user" ? textOutput : undefined;
714
+ const output = role === "assistant" ? textOutput : undefined;
685
715
  const tokens = normalizeUsage(parseMaybeJson(message.tokens));
686
716
  const usage = tokens ?? normalizeUsage(parseMaybeJson(message.usage));
687
717
  events.push({
688
718
  agent: "opencode",
689
719
  sourcePath: db,
690
720
  sessionId: sessionId ?? stableId(db),
691
- recordId: asString(message.id) ?? stableId(JSON.stringify(message)),
692
- name: `opencode ${asString(message.role) ?? "message"}`,
693
- role: asString(message.role),
721
+ recordId: messageId,
722
+ name: `opencode ${role ?? "message"}`,
723
+ role,
694
724
  model: asString(message.model_id) ?? asString(message.nested_model_id),
695
725
  provider: asString(message.provider_id) ??
696
726
  asString(message.nested_provider_id),
@@ -699,12 +729,86 @@ function opencodeEvents(homeDir, rowLimit) {
699
729
  startMs: getTimestampMs(message.time_created),
700
730
  endMs: getTimestampMs(message.time_updated),
701
731
  parentRecordId: asString(message.parent_id) ?? "session",
732
+ input: textInput,
733
+ output,
702
734
  usage,
703
735
  metadata: pick(message, ["agent", "mode", "error"]),
704
736
  });
737
+ for (const part of messageParts) {
738
+ const data = asRecord(parseMaybeJson(part.data));
739
+ const type = asString(part.type) ?? asString(data.type);
740
+ const partId = asString(part.id) ?? stableId(JSON.stringify(part));
741
+ const partStartMs = getTimestampMs(part.time_created, getTimestampMs(message.time_created));
742
+ if (type === "reasoning") {
743
+ events.push({
744
+ agent: "opencode",
745
+ sourcePath: db,
746
+ sessionId: sessionId ?? stableId(db),
747
+ recordId: `reasoning-${partId}`,
748
+ name: "opencode reasoning",
749
+ model: asString(message.model_id) ?? asString(message.nested_model_id),
750
+ provider: asString(message.provider_id) ??
751
+ asString(message.nested_provider_id),
752
+ cwd: asString(message.cwd) ??
753
+ asString(asRecord(session).directory),
754
+ startMs: partStartMs,
755
+ endMs: getTimestampMs(part.time_updated, partStartMs + 1),
756
+ parentRecordId: messageId,
757
+ output: extractText(data.text),
758
+ metadata: {
759
+ has_encrypted_content: getPath(data, ["metadata", "openai", "reasoningEncryptedContent"]) !== undefined,
760
+ },
761
+ });
762
+ }
763
+ if (type === "tool") {
764
+ const state = asRecord(data.state);
765
+ const callId = asString(data.callID) ?? asString(data.call_id) ?? partId;
766
+ events.push({
767
+ agent: "opencode",
768
+ sourcePath: db,
769
+ sessionId: sessionId ?? stableId(db),
770
+ recordId: `tool-${callId}`,
771
+ name: `opencode tool ${asString(data.tool) ?? "call"}`,
772
+ model: asString(message.model_id) ?? asString(message.nested_model_id),
773
+ provider: asString(message.provider_id) ??
774
+ asString(message.nested_provider_id),
775
+ cwd: asString(message.cwd) ??
776
+ asString(asRecord(session).directory),
777
+ startMs: partStartMs,
778
+ endMs: getTimestampMs(part.time_updated, partStartMs + 1),
779
+ parentRecordId: messageId,
780
+ input: state.input,
781
+ output: state.output,
782
+ metadata: {
783
+ status: asString(state.status),
784
+ title: asString(state.title),
785
+ },
786
+ });
787
+ }
788
+ }
705
789
  }
706
790
  return events;
707
791
  }
792
+ function opencodeTextFromParts(parts) {
793
+ const text = parts
794
+ .map((part) => {
795
+ const data = asRecord(parseMaybeJson(part.data));
796
+ const type = asString(part.type) ?? asString(data.type);
797
+ if (type === "text")
798
+ return extractText(data.text, 8000);
799
+ if (type === "file") {
800
+ const filename = asString(data.filename);
801
+ const url = asString(data.url);
802
+ if (filename && url)
803
+ return `${filename}\n${url}`;
804
+ return filename ?? url;
805
+ }
806
+ return undefined;
807
+ })
808
+ .filter((value) => Boolean(value))
809
+ .join("\n");
810
+ return text ? text.slice(0, 8000) : undefined;
811
+ }
708
812
  function sqliteJson(db, sql) {
709
813
  const output = execFileSync("sqlite3", ["-readonly", "-json", db, sql], {
710
814
  encoding: "utf8",
@@ -851,11 +955,14 @@ function parseMaybeJson(value) {
851
955
  function stableId(input) {
852
956
  return createHash("sha256").update(input).digest("hex").slice(0, 32);
853
957
  }
958
+ function importIdentity(event) {
959
+ return importIdentityVersions[event.agent] ?? importIdentityVersion;
960
+ }
854
961
  function fingerprint(event) {
855
- return `${importIdentityVersion}:${event.agent}:${event.sessionId}:${event.recordId}`;
962
+ return `${importIdentity(event)}:${event.agent}:${event.sessionId}:${event.recordId}`;
856
963
  }
857
964
  function traceFingerprint(event) {
858
- return `${importIdentityVersion}:${event.agent}:${event.sessionId}`;
965
+ return `${importIdentity(event)}:${event.agent}:${event.sessionId}`;
859
966
  }
860
967
  function traceId(event) {
861
968
  return stableId(traceFingerprint(event));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ramarivera/coding-agent-langfuse",
3
- "version": "0.1.18",
3
+ "version": "0.1.19",
4
4
  "description": "Universal coding-agent Langfuse backfiller and live OTLP helpers",
5
5
  "type": "module",
6
6
  "license": "MIT",