@ramarivera/coding-agent-langfuse 0.1.43 → 0.1.44

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
@@ -45,12 +45,18 @@ records a total cost, that recorded value wins. Otherwise, the importer
45
45
  calculates per-usage-type USD costs from a model catalog using rates in USD per
46
46
  1M tokens.
47
47
 
48
- The built-in catalog covers OpenAI GPT-5.5 API list pricing plus the toolbox/Pi
48
+ The built-in catalog covers OpenAI GPT-5.5, GPT-5.4, and GPT-5.3-Codex API list
49
+ pricing, Anthropic Claude Opus/Sonnet 4 API list pricing, plus the toolbox/Pi
49
50
  models already used in local configuration, including Fireworks Kimi K2.6,
50
51
  Fireworks DeepSeek V4 Pro, MiniMax-M3, Together DeepSeek/Kimi/GLM/MiniMax, and
51
52
  Zai GLM. `gpt-5.5` is charged at current standard API list price by default:
52
53
  `$5.00` input, `$0.50` cached input, and `$30.00` output per 1M tokens. GPT-5.5
53
- Pro defaults to `$30.00` input and `$180.00` output per 1M tokens.
54
+ Pro defaults to `$30.00` input and `$180.00` output per 1M tokens. Claude Opus 4
55
+ models default to `$15.00` input, `$1.50` cache hits, `$18.75` 5-minute cache
56
+ writes, `$30.00` 1-hour cache writes, and `$75.00` output per 1M tokens.
57
+ When a source only records a total token count without input/output/cache
58
+ breakdown, the importer charges that total at the model input rate and marks the
59
+ cost source as `calculated_total_as_input`.
54
60
 
55
61
  Use an override only when you intentionally want a different accounting policy:
56
62
 
@@ -72,7 +78,9 @@ services. A file can be either a direct model map or `{ "rates": { ... } }`:
72
78
  "input": 1,
73
79
  "output": 2,
74
80
  "cacheRead": 0.1,
75
- "cacheWrite": 0
81
+ "cacheWrite": 0,
82
+ "cacheWrite5m": 0,
83
+ "cacheWrite1h": 0
76
84
  }
77
85
  }
78
86
  }
@@ -138,8 +146,26 @@ npx @ramarivera/coding-agent-langfuse@latest \
138
146
  --endpoint https://langfuse.ai.roxasroot.net/otel/v1/traces
139
147
  ```
140
148
 
141
- Deduplication is state-file based and keyed by agent, session id, and source
142
- record id. Reuse the same `--state` path for repeat repairs on a host.
149
+ Deduplication is state-file based and keyed by importer state identity, agent,
150
+ session id, and source record id. Reuse the same `--state` path for normal
151
+ incremental runs.
152
+
153
+ For an intentional repair replay, add `--force` to resend the selected window
154
+ even when the state file says those events were already sent:
155
+
156
+ ```sh
157
+ npx @ramarivera/coding-agent-langfuse@latest \
158
+ --agents claude,codex,grok,pi,opencode \
159
+ --since 2026-05-01T00:00:00Z \
160
+ --until 2026-06-01T00:00:00Z \
161
+ --force \
162
+ --endpoint https://langfuse.ai.roxasroot.net/otel/v1/traces
163
+ ```
164
+
165
+ The Langfuse trace/span IDs intentionally stay pinned to the original pre-cost
166
+ identity, while the state-file key can advance with importer payload changes.
167
+ That lets cost repairs replace historical zero-cost rows instead of creating a
168
+ new duplicate identity for the same source event.
143
169
 
144
170
  ## Verification
145
171
 
@@ -6,6 +6,8 @@ type Usage = {
6
6
  reasoning?: number;
7
7
  cacheRead?: number;
8
8
  cacheWrite?: number;
9
+ cacheWrite5m?: number;
10
+ cacheWrite1h?: number;
9
11
  total?: number;
10
12
  cost?: number;
11
13
  inputIncludesCache?: boolean;
@@ -34,6 +36,7 @@ type BackfillOptions = {
34
36
  statePath: string;
35
37
  homeDir: string;
36
38
  dryRun: boolean;
39
+ force: boolean;
37
40
  follow: boolean;
38
41
  pollIntervalMs: number;
39
42
  idleExitAfterMs?: number;
@@ -53,6 +56,8 @@ type CostRates = {
53
56
  reasoning?: number;
54
57
  cacheRead?: number;
55
58
  cacheWrite?: number;
59
+ cacheWrite5m?: number;
60
+ cacheWrite1h?: number;
56
61
  };
57
62
  type CostCatalog = Record<string, CostRates>;
58
63
  type OtlpOptions = {
package/dist/backfill.js CHANGED
@@ -5,14 +5,23 @@ import { existsSync, mkdirSync, renameSync, readdirSync, readFileSync, statSync,
5
5
  import { hostname, 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-cost-details";
9
- const importIdentityVersions = {
8
+ const importStateIdentityVersion = "v9-cost-details";
9
+ const importStateIdentityVersions = {
10
10
  claude: "v12-cost-details",
11
11
  codex: "v10-cost-details",
12
12
  grok: "v12-cost-details",
13
13
  opencode: "v11-cost-details",
14
14
  pi: "v12-cost-details",
15
15
  };
16
+ const langfuseIdIdentityVersion = "v8-cached-input-token-split";
17
+ const langfuseIdIdentityVersions = {
18
+ claude: "v11-tool-results",
19
+ codex: "v9-codex-conversation-events",
20
+ grok: "v11-chat-history-only",
21
+ opencode: "v10-opencode-message-parts",
22
+ pi: "v11-tool-results",
23
+ };
24
+ const importPayloadVersion = "v10-cost-details";
16
25
  const defaultEndpoint = "https://langfuse.ai.roxasroot.net/otel/v1/traces";
17
26
  const deadRemoteEndpoint = "http://langfuse.ai.roxasroot.net:14318/v1/traces";
18
27
  const defaultMaxRequestBytes = 12 * 1024 * 1024;
@@ -49,11 +58,63 @@ const gpt55ProRates = {
49
58
  cacheRead: 30,
50
59
  cacheWrite: 30,
51
60
  };
61
+ const gpt54Rates = {
62
+ input: 2.5,
63
+ output: 15,
64
+ cacheRead: 0.25,
65
+ cacheWrite: 2.5,
66
+ };
67
+ const gpt53CodexRates = {
68
+ input: 1.75,
69
+ output: 14,
70
+ cacheRead: 0.175,
71
+ cacheWrite: 1.75,
72
+ };
73
+ const claudeOpus4Rates = {
74
+ input: 15,
75
+ output: 75,
76
+ cacheRead: 1.5,
77
+ cacheWrite: 18.75,
78
+ cacheWrite5m: 18.75,
79
+ cacheWrite1h: 30,
80
+ };
81
+ const claudeSonnet4Rates = {
82
+ input: 3,
83
+ output: 15,
84
+ cacheRead: 0.3,
85
+ cacheWrite: 3.75,
86
+ cacheWrite5m: 3.75,
87
+ cacheWrite1h: 6,
88
+ };
52
89
  const defaultCostRates = {
53
90
  "gpt-5.5": gpt55Rates,
54
91
  "openai/gpt-5.5": gpt55Rates,
55
92
  "gpt-5.5-pro": gpt55ProRates,
56
93
  "openai/gpt-5.5-pro": gpt55ProRates,
94
+ "gpt-5.4": gpt54Rates,
95
+ "openai/gpt-5.4": gpt54Rates,
96
+ "gpt-5.3-codex": gpt53CodexRates,
97
+ "openai/gpt-5.3-codex": gpt53CodexRates,
98
+ "claude-opus-4": claudeOpus4Rates,
99
+ "anthropic/claude-opus-4": claudeOpus4Rates,
100
+ "claude-opus-4-1": claudeOpus4Rates,
101
+ "anthropic/claude-opus-4-1": claudeOpus4Rates,
102
+ "claude-opus-4-6": claudeOpus4Rates,
103
+ "anthropic/claude-opus-4-6": claudeOpus4Rates,
104
+ "claude-opus-4-7": claudeOpus4Rates,
105
+ "anthropic/claude-opus-4-7": claudeOpus4Rates,
106
+ "claude-opus-4-8": claudeOpus4Rates,
107
+ "anthropic/claude-opus-4-8": claudeOpus4Rates,
108
+ "claude-sonnet-4": claudeSonnet4Rates,
109
+ "anthropic/claude-sonnet-4": claudeSonnet4Rates,
110
+ "claude-sonnet-4-5": claudeSonnet4Rates,
111
+ "anthropic/claude-sonnet-4-5": claudeSonnet4Rates,
112
+ "claude-sonnet-4.5": claudeSonnet4Rates,
113
+ "anthropic/claude-sonnet-4.5": claudeSonnet4Rates,
114
+ "claude-sonnet-4-6": claudeSonnet4Rates,
115
+ "anthropic/claude-sonnet-4-6": claudeSonnet4Rates,
116
+ "claude-sonnet-4.6": claudeSonnet4Rates,
117
+ "anthropic/claude-sonnet-4.6": claudeSonnet4Rates,
57
118
  "accounts/fireworks/routers/kimi-k2p6-turbo": kimiFirepassRates,
58
119
  "fireworks-firepass/accounts/fireworks/routers/kimi-k2p6-turbo": kimiFirepassRates,
59
120
  "kimi-for-coding": kimiFirepassRates,
@@ -67,24 +128,48 @@ const defaultCostRates = {
67
128
  cacheRead: 0.2,
68
129
  cacheWrite: 0,
69
130
  },
131
+ "deepseek-ai/DeepSeek-V4-Pro": {
132
+ input: 2.1,
133
+ output: 4.4,
134
+ cacheRead: 0.2,
135
+ cacheWrite: 0,
136
+ },
70
137
  "together/zai-org/GLM-5.1": {
71
138
  input: 1.4,
72
139
  output: 4.4,
73
140
  cacheRead: 0.2,
74
141
  cacheWrite: 0,
75
142
  },
143
+ "zai-org/GLM-5.1": {
144
+ input: 1.4,
145
+ output: 4.4,
146
+ cacheRead: 0.2,
147
+ cacheWrite: 0,
148
+ },
76
149
  "together/moonshotai/Kimi-K2.6": {
77
150
  input: 1.2,
78
151
  output: 4.5,
79
152
  cacheRead: 0.2,
80
153
  cacheWrite: 0,
81
154
  },
155
+ "moonshotai/Kimi-K2.6": {
156
+ input: 1.2,
157
+ output: 4.5,
158
+ cacheRead: 0.2,
159
+ cacheWrite: 0,
160
+ },
82
161
  "together/MiniMaxAI/MiniMax-M2.7": {
83
162
  input: 0.3,
84
163
  output: 1.2,
85
164
  cacheRead: 0.06,
86
165
  cacheWrite: 0,
87
166
  },
167
+ "MiniMax-M2.7": {
168
+ input: 0.3,
169
+ output: 1.2,
170
+ cacheRead: 0.06,
171
+ cacheWrite: 0,
172
+ },
88
173
  "zai/glm-5.1": {
89
174
  input: 1.4,
90
175
  output: 4.4,
@@ -125,6 +210,7 @@ Options:
125
210
  --poll-interval-ms N Delay between --follow scans (default: 5000)
126
211
  --idle-exit-after-ms N Stop --follow after this much time without new sends
127
212
  --dry-run Discover and dedupe without sending or mutating state
213
+ --force Resend discovered events even when present in local state
128
214
  --help Show this help
129
215
  `;
130
216
  }
@@ -133,6 +219,7 @@ function parseArgs(argv) {
133
219
  let statePath = process.env.LANGFUSE_BACKFILL_STATE ?? defaultStatePath;
134
220
  let homeDir = process.env.HOME ?? homedir();
135
221
  let dryRun = false;
222
+ let force = false;
136
223
  let limit;
137
224
  let sinceMs;
138
225
  let untilMs;
@@ -167,6 +254,9 @@ function parseArgs(argv) {
167
254
  if (arg === "--dry-run") {
168
255
  dryRun = true;
169
256
  }
257
+ else if (arg === "--force") {
258
+ force = true;
259
+ }
170
260
  else if (arg === "--endpoint") {
171
261
  endpoint = normalizeEndpoint(next());
172
262
  }
@@ -261,6 +351,7 @@ function parseArgs(argv) {
261
351
  statePath,
262
352
  homeDir,
263
353
  dryRun,
354
+ force,
264
355
  follow,
265
356
  pollIntervalMs,
266
357
  idleExitAfterMs,
@@ -329,6 +420,14 @@ function normalizeCostRates(value, modelKey, source) {
329
420
  asNumber(record.cache_write) ??
330
421
  asNumber(record.inputCacheCreation) ??
331
422
  asNumber(record.input_cache_creation),
423
+ cacheWrite5m: asNumber(record.cacheWrite5m) ??
424
+ asNumber(record.cache_write_5m) ??
425
+ asNumber(record.inputCacheCreation5m) ??
426
+ asNumber(record.input_cache_creation_5m),
427
+ cacheWrite1h: asNumber(record.cacheWrite1h) ??
428
+ asNumber(record.cache_write_1h) ??
429
+ asNumber(record.inputCacheCreation1h) ??
430
+ asNumber(record.input_cache_creation_1h),
332
431
  };
333
432
  const values = Object.entries(rates).filter(([, rate]) => rate !== undefined);
334
433
  if (values.length === 0)
@@ -467,11 +566,27 @@ function normalizeUsage(value) {
467
566
  const record = asRecord(value);
468
567
  const nestedCost = asRecord(record.cost);
469
568
  const cache = asRecord(record.cache);
569
+ const cacheCreation = asRecord(record.cache_creation);
470
570
  const inputDetails = asRecord(record.input_tokens_details);
471
571
  const outputDetails = asRecord(record.output_tokens_details);
472
572
  const directInput = asNumber(record.input);
473
573
  const aggregateInput = asNumber(record.input_tokens) ??
474
574
  asNumber(record.prompt_tokens);
575
+ const cacheWrite5m = asNumber(record.cacheWrite5m) ??
576
+ asNumber(record.cache_write_5m) ??
577
+ asNumber(record.input_cache_creation_5m) ??
578
+ asNumber(cacheCreation.ephemeral_5m_input_tokens);
579
+ const cacheWrite1h = asNumber(record.cacheWrite1h) ??
580
+ asNumber(record.cache_write_1h) ??
581
+ asNumber(record.input_cache_creation_1h) ??
582
+ asNumber(cacheCreation.ephemeral_1h_input_tokens);
583
+ const untypedCacheWrite = asNumber(record.cacheWrite) ??
584
+ asNumber(record.cache_creation_input_tokens) ??
585
+ asNumber(cache.write);
586
+ const hasTypedCacheWrite = cacheWrite5m !== undefined || cacheWrite1h !== undefined;
587
+ const hasAnthropicCacheShape = hasTypedCacheWrite ||
588
+ asNumber(record.cache_creation_input_tokens) !== undefined ||
589
+ asNumber(record.cache_read_input_tokens) !== undefined;
475
590
  const usage = {
476
591
  input: directInput ?? aggregateInput,
477
592
  output: asNumber(record.output) ??
@@ -487,9 +602,9 @@ function normalizeUsage(value) {
487
602
  asNumber(record.cached_tokens) ??
488
603
  asNumber(inputDetails.cached_tokens) ??
489
604
  asNumber(cache.read),
490
- cacheWrite: asNumber(record.cacheWrite) ??
491
- asNumber(record.cache_creation_input_tokens) ??
492
- asNumber(cache.write),
605
+ cacheWrite: hasTypedCacheWrite ? undefined : untypedCacheWrite,
606
+ cacheWrite5m,
607
+ cacheWrite1h,
493
608
  total: asNumber(record.totalTokens) ?? asNumber(record.total_tokens) ??
494
609
  asNumber(record.total),
495
610
  cost: asNumber(nestedCost.total) ??
@@ -499,14 +614,17 @@ function normalizeUsage(value) {
499
614
  asNumber(record.cost),
500
615
  };
501
616
  if (usage.input !== undefined) {
502
- usage.inputIncludesCache = directInput === undefined;
617
+ usage.inputIncludesCache = directInput === undefined && !hasAnthropicCacheShape;
503
618
  }
504
619
  if (usage.total === undefined) {
505
620
  const cacheRead = usage.inputIncludesCache === false ? (usage.cacheRead ?? 0) : 0;
621
+ const cacheWrite = (usage.cacheWrite ?? 0) +
622
+ (usage.cacheWrite5m ?? 0) +
623
+ (usage.cacheWrite1h ?? 0);
506
624
  const total = (usage.input ?? 0) +
507
625
  (usage.output ?? 0) +
508
626
  (usage.reasoning ?? 0) +
509
- (usage.cacheWrite ?? 0) +
627
+ cacheWrite +
510
628
  cacheRead;
511
629
  if (total > 0)
512
630
  usage.total = total;
@@ -529,10 +647,13 @@ function usageDetails(usage) {
529
647
  return undefined;
530
648
  const details = {};
531
649
  const cachedInput = usage.inputIncludesCache === false ? 0 : (usage.cacheRead ?? 0);
532
- const cacheWrite = usage.inputIncludesCache === false ? 0 : (usage.cacheWrite ?? 0);
650
+ const cacheWriteTotal = (usage.cacheWrite ?? 0) +
651
+ (usage.cacheWrite5m ?? 0) +
652
+ (usage.cacheWrite1h ?? 0);
653
+ const cacheWriteInInput = usage.inputIncludesCache === false ? 0 : cacheWriteTotal;
533
654
  const regularInput = usage.input === undefined
534
655
  ? undefined
535
- : Math.max(usage.input - cachedInput - cacheWrite, 0);
656
+ : Math.max(usage.input - cachedInput - cacheWriteInInput, 0);
536
657
  if (regularInput !== undefined)
537
658
  details.input = regularInput;
538
659
  if (usage.output !== undefined)
@@ -543,6 +664,15 @@ function usageDetails(usage) {
543
664
  details.input_cached_tokens = usage.cacheRead;
544
665
  if (usage.cacheWrite !== undefined)
545
666
  details.input_cache_creation = usage.cacheWrite;
667
+ if (usage.cacheWrite5m !== undefined) {
668
+ details.input_cache_creation_5m = usage.cacheWrite5m;
669
+ }
670
+ if (usage.cacheWrite1h !== undefined) {
671
+ details.input_cache_creation_1h = usage.cacheWrite1h;
672
+ }
673
+ if (usage.cacheWrite === undefined && cacheWriteTotal > 0) {
674
+ details.input_cache_creation = cacheWriteTotal;
675
+ }
546
676
  if (usage.total !== undefined)
547
677
  details.total = usage.total;
548
678
  return Object.keys(details).length > 0 ? details : undefined;
@@ -565,13 +695,30 @@ function calculateCost(event, usage, costRates) {
565
695
  setCostPart(details, "output", usage.output, rates.output);
566
696
  setCostPart(details, "output_reasoning", usage.output_reasoning, rates.reasoning ?? rates.output);
567
697
  setCostPart(details, "input_cached_tokens", usage.input_cached_tokens, rates.cacheRead ?? rates.input);
568
- setCostPart(details, "input_cache_creation", usage.input_cache_creation, rates.cacheWrite ?? rates.input);
698
+ const hasTypedCacheWrite = usage.input_cache_creation_5m !== undefined ||
699
+ usage.input_cache_creation_1h !== undefined;
700
+ setCostPart(details, "input_cache_creation_5m", usage.input_cache_creation_5m, rates.cacheWrite5m ?? rates.cacheWrite ?? rates.input);
701
+ setCostPart(details, "input_cache_creation_1h", usage.input_cache_creation_1h, rates.cacheWrite1h ?? rates.cacheWrite ?? rates.input);
702
+ if (!hasTypedCacheWrite) {
703
+ setCostPart(details, "input_cache_creation", usage.input_cache_creation, rates.cacheWrite ?? rates.input);
704
+ }
705
+ const calculatedTotal = Object.values(details).reduce((sum, value) => sum + value, 0);
706
+ let source = "calculated";
707
+ if (calculatedTotal === 0 &&
708
+ usage.total !== undefined &&
709
+ usage.total > 0 &&
710
+ rates.input !== undefined) {
711
+ for (const key of Object.keys(details))
712
+ delete details[key];
713
+ setCostPart(details, "input", usage.total, rates.input);
714
+ source = "calculated_total_as_input";
715
+ }
569
716
  if (Object.keys(details).length === 0)
570
717
  return undefined;
571
718
  details.total = roundCost(Object.values(details).reduce((sum, value) => sum + value, 0));
572
719
  return {
573
720
  details,
574
- source: "calculated",
721
+ source,
575
722
  modelKey,
576
723
  rates,
577
724
  };
@@ -1343,19 +1490,25 @@ function stableId(input) {
1343
1490
  return createHash("sha256").update(input).digest("hex").slice(0, 32);
1344
1491
  }
1345
1492
  function importIdentity(event) {
1346
- return importIdentityVersions[event.agent] ?? importIdentityVersion;
1493
+ return importStateIdentityVersions[event.agent] ?? importStateIdentityVersion;
1494
+ }
1495
+ function langfuseIdIdentity(event) {
1496
+ return langfuseIdIdentityVersions[event.agent] ?? langfuseIdIdentityVersion;
1347
1497
  }
1348
1498
  function fingerprint(event) {
1349
1499
  return `${importIdentity(event)}:${event.agent}:${event.sessionId}:${event.recordId}`;
1350
1500
  }
1501
+ function langfuseFingerprint(event) {
1502
+ return `${langfuseIdIdentity(event)}:${event.agent}:${event.sessionId}:${event.recordId}`;
1503
+ }
1351
1504
  function traceFingerprint(event) {
1352
- return `${importIdentity(event)}:${event.agent}:${event.sessionId}`;
1505
+ return `${langfuseIdIdentity(event)}:${event.agent}:${event.sessionId}`;
1353
1506
  }
1354
1507
  function traceId(event) {
1355
1508
  return stableId(traceFingerprint(event));
1356
1509
  }
1357
1510
  function spanId(event) {
1358
- return stableId(fingerprint(event)).slice(0, 16);
1511
+ return stableId(langfuseFingerprint(event)).slice(0, 16);
1359
1512
  }
1360
1513
  function rootSpanId(event) {
1361
1514
  return stableId(`${traceFingerprint(event)}:root`).slice(0, 16);
@@ -1446,6 +1599,9 @@ function toOtlp(events, options = {}) {
1446
1599
  attr("langfuse.trace.metadata.project_path", firstProject.projectPath),
1447
1600
  attr("langfuse.trace.metadata.project_name", firstProject.projectName),
1448
1601
  attr("langfuse.trace.metadata.project_folder", firstProject.projectFolder),
1602
+ attr("langfuse.trace.metadata.import_payload_version", importPayloadVersion),
1603
+ attr("langfuse.trace.metadata.import_state_identity", importIdentity(first)),
1604
+ attr("langfuse.trace.metadata.langfuse_id_identity", langfuseIdIdentity(first)),
1449
1605
  attr("langfuse.observation.metadata.agent", first.agent),
1450
1606
  attr("langfuse.observation.metadata.host", currentHost),
1451
1607
  attr("langfuse.observation.metadata.machine", currentHost),
@@ -1456,6 +1612,9 @@ function toOtlp(events, options = {}) {
1456
1612
  attr("langfuse.observation.metadata.project_path", firstProject.projectPath),
1457
1613
  attr("langfuse.observation.metadata.project_name", firstProject.projectName),
1458
1614
  attr("langfuse.observation.metadata.project_folder", firstProject.projectFolder),
1615
+ attr("langfuse.observation.metadata.import_payload_version", importPayloadVersion),
1616
+ attr("langfuse.observation.metadata.import_state_identity", importIdentity(first)),
1617
+ attr("langfuse.observation.metadata.langfuse_id_identity", langfuseIdIdentity(first)),
1459
1618
  attr("source.path", first.sourcePath),
1460
1619
  attr("cwd", first.cwd),
1461
1620
  attr("project.path", firstProject.projectPath),
@@ -1507,6 +1666,9 @@ function toOtlp(events, options = {}) {
1507
1666
  attr("langfuse.observation.metadata.project_folder", eventProject.projectFolder),
1508
1667
  attr("langfuse.observation.metadata.model", modelName ?? event.model),
1509
1668
  attr("langfuse.observation.metadata.provider", event.provider),
1669
+ attr("langfuse.observation.metadata.import_payload_version", importPayloadVersion),
1670
+ attr("langfuse.observation.metadata.import_state_identity", importIdentity(event)),
1671
+ attr("langfuse.observation.metadata.langfuse_id_identity", langfuseIdIdentity(event)),
1510
1672
  attr("langfuse.observation.usage_details", generation ? usage : undefined),
1511
1673
  attr("langfuse.observation.cost_details", cost?.details),
1512
1674
  attr("langfuse.observation.metadata.usage_details", usage),
@@ -1677,7 +1839,9 @@ async function run(options) {
1677
1839
  for (const event of events) {
1678
1840
  discovered[event.agent] = (discovered[event.agent] ?? 0) + 1;
1679
1841
  }
1680
- const unsent = events.filter((event) => state.sent[fingerprint(event)] === undefined);
1842
+ const unsent = options.force
1843
+ ? events
1844
+ : events.filter((event) => state.sent[fingerprint(event)] === undefined);
1681
1845
  const selected = options.limit === undefined
1682
1846
  ? unsent
1683
1847
  : unsent.slice(0, options.limit);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ramarivera/coding-agent-langfuse",
3
- "version": "0.1.43",
3
+ "version": "0.1.44",
4
4
  "description": "Universal coding-agent Langfuse backfiller and live OTLP helpers",
5
5
  "type": "module",
6
6
  "license": "MIT",