@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 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 emitted
7
- as Langfuse generations with model names, usage details, and recorded or
8
- estimated cost details. Tool calls remain child spans under the same session.
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, and costs
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 cost = costDetails(event.usage, modelName);
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.cost_source", cost?.source),
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),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ramarivera/coding-agent-langfuse",
3
- "version": "0.1.38",
3
+ "version": "0.1.40",
4
4
  "description": "Universal coding-agent Langfuse backfiller and live OTLP helpers",
5
5
  "type": "module",
6
6
  "license": "MIT",