@ramarivera/coding-agent-langfuse 0.1.38 → 0.1.40
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/README.md +4 -4
- package/bin/coding-agent-langfuse.mjs +0 -0
- package/dist/backfill.js +30 -65
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -3,9 +3,9 @@
|
|
|
3
3
|
Universal coding-agent Langfuse backfiller and OTLP exporter helpers.
|
|
4
4
|
|
|
5
5
|
It imports local histories from Codex, Claude Code, Grok, OpenCode, and Pi into
|
|
6
|
-
Langfuse as session traces with child observations. LLM usage records are
|
|
7
|
-
as
|
|
8
|
-
|
|
6
|
+
Langfuse as session traces with child observations. LLM usage records are kept
|
|
7
|
+
as observation metadata so historical imports do not create Langfuse billing or
|
|
8
|
+
cost rows. Tool calls remain child spans under the same session.
|
|
9
9
|
|
|
10
10
|
```sh
|
|
11
11
|
coding-agent-langfuse-backfill --agents codex,claude,grok,pi,opencode
|
|
@@ -112,7 +112,7 @@ npm run test:e2e
|
|
|
112
112
|
The e2e suite verifies:
|
|
113
113
|
|
|
114
114
|
- Codex full session traces with messages, reasoning, tool calls, tool results,
|
|
115
|
-
usage
|
|
115
|
+
and usage metadata
|
|
116
116
|
- Follow mode picking up newly written Codex events
|
|
117
117
|
- One CLI run posting reconstructable traces for Claude Code, Codex, Grok,
|
|
118
118
|
OpenCode, and Pi
|
|
File without changes
|
package/dist/backfill.js
CHANGED
|
@@ -19,6 +19,17 @@ const defaultMaxRequestBytes = 12 * 1024 * 1024;
|
|
|
19
19
|
const defaultMaxFieldBytes = 512 * 1024;
|
|
20
20
|
const defaultStatePath = join(homedir(), ".local/state/coding-agent-langfuse/backfill-v6.json");
|
|
21
21
|
const currentHost = hostname();
|
|
22
|
+
function projectMetadata(cwd) {
|
|
23
|
+
if (!cwd)
|
|
24
|
+
return {};
|
|
25
|
+
const normalized = cwd.replace(/[\\/]+$/, "");
|
|
26
|
+
const projectName = normalized.split(/[\\/]+/).filter(Boolean).at(-1);
|
|
27
|
+
return {
|
|
28
|
+
projectPath: cwd,
|
|
29
|
+
projectName,
|
|
30
|
+
projectFolder: projectName,
|
|
31
|
+
};
|
|
32
|
+
}
|
|
22
33
|
function usage() {
|
|
23
34
|
return `Usage: coding-agent-langfuse-backfill [options]
|
|
24
35
|
|
|
@@ -363,53 +374,6 @@ function usageDetails(usage) {
|
|
|
363
374
|
details.total = usage.total;
|
|
364
375
|
return Object.keys(details).length > 0 ? details : undefined;
|
|
365
376
|
}
|
|
366
|
-
function pricingForModel(model) {
|
|
367
|
-
if (!model)
|
|
368
|
-
return undefined;
|
|
369
|
-
const normalized = normalizeModelName(model) ?? model;
|
|
370
|
-
if (normalized === "kimi-for-coding") {
|
|
371
|
-
return { input: 0.95, output: 4.0, cacheRead: 0.16, cacheWrite: 0 };
|
|
372
|
-
}
|
|
373
|
-
if (normalized.includes("accounts/fireworks/routers/kimi-k2p6-turbo")) {
|
|
374
|
-
return { input: 2.0, output: 8.0, cacheRead: 0.30, cacheWrite: 0 };
|
|
375
|
-
}
|
|
376
|
-
if (normalized.includes("accounts/fireworks/models/deepseek-v4-pro")) {
|
|
377
|
-
return { input: 1.74, output: 3.48, cacheRead: 0.15, cacheWrite: 0 };
|
|
378
|
-
}
|
|
379
|
-
if (normalized.includes("DeepSeek-V4-Pro")) {
|
|
380
|
-
return { input: 2.1, output: 4.4, cacheRead: 0.2, cacheWrite: 0 };
|
|
381
|
-
}
|
|
382
|
-
if (normalized.includes("Kimi-K2.6")) {
|
|
383
|
-
return { input: 1.2, output: 4.5, cacheRead: 0.2, cacheWrite: 0 };
|
|
384
|
-
}
|
|
385
|
-
if (normalized.includes("MiniMax-M2.7")) {
|
|
386
|
-
return { input: 0.3, output: 1.2, cacheRead: 0.06, cacheWrite: 0 };
|
|
387
|
-
}
|
|
388
|
-
return undefined;
|
|
389
|
-
}
|
|
390
|
-
function costDetails(usage, model) {
|
|
391
|
-
if (!usage)
|
|
392
|
-
return undefined;
|
|
393
|
-
const rates = pricingForModel(model);
|
|
394
|
-
if (rates) {
|
|
395
|
-
const cachedInput = usage.cacheRead ?? 0;
|
|
396
|
-
const cacheWriteTokens = usage.cacheWrite ?? 0;
|
|
397
|
-
const regularInput = Math.max((usage.input ?? 0) - cachedInput - cacheWriteTokens, 0);
|
|
398
|
-
const input = (regularInput * rates.input) / 1_000_000;
|
|
399
|
-
const output = (((usage.output ?? 0) + (usage.reasoning ?? 0)) * rates.output) /
|
|
400
|
-
1_000_000;
|
|
401
|
-
const cache_read = (cachedInput * rates.cacheRead) / 1_000_000;
|
|
402
|
-
const cache_write = (cacheWriteTokens * rates.cacheWrite) / 1_000_000;
|
|
403
|
-
const total = input + output + cache_read + cache_write;
|
|
404
|
-
if (total > 0) {
|
|
405
|
-
return { input, output, cache_read, cache_write, total, source: "estimated" };
|
|
406
|
-
}
|
|
407
|
-
}
|
|
408
|
-
if (usage.cost !== undefined && usage.cost > 0) {
|
|
409
|
-
return { total: usage.cost, source: "recorded" };
|
|
410
|
-
}
|
|
411
|
-
return undefined;
|
|
412
|
-
}
|
|
413
377
|
function isGenerationEvent(event) {
|
|
414
378
|
return event.usage !== undefined && event.role !== "user" &&
|
|
415
379
|
event.role !== "developer" && event.role !== "system";
|
|
@@ -1204,6 +1168,7 @@ function toOtlp(events, options = {}) {
|
|
|
1204
1168
|
const traceStartMs = sortedEvents[0]?.startMs ?? Date.now();
|
|
1205
1169
|
const traceEndMs = Math.max(...sortedEvents.map((event) => event.endMs ?? event.startMs + 1), traceStartMs + 1);
|
|
1206
1170
|
const shouldEmitRootSpan = sortedEvents.some((event) => event.recordId === "session");
|
|
1171
|
+
const firstProject = projectMetadata(first.cwd);
|
|
1207
1172
|
const rootAttributes = [
|
|
1208
1173
|
attr("service.name", `agent.${first.agent}`),
|
|
1209
1174
|
attr("deployment.environment", "local"),
|
|
@@ -1223,6 +1188,9 @@ function toOtlp(events, options = {}) {
|
|
|
1223
1188
|
attr("langfuse.trace.metadata.machine", currentHost),
|
|
1224
1189
|
attr("langfuse.trace.metadata.source_path", first.sourcePath),
|
|
1225
1190
|
attr("langfuse.trace.metadata.cwd", first.cwd),
|
|
1191
|
+
attr("langfuse.trace.metadata.project_path", firstProject.projectPath),
|
|
1192
|
+
attr("langfuse.trace.metadata.project_name", firstProject.projectName),
|
|
1193
|
+
attr("langfuse.trace.metadata.project_folder", firstProject.projectFolder),
|
|
1226
1194
|
attr("langfuse.observation.metadata.agent", first.agent),
|
|
1227
1195
|
attr("langfuse.observation.metadata.host", currentHost),
|
|
1228
1196
|
attr("langfuse.observation.metadata.machine", currentHost),
|
|
@@ -1230,8 +1198,14 @@ function toOtlp(events, options = {}) {
|
|
|
1230
1198
|
attr("langfuse.observation.metadata.record_id", "session-root"),
|
|
1231
1199
|
attr("langfuse.observation.metadata.source_path", first.sourcePath),
|
|
1232
1200
|
attr("langfuse.observation.metadata.cwd", first.cwd),
|
|
1201
|
+
attr("langfuse.observation.metadata.project_path", firstProject.projectPath),
|
|
1202
|
+
attr("langfuse.observation.metadata.project_name", firstProject.projectName),
|
|
1203
|
+
attr("langfuse.observation.metadata.project_folder", firstProject.projectFolder),
|
|
1233
1204
|
attr("source.path", first.sourcePath),
|
|
1234
1205
|
attr("cwd", first.cwd),
|
|
1206
|
+
attr("project.path", firstProject.projectPath),
|
|
1207
|
+
attr("project.name", firstProject.projectName),
|
|
1208
|
+
attr("project.folder", firstProject.projectFolder),
|
|
1235
1209
|
].filter((item) => Boolean(item));
|
|
1236
1210
|
const rootSpan = {
|
|
1237
1211
|
traceId: traceId(first),
|
|
@@ -1250,16 +1224,7 @@ function toOtlp(events, options = {}) {
|
|
|
1250
1224
|
const modelName = normalizeModelName(event.model);
|
|
1251
1225
|
const generation = isGenerationEvent(event);
|
|
1252
1226
|
const usage = usageDetails(event.usage);
|
|
1253
|
-
const
|
|
1254
|
-
const costForLangfuse = cost === undefined
|
|
1255
|
-
? undefined
|
|
1256
|
-
: {
|
|
1257
|
-
...(cost.input !== undefined ? { input: cost.input } : {}),
|
|
1258
|
-
...(cost.output !== undefined ? { output: cost.output } : {}),
|
|
1259
|
-
...(cost.cache_read !== undefined ? { cache_read: cost.cache_read } : {}),
|
|
1260
|
-
...(cost.cache_write !== undefined ? { cache_write: cost.cache_write } : {}),
|
|
1261
|
-
total: cost.total,
|
|
1262
|
-
};
|
|
1227
|
+
const eventProject = projectMetadata(event.cwd);
|
|
1263
1228
|
const attributes = [
|
|
1264
1229
|
attr("service.name", `agent.${event.agent}`),
|
|
1265
1230
|
attr("deployment.environment", "local"),
|
|
@@ -1268,13 +1233,6 @@ function toOtlp(events, options = {}) {
|
|
|
1268
1233
|
attr("session.id", event.sessionId),
|
|
1269
1234
|
attr("langfuse.observation.type", generation ? "generation" : "span"),
|
|
1270
1235
|
attr("langfuse.observation.model.name", generation ? modelName : undefined),
|
|
1271
|
-
attr("langfuse.observation.usage_details", usage),
|
|
1272
|
-
attr("langfuse.observation.cost_details", costForLangfuse),
|
|
1273
|
-
attr("gen_ai.response.model", generation ? modelName : undefined),
|
|
1274
|
-
attr("gen_ai.usage.input_tokens", usage?.input),
|
|
1275
|
-
attr("gen_ai.usage.output_tokens", usage?.output),
|
|
1276
|
-
attr("gen_ai.usage.total_tokens", usage?.total),
|
|
1277
|
-
attr("gen_ai.usage.cost", cost?.total),
|
|
1278
1236
|
attr("agent.name", event.agent),
|
|
1279
1237
|
attr("host.name", currentHost),
|
|
1280
1238
|
attr("agent.session_id", event.sessionId),
|
|
@@ -1288,13 +1246,20 @@ function toOtlp(events, options = {}) {
|
|
|
1288
1246
|
attr("langfuse.observation.metadata.record_id", event.recordId),
|
|
1289
1247
|
attr("langfuse.observation.metadata.source_path", event.sourcePath),
|
|
1290
1248
|
attr("langfuse.observation.metadata.cwd", event.cwd),
|
|
1249
|
+
attr("langfuse.observation.metadata.project_path", eventProject.projectPath),
|
|
1250
|
+
attr("langfuse.observation.metadata.project_name", eventProject.projectName),
|
|
1251
|
+
attr("langfuse.observation.metadata.project_folder", eventProject.projectFolder),
|
|
1291
1252
|
attr("langfuse.observation.metadata.model", modelName ?? event.model),
|
|
1292
1253
|
attr("langfuse.observation.metadata.provider", event.provider),
|
|
1293
|
-
attr("langfuse.observation.metadata.
|
|
1254
|
+
attr("langfuse.observation.metadata.usage_details", usage),
|
|
1255
|
+
attr("langfuse.observation.metadata.recorded_cost", event.usage?.cost),
|
|
1294
1256
|
attr("langfuse.observation.input", event.input),
|
|
1295
1257
|
attr("langfuse.observation.output", event.output),
|
|
1296
1258
|
attr("source.path", event.sourcePath),
|
|
1297
1259
|
attr("cwd", event.cwd),
|
|
1260
|
+
attr("project.path", eventProject.projectPath),
|
|
1261
|
+
attr("project.name", eventProject.projectName),
|
|
1262
|
+
attr("project.folder", eventProject.projectFolder),
|
|
1298
1263
|
attr("role", event.role),
|
|
1299
1264
|
attr("agent.model", event.model),
|
|
1300
1265
|
attr("agent.provider", event.provider),
|