@ramarivera/coding-agent-langfuse 0.1.19 → 0.1.21
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.d.ts +5 -1
- package/dist/backfill.js +104 -25
- package/package.json +1 -1
package/dist/backfill.d.ts
CHANGED
|
@@ -62,7 +62,11 @@ declare function codexEvents(homeDir: string): BackfillEvent[];
|
|
|
62
62
|
declare function claudeEvents(homeDir: string): BackfillEvent[];
|
|
63
63
|
declare function piEvents(homeDir: string): BackfillEvent[];
|
|
64
64
|
declare function grokEvents(homeDir: string): BackfillEvent[];
|
|
65
|
-
declare function opencodeEvents(homeDir: string,
|
|
65
|
+
declare function opencodeEvents(homeDir: string, options?: {
|
|
66
|
+
rowLimit?: number;
|
|
67
|
+
sinceMs?: number;
|
|
68
|
+
untilMs?: number;
|
|
69
|
+
}): BackfillEvent[];
|
|
66
70
|
declare function fingerprint(event: BackfillEvent): string;
|
|
67
71
|
declare function toOtlp(events: BackfillEvent[]): Record<string, unknown>;
|
|
68
72
|
declare function discoverEvents(options: BackfillOptions): BackfillEvent[];
|
package/dist/backfill.js
CHANGED
|
@@ -7,8 +7,11 @@ import { dirname, join } from "node:path";
|
|
|
7
7
|
const allAgents = ["claude", "codex", "grok", "opencode", "pi"];
|
|
8
8
|
const importIdentityVersion = "v8-cached-input-token-split";
|
|
9
9
|
const importIdentityVersions = {
|
|
10
|
+
claude: "v11-tool-results",
|
|
10
11
|
codex: "v9-codex-conversation-events",
|
|
12
|
+
grok: "v11-chat-history-only",
|
|
11
13
|
opencode: "v10-opencode-message-parts",
|
|
14
|
+
pi: "v11-tool-results",
|
|
12
15
|
};
|
|
13
16
|
const defaultEndpoint = "https://langfuse.ai.roxasroot.net/otel/v1/traces";
|
|
14
17
|
const deadRemoteEndpoint = "http://langfuse.ai.roxasroot.net:14318/v1/traces";
|
|
@@ -590,33 +593,56 @@ function piEvents(homeDir) {
|
|
|
590
593
|
continue;
|
|
591
594
|
const message = asRecord(row.message);
|
|
592
595
|
const usage = normalizeUsage(message.usage);
|
|
596
|
+
const role = asString(message.role);
|
|
597
|
+
const messageId = asString(row.id) ?? `message-${index}`;
|
|
598
|
+
const toolCallId = asString(message.toolCallId);
|
|
593
599
|
events.push({
|
|
594
600
|
agent: "pi",
|
|
595
601
|
sourcePath: path,
|
|
596
602
|
sessionId,
|
|
597
|
-
recordId:
|
|
598
|
-
name:
|
|
599
|
-
|
|
603
|
+
recordId: messageId,
|
|
604
|
+
name: role === "toolResult"
|
|
605
|
+
? `pi toolResult ${asString(message.toolName) ?? "call"}`
|
|
606
|
+
: `pi ${role ?? "message"}`,
|
|
607
|
+
role,
|
|
600
608
|
model: asString(message.model) ?? asString(row.model),
|
|
601
609
|
provider: asString(message.provider),
|
|
602
610
|
cwd,
|
|
603
611
|
startMs: getTimestampMs(row.timestamp ?? message.timestamp, startMs + index),
|
|
604
612
|
endMs: getTimestampMs(message.completed, startMs + index + 1),
|
|
605
|
-
parentRecordId:
|
|
606
|
-
|
|
613
|
+
parentRecordId: toolCallId
|
|
614
|
+
? `tool-${toolCallId}`
|
|
615
|
+
: asString(row.parentId) ?? "session",
|
|
616
|
+
input: role === "user"
|
|
607
617
|
? extractText(message.content)
|
|
608
618
|
: undefined,
|
|
609
|
-
output:
|
|
619
|
+
output: role === "assistant" || role === "toolResult"
|
|
610
620
|
? extractText(message.content)
|
|
611
621
|
: undefined,
|
|
612
622
|
usage,
|
|
623
|
+
metadata: pick(message, ["toolCallId", "toolName", "isError", "stopReason"]),
|
|
613
624
|
});
|
|
625
|
+
for (const reasoning of reasoningFromContent(message.content)) {
|
|
626
|
+
events.push({
|
|
627
|
+
agent: "pi",
|
|
628
|
+
sourcePath: path,
|
|
629
|
+
sessionId,
|
|
630
|
+
recordId: `reasoning-${messageId}-${reasoning.index}`,
|
|
631
|
+
name: "pi reasoning",
|
|
632
|
+
cwd,
|
|
633
|
+
model: asString(message.model),
|
|
634
|
+
startMs: getTimestampMs(row.timestamp, startMs + index),
|
|
635
|
+
parentRecordId: messageId,
|
|
636
|
+
output: reasoning.text,
|
|
637
|
+
metadata: { has_signature: reasoning.hasSignature },
|
|
638
|
+
});
|
|
639
|
+
}
|
|
614
640
|
for (const tool of toolCallsFromContent(message.content)) {
|
|
615
641
|
events.push({
|
|
616
642
|
agent: "pi",
|
|
617
643
|
sourcePath: path,
|
|
618
644
|
sessionId,
|
|
619
|
-
recordId: tool.id
|
|
645
|
+
recordId: `tool-${tool.id}`,
|
|
620
646
|
name: `pi tool ${tool.name}`,
|
|
621
647
|
cwd,
|
|
622
648
|
model: asString(message.model),
|
|
@@ -630,18 +656,19 @@ function piEvents(homeDir) {
|
|
|
630
656
|
});
|
|
631
657
|
}
|
|
632
658
|
function grokEvents(homeDir) {
|
|
633
|
-
const files = listFiles(join(homeDir, ".grok/sessions"), (path) => path.endsWith(".jsonl"));
|
|
659
|
+
const files = listFiles(join(homeDir, ".grok/sessions"), (path) => path.endsWith("chat_history.jsonl"));
|
|
634
660
|
return genericJsonlEvents("grok", files, "grok session");
|
|
635
661
|
}
|
|
636
|
-
function opencodeEvents(homeDir,
|
|
662
|
+
function opencodeEvents(homeDir, options = {}) {
|
|
637
663
|
const db = join(homeDir, ".local/share/opencode/opencode.db");
|
|
638
664
|
if (!existsSync(db))
|
|
639
665
|
return [];
|
|
666
|
+
const timeWhere = sqliteTimeWhere(options.sinceMs, options.untilMs);
|
|
640
667
|
let sessions = [];
|
|
641
668
|
let messages = [];
|
|
642
669
|
let parts = [];
|
|
643
670
|
try {
|
|
644
|
-
sessions = sqliteJsonByRowid(db, "session", "id, directory, time_created, time_updated, title, version, slug, project_id", undefined, rowLimit, 5_000);
|
|
671
|
+
sessions = sqliteJsonByRowid(db, "session", "id, directory, time_created, time_updated, title, version, slug, project_id", undefined, options.rowLimit, 5_000);
|
|
645
672
|
messages = sqliteJsonByRowid(db, "message", [
|
|
646
673
|
"id",
|
|
647
674
|
"session_id",
|
|
@@ -660,7 +687,7 @@ function opencodeEvents(homeDir, rowLimit) {
|
|
|
660
687
|
"json_extract(data, '$.agent') as agent",
|
|
661
688
|
"json_extract(data, '$.mode') as mode",
|
|
662
689
|
"json_extract(data, '$.error') as error",
|
|
663
|
-
].join(", "),
|
|
690
|
+
].join(", "), timeWhere, options.rowLimit, 5_000);
|
|
664
691
|
parts = sqliteJsonByRowid(db, "part", [
|
|
665
692
|
"id",
|
|
666
693
|
"message_id",
|
|
@@ -669,7 +696,7 @@ function opencodeEvents(homeDir, rowLimit) {
|
|
|
669
696
|
"time_updated",
|
|
670
697
|
"json_extract(data, '$.type') as type",
|
|
671
698
|
"json_extract(data, '$') as data",
|
|
672
|
-
].join(", "),
|
|
699
|
+
].join(", "), timeWhere, options.rowLimit, 5_000);
|
|
673
700
|
}
|
|
674
701
|
catch (error) {
|
|
675
702
|
console.error(`Skipping OpenCode history from ${db}: ${error instanceof Error ? error.message : String(error)}`);
|
|
@@ -789,6 +816,13 @@ function opencodeEvents(homeDir, rowLimit) {
|
|
|
789
816
|
}
|
|
790
817
|
return events;
|
|
791
818
|
}
|
|
819
|
+
function sqliteTimeWhere(sinceMs, untilMs) {
|
|
820
|
+
const conditions = [
|
|
821
|
+
sinceMs === undefined ? undefined : `time_created >= ${Math.trunc(sinceMs)}`,
|
|
822
|
+
untilMs === undefined ? undefined : `time_created <= ${Math.trunc(untilMs)}`,
|
|
823
|
+
].filter((condition) => Boolean(condition));
|
|
824
|
+
return conditions.length > 0 ? conditions.join(" and ") : undefined;
|
|
825
|
+
}
|
|
792
826
|
function opencodeTextFromParts(parts) {
|
|
793
827
|
const text = parts
|
|
794
828
|
.map((part) => {
|
|
@@ -866,6 +900,12 @@ function genericJsonlEvents(agent, files, sessionName) {
|
|
|
866
900
|
for (const [index, row] of rows.entries()) {
|
|
867
901
|
const message = asRecord(row.message);
|
|
868
902
|
const role = asString(message.role) ?? asString(row.type);
|
|
903
|
+
const recordId = asString(row.uuid) ??
|
|
904
|
+
asString(row.id) ??
|
|
905
|
+
asString(row.toolUseID) ??
|
|
906
|
+
`row-${index}`;
|
|
907
|
+
const toolUseId = asString(row.toolUseID) ?? asString(row.tool_use_id);
|
|
908
|
+
const content = message.content ?? row.content;
|
|
869
909
|
const timestamp = getTimestampMs(row.timestamp ?? row.time_created, startMs + index);
|
|
870
910
|
const usage = normalizeUsage(message.usage ?? row.usage);
|
|
871
911
|
events.push({
|
|
@@ -873,22 +913,23 @@ function genericJsonlEvents(agent, files, sessionName) {
|
|
|
873
913
|
sourcePath: path,
|
|
874
914
|
sessionId: asString(row.sessionId) ?? asString(row.session_id) ??
|
|
875
915
|
sessionId,
|
|
876
|
-
recordId
|
|
877
|
-
|
|
878
|
-
asString(row.
|
|
879
|
-
|
|
880
|
-
name: `${agent} ${role ?? "event"}`,
|
|
916
|
+
recordId,
|
|
917
|
+
name: role === "tool_result" || role === "toolResult"
|
|
918
|
+
? `${agent} toolResult ${asString(row.toolName) ?? "call"}`
|
|
919
|
+
: `${agent} ${role ?? "event"}`,
|
|
881
920
|
role,
|
|
882
921
|
model: asString(getPath(message, ["model"])) ?? asString(row.model),
|
|
883
922
|
cwd: asString(row.cwd) ?? cwd,
|
|
884
923
|
startMs: timestamp,
|
|
885
|
-
parentRecordId:
|
|
886
|
-
|
|
924
|
+
parentRecordId: toolUseId
|
|
925
|
+
? `tool-${toolUseId}`
|
|
926
|
+
: asString(row.parentUuid) ?? asString(row.parentId) ?? "session",
|
|
887
927
|
input: role === "user"
|
|
888
|
-
? extractText(
|
|
928
|
+
? extractText(content)
|
|
889
929
|
: undefined,
|
|
890
|
-
output: role === "assistant"
|
|
891
|
-
|
|
930
|
+
output: role === "assistant" || role === "tool_result" ||
|
|
931
|
+
role === "toolResult"
|
|
932
|
+
? extractText(content)
|
|
892
933
|
: undefined,
|
|
893
934
|
usage,
|
|
894
935
|
metadata: pick(row, [
|
|
@@ -899,12 +940,26 @@ function genericJsonlEvents(agent, files, sessionName) {
|
|
|
899
940
|
"error",
|
|
900
941
|
]),
|
|
901
942
|
});
|
|
902
|
-
for (const
|
|
943
|
+
for (const reasoning of reasoningFromContent(content)) {
|
|
944
|
+
events.push({
|
|
945
|
+
agent,
|
|
946
|
+
sourcePath: path,
|
|
947
|
+
sessionId,
|
|
948
|
+
recordId: `reasoning-${recordId}-${reasoning.index}`,
|
|
949
|
+
name: `${agent} reasoning`,
|
|
950
|
+
cwd,
|
|
951
|
+
startMs: timestamp,
|
|
952
|
+
parentRecordId: recordId,
|
|
953
|
+
output: reasoning.text,
|
|
954
|
+
metadata: { has_signature: reasoning.hasSignature },
|
|
955
|
+
});
|
|
956
|
+
}
|
|
957
|
+
for (const tool of toolCallsFromContent(content)) {
|
|
903
958
|
events.push({
|
|
904
959
|
agent,
|
|
905
960
|
sourcePath: path,
|
|
906
961
|
sessionId,
|
|
907
|
-
recordId: tool.id
|
|
962
|
+
recordId: `tool-${tool.id}`,
|
|
908
963
|
name: `${agent} tool ${tool.name}`,
|
|
909
964
|
cwd,
|
|
910
965
|
startMs: timestamp,
|
|
@@ -934,6 +989,26 @@ function toolCallsFromContent(content) {
|
|
|
934
989
|
];
|
|
935
990
|
});
|
|
936
991
|
}
|
|
992
|
+
function reasoningFromContent(content) {
|
|
993
|
+
if (!Array.isArray(content))
|
|
994
|
+
return [];
|
|
995
|
+
return content.flatMap((item, index) => {
|
|
996
|
+
const record = asRecord(item);
|
|
997
|
+
const type = asString(record.type);
|
|
998
|
+
if (type !== "thinking" && type !== "reasoning")
|
|
999
|
+
return [];
|
|
1000
|
+
const text = extractText(record.thinking ?? record.text, 8000);
|
|
1001
|
+
if (!text)
|
|
1002
|
+
return [];
|
|
1003
|
+
return [
|
|
1004
|
+
{
|
|
1005
|
+
index,
|
|
1006
|
+
text,
|
|
1007
|
+
hasSignature: record.thinkingSignature !== undefined || record.signature !== undefined,
|
|
1008
|
+
},
|
|
1009
|
+
];
|
|
1010
|
+
});
|
|
1011
|
+
}
|
|
937
1012
|
function pick(source, keys) {
|
|
938
1013
|
const out = {};
|
|
939
1014
|
for (const key of keys) {
|
|
@@ -1180,7 +1255,11 @@ function discoverEvents(options) {
|
|
|
1180
1255
|
claude: (inner) => claudeEvents(inner.homeDir),
|
|
1181
1256
|
codex: (inner) => codexEvents(inner.homeDir),
|
|
1182
1257
|
grok: (inner) => grokEvents(inner.homeDir),
|
|
1183
|
-
opencode: (inner) => opencodeEvents(inner.homeDir,
|
|
1258
|
+
opencode: (inner) => opencodeEvents(inner.homeDir, {
|
|
1259
|
+
rowLimit: inner.limit,
|
|
1260
|
+
sinceMs: inner.sinceMs,
|
|
1261
|
+
untilMs: inner.untilMs,
|
|
1262
|
+
}),
|
|
1184
1263
|
pi: (inner) => piEvents(inner.homeDir),
|
|
1185
1264
|
};
|
|
1186
1265
|
return allAgents
|