@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.
- package/dist/backfill.js +113 -6
- 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 = "
|
|
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:
|
|
692
|
-
name: `opencode ${
|
|
693
|
-
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 `${
|
|
962
|
+
return `${importIdentity(event)}:${event.agent}:${event.sessionId}:${event.recordId}`;
|
|
856
963
|
}
|
|
857
964
|
function traceFingerprint(event) {
|
|
858
|
-
return `${
|
|
965
|
+
return `${importIdentity(event)}:${event.agent}:${event.sessionId}`;
|
|
859
966
|
}
|
|
860
967
|
function traceId(event) {
|
|
861
968
|
return stableId(traceFingerprint(event));
|